import numpy as np
import matplotlib.pyplot as plt
from math import comb
# ==========================
# Utilidades matemáticas
# ==========================
def bezier_curve(control_points, num=200):
"""
Evalúa una curva Bézier (grado n-1) usando la forma de Bernstein.
control_points: array de shape (n, 2)
num: número de muestras en [0,1]
"""
P = np.asarray(control_points, dtype=float)
n = P.shape[0] - 1
t = np.linspace(0.0, 1.0, num)
# Coeficientes binomiales
C = np.array([comb(n, i) for i in range(n+1)], dtype=float)
# Potencias de t y (1-t)
T = np.array([t**i for i in range(n+1)])
U = np.array([(1 - t)**(n - i) for i in range(n+1)])
# Base de Bernstein para cada i en 0..n y cada t
B = C[:, None] * T * U # shape (n+1, num)
# Curva: sum_i B_i(t) * P_i
curve = B.T @ P # shape (num, 2)
return curve, t
def make_open_uniform_knot_vector(n_ctrl, degree):
"""
Vector nudo abierto y uniforme.
n_ctrl: número de puntos de control
degree: grado p
"""
p = degree
m = n_ctrl + p + 1 # número de nudos
# p+1 ceros, n_ctrl - p - 1 intermedios uniformes, p+1 unos
knots = np.zeros(m)
knots[p : m - p] = np.linspace(0, 1, m - 2*p)
knots[m - p :] = 1.0
return knots
def de_boor(k, x, t, c, p):
"""
Algoritmo de De Boor para evaluar B-spline.
k: índice tal que t[k] <= x < t[k+1]
x: parámetro en [t[p], t[-p-1]]
t: vector nudo
c: puntos de control (array Nx2)
p: grado
"""
d = [c[i + k - p].astype(float).copy() for i in range(0, p + 1)]
for r in range(1, p + 1):
for j in range(p, r - 1, -1):
i = j + k - p
denom = t[i + p + 1 - r] - t[i]
if denom == 0:
alpha = 0.0
else:
alpha = (x - t[i]) / denom
d[j] = (1.0 - alpha) * d[j - 1] + alpha * d[j]
return d[p]
def bspline_curve(control_points, degree=3, num=300):
"""
Evalúa una B-spline abierta y uniforme.
control_points: array de shape (n,2)
degree: grado p (por defecto cúbica)
num: número de muestras
"""
P = np.asarray(control_points, dtype=float)
n_ctrl = P.shape[0]
p = degree
if n_ctrl <= p:
raise ValueError("Se requieren más puntos de control que el grado (n_ctrl >
degree).")
knots = make_open_uniform_knot_vector(n_ctrl, p)
# Rango válido de x: [t[p], t[-p-1]]
low, high = knots[p], knots[-p-1]
xs = np.linspace(low, high, num, endpoint=False) # evitar tocar el último nudo
C = np.array([de_boor(np.searchsorted(knots, x) - 1, x, knots, P, p) for x in
xs])
return C, xs, knots
# ============================
# Ejemplo 1: Bézier cúbica
# ============================
bezier_ctrl = np.array([
[0.0, 0.0],
[0.5, 1.2],
[1.2, -0.8],
[2.0, 0.8]
])
curve_bz, tbz = bezier_curve(bezier_ctrl, num=400)
plt.figure(figsize=(7, 5))
# Control polygon
plt.plot(bezier_ctrl[:, 0], bezier_ctrl[:, 1], marker='o', linestyle='--',
linewidth=1.0, label='Polígono de control')
# Curve
plt.plot(curve_bz[:, 0], curve_bz[:, 1], linewidth=2.0, label='Curva Bézier (grado
3)')
plt.title("Curva Bézier cúbica")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.axis('equal')
plt.grid(True)
plt.show()
# ============================
# Ejemplo 2: B-spline cúbica
# ============================
bspline_ctrl = np.array([
[0.0, 0.0],
[0.5, 1.0],
[1.2, 1.2],
[2.0, 0.2],
[2.5, 0.8],
[3.0, 0.0],
[3.4, 1.0]
])
curve_bs, tbs, knots = bspline_curve(bspline_ctrl, degree=3, num=600)
plt.figure(figsize=(7, 5))
plt.plot(bspline_ctrl[:, 0], bspline_ctrl[:, 1], marker='o', linestyle='--',
linewidth=1.0, label='Polígono de control')
plt.plot(curve_bs[:, 0], curve_bs[:, 1], linewidth=2.0, label='B-spline cúbica
(abierta, uniforme)')
plt.title("Curva B-spline cúbica (control local)")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.axis('equal')
plt.grid(True)
plt.show()
# Impresión breve de info útil (opcional)
print("Vector nudo (B-spline):", np.round(knots, 3))