Skip to content

Euclidean Curves

Euclidean curves, also known as conic sections, have been studied for centuries. They represent the most basic and commonly used curves in geometry. Traditionally they are considered in the plane but by transformation can be situated anywhere in space.

Line

Lines and finite segments thereof are the most common curves used in geometry. A line can be defined parametrically either by two points p and q as L( t ) = p * ( 1 - t ) + q * t, or by an origin point o and a direction u as L( t ) = o + u * t. Even though they are equivalent, the two-point representation is associated with linear interpolation and commonly used for line segments, where t: [0, 1], while the point-vector form is closer semantically to a ray or half-line, where t: [0, +oo), or commonly used for infinite lines, where t: (-oo, +oo).

class Line:
    def __init__( self, source, target ):
        self.Source = source
        self.Target = target

    def Evaluate( self, t ):
        return ( self.Source * ( 1 - t ) +
                 self.Target * t )

    def Tangent( self, t ):
        T = self.Target - self.Source
        T.Unitize( )
        return T

    def Normal( self, t ):
        return Plane( self.Source,
            self.Target - self.Source ).XAxis

A Line class can be defined as seen in the code. Note that the tangent vector is constant and independent from the parameter t. Additionally, the normal vector is ill-defined so a basis vector from a normal plane is used instead.

Rhino provides the Line data types which can be defined using either a point and vector or two points, see documentation. Internally, the line is defined by its end-points named as From and To. The origin is the point From and the direction as To - From or access using the Direction property.

""" Line from Two Points
"""
p = Point3d( px, py, pz )
q = Point3d( qx, qy, qz )

ln = Line( p, q )

""" Line from Origin and Direction
"""
o =  Point3d( ox, oy, oz )
u = Vector3d( ux, uy, uz )

ln = Line( o, u )

""" Start and End Points
"""
source = ln.From
target = ln.To

""" Line's Direction
"""
direction = target - source
direction = ln.Direction

Circle

Circle is the second most commonly used curve and can be defined in the plane by a center point c and a radius r. Additionally, in 3D space, a coordinate system or plane basis is required such as P = { o, u, v }, where o is the origin and u and v are its orthonormal basis vectors. By convention, spatial circle's centers coincide with the planes' origins c = o. Therefore, a general 3D circle is defined as C = { o, u, v, r }. The parametric equation of the circle based on those is C( t ) = o + u * cos( t ) * r + v * sin( t ) * r. It is also possible to absorb the radius into the basis vectors such that |u| = |v| = r and express the circle using just its basis { o, u, v } as C( t ) = o + u * cos( t ) + v * sin( t ). The parameterization of a circle is typically in the angle sense, where t: [0, 2π).

class Circle:
    def __init__( self, basis, radius = 1.0 ):
        self.Basis  = basis
        self.Radius = radius

    def Evaluate( self, angle ):
        x = self.Radius * math.cos( angle )
        y = self.Radius * math.sin( angle )

        return ( self.Basis.Origin +
                 self.Basis.XAxis * x +
                 self.Basis.YAxis * y )

    def Tangent( self, angle ):
        x = -math.sin( angle )
        y =  math.cos( angle )

        return ( self.Basis.XAxis * x +
                 self.Basis.YAxis * y )

    def Normal( self, angle ):
        x = -math.cos( angle )
        y = -math.sin( angle )

        return ( self.Basis.XAxis * x +
                 self.Basis.YAxis * y )

Rhino provides the Circle class, see documention, which allows constructing circles by a center point and a radius in 2D, or a plane and a radius in 3D. Note that in both cases the circle is internally stored using the plane-radius form. The center-radius definition is converted into a translated XY-plane at the circle's center. The circle's center and radius can be accessed using the Center and Radius properties, while its basis using the Plane property.

""" Planar Circle
"""
center = Point3d( cx, cy, cz )
radius = float( positive )

circle = Circle( center, radius )

""" Spatial Circle
"""
circle = Circle( Plane.WorldYZ, radius )

""" Circle's Properties
"""
center = circle.Center
radius = circle.Radius
plane  = circle.Plane

