SOLVED 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://plugincafe.maxon.net/topic/6302/6696_axis-position/7

Basically, 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 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

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.