Bilinear surface#

A bilinear patch is the simplest tensor-product surface: four corners define a net of shape (3, 2, 2) and degrees \((1,1)\). Its coordinate lines are straight, although four noncoplanar corners produce a curved surface.

Inputs and corner convention#

nurbspy.nurbs_surface_bilinear.NurbsSurfaceBilinear takes four three-dimensional corner arrays. Its argument names follow this mapping:

Argument

Surface parameter

Control-net entry

P00

\((0,0)\)

P[:, 0, 0]

P01

\((1,0)\)

P[:, 1, 0]

P10

\((0,1)\)

P[:, 0, 1]

P11

\((1,1)\)

P[:, 1, 1]

In this helper’s notation,

\[\mathbf S(u,v) =(1-v)[(1-u)\mathbf P_{00}+u\mathbf P_{01}] +v[(1-u)\mathbf P_{10}+u\mathbf P_{11}].\]

The helper’s .NurbsSurface attribute exposes the usual evaluation and plotting interface.

Complete script#

Run python demos/documentation/bilinear_surface.py, or download the script.

"""Construct a bilinear surface from four corner points."""
import numpy as np
import matplotlib.pyplot as plt
import nurbspy as nrb

nrb.set_plot_options()

# This helper names its corners P00, P01, P10, P11 in this order:
# (u,v) = (0,0), (1,0), (0,1), (1,1).
P00 = np.array([0., 0., 0.])
P01 = np.array([2., 0., 0.3])
P10 = np.array([0., 2., 0.5])
P11 = np.array([2., 2., 1.5])
surface = nrb.NurbsSurfaceBilinear(P00, P01, P10, P11).NurbsSurface

u = np.array([0., 1., 0., 1.])
v = np.array([0., 0., 1., 1.])
corners = np.column_stack([P00, P01, P10, P11])
corner_error = np.max(np.abs(surface.get_value(u, v) - corners))
print("Control net shape:", surface.P.shape)
print(f"Degrees: ({surface.p}, {surface.q})")
print("Midpoint:", surface.get_value(0.5, 0.5)[:, 0].round(6))
print(f"Maximum corner error: {corner_error:.2e}")

fig, ax = surface.plot(surface_color=nrb.COLORS_MATLAB[0],
                       control_points=True, isocurves_u=7, isocurves_v=7)
ax.set_title("Bilinear surface from four corners")
ax.view_init(elev=25, azim=-55)
# Leave room for the 3D axis labels when displaying or exporting the figure.
fig.set_size_inches(7, 6)
fig.subplots_adjust(left=0.05, right=0.85, bottom=0.15, top=0.9)
ax.set(xlabel="$x$", ylabel="$y$", zlabel="$z$")
for axis in (ax.xaxis, ax.yaxis, ax.zaxis):
    axis.labelpad = 4
plt.show()

Output#

The midpoint is [1.    1.    0.575], the average of the four corners. The script checks that each corner is interpolated exactly to numerical precision. The red control net and black isoparametric curves are drawn by surface.plot(control_points=True, isocurves_u=7, isocurves_v=7).

A nonplanar bilinear patch with four corner control points and straight coordinate lines.

Four corners define a surface that is linear in each parameter separately.#