CE7453: B-splines
Published:
This post serves as a comprehensive exam preparation guide for CE7453, focusing on B-splines and NURBS - fundamental techniques for curve and surface representation in computer graphics and CAD.
B-splines
B-splines (Basis splines) represent a powerful mathematical tool for curve and surface representation in computer graphics, CAD systems, and geometric modeling. This post provides a comprehensive overview of B-splines, including their mathematical foundations, key properties, algorithms for evaluation and manipulation, and practical applications.
Historical Context
B-splines evolved from the need to overcome limitations of Bezier curves:
- 1946: Schoenberg introduces the concept of B-splines in approximation theory
- 1960s: Development of the recursive definition by de Boor, Cox, and Mansfield
- 1970s: Integration into CAD/CAM systems
- 1980s: Development of NURBS (Non-Uniform Rational B-Splines)
- Current: Industry standard for curve and surface representation
Key Concepts
1. B-spline Basis Functions
B-spline basis functions $N_{i,p}(t)$ are defined recursively:
Degree 0 (constant): \(N_{i,0}(t) = \begin{cases} 1 & \text{if } t_i \leq t < t_{i+1} \\ 0 & \text{otherwise} \end{cases}\)
Higher degrees: \(N_{i,p}(t) = \frac{t - t_i}{t_{i+p} - t_i} N_{i,p-1}(t) + \frac{t_{i+p+1} - t}{t_{i+p+1} - t_{i+1}} N_{i+1,p-1}(t)\)
where:
- $t$ is the parameter value
- $t_i$ are knot values from the knot vector
- $p$ is the degree of the basis function
Properties of Basis Functions
- Local Support: Each $N_{i,p}(t)$ is non-zero only in the interval $[t_i, t_{i+p+1}]$
- Partition of Unity: At any parameter value $t$, $\sum_{i=0}^{n} N_{i,p}(t) = 1$
- Non-negativity: $N_{i,p}(t) \geq 0$ for all $i$, $p$, and $t$
- Continuity: $C^{p-k}$ continuity at a knot with multiplicity $k$
2. Knot Vectors
A knot vector $T = {t_0, t_1, \ldots, t_m}$ is a non-decreasing sequence of parameter values:
- Uniform: Knots are equally spaced
- Open (clamped): First and last knots have multiplicity $p+1$
- Non-uniform: Unequal spacing between knots
Knot Vector Formulation
For a B-spline of degree $p$ with $n+1$ control points, the knot vector has $m+1$ knots where $m = n+p+1$.
Examples:
- Uniform: $T = {0, 1, 2, 3, 4, 5, 6}$
- Open uniform: $T = {0, 0, 0, 1, 2, 3, 3, 3}$ (cubic with multiplicity 3 at endpoints)
- Non-uniform: $T = {0, 0, 0, 1, 2, 2, 3, 3, 3}$ (multiplicity 2 at interior knot $t=2$)
Effect of Knot Multiplicity
- Single knot: $C^{p-1}$ continuity at that knot
- Double knot: $C^{p-2}$ continuity
- Multiplicity $k$: $C^{p-k}$ continuity
- Multiplicity $p$: $C^0$ continuity (curve passes through control point)
- Multiplicity $p+1$: Discontinuity at that knot
3. B-spline Curves
A B-spline curve is defined as:
\[P(t) = \sum_{i=0}^{n} P_i N_{i,p}(t)\]where:
- $P_i$ are the control points
- $N_{i,p}(t)$ are the B-spline basis functions of degree $p$
- $t$ is the parameter value within the domain $[t_p, t_{n+1}]$
Properties of B-spline Curves
- Local Control: Moving a control point affects only the curve segment within $p+1$ spans
- Convex Hull: Curve lies within the convex hull of $p+1$ consecutive control points
- Variation Diminishing: Curve doesn’t oscillate more than its control polygon
- Affine Invariance: Affine transformations can be applied to control points
- Endpoint Interpolation: With an open knot vector, the curve passes through first and last control points
4. Evaluation Algorithms
Finding the Knot Span
def find_span(t, knots):
"""
Find the knot span index for parameter t.
Parameters:
- t: Parameter value
- knots: Knot vector
Returns:
- Index of the knot span
"""
n = len(knots) - 1
# Special cases
if t >= knots[n]:
return n - 1
if t <= knots[0]:
return 0
# Binary search
low = 0
high = n
mid = (low + high) // 2
while t < knots[mid] or t >= knots[mid+1]:
if t < knots[mid]:
high = mid
else:
low = mid
mid = (low + high) // 2
return mid
Evaluating Basis Functions
def basis_functions(i, p, t, knots):
"""
Calculate all B-spline basis functions of degree p at parameter t.
Parameters:
- i: Knot span index
- p: Degree of the B-spline
- t: Parameter value
- knots: Knot vector
Returns:
- Array of p+1 basis function values
"""
N = [0.0] * (p + 1)
left = [0.0] * (p + 1)
right = [0.0] * (p + 1)
# Initialize degree 0 basis function
N[0] = 1.0
# Compute basis functions of increasing degree
for j in range(1, p + 1):
left[j] = t - knots[i + 1 - j]
right[j] = knots[i + j] - t
saved = 0.0
for r in range(j):
# Avoid division by zero
if right[r + 1] + left[j - r] == 0.0:
temp = 0.0
else:
temp = N[r] / (right[r + 1] + left[j - r])
N[r] = saved + right[r + 1] * temp
saved = left[j - r] * temp
N[j] = saved
return N
Evaluating B-spline Curve Points
def bspline_curve_point(t, control_points, knots, degree):
"""
Evaluate a B-spline curve at parameter t.
Parameters:
- t: Parameter value
- control_points: Array of control points (each can be a multidimensional point)
- knots: Knot vector
- degree: Degree of the B-spline
Returns:
- Point on the B-spline curve
"""
import numpy as np
# Find knot span
span = find_span(t, knots)
# Calculate basis functions
N = basis_functions(span, degree, t, knots)
# Initialize point with zeros matching the dimension of control points
point = np.zeros_like(control_points[0]).astype(float)
# Compute curve point as weighted sum of control points
for j in range(degree + 1):
point += N[j] * control_points[span - degree + j]
return point
Python Implementation: De Boor Algorithm
def de_boor(t, control_points, knots, degree):
"""
Evaluate B-spline curve at parameter t using De Boor algorithm.
Parameters:
- t: Parameter value
- control_points: Array of control points
- knots: Knot vector
- degree: Degree of the B-spline
Returns:
- Point on the B-spline curve
"""
# Find knot span
span = find_span(t, knots)
# Special case: t is at a knot
if t == knots[span]:
# Handle multiplicity (simplified for this example)
return control_points[span - degree]
# Initialize d array
d = [control_points[i] for i in range(span - degree, span + 1)]
# Apply De Boor recursion
for r in range(1, degree + 1):
for j in range(degree, r - 1, -1):
i = span - degree + j
alpha = (t - knots[i]) / (knots[i + degree - r + 1] - knots[i])
d[j] = (1.0 - alpha) * d[j - 1] + alpha * d[j]
return d[degree]
2. Knot Insertion
- Purpose: Add knots without changing curve shape
- Applications:
- Refinement for analysis
- Splitting curves
- Converting to Bezier representation
Knot Insertion Algorithm
To insert a knot t̄ into knot vector T (between tk and tk+1):
- New knot vector: T’ = {t₀, t₁, …, tk, t̄, tk+1, …, tm}
- New control points: Pi’ = αᵢPi + (1-αᵢ)Pi-1 for i = k-p+1, …, k where:
- αᵢ = (t̄ - ti)/(ti+p - ti) if i ∈ [k-p+1, k]
- αᵢ = 1 if i > k
- αᵢ = 0 if i < k-p+1
- Original control points outside the range [k-p+1, k] remain unchanged
def knot_insertion(t_bar, control_points, knots, degree):
"""
Insert a knot into a B-spline curve without changing its shape.
Parameters:
- t_bar: Parameter value of new knot
- control_points: Original control points
- knots: Original knot vector
- degree: Degree of the B-spline
Returns:
- New control points
- New knot vector
"""
import numpy as np
n = len(control_points) - 1
# Find knot span containing t_bar
k = find_span(t_bar, knots)
# Create new knot vector
new_knots = knots.copy()
new_knots.insert(k + 1, t_bar)
# Create new control points array
new_control_points = [None] * (n + 2)
# Copy unaffected control points
for i in range(k - degree + 1):
new_control_points[i] = control_points[i]
for i in range(k + 1, n + 1):
new_control_points[i + 1] = control_points[i]
# Calculate affected control points
for i in range(k - degree + 1, k + 1):
# Calculate alpha
if i <= k - degree:
alpha = 0.0
elif i >= k + 1:
alpha = 1.0
else:
alpha = (t_bar - knots[i]) / (knots[i + degree] - knots[i])
# Calculate new control point
new_control_points[i] = (1 - alpha) * control_points[i - 1] + alpha * control_points[i]
return new_control_points, new_knots
Bezier Decomposition
By inserting knots of multiplicity p at each interior knot, a B-spline can be converted into a series of Bezier curves. This is useful for rendering and intersection algorithms.
def bezier_decomposition(control_points, knots, degree):
"""
Decompose a B-spline curve into a series of Bezier curves.
Parameters:
- control_points: B-spline control points
- knots: B-spline knot vector
- degree: Degree of the B-spline
Returns:
- List of Bezier control point sets
"""
# Create deep copies to avoid modifying originals
cp = control_points.copy()
kv = knots.copy()
# Get unique internal knots
unique_internal_knots = []
for k in kv[degree:-degree]:
if k not in unique_internal_knots:
unique_internal_knots.append(k)
# Insert knots to get Bezier segments
for knot in unique_internal_knots:
# Determine current multiplicity
mult = kv.count(knot)
# Insert knot until multiplicity reaches degree
for _ in range(degree - mult):
cp, kv = knot_insertion(knot, cp, kv, degree)
# Extract Bezier segments
bezier_segments = []
segment_size = degree + 1
# For each segment
for i in range(len(cp) - degree):
if i % degree == 0:
segment = cp[i:i+segment_size]
if len(segment) == segment_size:
bezier_segments.append(segment)
return bezier_segments
3. Degree Elevation
- Process: Increase degree while preserving shape
- Application: Compatibility between curves of different degrees
Degree Elevation Algorithm
To increase the degree of a B-spline from p to p+1:
- Create a new knot vector by adding duplicates of each knot
- Calculate new control points through a linear combination of original points
- Preserve the shape of the original curve
def degree_elevate(control_points, knots, degree):
"""
Elevate the degree of a B-spline curve by 1.
Parameters:
- control_points: Original control points
- knots: Original knot vector
- degree: Original degree
Returns:
- New control points
- New knot vector
"""
n = len(control_points) - 1
m = len(knots) - 1
# New dimensions
new_n = n + (n + 1) # (n+1) new points
new_degree = degree + 1
new_m = m + 2 # 2 new knots
# Create new knot vector
new_knots = [0] * (new_m + 1)
# First degree+1 knots are same
for i in range(degree + 1):
new_knots[i] = knots[i]
# Last degree+1 knots are same
for i in range(1, degree + 2):
new_knots[new_m - degree - 1 + i] = knots[m - degree + i]
# Interior knots
for i in range(1, m - 2 * degree):
# For each original knot, insert it twice
new_knots[degree + 2*i] = knots[degree + i]
new_knots[degree + 2*i + 1] = knots[degree + i]
# Calculate new control points
new_control_points = [None] * (new_n + 1)
# First and last control points remain unchanged
new_control_points[0] = control_points[0]
new_control_points[new_n] = control_points[n]
# Calculate intermediate control points
for i in range(1, n):
alpha = i / (n + 1)
new_control_points[2*i] = (1 - alpha) * control_points[i-1] + alpha * control_points[i]
new_control_points[2*i+1] = (1 - alpha) * control_points[i] + alpha * control_points[i+1]
return new_control_points, new_knots
4. Non-Uniform Rational B-splines (NURBS)
- Definition: Rational extension of B-splines
- Equation: P(t) = Σi=0n wiPi Ni,k(t) / Σi=0n wi Ni,k(t)
- Advantages:
- Exact representation of conic sections
- Perspective invariance
- Extended modeling capabilities
NURBS Formulation
NURBS extend B-splines by associating a weight wi with each control point Pi. The rational basis functions are:
Ri,p(t) = wiNi,p(t) / Σj=0n wjNj,p(t)
The NURBS curve is then: P(t) = Σi=0n PiRi,p(t)
Representing Conic Sections
NURBS can exactly represent circles, ellipses, and other conic sections:
Circle: With degree p=2, n+1=9 control points, and weights:
- P₀, P₂, P₄, P₆, P₈ on the circle with w=1
- P₁, P₃, P₅, P₇ at intersections of tangent lines with w=cos(π/4) = 1/√2 ≈ 0.7071
def create_circle_nurbs():
"""
Create a NURBS representation of a circle.
Returns:
- Control points
- Weights
- Knot vector
"""
import numpy as np
# Degree
p = 2
# Control points for circle of radius 1 centered at origin
control_points = [
[1, 0], # P0
[1, 1], # P1
[0, 1], # P2
[-1, 1], # P3
[-1, 0], # P4
[-1, -1], # P5
[0, -1], # P6
[1, -1], # P7
[1, 0] # P8 (same as P0 to close the circle)
]
# Weights: 1 for points on circle, 1/√2 for corner points
weights = [
1.0, # P0
1.0/np.sqrt(2), # P1
1.0, # P2
1.0/np.sqrt(2), # P3
1.0, # P4
1.0/np.sqrt(2), # P5
1.0, # P6
1.0/np.sqrt(2), # P7
1.0 # P8
]
# Knot vector: open uniform
knots = [0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4]
return control_points, weights, knots
Python Implementation: NURBS Curve
def nurbs_curve_point(t, control_points, weights, knots, degree):
"""
Evaluate a NURBS curve at parameter t.
Parameters:
- t: Parameter value
- control_points: Array of control points
- weights: Array of weights
- knots: Knot vector
- degree: Degree of the NURBS
Returns:
- Point on the NURBS curve
"""
import numpy as np
n = len(control_points) - 1
num = np.zeros_like(control_points[0]).astype(float)
den = 0.0
# Find the span and calculate basis functions
span = find_span(t, knots)
basis = basis_functions(span, degree, t, knots)
# Calculate the point
for j in range(degree + 1):
i = span - degree + j
if 0 <= i <= n:
weight_basis = weights[i] * basis[j]
num += control_points[i] * weight_basis
den += weight_basis
return num / den if den != 0 else num
Visualization Example
Here’s a complete example to visualize B-spline curves with interactive control:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.path import Path
import matplotlib.patches as patches
def plot_bspline(control_points, knots, degree, num_points=100):
"""
Plot a B-spline curve with its control polygon.
"""
# Extract x and y coordinates
x_points = [p[0] for p in control_points]
y_points = [p[1] for p in control_points]
# Parameter range
t_min = knots[degree]
t_max = knots[len(knots) - degree - 1]
t_range = np.linspace(t_min, t_max, num_points)
# Calculate curve points
curve_points = []
for t in t_range:
point = bspline_curve_point(t, control_points, knots, degree)
curve_points.append(point)
# Extract curve coordinates
curve_x = [p[0] for p in curve_points]
curve_y = [p[1] for p in curve_points]
# Create plot
fig, ax = plt.subplots(figsize=(10, 8))
plt.subplots_adjust(bottom=0.25)
# Plot control polygon
control_line, = ax.plot(x_points, y_points, 'o-', color='gray', alpha=0.5, label='Control Polygon')
# Plot B-spline curve
curve_line, = ax.plot(curve_x, curve_y, 'r-', linewidth=2, label='B-spline Curve')
# Add legend and grid
ax.legend()
ax.grid(True)
ax.set_title(f'B-spline Curve (Degree {degree})')
ax.axis('equal')
plt.show()
# Example usage
if __name__ == "__main__":
# Control points for an S-shaped curve
control_points = [
[0, 0],
[1, 2],
[3, -1],
[5, 3],
[7, 0]
]
# Knot vector for cubic B-spline (degree 3)
knots = [0, 0, 0, 0, 0.5, 1, 1, 1, 1]
# Plot cubic B-spline
plot_bspline(control_points, knots, 3)
Applications
- CAD/CAM: Industry standard for representing curves and surfaces
- Animation: Control of smooth motion paths
- Font Design: TrueType fonts use B-splines
- Image Processing: Representing contours and geometric features
- FEA: Isogeometric analysis based on NURBS
B-splines in CAD/CAM
Modern CAD systems like CATIA, SolidWorks, and AutoCAD use NURBS as their underlying representation for curves and surfaces. This provides:
- Precise control over complex geometries
- Compact mathematical representation
- Efficient evaluation for rendering and analysis
- Preservation of design intent during editing
B-splines in Animation
Animation systems use B-splines for:
- Character motion paths
- Camera paths
- Interpolation between keyframes
- Deformations of character models
def create_animation_path(keyframes, frames_per_segment=30):
"""
Create a smooth animation path from keyframes using a B-spline.
Parameters:
- keyframes: List of key positions [[x0,y0], [x1,y1], ...]
- frames_per_segment: Number of frames between keyframes
Returns:
- List of path positions for each frame
"""
# Create a cubic B-spline
degree = 3
# Add duplicate end points for clamped B-spline
control_points = []
control_points.append(keyframes[0]) # Duplicate first
control_points.extend(keyframes)
control_points.append(keyframes[-1]) # Duplicate last
# Create open uniform knot vector
n = len(control_points) - 1
knots = []
# Add degree+1 zeros at start
for i in range(degree+1):
knots.append(0)
# Add middle knots
middle_knots = n - degree
for i in range(1, middle_knots):
knots.append(i/middle_knots)
# Add degree+1 ones at end
for i in range(degree+1):
knots.append(1)
# Generate path points
path = []
total_frames = frames_per_segment * (len(keyframes) - 1)
for frame in range(total_frames):
t = frame / total_frames
point = bspline_curve_point(t, control_points, knots, degree)
path.append(point)
return path
Isogeometric Analysis
This advanced technique unifies CAD and finite element analysis by using the same NURBS basis functions for both geometry representation and analysis, eliminating the need for geometry approximation through meshing.
def calculate_isogeometric_basis(t, knots, degree, derivative=0):
"""
Calculate NURBS basis functions for isogeometric analysis.
Parameters:
- t: Parameter value
- knots: Knot vector
- degree: Degree of basis functions
- derivative: Order of derivative (0=function, 1=first derivative)
Returns:
- Array of basis function values or derivatives
"""
# Basis function derivatives calculation
# Implementation depends on specific IGA approach
# This is a simplified example
span = find_span(t, knots)
if derivative == 0:
# Just return the basis functions
return basis_functions(span, degree, t, knots)
else:
# Calculate derivatives using finite differences
# Real implementations would use analytical derivatives
epsilon = 1e-6
basis_at_t = basis_functions(span, degree, t, knots)
basis_at_t_plus_e = basis_functions(span, degree, t + epsilon, knots)
# First derivative approximation
derivatives = [(b2 - b1) / epsilon for b1, b2 in zip(basis_at_t, basis_at_t_plus_e)]
return derivatives
Comparison with Bezier Curves
- Local Control: B-splines offer local control
- Continuity: Automatic higher-order continuity between segments
- Flexibility: Variable degree and knot placement
- Complexity: More parameters to control (knot vector, weights)
Side-by-Side Comparison
Feature | Bezier Curves | B-splines |
---|---|---|
Local Control | No | Yes |
Number of Control Points | Fixed by degree | Independent of degree |
Continuity Between Segments | Requires constraints | Automatic |
Exact Representation of Circle | No | Yes (with NURBS) |
Implementation Complexity | Simpler | More complex |
Degree | Same as number of control points - 1 | Independent parameter |
Exam Focus Areas
- Basis Function Calculation: Recursive calculation of basis functions
- Knot Vector Design: Understand effects of different knot vectors
- De Boor Algorithm: Implementation and application
- Continuity Analysis: Determining continuity at knots
- NURBS Understanding: Basic principles and advantages
Sample Exam Questions
- Calculate the non-zero basis functions at a specific parameter value.
- Determine the minimum degree required for specific continuity conditions.
- Explain how knot multiplicity affects the curve’s continuity.
- Convert a B-spline representation to a Bezier representation.
- Design a B-spline to interpolate a set of points with specific continuity.
Practice Problems
- Calculate B-spline basis functions for a given knot vector
- Implement the De Boor algorithm for curve evaluation
- Determine the effect of knot insertion on a B-spline curve
- Convert a B-spline curve to a set of Bezier curves
Challenge Problem
Design a cubic B-spline curve that interpolates the points (0,0), (2,4), (5,1), and (8,3) with C² continuity everywhere. Determine the appropriate knot vector and control points.
Solution Approach:
- Set up an interpolation matrix using the B-spline basis functions
- Solve the linear system to find control points
- Verify continuity conditions at the interpolation points
def solve_interpolation_problem():
"""
Solve the challenge problem: Interpolate points with C² continuity.
"""
import numpy as np
# Points to interpolate
points = np.array([
[0, 0],
[2, 4],
[5, 1],
[8, 3]
])
# For cubic B-spline with C² continuity
degree = 3
# Number of points
n = len(points)
# We need n+2 control points for interpolation with C² continuity
# Create an open uniform knot vector with appropriate multiplicity
# For cubic curve: [0,0,0,0, ..., 1,1,1,1]
knots = [0] * (degree + 1) + list(np.linspace(0, 1, n-2)[1:-1]) + [1] * (degree + 1)
# Parameter values for interpolation (chord length parameterization)
chord_lengths = [0]
total_length = 0
for i in range(1, n):
# Calculate Euclidean distance
dist = np.linalg.norm(points[i] - points[i-1])
total_length += dist
chord_lengths.append(total_length)
# Normalize parameters to [0,1]
params = [t / chord_lengths[-1] for t in chord_lengths]
# Set up interpolation system: A*x = b
A = np.zeros((n, n+2))
# First and last rows for C² end conditions
A[0, 0:3] = [1, -2, 1] # Second derivative at start = 0
A[n-1, n-1:n+2] = [1, -2, 1] # Second derivative at end = 0
# Middle rows for interpolation conditions
for i in range(1, n-1):
# For each parameter value, calculate basis functions
param = params[i]
span = find_span(param, knots)
basis = basis_functions(span, degree, param, knots)
# Fill row with basis function values
for j in range(degree + 1):
if span - degree + j < n+2:
A[i, span - degree + j] = basis[j]
# Set up right-hand side
b = np.zeros((n, 2))
b[1:n-1] = points[1:n-1] # Interior points
# End derivatives constraints remain zero
# Solve the system
control_points = np.linalg.solve(A, b)
return control_points, knots, degree, points
Beyond Basic B-splines: Advanced Topics
1. Tensor Product Surfaces
B-spline surfaces are formed using tensor products, with bidirectional control points and two knot vectors:
\[S(u,v) = \sum_{i=0}^{n} \sum_{j=0}^{m} P_{i,j} N_{i,p}(u) N_{j,q}(v)\]2. T-splines
T-splines extend NURBS by allowing T-junctions in the control mesh, enabling adaptive refinement without propagating control points.
3. Subdivision Surfaces
An alternative to NURBS that combines faceted modeling with smooth surface generation through recursive subdivision.
Conclusion
B-splines provide a powerful and flexible framework for representing curves and surfaces in computer graphics and CAD. Their mathematical properties, efficient evaluation algorithms, and intuitive geometric interpretation make them an essential tool for geometric modeling. Understanding B-splines and their extensions (NURBS) is crucial for anyone working in computer graphics, CAD/CAM, animation, and related fields.
References and Further Reading
- Piegl, L., & Tiller, W. (1997). The NURBS Book. Springer.
- Rogers, D. F. (2000). An Introduction to NURBS: With Historical Perspective. Morgan Kaufmann.
- Farin, G. (2002). Curves and Surfaces for CAGD: A Practical Guide. Morgan Kaufmann.
- de Boor, C. (1978). A Practical Guide to Splines. Springer-Verlag.
- Hughes, T. J. R., Cottrell, J. A., & Bazilevs, Y. (2005). Isogeometric analysis: CAD, finite elements, NURBS, exact geometry and mesh refinement. Computer Methods in Applied Mechanics and Engineering.