Ellipse

In one sense, an ellipse is a generalization of a circle using two scaling factors a and b instead of a single one, namely its radius r. Points on an ellipse are expressed in space using the form E( t ) = o + u * cos( t ) * a + v * sin( t ) * b. The scalars a and b are also known as the semi-major and semi-minor axes coefficients. Again it is possible to absorb them into the basis vectors such that |u| = a and |v| = b and therefore use exactly the same equation as the circle E( t ) = o + u * cos( t ) + v * sin( t ). The parameterization is also in angle sense, where t: [0, 2π).

class Ellipse:
    def __init__( self, basis, major = 1.0, minor = 1.0 ):
        self.Basis  = basis
        self.Radius = Vector2d( major, minor )

    def Evaluate( self, angle ):
        x = self.Radius.X * math.cos( angle )
        y = self.Radius.Y * math.sin( angle )

        return ( self.Basis.Origin +
                 self.Basis.XAxis * x +
                 self.Basis.YAxis * y )

    def Tangent( self, angle ):
        x = -self.Radius.X * math.sin( angle )
        y =  self.Radius.Y * math.cos( angle )

        T = ( self.Basis.XAxis * x +
              self.Basis.YAxis * y )
        T.Unitize( )
        return T

    def Normal( self, angle ):
        return Vector3d.CrossProduct(
            self.Basis.ZAxis, self.Tangent( angle ) )

Rhino provides the Ellipse class, see documention, which allows constructing ellipses by a plane and radii or three points. The ellipse's center and radii can be accessed using the Center, Radius1, Radius2 properties, and its basis using the Plane property. However, the Ellipse does not implement the Curve interface requiring converting the curve into a Nurbs form using ToNurbsCurve( ) method.

""" Ellipse
"""
center = Point3d( cx, cy, cz )
a, b = float( positive ), float( positive )

ellipse = Ellipse( Plane( center,
    Vector3d.XAxis, Vector3d.YAxis ), a, b )

""" Circle's Properties
"""
center = ellipse.Center
a, b   = ellipse.Radius1, ellipse.Radius2
plane  = ellipse.Plane

Parabola

Parabolas are quadratic polynomial curves defined as y = ax2. There are more complex versions including additional linear terms such as y = ax2 + bx + c. To spatialize a parabola a basis plane is required such that points on the curve can be expressed as seen below.

class Parabola:
    def __init__( self, basis, a = 1, b = 0, c = 0 ):
        self.Basis = basis
        self.Coefficients = [a, b, c]

    def Evaluate( self, x ):
        y = ( self.Coefficients[0] * x * x +
              self.Coefficients[1] * x +
              self.Coefficients[2] )

        return ( self.Basis.Origin +
                 self.Basis.XAxis * x +
                 self.Basis.YAxis * y )

    def Tangent( self, x ):
        y = ( self.Coefficients[0] * x * 2 +
              self.Coefficients[1] )

        T = ( self.Basis.XAxis * 1 +
              self.Basis.YAxis * y )
        T.Unitize( )
        return T

    def Normal( self, angle ):
        return Vector3d.CrossProduct(
            self.Basis.ZAxis, self.Tangent( angle ) )

Parabolas are conic sections where a plane intersects a cone at a bias while cutting through the cone's axis. Another definition for a parabola is the curve whose points are equidistant from a point, also known as focus and a line, also known as a directrix, see additional information.

Rhino represents parabolas as second degree Bezier curves with three control points, and provides three methods for constructing them, namely CreateParabolaFromFocus( ), CreateParabolaFromPoints( ) and CreateParabolaFromVertex( ), see documentation.

""" Parabola from vertex, start and end points
"""
parabola = NurbsCurve.CreateParabolaFromVertex(
    Point3d( vx, vy, vz ),
    Point3d( sx, sy, sz ),
    Point3d( ex, ey, ez ) )

Hyperbola

