1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
# Copyright (c) 2024, Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import Sequence, Iterable, Iterator
import time
import numpy as np
from ezdxf.math import Bezier3P, Vec3, UVec, Bezier4P
class NumpyQuadraticBezier:
def __init__(self, control_points: Iterable[UVec]) -> None:
cpts = np.array(Vec3.list(control_points), dtype=np.double)
assert cpts.shape[0] == 3
# first control point is (0, 0, 0)
offset = cpts[0].copy()
cpts -= offset
self._cpts = cpts
self._offset = offset
def points(self, params: Sequence[float]) -> Iterator[Vec3]:
# 1st control point is always (0, 0, 0) => a = 0
t = np.array(params, dtype=np.double)
b = t * 2.0 * (1.0 - t)
c = t * t
return self._eval(Vec3(self._offset), b, c)
def tangents(self, params: Sequence[float]) -> Iterator[Vec3]:
# 1st control point is always (0, 0, 0) => a = 0
t = np.array(params, dtype=np.double)
b = t * -4.0 + 2.0
c = t * 2.0
return self._eval(Vec3(), b, c)
def _eval(self, offset: Vec3, b, c) -> Iterator[Vec3]:
_, cp1, cp2 = self._cpts
x = b * cp1[0] + c * cp2[0]
y = b * cp1[1] + c * cp2[1]
z = b * cp1[2] + c * cp2[2]
return (offset + xyz for xyz in np.column_stack((x, y, z)))
class NumpyCubicBezier:
def __init__(self, control_points: Iterable[UVec]) -> None:
cpts = np.array(Vec3.list(control_points), dtype=np.double)
assert cpts.shape[0] == 4
# first control point is (0, 0, 0)
offset = cpts[0].copy()
cpts -= offset
self._cpts = cpts
self._offset = offset
def points(self, params: Sequence[float]) -> Iterator[Vec3]:
# 1st control point is always (0, 0, 0) => a = 0
t = np.array(params, dtype=np.double)
t2 = t * t
_1_minus_t = 1.0 - t
_1_minus_t_square = _1_minus_t * _1_minus_t
b = 3.0 * _1_minus_t_square * t
c = 3.0 * _1_minus_t * t2
d = t2 * t
return self._eval(Vec3(self._offset), b, c, d)
def tangents(self, params: Sequence[float]) -> Iterator[Vec3]:
# 1st control point is always (0, 0, 0) => a = 0
t = np.array(params, dtype=np.double)
_3t = t * 3.0
d = t * _3t
b = 3.0 * (1.0 - 4.0 * t + d)
c = _3t * (2.0 - _3t)
return self._eval(Vec3(), b, c, d)
def _eval(self, offset: Vec3, b, c, d) -> Iterator[Vec3]:
_, cp1, cp2, cp3 = self._cpts
x = b * cp1[0] + c * cp2[0] + d * cp3[0]
y = b * cp1[1] + c * cp2[1] + d * cp3[1]
z = b * cp1[2] + c * cp2[2] + d * cp3[2]
return (offset + xyz for xyz in np.column_stack((x, y, z)))
def compare_quad_curves():
print("\nQuadratic Bèzier Curve")
cpts = [(1, 2, 3), (4, 3, 1), (5, 6, 7)]
c0 = NumpyQuadraticBezier(cpts)
c1 = Bezier3P(cpts)
execute(c0, c1)
def compare_cubic_curves():
print("\nCubic Bèzier Curve")
cpts = [(1, 2, 3), (4, 3, 1), (5, 6, 7), (3, 4, 8)]
c0 = NumpyCubicBezier(cpts)
c1 = Bezier4P(cpts)
execute(c0, c1)
def execute(c0, c1):
params = [0.5, 0.6, 0.7]
print("Points Calculation")
print("Numpy: ", list(c0.points(params)))
print("Cython: ", [c1.point(t) for t in params])
print("\n1st Derivative Calculation")
print("Numpy: ", list(c0.tangents(params)))
print("Cython: ", [c1.tangent(t) for t in params])
def profile():
print("\nQuadratic Bèzier Curve")
cpts = [(1, 2, 3), (4, 3, 1), (5, 6, 7)]
curve = NumpyQuadraticBezier(cpts)
params = np.linspace(0.1, 0.9, 100)
count = 10_000
t0 = time.perf_counter()
profile_numpy(count, curve, params)
print(f"Numpy: {time.perf_counter() - t0}")
curve = Bezier3P(cpts)
t0 = time.perf_counter()
profile_cython(count, curve, params)
print(f"Cython: {time.perf_counter() - t0}")
print("\nCubic Bèzier Curve")
cpts = [(1, 2, 3), (4, 3, 1), (5, 6, 7), (3, 4, 8)]
curve = NumpyCubicBezier(cpts)
t0 = time.perf_counter()
profile_numpy(count, curve, params)
print(f"Numpy: {time.perf_counter() - t0}")
curve = Bezier4P(cpts)
t0 = time.perf_counter()
profile_cython(count, curve, params)
print(f"Cython: {time.perf_counter() - t0}")
def profile_numpy(count, curve, params):
for _ in range(count):
list(curve.points(params))
list(curve.tangents(params))
def profile_cython(count, curve, params):
for _ in range(count):
[curve.point(t) for t in params]
[curve.tangent(t) for t in params]
if __name__ == "__main__":
compare_quad_curves()
compare_cubic_curves()
profile()
|