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

    Set Spline Tangents with Python Generator

    Cinema 4D SDK
    2
    3
    1.1k
    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.
    • J
      johntravolski
      last edited by

      My goal is to use a Python generator to create a spline with Bezier interpolation where each point has tangents lying in the same plane as the plane spanned by the point itself and its two adjacent points and is also parallel to the line going through the two adjacent points. C4D does this natively when you double click on a point. See the following video example:

      The video does exactly what I want, but now I need to figure out the mathematics of this in the Python generator. This is the code that I have so far, but I don't understand how to calculate the tangents to get exactly what I want:

      def main():
          verts = [c4d.Vector(50*ii, 50*(-1)**ii, 50*(-1)**int(ii/2)) for ii in range(10)] # just a sample
          total_points = len(verts)
          spline = c4d.SplineObject(total_points, c4d.SPLINETYPE_BEZIER)
          spline.ResizeObject(total_points, 1)
          spline.SetSegment(0, total_points, False)
      
          mag = op[c4d.ID_USERDATA,1]
          
          for pntnum, vert in enumerate(verts):
              spline.SetPoint(pntnum, vert)
              # i need to set the tangent here somehow!
      
          spline.Message(c4d.MSG_UPDATE)
          return spline
      

      The magnitude of the tangents would be a scalar value that I would want to control separately. If you could help me figure out how to calculate the tangents that are identical to what C4D created automatically in that video (besides magnitude), that would really help me out. Does anybody know the mathematics to calculate these tangents?

      1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand
        last edited by ferdinand

        Hi,

        the common way to do this would be constructing a frame for that vertex. Normally you would also not separate the calculation of the tangent lengths, because you have everything you need right there when building the frame. Here is a narrative example. Start reading in the main() function.

        """Example for constructing smooth tangents in a bezier spline.
        
        Run this with a spline object selected that has at least three vertices. It
        will set the tangents of the second vertex of the spline in respect to its
        neighbors.
        """
        
        import c4d
        
        
        def project_on_line(p, a, b):
            """Projects the point p onto the line which contains the line segment ab.
        
            Args:
                p (c4d.Vector): The point to project.
                a (c4d.Vector): The first point of the line segment.
                b (c4d.Vector): The second point of the line segment.
        
            Returns:
                c4d.Vector: The projection of p onto the line described by the line
                 segment ab.
            """
            # Not much to explain here, check a trigonometry textbook on point line
            # projections / the dot product if you are lost.
            ab = b - a
            t = ((p - a) * ab) / ab.GetLengthSquared()
            return a + ab * t
        
        
        def construct_vertex_frame(p, q, r):
            """Constructs a frame for a spline vertex and its two normalized tangent
             lengths.
        
            Args:
                p (c4d.Vector): The point of the vertex for which to construct the
                 frame.
                q (c4d.Vector): The point of the vertex which is the vertex before p.
                r (c4d.Vector): The point of the vertex which is the vertex after p.
        
            Returns:
                float, float, c4d.Vector: The normalized length of the left and
                 right tangent and the frame.
            """
        
            # The Vectors pointing from p to q and from p to r.
            a, b = q - p, r - p
        
            # Now we construct our frame axis, I did use axis labels instead of the
            # terms normal, binormal and tangent to keep things unambiguous.
        
            # The x-axis or tangent of the frame, i.e. the axis along which we will
            # place out tangent control points.
            x = (r - q).GetNormalized()
            # The z-axis or normal of the frame, which is the normal to the plane
            # of the three points. We just take the cross product of two vectors that
            # lie in that plane.
            z = (x % a).GetNormalized()
            # The y axis or binormal of the frame, we just build it with the two
            # other axis.
            y = (x % z).GetNormalized()
            # The finished matrix/frame. It is important to set the offset to the
            # zero vector and not p, since each tangent pair lives in its own tangent
            # space relative to their vertex.
            frame = c4d.Matrix(c4d.Vector(), x, y, z)
        
            # We are technically finished here, but we could do some more, as we
            # will have to determine the length of each tangent. We could just set
            # these to a fraction of the vectors a, b, but this will give us odd
            # results when theta, the angle between a and b is getting smaller and
            # smaller. Instead we will project both a and b onto x and return the
            # lengths of those two projections as the normalized length for the
            # respective tangent.
            origin = c4d.Vector()
            tlength_left = project_on_line(a, origin, x).GetLength()
            tlength_right = project_on_line(b, origin, x).GetLength()
        
            # Return the length of the left and right tangent and the frame.
            return tlength_left, tlength_right, frame
        
        
        def align_tangents(node, i, rel_scale_left=.5, rel_scale_right=.5):
            """Aligns the tangents of a spline at a given index.
        
            Calculates the frame for the given vertex and sets its tangents for a
            smooth interpolation in respect to its two neighbors. Will also force
            the spline into bezier mode.
        
            Note:
                This currently cannot handle the first and last vertex. It is trivial
                to implement this, but things would have gotten more bloated, so I
                kept short and (slightly) flawed.
        
            Args:
                node (c4d.SplineObject): The spline.
                i (int): The index of the vertex to align the tangents for. Has to
                 satisfy "0 < i < vertex_count - 1".
                rel_scale_left (float, optional): The relative scale of the left
                 tangent. Defaults to 50%.
                rel_scale_right (float, optional): The relative scale of the right
                 tangent. Defaults to 50%.
        
            Raises:
                TypeError: When 'node' is not a SplineObject. 
                ValueError: When 'i' is out of bounds.
            """
            if not isinstance(node, c4d.SplineObject):
                raise TypeError("Expected SplineObject as input.")
        
            points = node.GetAllPoints()
            # Out of bounds condition for the spline vertex indices.
            if i < 1 or len(points) < i + 1:
                raise ValueError("Index out of bounds.")
        
            # The three consecutive spline vertices that form the plane. p is
            # the mid vertex for which we want to set the tangents and q and r
            # are the vertices previous and next to it.
            q, p, r = points[i-1:i+2]
            # The normalized length of the tangents and the frame.
            tlength_left, tlength_right, frame = construct_vertex_frame(p, q, r)
        
            # Constructing the tangents:
            tangent_left = c4d.Vector(-tlength_left * rel_scale_left, 0, 0) * frame
            tangent_right = c4d.Vector(tlength_right * rel_scale_right, 0, 0) * frame
        
            # Update the spline.
            if node[c4d.SPLINEOBJECT_TYPE] is not c4d.SPLINETYPE_BEZIER:
                node[c4d.SPLINEOBJECT_TYPE] = c4d.SPLINETYPE_BEZIER
            node.SetTangent(i, tangent_left, tangent_right)
            node.Message(c4d.MSG_UPDATE)
        
            # For visualization purposes we insert the frame of our vertex as a
            # null object. SHOULD BE COMMENTED OUT.
            null = c4d.BaseList2D(c4d.Onull)
            null.SetMg(frame)
            null.SetName("frame_for_vertex_" + str(i))
            doc.InsertObject(null)
        
            # Push the changes to Cinema.
            c4d.EventAdd()
        
        
        def main():
            """Entry point.
            """
            # ALign the tangents of the second vertex of the selected spline object.
            if op:
                align_tangents(op, 1)
        
        
        if __name__ == "__main__":
            main()
        
        

        Cheers,
        zipit

        MAXON SDK Specialist
        developers.maxon.net

        J 1 Reply Last reply Reply Quote 1
        • J
          johntravolski @ferdinand
          last edited by Manuel

          @zipit said in Set Spline Tangents with Python Generator:

          > """Example for constructing smooth tangents in a bezier spline.
          > 
          > Run this with a spline object selected that has at least three vertices. It
          > will set the tangents of the second vertex of the spline in respect to its
          > neighbors.
          > """
          > 
          > import c4d
          > 
          > 
          > def project_on_line(p, a, b):
          >     """Projects the point p onto the line which contains the line segment ab.
          > 
          >     Args:
          >         p (c4d.Vector): The point to project.
          >         a (c4d.Vector): The first point of the line segment.
          >         b (c4d.Vector): The second point of the line segment.
          > 
          >     Returns:
          >         c4d.Vector: The projection of p onto the line described by the line
          >          segment ab.
          >     """
          >     # Not much to explain here, check a trigonometry textbook on point line
          >     # projections / the dot product if you are lost.
          >     ab = b - a
          >     t = ((p - a) * ab) / ab.GetLengthSquared()
          >     return a + ab * t
          > 
          > 
          > def construct_vertex_frame(p, q, r):
          >     """Constructs a frame for a spline vertex and its two normalized tangent
          >      lengths.
          > 
          >     Args:
          >         p (c4d.Vector): The point of the vertex for which to construct the
          >          frame.
          >         q (c4d.Vector): The point of the vertex which is the vertex before p.
          >         r (c4d.Vector): The point of the vertex which is the vertex after p.
          > 
          >     Returns:
          >         float, float, c4d.Vector: The normalized length of the left and
          >          right tangent and the frame.
          >     """
          > 
          >     # The Vectors pointing from p to q and from p to r.
          >     a, b = q - p, r - p
          > 
          >     # Now we construct our frame axis, I did use axis labels instead of the
          >     # terms normal, binormal and tangent to keep things unambiguous.
          > 
          >     # The x-axis or tangent of the frame, i.e. the axis along which we will
          >     # place out tangent control points.
          >     x = (r - q).GetNormalized()
          >     # The z-axis or normal of the frame, which is the normal to the plane
          >     # of the three points. We just take the cross product of two vectors that
          >     # lie in that plane.
          >     z = (x % a).GetNormalized()
          >     # The y axis or binormal of the frame, we just build it with the two
          >     # other axis.
          >     y = (x % z).GetNormalized()
          >     # The finished matrix/frame. It is important to set the offset to the
          >     # zero vector and not p, since each tangent pair lives in its own tangent
          >     # space relative to their vertex.
          >     frame = c4d.Matrix(c4d.Vector(), x, y, z)
          > 
          >     # We are technically finished here, but we could do some more, as we
          >     # will have to determine the length of each tangent. We could just set
          >     # these to a fraction of the vectors a, b, but this will give us odd
          >     # results when theta, the angle between a and b is getting smaller and
          >     # smaller. Instead we will project both a and b onto x and return the
          >     # lengths of those two projections as the normalized length for the
          >     # respective tangent.
          >     origin = c4d.Vector()
          >     tlength_left = project_on_line(a, origin, x).GetLength()
          >     tlength_right = project_on_line(b, origin, x).GetLength()
          > 
          >     # Return the length of the left and right tangent and the frame.
          >     return tlength_left, tlength_right, frame
          > 
          > 
          > def align_tangents(node, i, rel_scale_left=.5, rel_scale_right=.5):
          >     """Aligns the tangents of a spline at a given index.
          > 
          >     Calculates the frame for the given vertex and sets its tangents for a
          >     smooth interpolation in respect to its two neighbors. Will also force
          >     the spline into bezier mode.
          > 
          >     Note:
          >         This currently cannot handle the first and last vertex. It is trivial
          >         to implement this, but things would have gotten more bloated, so I
          >         kept short and (slightly) flawed.
          > 
          >     Args:
          >         node (c4d.SplineObject): The spline.
          >         i (int): The index of the vertex to align the tangents for. Has to
          >          satisfy "0 < i < vertex_count - 1".
          >         rel_scale_left (float, optional): The relative scale of the left
          >          tangent. Defaults to 50%.
          >         rel_scale_right (float, optional): The relative scale of the right
          >          tangent. Defaults to 50%.
          > 
          >     Raises:
          >         TypeError: When 'node' is not a SplineObject. 
          >         ValueError: When 'i' is out of bounds.
          >     """
          >     if not isinstance(node, c4d.SplineObject):
          >         raise TypeError("Expected SplineObject as input.")
          > 
          >     points = node.GetAllPoints()
          >     # Out of bounds condition for the spline vertex indices.
          >     if i < 1 or len(points) < i + 1:
          >         raise ValueError("Index out of bounds.")
          > 
          >     # The three consecutive spline vertices that form the plane. p is
          >     # the mid vertex for which we want to set the tangents and q and r
          >     # are the vertices previous and next to it.
          >     q, p, r = points[i-1:i+2]
          >     # The normalized length of the tangents and the frame.
          >     tlength_left, tlength_right, frame = construct_vertex_frame(p, q, r)
          > 
          >     # Constructing the tangents:
          >     tangent_left = c4d.Vector(-tlength_left * rel_scale_left, 0, 0) * frame
          >     tangent_right = c4d.Vector(tlength_right * rel_scale_right, 0, 0) * frame
          > 
          >     # Update the spline.
          >     if node[c4d.SPLINEOBJECT_TYPE] is not c4d.SPLINETYPE_BEZIER:
          >         node[c4d.SPLINEOBJECT_TYPE] = c4d.SPLINETYPE_BEZIER
          >     node.SetTangent(i, tangent_left, tangent_right)
          >     node.Message(c4d.MSG_UPDATE)
          > 
          >     # For visualization purposes we insert the frame of our vertex as a
          >     # null object. SHOULD BE COMMENTED OUT.
          >     null = c4d.BaseList2D(c4d.Onull)
          >     null.SetMg(frame)
          >     null.SetName("frame_for_vertex_" + str(i))
          >     doc.InsertObject(null)
          > 
          >     # Push the changes to Cinema.
          >     c4d.EventAdd()
          > 
          > 
          > def main():
          >     """Entry point.
          >     """
          >     # ALign the tangents of the second vertex of the selected spline object.
          >     if op:
          >         align_tangents(op, 1)
          > 
          > 
          > if __name__ == "__main__":
          >     main()
          

          This was extremely helpful, thank you very much. My 3d geometry / Calc III is pretty rusty and seeing this really helped.

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