Handling direction of the normal tag
-
I trying to make the axis tool in my plugin.
After moving or rotating the object, use the inverse matrix to return each point to its original position.
My method almost similar to this topic
https://developers.maxon.net/forum/topic/6302/6696_axis-position/7Basically, move and rotation are working well.
But, if the object has a normal tag, normal directions are not back to correct.I have not enough knowledge about handling the orientation of normals, so probably I overlook something.
Would you have any solution?
Best regards,
Makoto Tamura -
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
to65535
. 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]
theGetAllHighlevelData
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 is3200
whilst it should be55935
).
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
-
Hi Riccardo-san,
Thank you for your very helpful!!
And sorry late reply.Thanks to my understanding of what I should do.
I will add code to solve it from now on and report again. It may take a while, but if I do not understand, I may post a question again.However, your advice was very helpful. Thanks a million!
Makoto Tamura
-
Hi Tamura-san,
with regard to the discussion above, digging down the research on NormalTag, I'm sorry to notify that we'd discovered that the VariableTag::GetAllHighlevelData() is currently affected by a bug and can return unexpected results.
The issue has been identified and the fix will be available in the next revision.
Best, Riccardo
-
Hi Tamura-san,
as temporary workaround to the issue mentioned above, you could consider accessing the low-level data and convert them accordingly with the following functions
def bytes2float(low_byte, high_byte): # assemble the UInt representation of the normal int_value = low_byte + 256 * high_byte # just convert to float if int_value > 33535: # should be converted in a negative value return float((int_value - 65536) / 32000.0) else: return float(int_value / 32000.0) # Set the normal values for the vertexes belonging to a given polygon def GetLowLevelNormals(tag, polygon): normal_list = [] normal_buffer = tag.GetLowlevelDataAddressR() vector_size = 6 component_size = 2 # loop over the number of normals stored per polygon for v in range(0,4): nrm = c4d.Vector(0); # loop over the number of componentrs stored per normal for c in range(0,3): # get the two-bytes representation of the component's float low_byte = ord(normal_buffer[tag.GetDataSize() * polygon + v * vector_size + c * component_size + 0]) high_byte = ord(normal_buffer[tag.GetDataSize() * polygon + v * vector_size + c * component_size + 1]) # just convert to float nrm[c] = bytes2float(low_byte, high_byte) normal_list.append(nrm) return normal_list
Let me know if you need further details on it.
Best, Riccardo
-
Issues with GetAllHighlevelData() and VariableTag.SetAllHighlevelData() is now fixed in R21.
Cheers,
Maxime.