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 )