Hi Tamura-san, thanks for reaching out us.
I apologize for coming a little bit late than usual, but although the question's resolution was straightforward at the begin finding a way to retrieve and set NormalTag values was less straightforward than expected.
Going back to your question, transforming a vector stored in a normal tag undergoes the same rules of transforming any other vector: left multiply the vector by a transformation matrix is the way to go.
An exception to this rules is that in the case of directions (as normals are) the transformation matrix should have the translation vector set to 0,0,0 otherwise the results might be unpredictable.
Being said that, the less-straightforward part: although I would have imagined that getting/setting values in a NormalTag should have followed the same rules for any other variable tag, I experienced some troubles on the course of "properly" reaching the values.
To recap here a list of old threads in the forum talking about NormalTag:
Running through all of them, it seems that setting values in a NormalTag in Python, being NormalHandle and NormalStruct missing in the Python API, requires some gimmick to happen.
In Modifiying the normal-tag it's described how to set the values by accessing in write-mode the low-level data, but I'd say that it could be discouraging at a first sight especially for newbies.
The way to go is instead to use GetAllHighlevelData / SetAllHighlevelData which are indeed more friendly.
By using these two methods, you're expected to receive (in the getter function) or to pass (in the setter function) a list of values ranging from 0
to 65535
. Given a mesh with 2 polys the normal list returned by - or passed to - will look like:
normalList = [ x1a1 x1a2 x1a3 x1b1 x1b2 x1b3 x1c1 x1c2 x1c3 x1d1 x1d2 x1d3 x2a1 x2a2 x2a3 x2b1 x2b2 x2b3 x2c1 x2c2 x2c3 x2d1 x2d2 x2d3]
containing 24 elements (2 polys x 4 normals per poly x 3 component per normal)
In addition the values stored in such a list should be converted from float [-1, 1] to a int representation [0, 65535] given the following mapping schema:
[ 0.0, 1.0] --> [0, 32000]
[ -0.0, -1.0] --> [65536, 33536] // note that 0 == 65536 in UInt16
Given all this information, there's one last point to note down (which looks like a bug in the current GetAllHighlevelData
implementation). Assuming we still have a 2 polys mesh and we set the values of a NormalTag to a list of values composed by
[3200, 6400, 9600, 12800, 16000, 19200, 22400, 25600, 28800, 62335, 59135, 55935, 3200, 6400, 9600, 12800, 16000, 19200, 22400, 25600, 28800, 62335, 59135, 55935]
the GetAllHighlevelData
returns
[3200, 6400, 9600, 12800, 16000, 19200, 22400, 25600, 28800, 62335, 59135, 55935, 6400, 9600, 12800, 16000, 19200, 22400, 25600, 28800, 62335, 59135, 55935, 3200]
shifting for the second poly the returned stored values by one unit (note that the last value is 3200
whilst it should be 55935
).
Then a realignment operation is required for every polygon expect for the first.
Finally the code looks like
import c4d, math
# convert from [-1,1] to [0,65535]
def ConvertFromReal(value):
res = 0
if value < 0:
res = int(value * 32000 + 65536)
else:
res = int(value * 32000)
return res
# convert [-1,1]-represented vector to [0,65535]-represented vector
def ConvertFromRealVector(vec):
res = c4d.Vector()
for i in xrange(3):
res[i] = ConvertFromReal(vec[i])
return res
# helper function to set the values in a NormalTag
def SetAllHighLevelNormals(tag, normalList):
dataList = []
for normal in normalList:
temp = ConvertFromRealVector(normal)
dataList.append(int(temp[0]))
dataList.append(int(temp[1]))
dataList.append(int(temp[2]))
tag.SetAllHighlevelData(dataList)
# convert [0,65535] to [-1,1]
def ConvertToReal(value):
res = 0
if value > 33535:
# should be converted in a negative value
res = float((value - 65536) / 32000)
else:
res = float(value / 32000)
return res
# convert [0,65535]-represented vector to [-1,1]-represented vector
def ConvertToRealVector(vec):
res = c4d.Vector()
for i in xrange(3):
res[i] = ConvertToReal(vec[i])
return res
# helper function to get the values in a NormalTag
def GetAllHighLevelNormals(tag):
# access the high-level data of the NormalTag
data = tag.GetAllHighlevelData() # NOTE: returned data is currently shifted
polygonCnt = len(data) / 12
normalList = [];
for i in xrange(polygonCnt):
# realign the normal data
subData = data[i*12:i*12 + 12]
shiftedSubData = subData[-i:] + subData[:-i]
for j in xrange(4):
normal = c4d.Vector(shiftedSubData[ j * 3 + 0], shiftedSubData[ j * 3 + 1], shiftedSubData[ j * 3 + 2])
normal = ConvertToRealVector(normal)
normalList.append(normal)
return normalList
# Create a normal Tag
def CreateNormalTag(op):
# create a NormalTag
polyCnt = op.GetPolygonCount()
nrmTag = c4d.NormalTag(polyCnt)
if nrmTag is None:
return
# insert the tag
op.InsertTag(nrmTag)
c4d.EventAdd()
# Main function
def main():
print op
if op is None:
return
if not op.GetType() == c4d.Opolygon:
return
nrmTag = op.GetTag(c4d.Tnormal)
if nrmTag is None:
CreateNormalTag(op)
nrmTag = op.GetTag(c4d.Tnormal)
# let's assume that all the normals stored in NormalTag should point up-ward
a = c4d.Vector(0.0, 1.0, 0.0)
b = c4d.Vector(0.0, 1.0, 0.0)
c = c4d.Vector(0.0, 1.0, 0.0)
d = c4d.Vector(0.0, 1.0, 0.0)
polyCnt = op.GetPolygonCount()
normalList = [a,b,c,d] * polyCnt
# set the normal values
SetAllHighLevelNormals(nrmTag, normalList)
# create a transformation matrix and its inverse
rotAxis = c4d.Vector(0,0,1)
rotAngle = math.radians(45)
trf = c4d.Matrix()
trf = c4d.utils.RotAxisToMatrix(rotAxis, rotAngle)
trf.off = c4d.Vector(0,100,0)
itrf = ~trf
# get all points and transform them accordingly to the matrix
points = op.GetAllPoints()
for i in xrange(len(points)):
points[i] = itrf.Mul(points[i])
op.SetAllPoints(points)
# get all the values stored in the normal tag and transform them accordingly to the matrix
normalList = GetAllHighLevelNormals(nrmTag)
for i in xrange(len(normalList)):
normalList[i] = itrf.MulV(normalList[i])
normalList[i].Normalize()
SetAllHighLevelNormals(nrmTag, normalList)
op.Message(c4d.MSG_UPDATE)
c4d.EventAdd()
# Execute main()
if __name__=='__main__':
main()
I'm sorry for the length discussion and if there are still doubts around the setting / getting values for a NormalTag just drop a note.
Best, Riccardo