Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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
    • Recent
    • Tags
    • Users
    • Register
    • Login

    Reverse direction of multi-segment splines

    Scheduled Pinned Locked Moved Cinema 4D SDK
    windows2024python
    6 Posts 2 Posters 18 Views 1 Watching
    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.
    • T Offline
      Tpaxep
      last edited by

      Hi forum! When creating a spline with multiple segments (mostly text), the directions of some of the spline segments are inverted. I'd like to know if there's a simple and reliable way to fix this.I've tried several options, but they don't work correctly with deformed splines. Any advice would be greatly appreciated.spline_dir_problem.mp4

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

        Hello @Tpaxep,

        Your link does not work as your file upload seems to have failed. But there are in general two ways to achieve this.

        1. Manually reverse the spline. For a simple non-bezier spline this is trivial, you just reverse both the points and the segments. When you want to also support bezier splines, i.e., tangents, this becomes a bit more work.
        2. Just use the modeling command MCOMMAND_SPLINE_REVERSE. Modeling commands can be undesirable in some contexts, see the extrude example in Modeling Commands for details.

        Cheers,
        Ferdinand

        """Reverses the selected editable spline object.
        """
        
        import c4d
        
        doc: c4d.documents.BaseDocument  # The currently active document.
        op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
        
        def main() -> None:
            """Called by Cinema 4D when the script is being executed.
            """
            if not isinstance(op, c4d.SplineObject):
                return c4d.gui.MessageDialog("Please select a spline object.")
            
            doc.StartUndo()
            if not c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_SPLINE_REVERSE, list=[op], 
                                                 mode=c4d.MODELINGCOMMANDMODE_ALL, doc=doc):
                c4d.gui.MessageDialog("Failed to reverse the spline.")
        
            doc.EndUndo()
        
        if __name__ == '__main__':
            main()
        

        MAXON SDK Specialist
        developers.maxon.net

        T 1 Reply Last reply Reply Quote 0
        • T Offline
          Tpaxep @ferdinand
          last edited by Tpaxep

          Hi @ferdinand! I'm having trouble loading the site, so I'll leave the links. The reverse isn't the problem. I can't figure out how to automatically detect segments with "incorrect" direction, given their deformation, etc.

          gif
          https://www.dropbox.com/scl/fi/jqn3m9kpmd9i4q2d17lmn/spline_dir_problem.gif?rlkey=b1mt8rrb9uz8ecvge6lz6uppv&st=fhblss97&dl=0

          scene
          https://www.dropbox.com/scl/fi/nhpt7a2p2vapyvtl273ki/spline_dir_problem.c4d?rlkey=ykhkv4i5o895d124lcsqbomp2&st=4i07qpmq&dl=0

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

            Hey @Tpaxep,

            please have a look at How to Ask Questions. Your question is very ambigous, and it is impossible to help you like this. I assume the issue for you is that the "holes" in your font spline have another winding direction than the outline?

            4b5168f0-188e-435e-99ce-6a0906d6f706-image.png

            I.e., the 'hole' in the R is winding counter-clockwise/negatively, while the outline is winding clockwise/positively. This is a non-trivial problem from geometry processing, and our API does not offer any ready made tools for this niche problem. One of the many approaches can be to compute the signed area of the segment (or a bit more advanced: the shoelace formula), but this can fail for self-intersecting splines and will not work for 3D splines. You can also try to compute the winding direction of the spline by looking at the cross product of the tangents, but this will also fail for self-intersecting splines and 3D splines. For 3D splines, you can work with the vector area of the spline, but this will also fail for self-intersecting splines.

            There are generic solutions to this problem, but they are not trivial and discussing such problems is out of scope of support, unless the user approaches us with a specific question for an already implemented solution.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

            T 1 Reply Last reply Reply Quote 0
            • T Offline
              Tpaxep @ferdinand
              last edited by

              @ferdinand Yes, you really understood the problem correctly, and I also knew the solution was unlikely to be simple. I was more interested in the possible options. Thank you for your analysis and recommendations.

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

                Okay,

                I think I was a bit overly cautious in my answer here. You gave my a very broad question, or to be precise, you gave me a video and a scene file, and did not really ask a precise question. I understand that asking good questions can be hard, especially with a language barrier. But when I have nothing to go by, I of course assume the worst case possible.

                The scene you have there is a trivial case, even the best case possible. You have there spline segments which lie in a perfect plane and which have no self intersections. You can treat them just like polygons (in the CG sense, not in the mathematical sense) and simply compute their normal over the first three vertices of each segment. Due to the fundamental property of a polygon - reversing the order of the vertices reverses the normal - you can then easily determine if two segments have opposite or equal winding.

                But all this starts to fall apart, as soon as you cannot make these assumptions. And I cannot help you to write the code for this, as this is then more than just a few lines. Hope this helps, and that I my answer was now less 'overly cautious'.

                Cheers,
                Ferdinand

                Result

                85f415e7-7f45-4c00-9651-b4091ee543e2-image.png
                The code correctly identifies that in this six segment spline are four segments of one winding direction (clockwise in this case) and two of the other winding direction.

                winding_direction.c4d

                Code

                """Treats spline segments as polygons and compares their plane normals to find 
                segments with reversed winding order.
                """
                
                import c4d
                
                op: c4d.SplineObject # The currently active object in the scene.
                
                def main() -> None:
                    """Called by Cinema 4D when the script is being executed.
                    """
                    if not isinstance(op, c4d.SplineObject):
                        return c4d.gui.MessageDialog("Please select a spline object.")
                    
                    # Get all points in the spline and organize them into their segments.
                    points: list[c4d.Vector] = op.GetAllPoints()
                    segments: list[list[c4d.Vector]] = []
                
                    j: int = 0
                    for i in [op.GetSegment(i)["cnt"] for i in range(op.GetSegmentCount())]:
                        segments.append(points[j:j+i])
                        j += i
                
                    if len(segments) < 2:
                        return c4d.gui.MessageDialog("Please select a spline object with at least 2 segments.") 
                
                    # Now build normal data for the spline segments. This assumes:
                    #
                    #   - A spline where all points lie in a single plane, a '2D' spline in 3D space.
                    #   - No self intersections in the spline.
                    #   - A piecewise linear spline, i.e., what Cinema 4D calls a 'Linear' spline. When we have a
                    #     'Cubic' or 'Bezier' spline, we would have make it linear with 'Current State to Object' 
                    #     first.
                    #
                    # We build the normal for each segment over its three first vertices. The reason why we are doing 
                    # this is because of the fundamental identity of a polygon (in a computer graphics sense), 
                    # reversing the order of the vertices of a polygon will reverse the normal. So if we have a 
                    # spline with two segments with reversed winding order, they will have antiparallel normals (normals 
                    # pointing in opposite directions). 
                    segmentNormals: list[c4d.Vector] = []
                    for segment in segments:
                        if len(segment) < 3:
                            print("Segment has less than 3 points, skipping normal calculation.")
                            continue
                
                        a, b, c = segment[0], segment[1], segment[2]
                        edge1: c4d.Vector = b - a
                        edge2: c4d.Vector = c - b
                        normal: c4d.Vector = edge1.Cross(edge2).GetNormalized()
                        segmentNormals.append(normal)
                
                    # Now we just declare one segment as 'ground truth' and check if the other segments have normals 
                    # that are parallel or antiparallel to it. When we found a segment with an antiparallel normal, we 
                    # know we found a segment with reversed winding order. To check if two normals are parallel or 
                    # antiparallel, we just compute their dot product (i.e., spanned angle). When the dot product is 
                    # negative, the normals are antiparallel.
                    print(f"Establishing the first segment normal {segmentNormals[0]} as ground truth.")
                    baseNormal: c4d.Vector = segmentNormals[0]
                    for i, normal in enumerate(segmentNormals[1:], start=1):
                        isAntiparallel: bool = baseNormal.Dot(normal) < 0
                        print(f"Segment {i} normal: {normal} is {'antiparallel' if isAntiparallel else 'parallel'} "
                              f"to the base normal {baseNormal}.")
                        
                if __name__ == '__main__':
                    main()
                

                MAXON SDK Specialist
                developers.maxon.net

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