Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Handling direction of the normal tag

    Cinema 4D SDK
    r20 python
    3
    6
    1.5k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • m_tamuraM
      m_tamura
      last edited by

      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/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

      1 Reply Last reply Reply Quote 0
      • r_giganteR
        r_gigante
        last edited by r_gigante

        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:

        • Creating a NormalTag
        • NormalTag GetDataAddressW
        • normal tag?
        • Modifiying the normal-tag

        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

        1 Reply Last reply Reply Quote 3
        • m_tamuraM
          m_tamura
          last edited by

          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

          1 Reply Last reply Reply Quote 0
          • r_giganteR
            r_gigante
            last edited by

            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

            1 Reply Last reply Reply Quote 0
            • r_giganteR
              r_gigante
              last edited by

              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

              1 Reply Last reply Reply Quote 3
              • M
                m_adam
                last edited by

                Issues with GetAllHighlevelData() and VariableTag.SetAllHighlevelData() is now fixed in R21.

                Cheers,
                Maxime.

                MAXON SDK Specialist

                Development Blog, MAXON Registered Developer

                1 Reply Last reply Reply Quote 1
                • First post
                  Last post