Hyperbolas in their simplest form are curves defined as y = a / x. They also represent conic sections where the plane is parallel to the cone's axis. Alternatively, a hyperbola can be defined by the relationship where the difference of distances between two points, also known as focii, is fixed for all points on the curve, see additional information.

class Hyperbola:
    def __init__( self, basis, alpha = 1 ):
        self.Basis = basis
        self.Alpha = alpha

    def Evaluate( self, x ):
        return ( self.Basis.Origin +
                 self.Basis.XAxis * x +
                 self.Basis.YAxis * self.Alpha / x )

    def Tangent( self, x ):
        T = ( self.Basis.XAxis * 1 +
              self.Basis.YAxis * -self.Alpha / ( x * x ) )
        T.Unitize( )
        return T

    def Normal( self, angle ):
        return Vector3d.CrossProduct(
            self.Basis.ZAxis, self.Tangent( angle ) )

Rhino does not provide a method for constructing hyperbolas programmatically even though it is possible to construct them via the user interface. The curves created are second degree rational Bezier curves.

Spiral

The term spiral captures a family of planar curves associated with a circular motion where the radius is continuously changing, or in other words the radius is a function of the angle. The definition below captures all spiral curves by requiring a callable radius parameter. The tangent and normal vectors are compute approximately using the finite differences scheme.

class Spiral:
    def __init__( self, basis, radius = None ):
        self.Basis  = basis
        if( radius is None )
            self.Radius = lambda angle: angle
        else:
            self.Radius = radius

    def Evaluate( self, angle ):
        x = self.Radius( angle ) * math.cos( angle )
        y = self.Radius( angle ) * math.sin( angle )
        return ( self.Basis.Origin +
                 self.Basis.XAxis * x +
                 self.Basis.YAxis * y )

    def Tangent( self, angle ): #-- Approximate
        T = ( self.Evaluate( angle + 1e-5 ) -
              self.Evaluate( angle        ) )
        T.Unitize( )
        return T

    def Normal( self, angle ): #-- Approximate
        return Vector3d.CrossProduct(
            self.Basis.ZAxis, self.Tangent( angle ) )

Specific spirals include the Archimedean, where radius = lambda angle: angle, hyperbolic, where radius = lambda alpha: 1.0 / alpha, and many others, see additional information

""" Archimedean Spiral
"""
spiral = Spiral( Plane.WorldXY )

""" Hyperbolic Spiral
"""
spiral = Spiral( Plane.WorldXY,
    lambda angle: 1.0 / angle )

Helix

Helices are associated with spirals but they three-dimensional curves. They are defined using a basis and a radius function. The helical shape is accomplished by translating along the basis' plane Z-axis, in typically linear fashion, as seen below.

class Helix:
    def __init__( self, basis, radius = None ):
        self.Basis  = basis
        if( radius is None ):
            self.Radius = lambda angle: 1.0
        elif( isinstance( radius, float ) ):
            self.Radius = lambda angle: radius
        else:
            self.Radius = radius

    def Evaluate( self, t ):
        x = self.Radius( t ) * math.cos( t * math.pi * 2 )
        y = self.Radius( t ) * math.sin( t * math.pi * 2 )
        return ( self.Basis.Origin +
                 self.Basis.XAxis * x +
                 self.Basis.YAxis * y +
                 self.Basis.ZAxis * t )

    def Tangent( self, angle ): #-- Approximate
        T = ( self.Evaluate( angle + 1e-5 ) -
              self.Evaluate( angle        ) )
        T.Unitize( )
        return T

    def Normal( self, angle ): #-- Approximate
        return Vector3d.CrossProduct(
            self.Basis.ZAxis, self.Tangent( angle ) )

Examples of various helices are presented below. Note that if the radius is omitted, then the helix has unit radius. Alternatively, if the radius parameter is a number, then the radius is constant. In the most general case a lambda can be passed.

""" Unit Radius
"""
helix = Helix( Plane.WorldXY )

""" Constant Radius
"""
helix = Helix( Plane.WorldXY, 2.0 )

""" Archimedean
"""
helix = Helix( Plane.WorldXY,
    lambda t: t * math.pi * 2 )