> """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.