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


Log in to reply