initial
This commit is contained in:
39
.venv/lib/python3.12/site-packages/ezdxf/acc/__init__.py
Normal file
39
.venv/lib/python3.12/site-packages/ezdxf/acc/__init__.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) 2020-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
import sys
|
||||
from ezdxf._options import options
|
||||
|
||||
# Set environment variable EZDXF_DISABLE_C_EXT to '1' or 'True' to disable
|
||||
# the usage of C extensions implemented by Cython.
|
||||
#
|
||||
# Important: If you change the EZDXF_DISABLE_C_EXT state, you have to restart
|
||||
# the Python interpreter, because C extension integration is done at the
|
||||
# ezdxf import!
|
||||
#
|
||||
# Config files:
|
||||
# Section: core
|
||||
# Key: disable_c_ext = 1
|
||||
#
|
||||
# Direct imports from the C extension modules can not be disabled,
|
||||
# just the usage by the ezdxf core package.
|
||||
# For an example see ezdxf.math.__init__, if you import Vec3 from ezdxf.math
|
||||
# the implementation depends on DISABLE_C_EXT and the existence of the C
|
||||
# extension, but if you import Vec3 from ezdxf.math.vectors, you always get
|
||||
# the Python implementation.
|
||||
|
||||
USE_C_EXT = not options.disable_c_ext
|
||||
|
||||
# C-extensions are always disabled for pypy because JIT compiled Python code is
|
||||
# much faster!
|
||||
PYPY = hasattr(sys, 'pypy_version_info')
|
||||
if PYPY:
|
||||
USE_C_EXT = False
|
||||
|
||||
if USE_C_EXT:
|
||||
try:
|
||||
from ezdxf.acc import vector
|
||||
except ImportError:
|
||||
USE_C_EXT = False
|
||||
|
||||
# set actual state of C-extension usage
|
||||
options._use_c_ext = USE_C_EXT
|
||||
Binary file not shown.
13516
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier3p.c
Normal file
13516
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier3p.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier3p.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier3p.cpython-312-darwin.so
Executable file
Binary file not shown.
222
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier3p.pyx
Normal file
222
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier3p.pyx
Normal file
@@ -0,0 +1,222 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2021-2024 Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing import TYPE_CHECKING, Sequence
|
||||
from .vector cimport Vec3, isclose, v3_dist, v3_lerp, v3_add, Vec2
|
||||
from .matrix44 cimport Matrix44
|
||||
import warnings
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.math import UVec
|
||||
|
||||
__all__ = ['Bezier3P']
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
|
||||
cdef double RECURSION_LIMIT = 1000
|
||||
|
||||
|
||||
cdef class Bezier3P:
|
||||
cdef:
|
||||
FastQuadCurve curve # pyright: ignore
|
||||
readonly Vec3 start_point
|
||||
Vec3 cp1
|
||||
readonly Vec3 end_point
|
||||
|
||||
def __cinit__(self, defpoints: Sequence[UVec]):
|
||||
if not isinstance(defpoints[0], (Vec2, Vec3)):
|
||||
warnings.warn(
|
||||
DeprecationWarning,
|
||||
"Bezier3P requires defpoints of type Vec2 or Vec3 in the future",
|
||||
)
|
||||
if len(defpoints) == 3:
|
||||
self.start_point = Vec3(defpoints[0])
|
||||
self.cp1 = Vec3(defpoints[1])
|
||||
self.end_point = Vec3(defpoints[2])
|
||||
|
||||
self.curve = FastQuadCurve(
|
||||
self.start_point,
|
||||
self.cp1,
|
||||
self.end_point
|
||||
)
|
||||
else:
|
||||
raise ValueError("Three control points required.")
|
||||
|
||||
@property
|
||||
def control_points(self) -> tuple[Vec3, Vec3, Vec3]:
|
||||
return self.start_point, self.cp1, self.end_point
|
||||
|
||||
def __reduce__(self):
|
||||
return Bezier3P, (self.control_points,)
|
||||
|
||||
def point(self, double t) -> Vec3:
|
||||
if 0.0 <= t <= 1.0:
|
||||
return self.curve.point(t)
|
||||
else:
|
||||
raise ValueError("t not in range [0 to 1]")
|
||||
|
||||
def tangent(self, double t) -> Vec3:
|
||||
if 0.0 <= t <= 1.0:
|
||||
return self.curve.tangent(t)
|
||||
else:
|
||||
raise ValueError("t not in range [0 to 1]")
|
||||
|
||||
def approximate(self, int segments) -> list[Vec3]:
|
||||
cdef double delta_t
|
||||
cdef int segment
|
||||
cdef list points = [self.start_point]
|
||||
|
||||
if segments < 1:
|
||||
raise ValueError(segments)
|
||||
delta_t = 1.0 / segments
|
||||
for segment in range(1, segments):
|
||||
points.append(self.point(delta_t * segment))
|
||||
points.append(self.end_point)
|
||||
return points
|
||||
|
||||
def flattening(self, double distance, int segments = 4) -> list[Vec3]:
|
||||
cdef double dt = 1.0 / segments
|
||||
cdef double t0 = 0.0, t1
|
||||
cdef _Flattening f = _Flattening(self, distance)
|
||||
cdef Vec3 start_point = self.start_point
|
||||
cdef Vec3 end_point
|
||||
while t0 < 1.0:
|
||||
t1 = t0 + dt
|
||||
if isclose(t1, 1.0, REL_TOL, ABS_TOL):
|
||||
end_point = self.end_point
|
||||
t1 = 1.0
|
||||
else:
|
||||
end_point = self.curve.point(t1)
|
||||
f.reset_recursion_check()
|
||||
f.flatten(start_point, end_point, t0, t1)
|
||||
if f.has_recursion_error():
|
||||
raise RecursionError(
|
||||
"Bezier3P flattening error, check for very large coordinates"
|
||||
)
|
||||
t0 = t1
|
||||
start_point = end_point
|
||||
return f.points
|
||||
|
||||
def approximated_length(self, segments: int = 128) -> float:
|
||||
cdef double length = 0.0
|
||||
cdef bint start_flag = 0
|
||||
cdef Vec3 prev_point, point
|
||||
|
||||
for point in self.approximate(segments):
|
||||
if start_flag:
|
||||
length += v3_dist(prev_point, point)
|
||||
else:
|
||||
start_flag = 1
|
||||
prev_point = point
|
||||
return length
|
||||
|
||||
def reverse(self) -> Bezier3P:
|
||||
return Bezier3P((self.end_point, self.cp1, self.start_point))
|
||||
|
||||
def transform(self, Matrix44 m) -> Bezier3P:
|
||||
return Bezier3P(tuple(m.transform_vertices(self.control_points)))
|
||||
|
||||
|
||||
cdef class _Flattening:
|
||||
cdef FastQuadCurve curve # pyright: ignore
|
||||
cdef double distance
|
||||
cdef list points
|
||||
cdef int _recursion_level
|
||||
cdef int _recursion_error
|
||||
|
||||
def __cinit__(self, Bezier3P curve, double distance):
|
||||
self.curve = curve.curve
|
||||
self.distance = distance
|
||||
self.points = [curve.start_point]
|
||||
self._recursion_level = 0
|
||||
self._recursion_error = 0
|
||||
|
||||
cdef has_recursion_error(self):
|
||||
return self._recursion_error
|
||||
|
||||
cdef reset_recursion_check(self):
|
||||
self._recursion_level = 0
|
||||
self._recursion_error = 0
|
||||
|
||||
cdef flatten(
|
||||
self,
|
||||
Vec3 start_point,
|
||||
Vec3 end_point,
|
||||
double start_t,
|
||||
double end_t
|
||||
):
|
||||
if self._recursion_level > RECURSION_LIMIT:
|
||||
self._recursion_error = 1
|
||||
return
|
||||
self._recursion_level += 1
|
||||
cdef double mid_t = (start_t + end_t) * 0.5
|
||||
cdef Vec3 mid_point = self.curve.point(mid_t)
|
||||
cdef double d = v3_dist(mid_point, v3_lerp(start_point, end_point, 0.5))
|
||||
if d < self.distance:
|
||||
self.points.append(end_point)
|
||||
else:
|
||||
self.flatten(start_point, mid_point, start_t, mid_t)
|
||||
self.flatten(mid_point, end_point, mid_t, end_t)
|
||||
self._recursion_level -= 1
|
||||
|
||||
|
||||
cdef class FastQuadCurve:
|
||||
cdef:
|
||||
double[3] offset
|
||||
double[3] p1
|
||||
double[3] p2
|
||||
|
||||
def __cinit__(self, Vec3 p0, Vec3 p1, Vec3 p2):
|
||||
self.offset[0] = p0.x
|
||||
self.offset[1] = p0.y
|
||||
self.offset[2] = p0.z
|
||||
|
||||
# 1st control point (p0) is always (0, 0, 0)
|
||||
self.p1[0] = p1.x - p0.x
|
||||
self.p1[1] = p1.y - p0.y
|
||||
self.p1[2] = p1.z - p0.z
|
||||
|
||||
self.p2[0] = p2.x - p0.x
|
||||
self.p2[1] = p2.y - p0.y
|
||||
self.p2[2] = p2.z - p0.z
|
||||
|
||||
|
||||
cdef Vec3 point(self, double t):
|
||||
# 1st control point (p0) is always (0, 0, 0)
|
||||
# => p0 * a is always (0, 0, 0)
|
||||
cdef:
|
||||
Vec3 result = Vec3()
|
||||
# double a = (1 - t) ** 2
|
||||
double b = 2.0 * t * (1.0 - t)
|
||||
double c = t * t
|
||||
|
||||
iadd_mul(result, self.p1, b)
|
||||
iadd_mul(result, self.p2, c)
|
||||
|
||||
# add offset at last - it is maybe very large
|
||||
result.x += self.offset[0]
|
||||
result.y += self.offset[1]
|
||||
result.z += self.offset[2]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cdef Vec3 tangent(self, double t):
|
||||
# tangent vector is independent from offset location!
|
||||
cdef:
|
||||
Vec3 result = Vec3()
|
||||
# double a = -2 * (1 - t)
|
||||
double b = 2.0 - 4.0 * t
|
||||
double c = 2.0 * t
|
||||
|
||||
iadd_mul(result, self.p1, b)
|
||||
iadd_mul(result, self.p2, c)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cdef void iadd_mul(Vec3 a, double[3] b, double c):
|
||||
a.x += b[0] * c
|
||||
a.y += b[1] * c
|
||||
a.z += b[2] * c
|
||||
18390
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier4p.c
Normal file
18390
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier4p.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier4p.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier4p.cpython-312-darwin.so
Executable file
Binary file not shown.
371
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier4p.pyx
Normal file
371
.venv/lib/python3.12/site-packages/ezdxf/acc/bezier4p.pyx
Normal file
@@ -0,0 +1,371 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2020-2024 Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing import TYPE_CHECKING, Sequence, Iterable, Iterator
|
||||
import cython
|
||||
import warnings
|
||||
from .vector cimport (
|
||||
Vec3,
|
||||
Vec2,
|
||||
isclose,
|
||||
v3_add,
|
||||
v3_mul,
|
||||
v3_dist,
|
||||
v3_lerp,
|
||||
v3_from_angle,
|
||||
normalize_rad_angle,
|
||||
)
|
||||
from .matrix44 cimport Matrix44
|
||||
from libc.math cimport ceil, tan, M_PI
|
||||
from .construct import arc_angle_span_deg
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.math import UVec
|
||||
from ezdxf.math.ellipse import ConstructionEllipse
|
||||
|
||||
__all__ = [
|
||||
'Bezier4P', 'cubic_bezier_arc_parameters',
|
||||
'cubic_bezier_from_arc', 'cubic_bezier_from_ellipse',
|
||||
]
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
const double M_TAU
|
||||
|
||||
cdef double DEG2RAD = M_PI / 180.0
|
||||
cdef double RECURSION_LIMIT = 1000
|
||||
|
||||
|
||||
cdef class Bezier4P:
|
||||
cdef:
|
||||
FastCubicCurve curve # pyright: ignore
|
||||
readonly Vec3 start_point
|
||||
Vec3 cp1
|
||||
Vec3 cp2
|
||||
readonly Vec3 end_point
|
||||
|
||||
def __cinit__(self, defpoints: Sequence[UVec]):
|
||||
if not isinstance(defpoints[0], (Vec2, Vec3)):
|
||||
warnings.warn(
|
||||
DeprecationWarning,
|
||||
"Bezier4P requires defpoints of type Vec2 or Vec3 in the future",
|
||||
)
|
||||
if len(defpoints) == 4:
|
||||
self.start_point = Vec3(defpoints[0])
|
||||
self.cp1 = Vec3(defpoints[1])
|
||||
self.cp2 = Vec3(defpoints[2])
|
||||
self.end_point = Vec3(defpoints[3])
|
||||
|
||||
self.curve = FastCubicCurve(
|
||||
self.start_point,
|
||||
self.cp1,
|
||||
self.cp2,
|
||||
self.end_point
|
||||
)
|
||||
else:
|
||||
raise ValueError("Four control points required.")
|
||||
|
||||
@property
|
||||
def control_points(self) -> tuple[Vec3, Vec3, Vec3, Vec3]:
|
||||
return self.start_point, self.cp1, self.cp2, self.end_point
|
||||
|
||||
def __reduce__(self):
|
||||
return Bezier4P, (self.control_points,)
|
||||
|
||||
def point(self, double t) -> Vec3:
|
||||
if 0.0 <= t <= 1.0:
|
||||
return self.curve.point(t)
|
||||
else:
|
||||
raise ValueError("t not in range [0 to 1]")
|
||||
|
||||
def tangent(self, double t) -> Vec3:
|
||||
if 0.0 <= t <= 1.0:
|
||||
return self.curve.tangent(t)
|
||||
else:
|
||||
raise ValueError("t not in range [0 to 1]")
|
||||
|
||||
def approximate(self, int segments) -> list[Vec3]:
|
||||
cdef double delta_t
|
||||
cdef int segment
|
||||
cdef list points = [self.start_point]
|
||||
|
||||
if segments < 1:
|
||||
raise ValueError(segments)
|
||||
delta_t = 1.0 / segments
|
||||
for segment in range(1, segments):
|
||||
points.append(self.curve.point(delta_t * segment))
|
||||
points.append(self.end_point)
|
||||
return points
|
||||
|
||||
def flattening(self, double distance, int segments = 4) -> list[Vec3]:
|
||||
cdef double dt = 1.0 / segments
|
||||
cdef double t0 = 0.0, t1
|
||||
cdef _Flattening f = _Flattening(self, distance)
|
||||
cdef Vec3 start_point = self.start_point
|
||||
cdef Vec3 end_point
|
||||
while t0 < 1.0:
|
||||
t1 = t0 + dt
|
||||
if isclose(t1, 1.0, REL_TOL, ABS_TOL):
|
||||
end_point = self.end_point
|
||||
t1 = 1.0
|
||||
else:
|
||||
end_point = self.curve.point(t1)
|
||||
f.reset_recursion_check()
|
||||
f.flatten(start_point, end_point, t0, t1)
|
||||
if f.has_recursion_error():
|
||||
raise RecursionError(
|
||||
"Bezier4P flattening error, check for very large coordinates"
|
||||
)
|
||||
t0 = t1
|
||||
start_point = end_point
|
||||
|
||||
return f.points
|
||||
|
||||
def approximated_length(self, segments: int = 128) -> float:
|
||||
cdef double length = 0.0
|
||||
cdef bint start_flag = 0
|
||||
cdef Vec3 prev_point, point
|
||||
|
||||
for point in self.approximate(segments):
|
||||
if start_flag:
|
||||
length += v3_dist(prev_point, point)
|
||||
else:
|
||||
start_flag = 1
|
||||
prev_point = point
|
||||
return length
|
||||
|
||||
def reverse(self) -> Bezier4P:
|
||||
return Bezier4P((self.end_point, self.cp2, self.cp1, self.start_point))
|
||||
|
||||
def transform(self, Matrix44 m) -> Bezier4P:
|
||||
return Bezier4P(tuple(m.transform_vertices(self.control_points)))
|
||||
|
||||
|
||||
cdef class _Flattening:
|
||||
cdef FastCubicCurve curve # pyright: ignore
|
||||
cdef double distance
|
||||
cdef list points
|
||||
cdef int _recursion_level
|
||||
cdef int _recursion_error
|
||||
|
||||
def __cinit__(self, Bezier4P curve, double distance):
|
||||
self.curve = curve.curve
|
||||
self.distance = distance
|
||||
self.points = [curve.start_point]
|
||||
self._recursion_level = 0
|
||||
self._recursion_error = 0
|
||||
|
||||
cdef has_recursion_error(self):
|
||||
return self._recursion_error
|
||||
|
||||
cdef reset_recursion_check(self):
|
||||
self._recursion_level = 0
|
||||
self._recursion_error = 0
|
||||
|
||||
cdef flatten(
|
||||
self,
|
||||
Vec3 start_point,
|
||||
Vec3 end_point,
|
||||
double start_t,
|
||||
double end_t
|
||||
):
|
||||
# Keep in sync with CPython implementation: ezdxf/math/_bezier4p.py
|
||||
# Test suite: 630a
|
||||
if self._recursion_level > RECURSION_LIMIT:
|
||||
self._recursion_error = 1
|
||||
return
|
||||
self._recursion_level += 1
|
||||
cdef double mid_t = (start_t + end_t) * 0.5
|
||||
cdef Vec3 mid_point = self.curve.point(mid_t)
|
||||
cdef double d = v3_dist(mid_point, v3_lerp(start_point, end_point, 0.5))
|
||||
if d < self.distance:
|
||||
self.points.append(end_point)
|
||||
else:
|
||||
self.flatten(start_point, mid_point, start_t, mid_t)
|
||||
self.flatten(mid_point, end_point, mid_t, end_t)
|
||||
self._recursion_level -= 1
|
||||
|
||||
cdef double DEFAULT_TANGENT_FACTOR = 4.0 / 3.0 # 1.333333333333333333
|
||||
cdef double OPTIMIZED_TANGENT_FACTOR = 1.3324407374108935
|
||||
cdef double TANGENT_FACTOR = DEFAULT_TANGENT_FACTOR
|
||||
|
||||
@cython.cdivision(True)
|
||||
def cubic_bezier_arc_parameters(
|
||||
double start_angle,
|
||||
double end_angle,
|
||||
int segments = 1,
|
||||
) -> Iterator[tuple[Vec3, Vec3, Vec3, Vec3]]:
|
||||
if segments < 1:
|
||||
raise ValueError('Invalid argument segments (>= 1).')
|
||||
cdef double delta_angle = end_angle - start_angle
|
||||
cdef int arc_count
|
||||
if delta_angle > 0:
|
||||
arc_count = <int> ceil(delta_angle / M_PI * 2.0)
|
||||
if segments > arc_count:
|
||||
arc_count = segments
|
||||
else:
|
||||
raise ValueError('Delta angle from start- to end angle has to be > 0.')
|
||||
|
||||
cdef double segment_angle = delta_angle / arc_count
|
||||
cdef double tangent_length = TANGENT_FACTOR * tan(segment_angle / 4.0)
|
||||
cdef double angle = start_angle
|
||||
cdef Vec3 start_point, end_point, cp1, cp2
|
||||
end_point = v3_from_angle(angle, 1.0)
|
||||
|
||||
for _ in range(arc_count):
|
||||
start_point = end_point
|
||||
angle += segment_angle
|
||||
end_point = v3_from_angle(angle, 1.0)
|
||||
cp1 = Vec3()
|
||||
cp1.x = start_point.x - start_point.y * tangent_length
|
||||
cp1.y = start_point.y + start_point.x * tangent_length
|
||||
cp2 = Vec3()
|
||||
cp2.x = end_point.x + end_point.y * tangent_length
|
||||
cp2.y = end_point.y - end_point.x * tangent_length
|
||||
yield start_point, cp1, cp2, end_point
|
||||
|
||||
def cubic_bezier_from_arc(
|
||||
center = (0, 0),
|
||||
double radius = 1.0,
|
||||
double start_angle = 0.0,
|
||||
double end_angle = 360.0,
|
||||
int segments = 1
|
||||
) -> Iterable[Bezier4P]:
|
||||
cdef Vec3 center_ = Vec3(center)
|
||||
cdef Vec3 tmp
|
||||
cdef list res
|
||||
cdef int i
|
||||
cdef double angle_span = arc_angle_span_deg(start_angle, end_angle)
|
||||
if abs(angle_span) < 1e-9:
|
||||
return
|
||||
|
||||
cdef double s = start_angle
|
||||
start_angle = (s * DEG2RAD) % M_TAU
|
||||
end_angle = (s + angle_span) * DEG2RAD
|
||||
while start_angle > end_angle:
|
||||
end_angle += M_TAU
|
||||
|
||||
for control_points in cubic_bezier_arc_parameters(start_angle, end_angle, segments):
|
||||
res = []
|
||||
for i in range(4):
|
||||
tmp = <Vec3> control_points[i]
|
||||
res.append(v3_add(center_, v3_mul(tmp, radius)))
|
||||
yield Bezier4P(res)
|
||||
|
||||
def cubic_bezier_from_ellipse(
|
||||
ellipse: ConstructionEllipse,
|
||||
int segments = 1
|
||||
) -> Iterator[Bezier4P]:
|
||||
cdef double param_span = ellipse.param_span
|
||||
if abs(param_span) < 1e-9:
|
||||
return
|
||||
|
||||
cdef double start_angle = normalize_rad_angle(ellipse.start_param)
|
||||
cdef double end_angle = start_angle + param_span
|
||||
|
||||
while start_angle > end_angle:
|
||||
end_angle += M_TAU
|
||||
|
||||
cdef Vec3 center = Vec3(ellipse.center)
|
||||
cdef Vec3 x_axis = Vec3(ellipse.major_axis)
|
||||
cdef Vec3 y_axis = Vec3(ellipse.minor_axis)
|
||||
cdef Vec3 cp
|
||||
cdef Vec3 c_res
|
||||
cdef list res
|
||||
for control_points in cubic_bezier_arc_parameters(start_angle, end_angle, segments):
|
||||
res = list()
|
||||
for i in range(4):
|
||||
cp = <Vec3> control_points[i]
|
||||
c_res = v3_add_3(center, v3_mul(x_axis, cp.x), v3_mul(y_axis, cp.y))
|
||||
res.append(c_res)
|
||||
yield Bezier4P(res)
|
||||
|
||||
|
||||
cdef Vec3 v3_add_3(Vec3 a, Vec3 b, Vec3 c):
|
||||
cdef Vec3 result = Vec3()
|
||||
|
||||
result.x = a.x + b.x + c.x
|
||||
result.y = a.y + b.y + c.y
|
||||
result.z = a.z + b.z + c.z
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cdef class FastCubicCurve:
|
||||
cdef:
|
||||
double[3] offset
|
||||
double[3] p1
|
||||
double[3] p2
|
||||
double[3] p3
|
||||
|
||||
def __cinit__(self, Vec3 p0, Vec3 p1, Vec3 p2, Vec3 p3):
|
||||
cdef:
|
||||
double x = p0.x
|
||||
double y = p0.y
|
||||
double z = p0.z
|
||||
|
||||
self.offset[0] = x
|
||||
self.offset[1] = y
|
||||
self.offset[2] = z
|
||||
|
||||
# 1st control point (p0) is always (0, 0, 0)
|
||||
self.p1[0] = p1.x - x
|
||||
self.p1[1] = p1.y - y
|
||||
self.p1[2] = p1.z - z
|
||||
|
||||
self.p2[0] = p2.x - x
|
||||
self.p2[1] = p2.y - y
|
||||
self.p2[2] = p2.z - z
|
||||
|
||||
self.p3[0] = p3.x - x
|
||||
self.p3[1] = p3.y - y
|
||||
self.p3[2] = p3.z - z
|
||||
|
||||
|
||||
cdef Vec3 point(self, double t):
|
||||
# 1st control point (p0) is always (0, 0, 0)
|
||||
# => p0 * a is always (0, 0, 0)
|
||||
cdef:
|
||||
Vec3 result = Vec3()
|
||||
double t2 = t * t
|
||||
double _1_minus_t = 1.0 - t
|
||||
# a = (1 - t) ** 3
|
||||
double b = 3.0 * _1_minus_t * _1_minus_t * t
|
||||
double c = 3.0 * _1_minus_t * t2
|
||||
double d = t2 * t
|
||||
|
||||
iadd_mul(result, self.p1, b)
|
||||
iadd_mul(result, self.p2, c)
|
||||
iadd_mul(result, self.p3, d)
|
||||
|
||||
# add offset at last - it is maybe very large
|
||||
result.x += self.offset[0]
|
||||
result.y += self.offset[1]
|
||||
result.z += self.offset[2]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cdef Vec3 tangent(self, double t):
|
||||
# tangent vector is independent from offset location!
|
||||
cdef:
|
||||
Vec3 result = Vec3()
|
||||
double t2 = t * t
|
||||
# a = -3 * (1 - t) ** 2
|
||||
double b = 3.0 * (1.0 - 4.0 * t + 3.0 * t2)
|
||||
double c = 3.0 * t * (2.0 - 3.0 * t)
|
||||
double d = 3.0 * t2
|
||||
|
||||
iadd_mul(result, self.p1, b)
|
||||
iadd_mul(result, self.p2, c)
|
||||
iadd_mul(result, self.p3, d)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
cdef void iadd_mul(Vec3 a, double[3] b, double c):
|
||||
a.x += b[0] * c
|
||||
a.y += b[1] * c
|
||||
a.z += b[2] * c
|
||||
|
||||
19393
.venv/lib/python3.12/site-packages/ezdxf/acc/bspline.c
Normal file
19393
.venv/lib/python3.12/site-packages/ezdxf/acc/bspline.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/bspline.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/bspline.cpython-312-darwin.so
Executable file
Binary file not shown.
400
.venv/lib/python3.12/site-packages/ezdxf/acc/bspline.pyx
Normal file
400
.venv/lib/python3.12/site-packages/ezdxf/acc/bspline.pyx
Normal file
@@ -0,0 +1,400 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2021-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing import Iterable, Sequence, Iterator
|
||||
import cython
|
||||
from cpython.mem cimport PyMem_Malloc, PyMem_Free
|
||||
from .vector cimport Vec3, isclose, v3_mul, v3_sub
|
||||
|
||||
__all__ = ['Basis', 'Evaluator']
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
const int MAX_SPLINE_ORDER
|
||||
|
||||
# factorial from 0 to 18
|
||||
cdef double[19] FACTORIAL = [
|
||||
1., 1., 2., 6., 24., 120., 720., 5040., 40320., 362880., 3628800.,
|
||||
39916800., 479001600., 6227020800., 87178291200., 1307674368000.,
|
||||
20922789888000., 355687428096000., 6402373705728000.
|
||||
]
|
||||
|
||||
NULL_LIST = [0.0]
|
||||
ONE_LIST = [1.0]
|
||||
|
||||
cdef Vec3 NULLVEC = Vec3()
|
||||
|
||||
@cython.cdivision(True)
|
||||
cdef double binomial_coefficient(int k, int i):
|
||||
cdef double k_fact = FACTORIAL[k]
|
||||
cdef double i_fact = FACTORIAL[i]
|
||||
cdef double k_i_fact
|
||||
if i > k:
|
||||
return 0.0
|
||||
k_i_fact = FACTORIAL[k - i]
|
||||
return k_fact / (k_i_fact * i_fact)
|
||||
|
||||
@cython.boundscheck(False)
|
||||
cdef int bisect_right(double *a, double x, int lo, int hi):
|
||||
cdef int mid
|
||||
while lo < hi:
|
||||
mid = (lo + hi) // 2
|
||||
if x < a[mid]:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid + 1
|
||||
return lo
|
||||
|
||||
cdef reset_double_array(double *a, int count, double value):
|
||||
cdef int i
|
||||
for i in range(count):
|
||||
a[i] = value
|
||||
|
||||
cdef class Basis:
|
||||
""" Immutable Basis function class. """
|
||||
# public:
|
||||
cdef readonly int order
|
||||
cdef readonly int count
|
||||
cdef readonly double max_t
|
||||
cdef tuple weights_ # public attribute for Cython Evaluator
|
||||
# private:
|
||||
cdef double *_knots
|
||||
cdef int knot_count
|
||||
|
||||
def __cinit__(
|
||||
self, knots: Iterable[float],
|
||||
int order,
|
||||
int count,
|
||||
weights: Sequence[float] = None
|
||||
):
|
||||
if order < 2 or order >= MAX_SPLINE_ORDER:
|
||||
raise ValueError('invalid order')
|
||||
self.order = order
|
||||
if count < 2:
|
||||
raise ValueError('invalid count')
|
||||
self.count = count
|
||||
self.knot_count = self.order + self.count
|
||||
self.weights_ = tuple(float(x) for x in weights) if weights else tuple()
|
||||
|
||||
cdef Py_ssize_t i = len(self.weights_)
|
||||
if i != 0 and i != self.count:
|
||||
raise ValueError('invalid weight count')
|
||||
|
||||
knots = [float(x) for x in knots]
|
||||
if len(knots) != self.knot_count:
|
||||
raise ValueError('invalid knot count')
|
||||
|
||||
self._knots = <double *> PyMem_Malloc(self.knot_count * sizeof(double))
|
||||
for i in range(self.knot_count):
|
||||
self._knots[i] = knots[i]
|
||||
self.max_t = self._knots[self.knot_count - 1]
|
||||
|
||||
def __dealloc__(self):
|
||||
PyMem_Free(self._knots)
|
||||
|
||||
@property
|
||||
def degree(self) -> int:
|
||||
return self.order - 1
|
||||
|
||||
@property
|
||||
def knots(self) -> tuple[float, ...]:
|
||||
return tuple(x for x in self._knots[:self.knot_count])
|
||||
|
||||
@property
|
||||
def weights(self) -> tuple[float, ...]:
|
||||
return self.weights_
|
||||
|
||||
@property
|
||||
def is_rational(self) -> bool:
|
||||
""" Returns ``True`` if curve is a rational B-spline. (has weights) """
|
||||
return bool(self.weights_)
|
||||
|
||||
cpdef list basis_vector(self, double t):
|
||||
""" Returns the expanded basis vector. """
|
||||
|
||||
cdef int span = self.find_span(t)
|
||||
cdef int p = self.order - 1
|
||||
cdef int front = span - p
|
||||
cdef int back = self.count - span - 1
|
||||
cdef list result
|
||||
if front > 0:
|
||||
result = NULL_LIST * front
|
||||
result.extend(self.basis_funcs(span, t))
|
||||
else:
|
||||
result = self.basis_funcs(span, t)
|
||||
if back > 0:
|
||||
result.extend(NULL_LIST * back)
|
||||
return result
|
||||
|
||||
cpdef int find_span(self, double u):
|
||||
""" Determine the knot span index. """
|
||||
# Linear search is more reliable than binary search of the Algorithm A2.1
|
||||
# from The NURBS Book by Piegl & Tiller.
|
||||
cdef double *knots = self._knots
|
||||
cdef int count = self.count # text book: n+1
|
||||
cdef int p = self.order - 1
|
||||
cdef int span
|
||||
if u >= knots[count]: # special case
|
||||
return count - 1
|
||||
|
||||
# common clamped spline:
|
||||
if knots[p] == 0.0: # use binary search
|
||||
# This is fast and works most of the time,
|
||||
# but Test 621 : test_weired_closed_spline()
|
||||
# goes into an infinity loop, because of
|
||||
# a weird knot configuration.
|
||||
return bisect_right(knots, u, p, count) - 1
|
||||
else: # use linear search
|
||||
span = 0
|
||||
while knots[span] <= u and span < count:
|
||||
span += 1
|
||||
return span - 1
|
||||
|
||||
cpdef list basis_funcs(self, int span, double u):
|
||||
# Source: The NURBS Book: Algorithm A2.2
|
||||
cdef int order = self.order
|
||||
cdef double *knots = self._knots
|
||||
cdef double[MAX_SPLINE_ORDER] N, left, right
|
||||
cdef list result
|
||||
reset_double_array(N, order, 0.0)
|
||||
reset_double_array(left, order, 0.0)
|
||||
reset_double_array(right, order, 0.0)
|
||||
|
||||
cdef int j, r, i1
|
||||
cdef double temp, saved, temp_r, temp_l
|
||||
N[0] = 1.0
|
||||
for j in range(1, order):
|
||||
i1 = span + 1 - j
|
||||
if i1 < 0:
|
||||
i1 = 0
|
||||
left[j] = u - knots[i1]
|
||||
right[j] = knots[span + j] - u
|
||||
saved = 0.0
|
||||
for r in range(j):
|
||||
temp_r = right[r + 1]
|
||||
temp_l = left[j - r]
|
||||
temp = N[r] / (temp_r + temp_l)
|
||||
N[r] = saved + temp_r * temp
|
||||
saved = temp_l * temp
|
||||
N[j] = saved
|
||||
result = [x for x in N[:order]]
|
||||
if self.is_rational:
|
||||
return self.span_weighting(result, span)
|
||||
else:
|
||||
return result
|
||||
|
||||
cpdef list span_weighting(self, nbasis: list[float], int span):
|
||||
cdef list products = [
|
||||
nb * w for nb, w in zip(
|
||||
nbasis,
|
||||
self.weights_[span - self.order + 1: span + 1]
|
||||
)
|
||||
]
|
||||
s = sum(products)
|
||||
if s != 0:
|
||||
return [p / s for p in products]
|
||||
else:
|
||||
return NULL_LIST * len(nbasis)
|
||||
|
||||
cpdef list basis_funcs_derivatives(self, int span, double u, int n = 1):
|
||||
# pyright: reportUndefinedVariable=false
|
||||
# pyright flags Cython multi-arrays incorrect:
|
||||
# cdef double[4][4] a # this is a valid array definition in Cython!
|
||||
# https://cython.readthedocs.io/en/latest/src/userguide/language_basics.html#c-arrays
|
||||
# Source: The NURBS Book: Algorithm A2.3
|
||||
cdef int order = self.order
|
||||
cdef int p = order - 1
|
||||
if n > p:
|
||||
n = p
|
||||
cdef double *knots = self._knots
|
||||
cdef double[MAX_SPLINE_ORDER] left, right
|
||||
reset_double_array(left, order, 1.0)
|
||||
reset_double_array(right, order, 1.0)
|
||||
|
||||
cdef double[MAX_SPLINE_ORDER][MAX_SPLINE_ORDER] ndu # pyright: ignore
|
||||
reset_double_array(<double *> ndu, MAX_SPLINE_ORDER*MAX_SPLINE_ORDER, 1.0)
|
||||
|
||||
cdef int j, r, i1
|
||||
cdef double temp, saved, tmp_r, tmp_l
|
||||
for j in range(1, order):
|
||||
i1 = span + 1 - j
|
||||
if i1 < 0:
|
||||
i1 = 0
|
||||
left[j] = u - knots[i1]
|
||||
right[j] = knots[span + j] - u
|
||||
saved = 0.0
|
||||
for r in range(j):
|
||||
# lower triangle
|
||||
tmp_r = right[r + 1]
|
||||
tmp_l = left[j - r]
|
||||
ndu[j][r] = tmp_r + tmp_l
|
||||
temp = ndu[r][j - 1] / ndu[j][r]
|
||||
# upper triangle
|
||||
ndu[r][j] = saved + (tmp_r * temp)
|
||||
saved = tmp_l * temp
|
||||
ndu[j][j] = saved
|
||||
|
||||
# load the basis_vector functions
|
||||
cdef double[MAX_SPLINE_ORDER][MAX_SPLINE_ORDER] derivatives # pyright: ignore
|
||||
reset_double_array(
|
||||
<double *> derivatives, MAX_SPLINE_ORDER*MAX_SPLINE_ORDER, 0.0
|
||||
)
|
||||
for j in range(order):
|
||||
derivatives[0][j] = ndu[j][p]
|
||||
|
||||
# loop over function index
|
||||
cdef double[2][MAX_SPLINE_ORDER] a # pyright: ignore
|
||||
reset_double_array(<double *> a, 2*MAX_SPLINE_ORDER, 1.0)
|
||||
|
||||
cdef int s1, s2, k, rk, pk, j1, j2, t
|
||||
cdef double d
|
||||
for r in range(order):
|
||||
s1 = 0
|
||||
s2 = 1
|
||||
# alternate rows in array a
|
||||
a[0][0] = 1.0
|
||||
|
||||
# loop to compute kth derivative
|
||||
for k in range(1, n + 1):
|
||||
d = 0.0
|
||||
rk = r - k
|
||||
pk = p - k
|
||||
if r >= k:
|
||||
a[s2][0] = a[s1][0] / ndu[pk + 1][rk]
|
||||
d = a[s2][0] * ndu[rk][pk]
|
||||
if rk >= -1:
|
||||
j1 = 1
|
||||
else:
|
||||
j1 = -rk
|
||||
if (r - 1) <= pk:
|
||||
j2 = k - 1
|
||||
else:
|
||||
j2 = p - r
|
||||
for j in range(j1, j2 + 1):
|
||||
a[s2][j] = (a[s1][j] - a[s1][j - 1]) / ndu[pk + 1][rk + j]
|
||||
d += (a[s2][j] * ndu[rk + j][pk])
|
||||
if r <= pk:
|
||||
a[s2][k] = -a[s1][k - 1] / ndu[pk + 1][r]
|
||||
d += (a[s2][k] * ndu[r][pk])
|
||||
derivatives[k][r] = d
|
||||
|
||||
# Switch rows
|
||||
t = s1
|
||||
s1 = s2
|
||||
s2 = t
|
||||
|
||||
# Multiply through by the correct factors
|
||||
cdef double rr = p
|
||||
for k in range(1, n + 1):
|
||||
for j in range(order):
|
||||
derivatives[k][j] *= rr
|
||||
rr *= (p - k)
|
||||
|
||||
# return result as Python lists
|
||||
cdef list result = [], row
|
||||
for k in range(0, n + 1):
|
||||
row = []
|
||||
result.append(row)
|
||||
for j in range(order):
|
||||
row.append(derivatives[k][j])
|
||||
return result
|
||||
|
||||
cdef class Evaluator:
|
||||
""" B-spline curve point and curve derivative evaluator. """
|
||||
cdef Basis _basis
|
||||
cdef tuple _control_points
|
||||
|
||||
def __cinit__(self, basis: Basis, control_points: Sequence[Vec3]):
|
||||
self._basis = basis
|
||||
self._control_points = Vec3.tuple(control_points)
|
||||
|
||||
cpdef Vec3 point(self, double u):
|
||||
# Source: The NURBS Book: Algorithm A3.1
|
||||
cdef Basis basis = self._basis
|
||||
if isclose(u, basis.max_t, REL_TOL, ABS_TOL):
|
||||
u = basis.max_t
|
||||
cdef:
|
||||
int p = basis.order - 1
|
||||
int span = basis.find_span(u)
|
||||
list N = basis.basis_funcs(span, u)
|
||||
int i
|
||||
Vec3 cpoint, v3_sum = Vec3()
|
||||
tuple control_points = self._control_points
|
||||
double factor
|
||||
|
||||
for i in range(p + 1):
|
||||
factor = <double> N[i]
|
||||
cpoint = <Vec3> control_points[span - p + i]
|
||||
v3_sum.x += cpoint.x * factor
|
||||
v3_sum.y += cpoint.y * factor
|
||||
v3_sum.z += cpoint.z * factor
|
||||
return v3_sum
|
||||
|
||||
def points(self, t: Iterable[float]) -> Iterator[Vec3]:
|
||||
cdef double u
|
||||
for u in t:
|
||||
yield self.point(u)
|
||||
|
||||
cpdef list derivative(self, double u, int n = 1):
|
||||
""" Return point and derivatives up to n <= degree for parameter u. """
|
||||
# Source: The NURBS Book: Algorithm A3.2
|
||||
|
||||
cdef Basis basis = self._basis
|
||||
if isclose(u, basis.max_t, REL_TOL, ABS_TOL):
|
||||
u = basis.max_t
|
||||
cdef:
|
||||
list CK = [], CKw = [], wders = []
|
||||
tuple control_points = self._control_points
|
||||
tuple weights
|
||||
Vec3 cpoint, v3_sum
|
||||
double wder, bas_func_weight, bas_func
|
||||
int k, j, i, p = basis.degree
|
||||
int span = basis.find_span(u)
|
||||
list basis_funcs_ders = basis.basis_funcs_derivatives(span, u, n)
|
||||
|
||||
if basis.is_rational:
|
||||
# Homogeneous point representation required:
|
||||
# (x*w, y*w, z*w, w)
|
||||
weights = basis.weights_
|
||||
for k in range(n + 1):
|
||||
v3_sum = Vec3()
|
||||
wder = 0.0
|
||||
for j in range(p + 1):
|
||||
i = span - p + j
|
||||
bas_func_weight = basis_funcs_ders[k][j] * weights[i]
|
||||
# control_point * weight * bas_func_der = (x*w, y*w, z*w) * bas_func_der
|
||||
cpoint = <Vec3> control_points[i]
|
||||
v3_sum.x += cpoint.x * bas_func_weight
|
||||
v3_sum.y += cpoint.y * bas_func_weight
|
||||
v3_sum.z += cpoint.z * bas_func_weight
|
||||
wder += bas_func_weight
|
||||
CKw.append(v3_sum)
|
||||
wders.append(wder)
|
||||
|
||||
# Source: The NURBS Book: Algorithm A4.2
|
||||
for k in range(n + 1):
|
||||
v3_sum = CKw[k]
|
||||
for j in range(1, k + 1):
|
||||
bas_func_weight = binomial_coefficient(k, j) * wders[j]
|
||||
v3_sum = v3_sub(
|
||||
v3_sum,
|
||||
v3_mul(CK[k - j], bas_func_weight)
|
||||
)
|
||||
CK.append(v3_sum / wders[0])
|
||||
else:
|
||||
for k in range(n + 1):
|
||||
v3_sum = Vec3()
|
||||
for j in range(p + 1):
|
||||
bas_func = basis_funcs_ders[k][j]
|
||||
cpoint = <Vec3> control_points[span - p + j]
|
||||
v3_sum.x += cpoint.x * bas_func
|
||||
v3_sum.y += cpoint.y * bas_func
|
||||
v3_sum.z += cpoint.z * bas_func
|
||||
CK.append(v3_sum)
|
||||
return CK
|
||||
|
||||
def derivatives(self, t: Iterable[float], int n = 1) -> Iterator[list[Vec3]]:
|
||||
cdef double u
|
||||
for u in t:
|
||||
yield self.derivative(u, n)
|
||||
8
.venv/lib/python3.12/site-packages/ezdxf/acc/constants.h
Normal file
8
.venv/lib/python3.12/site-packages/ezdxf/acc/constants.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Manfred Moitzi
|
||||
// License: MIT License
|
||||
// global constants
|
||||
#define ABS_TOL 1e-12
|
||||
#define REL_TOL 1e-9
|
||||
#define M_TAU 6.283185307179586
|
||||
// AutoCAD limits the degree of SPLINE to 11 or order = 12
|
||||
#define MAX_SPLINE_ORDER 12
|
||||
11803
.venv/lib/python3.12/site-packages/ezdxf/acc/construct.c
Normal file
11803
.venv/lib/python3.12/site-packages/ezdxf/acc/construct.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/construct.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/construct.cpython-312-darwin.so
Executable file
Binary file not shown.
359
.venv/lib/python3.12/site-packages/ezdxf/acc/construct.pyx
Normal file
359
.venv/lib/python3.12/site-packages/ezdxf/acc/construct.pyx
Normal file
@@ -0,0 +1,359 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2020-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing import Iterable, TYPE_CHECKING, Sequence, Optional
|
||||
from libc.math cimport fabs, M_PI, M_PI_2, M_PI_4, M_E, sin, tan, pow, atan, log
|
||||
from .vector cimport (
|
||||
isclose,
|
||||
Vec2,
|
||||
v2_isclose,
|
||||
Vec3,
|
||||
v3_sub,
|
||||
v3_add,
|
||||
v3_mul,
|
||||
v3_normalize,
|
||||
v3_cross,
|
||||
v3_magnitude_sqr,
|
||||
v3_isclose,
|
||||
)
|
||||
|
||||
import cython
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.math import UVec
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
const double M_TAU
|
||||
|
||||
cdef double RAD_ABS_TOL = 1e-15
|
||||
cdef double DEG_ABS_TOL = 1e-13
|
||||
cdef double TOLERANCE = 1e-10
|
||||
|
||||
def has_clockwise_orientation(vertices: Iterable[UVec]) -> bool:
|
||||
""" Returns True if 2D `vertices` have clockwise orientation. Ignores
|
||||
z-axis of all vertices.
|
||||
|
||||
Args:
|
||||
vertices: iterable of :class:`Vec2` compatible objects
|
||||
|
||||
Raises:
|
||||
ValueError: less than 3 vertices
|
||||
|
||||
"""
|
||||
cdef list _vertices = [Vec2(v) for v in vertices]
|
||||
if len(_vertices) < 3:
|
||||
raise ValueError('At least 3 vertices required.')
|
||||
|
||||
cdef Vec2 p1 = <Vec2> _vertices[0]
|
||||
cdef Vec2 p2 = <Vec2> _vertices[-1]
|
||||
cdef double s = 0.0
|
||||
cdef Py_ssize_t index
|
||||
|
||||
# Using the same tolerance as the Python implementation:
|
||||
if not v2_isclose(p1, p2, REL_TOL, ABS_TOL):
|
||||
_vertices.append(p1)
|
||||
|
||||
for index in range(1, len(_vertices)):
|
||||
p2 = <Vec2> _vertices[index]
|
||||
s += (p2.x - p1.x) * (p2.y + p1.y)
|
||||
p1 = p2
|
||||
return s > 0.0
|
||||
|
||||
def intersection_line_line_2d(
|
||||
line1: Sequence[Vec2],
|
||||
line2: Sequence[Vec2],
|
||||
bint virtual=True,
|
||||
double abs_tol=TOLERANCE) -> Optional[Vec2]:
|
||||
"""
|
||||
Compute the intersection of two lines in the xy-plane.
|
||||
|
||||
Args:
|
||||
line1: start- and end point of first line to test
|
||||
e.g. ((x1, y1), (x2, y2)).
|
||||
line2: start- and end point of second line to test
|
||||
e.g. ((x3, y3), (x4, y4)).
|
||||
virtual: ``True`` returns any intersection point, ``False`` returns
|
||||
only real intersection points.
|
||||
abs_tol: tolerance for intersection test.
|
||||
|
||||
Returns:
|
||||
``None`` if there is no intersection point (parallel lines) or
|
||||
intersection point as :class:`Vec2`
|
||||
|
||||
"""
|
||||
# Algorithm based on: http://paulbourke.net/geometry/pointlineplane/
|
||||
# chapter: Intersection point of two line segments in 2 dimensions
|
||||
cdef Vec2 s1, s2, c1, c2, res
|
||||
cdef double s1x, s1y, s2x, s2y, c1x, c1y, c2x, c2y, den, us, uc
|
||||
cdef double lwr = 0.0, upr = 1.0
|
||||
|
||||
s1 = line1[0]
|
||||
s2 = line1[1]
|
||||
c1 = line2[0]
|
||||
c2 = line2[1]
|
||||
|
||||
s1x = s1.x
|
||||
s1y = s1.y
|
||||
s2x = s2.x
|
||||
s2y = s2.y
|
||||
c1x = c1.x
|
||||
c1y = c1.y
|
||||
c2x = c2.x
|
||||
c2y = c2.y
|
||||
|
||||
den = (c2y - c1y) * (s2x - s1x) - (c2x - c1x) * (s2y - s1y)
|
||||
|
||||
if fabs(den) <= abs_tol:
|
||||
return None
|
||||
|
||||
|
||||
# den near zero is checked by if-statement above:
|
||||
with cython.cdivision(True):
|
||||
us = ((c2x - c1x) * (s1y - c1y) - (c2y - c1y) * (s1x - c1x)) / den
|
||||
|
||||
res = Vec2(s1x + us * (s2x - s1x), s1y + us * (s2y - s1y))
|
||||
if virtual:
|
||||
return res
|
||||
|
||||
# 0 = intersection point is the start point of the line
|
||||
# 1 = intersection point is the end point of the line
|
||||
# otherwise: linear interpolation
|
||||
if lwr <= us <= upr: # intersection point is on the subject line
|
||||
with cython.cdivision(True):
|
||||
uc = ((s2x - s1x) * (s1y - c1y) - (s2y - s1y) * (s1x - c1x)) / den
|
||||
if lwr <= uc <= upr: # intersection point is on the clipping line
|
||||
return res
|
||||
return None
|
||||
|
||||
cdef double _determinant(Vec3 v1, Vec3 v2, Vec3 v3):
|
||||
return v1.x * v2.y * v3.z + v1.y * v2.z * v3.x + \
|
||||
v1.z * v2.x * v3.y - v1.z * v2.y * v3.x - \
|
||||
v1.x * v2.z * v3.y - v1.y * v2.x * v3.z
|
||||
|
||||
def intersection_ray_ray_3d(
|
||||
ray1: tuple[Vec3, Vec3],
|
||||
ray2: tuple[Vec3, Vec3],
|
||||
double abs_tol=TOLERANCE
|
||||
) -> Sequence[Vec3]:
|
||||
"""
|
||||
Calculate intersection of two 3D rays, returns a 0-tuple for parallel rays,
|
||||
a 1-tuple for intersecting rays and a 2-tuple for not intersecting and not
|
||||
parallel rays with points of closest approach on each ray.
|
||||
|
||||
Args:
|
||||
ray1: first ray as tuple of two points as Vec3() objects
|
||||
ray2: second ray as tuple of two points as Vec3() objects
|
||||
abs_tol: absolute tolerance for comparisons
|
||||
|
||||
"""
|
||||
# source: http://www.realtimerendering.com/intersections.html#I304
|
||||
cdef:
|
||||
Vec3 o2_o1
|
||||
double det1, det2
|
||||
Vec3 o1 = Vec3(ray1[0])
|
||||
Vec3 p1 = Vec3(ray1[1])
|
||||
Vec3 o2 = Vec3(ray2[0])
|
||||
Vec3 p2 = Vec3(ray2[1])
|
||||
Vec3 d1 = v3_normalize(v3_sub(p1, o1), 1.0)
|
||||
Vec3 d2 = v3_normalize(v3_sub(p2, o2), 1.0)
|
||||
Vec3 d1xd2 = v3_cross(d1, d2)
|
||||
double denominator = v3_magnitude_sqr(d1xd2)
|
||||
|
||||
if denominator <= abs_tol:
|
||||
# ray1 is parallel to ray2
|
||||
return tuple()
|
||||
else:
|
||||
o2_o1 = v3_sub(o2, o1)
|
||||
det1 = _determinant(o2_o1, d2, d1xd2)
|
||||
det2 = _determinant(o2_o1, d1, d1xd2)
|
||||
with cython.cdivision(True): # denominator check is already done
|
||||
p1 = v3_add(o1, v3_mul(d1, (det1 / denominator)))
|
||||
p2 = v3_add(o2, v3_mul(d2, (det2 / denominator)))
|
||||
|
||||
if v3_isclose(p1, p2, abs_tol, abs_tol):
|
||||
# ray1 and ray2 have an intersection point
|
||||
return p1,
|
||||
else:
|
||||
# ray1 and ray2 do not have an intersection point,
|
||||
# p1 and p2 are the points of closest approach on each ray
|
||||
return p1, p2
|
||||
|
||||
def arc_angle_span_deg(double start, double end) -> float:
|
||||
if isclose(start, end, REL_TOL, DEG_ABS_TOL):
|
||||
return 0.0
|
||||
|
||||
start %= 360.0
|
||||
if isclose(start, end % 360.0, REL_TOL, DEG_ABS_TOL):
|
||||
return 360.0
|
||||
|
||||
if not isclose(end, 360.0, REL_TOL, DEG_ABS_TOL):
|
||||
end %= 360.0
|
||||
|
||||
if end < start:
|
||||
end += 360.0
|
||||
return end - start
|
||||
|
||||
def arc_angle_span_rad(double start, double end) -> float:
|
||||
if isclose(start, end, REL_TOL, RAD_ABS_TOL):
|
||||
return 0.0
|
||||
|
||||
start %= M_TAU
|
||||
if isclose(start, end % M_TAU, REL_TOL, RAD_ABS_TOL):
|
||||
return M_TAU
|
||||
|
||||
if not isclose(end, M_TAU, REL_TOL, RAD_ABS_TOL):
|
||||
end %= M_TAU
|
||||
|
||||
if end < start:
|
||||
end += M_TAU
|
||||
return end - start
|
||||
|
||||
|
||||
def is_point_in_polygon_2d(
|
||||
point: Vec2, polygon: list[Vec2], double abs_tol=TOLERANCE
|
||||
) -> int:
|
||||
"""
|
||||
Test if `point` is inside `polygon`. Returns +1 for inside, 0 for on the
|
||||
boundary and -1 for outside.
|
||||
|
||||
Supports convex and concave polygons with clockwise or counter-clockwise oriented
|
||||
polygon vertices. Does not raise an exception for degenerated polygons.
|
||||
|
||||
|
||||
Args:
|
||||
point: 2D point to test as :class:`Vec2`
|
||||
polygon: list of 2D points as :class:`Vec2`
|
||||
abs_tol: tolerance for distance check
|
||||
|
||||
Returns:
|
||||
+1 for inside, 0 for on the boundary, -1 for outside
|
||||
|
||||
"""
|
||||
# Source: http://www.faqs.org/faqs/graphics/algorithms-faq/
|
||||
# Subject 2.03: How do I find if a point lies within a polygon?
|
||||
# Numpy version was just 10x faster, this version is 23x faster than the Python
|
||||
# version!
|
||||
cdef double a, b, c, d, x, y, x1, y1, x2, y2
|
||||
cdef list vertices = polygon
|
||||
cdef Vec2 p1, p2
|
||||
cdef int size, last, i
|
||||
cdef bint inside = 0
|
||||
|
||||
size = len(vertices)
|
||||
if size < 3: # empty polygon
|
||||
return -1
|
||||
last = size - 1
|
||||
p1 = <Vec2> vertices[0]
|
||||
p2 = <Vec2> vertices[last]
|
||||
|
||||
if v2_isclose(p1, p2, REL_TOL, ABS_TOL): # open polygon
|
||||
size -= 1
|
||||
last -= 1
|
||||
if size < 3:
|
||||
return -1
|
||||
|
||||
x = point.x
|
||||
y = point.y
|
||||
p1 = <Vec2> vertices[last]
|
||||
x1 = p1.x
|
||||
y1 = p1.y
|
||||
|
||||
for i in range(size):
|
||||
p2 = <Vec2> vertices[i]
|
||||
x2 = p2.x
|
||||
y2 = p2.y
|
||||
|
||||
# is point on polygon boundary line:
|
||||
# is point in x-range of line
|
||||
a, b = (x2, x1) if x2 < x1 else (x1, x2)
|
||||
if a <= x <= b:
|
||||
# is point in y-range of line
|
||||
c, d = (y2, y1) if y2 < y1 else (y1, y2)
|
||||
if (c <= y <= d) and fabs(
|
||||
(y2 - y1) * x - (x2 - x1) * y + (x2 * y1 - y2 * x1)
|
||||
) <= abs_tol:
|
||||
return 0 # on boundary line
|
||||
if ((y1 <= y < y2) or (y2 <= y < y1)) and (
|
||||
x < (x2 - x1) * (y - y1) / (y2 - y1) + x1
|
||||
):
|
||||
inside = not inside
|
||||
x1 = x2
|
||||
y1 = y2
|
||||
if inside:
|
||||
return 1 # inside polygon
|
||||
else:
|
||||
return -1 # outside polygon
|
||||
|
||||
cdef double WGS84_SEMI_MAJOR_AXIS = 6378137
|
||||
cdef double WGS84_SEMI_MINOR_AXIS = 6356752.3142
|
||||
cdef double WGS84_ELLIPSOID_ECCENTRIC = 0.08181919092890624
|
||||
cdef double RADIANS = M_PI / 180.0
|
||||
cdef double DEGREES = 180.0 / M_PI
|
||||
|
||||
|
||||
def gps_to_world_mercator(double longitude, double latitude) -> tuple[float, float]:
|
||||
"""Transform GPS (long/lat) to World Mercator.
|
||||
|
||||
Transform WGS84 `EPSG:4326 <https://epsg.io/4326>`_ location given as
|
||||
latitude and longitude in decimal degrees as used by GPS into World Mercator
|
||||
cartesian 2D coordinates in meters `EPSG:3395 <https://epsg.io/3395>`_.
|
||||
|
||||
Args:
|
||||
longitude: represents the longitude value (East-West) in decimal degrees
|
||||
latitude: represents the latitude value (North-South) in decimal degrees.
|
||||
|
||||
"""
|
||||
# From: https://epsg.io/4326
|
||||
# EPSG:4326 WGS84 - World Geodetic System 1984, used in GPS
|
||||
# To: https://epsg.io/3395
|
||||
# EPSG:3395 - World Mercator
|
||||
# Source: https://gis.stackexchange.com/questions/259121/transformation-functions-for-epsg3395-projection-vs-epsg3857
|
||||
longitude = longitude * RADIANS # east
|
||||
latitude = latitude * RADIANS # north
|
||||
cdef double e_sin_lat = sin(latitude) * WGS84_ELLIPSOID_ECCENTRIC
|
||||
cdef double c = pow(
|
||||
(1.0 - e_sin_lat) / (1.0 + e_sin_lat), WGS84_ELLIPSOID_ECCENTRIC / 2.0
|
||||
) # 7-7 p.44
|
||||
y = WGS84_SEMI_MAJOR_AXIS * log(tan(M_PI_4 + latitude / 2.0) * c) # 7-7 p.44
|
||||
x = WGS84_SEMI_MAJOR_AXIS * longitude
|
||||
return x, y
|
||||
|
||||
|
||||
def world_mercator_to_gps(double x, double y, double tol = 1e-6) -> tuple[float, float]:
|
||||
"""Transform World Mercator to GPS.
|
||||
|
||||
Transform WGS84 World Mercator `EPSG:3395 <https://epsg.io/3395>`_
|
||||
location given as cartesian 2D coordinates x, y in meters into WGS84 decimal
|
||||
degrees as longitude and latitude `EPSG:4326 <https://epsg.io/4326>`_ as
|
||||
used by GPS.
|
||||
|
||||
Args:
|
||||
x: coordinate WGS84 World Mercator
|
||||
y: coordinate WGS84 World Mercator
|
||||
tol: accuracy for latitude calculation
|
||||
|
||||
"""
|
||||
# From: https://epsg.io/3395
|
||||
# EPSG:3395 - World Mercator
|
||||
# To: https://epsg.io/4326
|
||||
# EPSG:4326 WGS84 - World Geodetic System 1984, used in GPS
|
||||
# Source: Map Projections - A Working Manual
|
||||
# https://pubs.usgs.gov/pp/1395/report.pdf
|
||||
cdef double eccentric_2 = WGS84_ELLIPSOID_ECCENTRIC / 2.0
|
||||
cdef double t = pow(M_E, (-y / WGS84_SEMI_MAJOR_AXIS)) # 7-10 p.44
|
||||
cdef double e_sin_lat, latitude, latitude_prev
|
||||
|
||||
latitude_prev = M_PI_2 - 2.0 * atan(t) # 7-11 p.45
|
||||
while True:
|
||||
e_sin_lat = sin(latitude_prev) * WGS84_ELLIPSOID_ECCENTRIC
|
||||
latitude = M_PI_2 - 2.0 * atan(
|
||||
t * pow(((1.0 - e_sin_lat) / (1.0 + e_sin_lat)), eccentric_2)
|
||||
) # 7-9 p.44
|
||||
if fabs(latitude - latitude_prev) < tol:
|
||||
break
|
||||
latitude_prev = latitude
|
||||
|
||||
longitude = x / WGS84_SEMI_MAJOR_AXIS # 7-12 p.45
|
||||
return longitude * DEGREES, latitude * DEGREES
|
||||
11553
.venv/lib/python3.12/site-packages/ezdxf/acc/linetypes.c
Normal file
11553
.venv/lib/python3.12/site-packages/ezdxf/acc/linetypes.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/linetypes.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/linetypes.cpython-312-darwin.so
Executable file
Binary file not shown.
91
.venv/lib/python3.12/site-packages/ezdxf/acc/linetypes.pyx
Normal file
91
.venv/lib/python3.12/site-packages/ezdxf/acc/linetypes.pyx
Normal file
@@ -0,0 +1,91 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2022-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing import Iterator, TYPE_CHECKING, Sequence
|
||||
import cython
|
||||
from cpython.mem cimport PyMem_Malloc, PyMem_Free
|
||||
from .vector cimport Vec3, v3_isclose, v3_sub, v3_add, v3_mul, v3_magnitude
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.math import UVec
|
||||
|
||||
__all__ = ["_LineTypeRenderer"]
|
||||
LineSegment = tuple[Vec3, Vec3]
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
|
||||
|
||||
cdef class _LineTypeRenderer:
|
||||
cdef double *dashes
|
||||
cdef int dash_count
|
||||
cdef readonly bint is_solid
|
||||
cdef bint is_dash
|
||||
cdef int current_dash
|
||||
cdef double current_dash_length
|
||||
|
||||
def __init__(self, dashes: Sequence[float]):
|
||||
cdef list _dashes = list(dashes)
|
||||
cdef int i
|
||||
|
||||
self.dash_count = len(_dashes)
|
||||
self.dashes = <double *> PyMem_Malloc(self.dash_count * sizeof(double))
|
||||
for i in range(self.dash_count):
|
||||
self.dashes[i] = _dashes[i]
|
||||
|
||||
self.is_solid = True
|
||||
self.is_dash = False
|
||||
self.current_dash = 0
|
||||
self.current_dash_length = 0.0
|
||||
|
||||
if self.dash_count > 1:
|
||||
self.is_solid = False
|
||||
self.current_dash_length = self.dashes[0]
|
||||
self.is_dash = True
|
||||
|
||||
def __dealloc__(self):
|
||||
PyMem_Free(self.dashes)
|
||||
|
||||
def line_segment(self, start: UVec, end: UVec) -> Iterator[LineSegment]:
|
||||
cdef Vec3 v3_start = Vec3(start)
|
||||
cdef Vec3 v3_end = Vec3(end)
|
||||
cdef Vec3 segment_vec, segment_dir
|
||||
cdef double segment_length, dash_length
|
||||
cdef list dashes = []
|
||||
|
||||
if self.is_solid or v3_isclose(v3_start, v3_end, REL_TOL, ABS_TOL):
|
||||
yield v3_start, v3_end
|
||||
return
|
||||
|
||||
segment_vec = v3_sub(v3_end, v3_start)
|
||||
segment_length = v3_magnitude(segment_vec)
|
||||
with cython.cdivision:
|
||||
segment_dir = v3_mul(segment_vec, 1.0 / segment_length) # normalize
|
||||
|
||||
self._render_dashes(segment_length, dashes)
|
||||
for dash_length in dashes:
|
||||
v3_end = v3_add(v3_start, v3_mul(segment_dir, abs(dash_length)))
|
||||
if dash_length > 0:
|
||||
yield v3_start, v3_end
|
||||
v3_start = v3_end
|
||||
|
||||
cdef _render_dashes(self, double length, list dashes):
|
||||
if length <= self.current_dash_length:
|
||||
self.current_dash_length -= length
|
||||
dashes.append(length if self.is_dash else -length)
|
||||
if self.current_dash_length < ABS_TOL:
|
||||
self._cycle_dashes()
|
||||
else:
|
||||
# Avoid deep recursions!
|
||||
while length > self.current_dash_length:
|
||||
length -= self.current_dash_length
|
||||
self._render_dashes(self.current_dash_length, dashes)
|
||||
if length > 0.0:
|
||||
self._render_dashes(length, dashes)
|
||||
|
||||
cdef _cycle_dashes(self):
|
||||
with cython.cdivision:
|
||||
self.current_dash = (self.current_dash + 1) % self.dash_count
|
||||
self.current_dash_length = self.dashes[self.current_dash]
|
||||
self.is_dash = not self.is_dash
|
||||
16206
.venv/lib/python3.12/site-packages/ezdxf/acc/mapbox_earcut.c
Normal file
16206
.venv/lib/python3.12/site-packages/ezdxf/acc/mapbox_earcut.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/mapbox_earcut.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/mapbox_earcut.cpython-312-darwin.so
Executable file
Binary file not shown.
835
.venv/lib/python3.12/site-packages/ezdxf/acc/mapbox_earcut.pyx
Normal file
835
.venv/lib/python3.12/site-packages/ezdxf/acc/mapbox_earcut.pyx
Normal file
@@ -0,0 +1,835 @@
|
||||
# cython: language_level=3
|
||||
# Source: https://github.com/mapbox/earcut
|
||||
# License: ISC License (MIT compatible)
|
||||
#
|
||||
# Copyright (c) 2016, Mapbox
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
# with or without fee is hereby granted, provided that the above copyright notice
|
||||
# and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
# THIS SOFTWARE.
|
||||
#
|
||||
# Cython implementation of module ezdxf.math._mapbox_earcut.py
|
||||
# Copyright (c) 2022, Manfred Moitzi
|
||||
# License: MIT License
|
||||
# type: ignore
|
||||
from libc.math cimport fmin, fmax, fabs, INFINITY
|
||||
|
||||
|
||||
cdef class Node:
|
||||
cdef:
|
||||
int i
|
||||
double x
|
||||
double y
|
||||
int z
|
||||
bint steiner
|
||||
object point
|
||||
Node prev
|
||||
Node next
|
||||
Node prev_z
|
||||
Node next_z
|
||||
|
||||
def __cinit__(self, int i, object point):
|
||||
self.i = i
|
||||
self.point = point
|
||||
self.x = point.x
|
||||
self.y = point.y
|
||||
self.z = 0
|
||||
self.steiner = False
|
||||
self.prev = None
|
||||
self.next = None
|
||||
self.prev_z = None
|
||||
self.next_z = None
|
||||
|
||||
cdef bint equals(self, Node other):
|
||||
return self.x == other.x and self.y == other.y
|
||||
|
||||
def node_key(Node node):
|
||||
return node.x, node.y
|
||||
|
||||
def earcut(list exterior, list holes):
|
||||
"""Implements a modified ear slicing algorithm, optimized by z-order
|
||||
curve hashing and extended to handle holes, twisted polygons, degeneracies
|
||||
and self-intersections in a way that doesn't guarantee correctness of
|
||||
triangulation, but attempts to always produce acceptable results for
|
||||
practical data.
|
||||
|
||||
Source: https://github.com/mapbox/earcut
|
||||
|
||||
Args:
|
||||
exterior: outer path as list of points as objects which provide a
|
||||
`x`- and a `y`-attribute
|
||||
holes: list of holes, each hole is list of points, a hole with
|
||||
a single points is a Steiner point
|
||||
|
||||
Returns:
|
||||
Returns a list of triangles, each triangle is a tuple of three points,
|
||||
the output points are the same objects as the input points.
|
||||
|
||||
"""
|
||||
cdef:
|
||||
Node outer_node
|
||||
list triangles = []
|
||||
double max_x, max_y, x, y
|
||||
double min_x = 0.0
|
||||
double min_y = 0.0
|
||||
double inv_size = 0.0
|
||||
|
||||
if not exterior:
|
||||
return triangles
|
||||
|
||||
outer_node = linked_list(exterior, 0, ccw=True)
|
||||
if outer_node is None or outer_node.next is outer_node.prev:
|
||||
return triangles
|
||||
|
||||
if holes:
|
||||
outer_node = eliminate_holes(holes, len(exterior), outer_node)
|
||||
|
||||
# if the shape is not too simple, we'll use z-order curve hash later
|
||||
# calculate polygon bbox
|
||||
if len(exterior) > 80:
|
||||
min_x = max_x = exterior[0].x
|
||||
min_y = max_y = exterior[0].y
|
||||
for point in exterior:
|
||||
x = point.x
|
||||
y = point.y
|
||||
min_x = fmin(min_x, x)
|
||||
min_y = fmin(min_y, y)
|
||||
max_x = fmax(max_x, x)
|
||||
max_y = fmax(max_y, y)
|
||||
|
||||
# min_x, min_y and inv_size are later used to transform coords into
|
||||
# integers for z-order calculation
|
||||
inv_size = fmax(max_x - min_x, max_y - min_y)
|
||||
inv_size = 32767 / inv_size if inv_size != 0 else 0
|
||||
|
||||
earcut_linked(outer_node, triangles, min_x, min_y, inv_size, 0)
|
||||
return triangles
|
||||
|
||||
|
||||
cdef Node linked_list(list points, int start, bint ccw):
|
||||
"""Create a circular doubly linked list from polygon points in the specified
|
||||
winding order
|
||||
"""
|
||||
cdef:
|
||||
Node last = None
|
||||
int end
|
||||
|
||||
if ccw is (signed_area(points) < 0):
|
||||
for point in points:
|
||||
last = insert_node(start, point, last)
|
||||
start += 1
|
||||
else:
|
||||
end = start + len(points)
|
||||
for point in reversed(points):
|
||||
last = insert_node(end, point, last)
|
||||
end -= 1
|
||||
|
||||
# open polygon: where the 1st vertex is not coincident with the last vertex
|
||||
if last and last.equals(last.next):
|
||||
remove_node(last)
|
||||
last = last.next
|
||||
return last
|
||||
|
||||
|
||||
cdef double signed_area(list points):
|
||||
cdef:
|
||||
double s = 0.0
|
||||
double point_x, prev_x, point_y, prev_y
|
||||
if not len(points):
|
||||
return s
|
||||
prev = points[-1]
|
||||
prev_x = prev.x
|
||||
prev_y = prev.y
|
||||
for point in points:
|
||||
point_x = point.x
|
||||
point_y = point.y
|
||||
s += (point_x - prev_x) * (point_y + prev_y)
|
||||
prev_x = point_x
|
||||
prev_y = point_y
|
||||
# s < 0 is counter-clockwise
|
||||
# s > 0 is clockwise
|
||||
return s
|
||||
|
||||
|
||||
cdef double area(Node p, Node q, Node r):
|
||||
"""Returns signed area of a triangle"""
|
||||
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
|
||||
|
||||
|
||||
cdef bint is_valid_diagonal(Node a, Node b):
|
||||
"""Check if a diagonal between two polygon nodes is valid (lies in polygon
|
||||
interior)
|
||||
"""
|
||||
return (
|
||||
a.next.i != b.i
|
||||
and a.prev.i != b.i
|
||||
and not intersects_polygon(a, b) # doesn't intersect other edges
|
||||
and (
|
||||
locally_inside(a, b)
|
||||
and locally_inside(b, a)
|
||||
and middle_inside(a, b)
|
||||
and (
|
||||
area(a.prev, a, b.prev) or area(a, b.prev, b)
|
||||
) # does not create opposite-facing sectors
|
||||
or a.equals(b)
|
||||
and area(a.prev, a, a.next) > 0
|
||||
and area(b.prev, b, b.next) > 0
|
||||
) # special zero-length case
|
||||
)
|
||||
|
||||
|
||||
cdef bint intersects_polygon(Node a, Node b):
|
||||
"""Check if a polygon diagonal intersects any polygon segments"""
|
||||
cdef Node p = a
|
||||
while True:
|
||||
if (
|
||||
p.i != a.i
|
||||
and p.next.i != a.i
|
||||
and p.i != b.i
|
||||
and p.next.i != b.i
|
||||
and intersects(p, p.next, a, b)
|
||||
):
|
||||
return True
|
||||
p = p.next
|
||||
if p is a:
|
||||
break
|
||||
return False
|
||||
|
||||
|
||||
cdef int sign(double num):
|
||||
if num < 0.0:
|
||||
return -1
|
||||
if num > 0.0:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
cdef bint on_segment(p: Node, q: Node, r: Node):
|
||||
return fmax(p.x, r.x) >= q.x >= fmin(p.x, r.x) and fmax(
|
||||
p.y, r.y
|
||||
) >= q.y >= fmin(p.y, r.y)
|
||||
|
||||
|
||||
cdef bint intersects(Node p1, Node q1, Node p2, Node q2):
|
||||
"""check if two segments intersect"""
|
||||
cdef:
|
||||
int o1 = sign(area(p1, q1, p2))
|
||||
int o2 = sign(area(p1, q1, q2))
|
||||
int o3 = sign(area(p2, q2, p1))
|
||||
int o4 = sign(area(p2, q2, q1))
|
||||
|
||||
if o1 != o2 and o3 != o4:
|
||||
return True # general case
|
||||
|
||||
if o1 == 0 and on_segment(p1, p2, q1):
|
||||
return True # p1, q1 and p2 are collinear and p2 lies on p1q1
|
||||
if o2 == 0 and on_segment(p1, q2, q1):
|
||||
return True # p1, q1 and q2 are collinear and q2 lies on p1q1
|
||||
if o3 == 0 and on_segment(p2, p1, q2):
|
||||
return True # p2, q2 and p1 are collinear and p1 lies on p2q2
|
||||
if o4 == 0 and on_segment(p2, q1, q2):
|
||||
return True # p2, q2 and q1 are collinear and q1 lies on p2q2
|
||||
return False
|
||||
|
||||
|
||||
cdef Node insert_node(int i, point, Node last):
|
||||
"""create a node and optionally link it with previous one (in a circular
|
||||
doubly linked list)
|
||||
"""
|
||||
cdef Node p = Node(i, point)
|
||||
|
||||
if last is None:
|
||||
p.prev = p
|
||||
p.next = p
|
||||
else:
|
||||
p.next = last.next
|
||||
p.prev = last
|
||||
last.next.prev = p
|
||||
last.next = p
|
||||
return p
|
||||
|
||||
|
||||
cdef remove_node(Node p):
|
||||
p.next.prev = p.prev
|
||||
p.prev.next = p.next
|
||||
|
||||
if p.prev_z is not None:
|
||||
p.prev_z.next_z = p.next_z
|
||||
if p.next_z is not None:
|
||||
p.next_z.prev_z = p.prev_z
|
||||
|
||||
|
||||
cdef Node eliminate_holes(list holes, int start, Node outer_node):
|
||||
"""link every hole into the outer loop, producing a single-ring polygon
|
||||
without holes
|
||||
"""
|
||||
cdef:
|
||||
list queue = []
|
||||
list hole
|
||||
for hole in holes:
|
||||
if len(hole) < 1: # skip empty holes
|
||||
continue
|
||||
# hole vertices in clockwise order
|
||||
_list = linked_list(hole, start, ccw=False)
|
||||
if _list is _list.next:
|
||||
_list.steiner = True
|
||||
start += len(hole)
|
||||
queue.append(get_leftmost(_list))
|
||||
queue.sort(key=node_key)
|
||||
|
||||
# process holes from left to right
|
||||
for hole_ in queue:
|
||||
outer_node = eliminate_hole(hole_, outer_node)
|
||||
return outer_node
|
||||
|
||||
|
||||
cdef Node eliminate_hole(Node hole, Node outer_node):
|
||||
"""Find a bridge between vertices that connects hole with an outer ring and
|
||||
link it
|
||||
"""
|
||||
cdef:
|
||||
Node bridge = find_hole_bridge(hole, outer_node)
|
||||
Node bridge_reverse
|
||||
|
||||
if bridge is None:
|
||||
return outer_node
|
||||
|
||||
bridge_reverse = split_polygon(bridge, hole)
|
||||
|
||||
# filter collinear points around the cuts
|
||||
filter_points(bridge_reverse, bridge_reverse.next)
|
||||
return filter_points(bridge, bridge.next)
|
||||
|
||||
|
||||
cdef Node filter_points(Node start, Node end = None):
|
||||
"""eliminate colinear or duplicate points"""
|
||||
cdef:
|
||||
Node p
|
||||
bint again
|
||||
|
||||
if start is None:
|
||||
return start
|
||||
if end is None:
|
||||
end = start
|
||||
|
||||
p = start
|
||||
while True:
|
||||
again = False
|
||||
if not p.steiner and (
|
||||
p.equals(p.next) or area(p.prev, p, p.next) == 0
|
||||
):
|
||||
remove_node(p)
|
||||
p = end = p.prev
|
||||
if p is p.next:
|
||||
break
|
||||
again = True
|
||||
else:
|
||||
p = p.next
|
||||
if not (again or p is not end):
|
||||
break
|
||||
return end
|
||||
|
||||
|
||||
# main ear slicing loop which triangulates a polygon (given as a linked list)
|
||||
cdef earcut_linked(
|
||||
Node ear,
|
||||
list triangles,
|
||||
double min_x,
|
||||
double min_y,
|
||||
double inv_size,
|
||||
int pass_,
|
||||
):
|
||||
cdef:
|
||||
Node stop, prev, next
|
||||
bint _is_ear
|
||||
|
||||
if ear is None:
|
||||
return
|
||||
|
||||
# interlink polygon nodes in z-order
|
||||
if not pass_ and inv_size:
|
||||
index_curve(ear, min_x, min_y, inv_size)
|
||||
|
||||
stop = ear
|
||||
|
||||
# iterate through ears, slicing them one by one
|
||||
while ear.prev is not ear.next:
|
||||
prev = ear.prev
|
||||
next = ear.next
|
||||
|
||||
_is_ear = (
|
||||
is_ear_hashed(ear, min_x, min_y, inv_size)
|
||||
if inv_size
|
||||
else is_ear(ear)
|
||||
)
|
||||
if _is_ear:
|
||||
# cut off the triangle
|
||||
triangles.append((prev.point, ear.point, next.point))
|
||||
remove_node(ear)
|
||||
|
||||
# skipping the next vertex leads to less sliver triangles
|
||||
ear = next.next
|
||||
stop = next.next
|
||||
continue
|
||||
|
||||
ear = next
|
||||
|
||||
# if we looped through the whole remaining polygon and can't find any more ears
|
||||
if ear is stop:
|
||||
# try filtering points and slicing again
|
||||
if not pass_:
|
||||
earcut_linked(
|
||||
filter_points(ear),
|
||||
triangles,
|
||||
min_x,
|
||||
min_y,
|
||||
inv_size,
|
||||
1,
|
||||
)
|
||||
|
||||
# if this didn't work, try curing all small self-intersections locally
|
||||
elif pass_ == 1:
|
||||
ear = cure_local_intersections(filter_points(ear), triangles)
|
||||
earcut_linked(ear, triangles, min_x, min_y, inv_size, 2)
|
||||
|
||||
# as a last resort, try splitting the remaining polygon into two
|
||||
elif pass_ == 2:
|
||||
split_ear_cut(ear, triangles, min_x, min_y, inv_size)
|
||||
break
|
||||
|
||||
|
||||
cdef bint is_ear(Node ear):
|
||||
"""check whether a polygon node forms a valid ear with adjacent nodes"""
|
||||
cdef:
|
||||
Node a = ear.prev
|
||||
Node b = ear
|
||||
Node c = ear.next
|
||||
Node p
|
||||
double x0, x1, y0, y1
|
||||
|
||||
if area(a, b, c) >= 0:
|
||||
return False # reflex, can't be an ear
|
||||
|
||||
# now make sure we don't have other points inside the potential ear
|
||||
# triangle bbox
|
||||
x0 = fmin(a.x, fmin(b.x, c.x))
|
||||
x1 = fmax(a.x, fmax(b.x, c.x))
|
||||
y0 = fmin(a.y, fmin(b.y, c.y))
|
||||
y1 = fmax(a.y, fmax(b.y, c.y))
|
||||
p = c.next
|
||||
|
||||
while p is not a:
|
||||
if (
|
||||
x0 <= p.x <= x1
|
||||
and y0 <= p.y <= y1
|
||||
and point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y)
|
||||
and area(p.prev, p, p.next) >= 0
|
||||
):
|
||||
return False
|
||||
p = p.next
|
||||
|
||||
return True
|
||||
|
||||
|
||||
cdef bint is_ear_hashed(Node ear, double min_x, double min_y, double inv_size):
|
||||
cdef:
|
||||
Node a = ear.prev
|
||||
Node b = ear
|
||||
Node c = ear.next
|
||||
double x0, x1, y0, y1, min_z, max_z
|
||||
Node p, n
|
||||
|
||||
if area(a, b, c) >= 0:
|
||||
return False # reflex, can't be an ear
|
||||
|
||||
# triangle bbox
|
||||
x0 = fmin(a.x, fmin(b.x, c.x))
|
||||
x1 = fmax(a.x, fmax(b.x, c.x))
|
||||
y0 = fmin(a.y, fmin(b.y, c.y))
|
||||
y1 = fmax(a.y, fmax(b.y, c.y))
|
||||
|
||||
# z-order range for the current triangle bbox;
|
||||
min_z = z_order(x0, y0, min_x, min_y, inv_size)
|
||||
max_z = z_order(x1, y1, min_x, min_y, inv_size)
|
||||
|
||||
p = ear.prev_z
|
||||
n = ear.next_z
|
||||
|
||||
# look for points inside the triangle in both directions
|
||||
while p and p.z >= min_z and n and n.z <= max_z:
|
||||
if (
|
||||
x0 <= p.x <= x1
|
||||
and y0 <= p.y <= y1
|
||||
and p is not a
|
||||
and p is not c
|
||||
and point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y)
|
||||
and area(p.prev, p, p.next) >= 0
|
||||
):
|
||||
return False
|
||||
p = p.prev_z
|
||||
|
||||
if (
|
||||
x0 <= n.x <= x1
|
||||
and y0 <= n.y <= y1
|
||||
and n is not a
|
||||
and n is not c
|
||||
and point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y)
|
||||
and area(n.prev, n, n.next) >= 0
|
||||
):
|
||||
return False
|
||||
n = n.next_z
|
||||
|
||||
# look for remaining points in decreasing z-order
|
||||
while p and p.z >= min_z:
|
||||
if (
|
||||
x0 <= p.x <= x1
|
||||
and y0 <= p.y <= y1
|
||||
and p is not a
|
||||
and p is not c
|
||||
and point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y)
|
||||
and area(p.prev, p, p.next) >= 0
|
||||
):
|
||||
return False
|
||||
p = p.prev_z
|
||||
|
||||
# look for remaining points in increasing z-order
|
||||
while n and n.z <= max_z:
|
||||
if (
|
||||
x0 <= n.x <= x1
|
||||
and y0 <= n.y <= y1
|
||||
and n is not a
|
||||
and n is not c
|
||||
and point_in_triangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y)
|
||||
and area(n.prev, n, n.next) >= 0
|
||||
):
|
||||
return False
|
||||
n = n.next_z
|
||||
return True
|
||||
|
||||
|
||||
cdef Node get_leftmost(Node start):
|
||||
"""Find the leftmost node of a polygon ring"""
|
||||
cdef:
|
||||
Node p = start
|
||||
Node leftmost = start
|
||||
|
||||
while True:
|
||||
if p.x < leftmost.x or (p.x == leftmost.x and p.y < leftmost.y):
|
||||
leftmost = p
|
||||
p = p.next
|
||||
if p is start:
|
||||
break
|
||||
return leftmost
|
||||
|
||||
|
||||
cdef bint point_in_triangle(
|
||||
double ax,
|
||||
double ay,
|
||||
double bx,
|
||||
double by_,
|
||||
double cx,
|
||||
double cy,
|
||||
double px,
|
||||
double py,
|
||||
):
|
||||
"""Check if a point lies within a convex triangle"""
|
||||
return (
|
||||
(cx - px) * (ay - py) >= (ax - px) * (cy - py)
|
||||
and (ax - px) * (by_ - py) >= (bx - px) * (ay - py)
|
||||
and (bx - px) * (cy - py) >= (cx - px) * (by_ - py)
|
||||
)
|
||||
|
||||
|
||||
cdef bint sector_contains_sector(Node m, Node p):
|
||||
"""Whether sector in vertex m contains sector in vertex p in the same
|
||||
coordinates.
|
||||
"""
|
||||
return area(m.prev, m, p.prev) < 0 and area(p.next, m, m.next) < 0
|
||||
|
||||
|
||||
cdef index_curve(Node start, double min_x, double min_y, double inv_size):
|
||||
"""Interlink polygon nodes in z-order"""
|
||||
cdef Node p = start
|
||||
while True:
|
||||
if p.z == 0:
|
||||
p.z = z_order(p.x, p.y, min_x, min_y, inv_size)
|
||||
p.prev_z = p.prev
|
||||
p.next_z = p.next
|
||||
p = p.next
|
||||
if p is start:
|
||||
break
|
||||
|
||||
p.prev_z.next_z = None
|
||||
p.prev_z = None
|
||||
|
||||
sort_linked(p)
|
||||
|
||||
|
||||
cdef int z_order(
|
||||
double x0, double y0, double min_x, double min_y, double inv_size
|
||||
):
|
||||
"""Z-order of a point given coords and inverse of the longer side of data
|
||||
bbox.
|
||||
"""
|
||||
# coords are transformed into non-negative 15-bit integer range
|
||||
cdef:
|
||||
int x = int((x0 - min_x) * inv_size)
|
||||
int y = int ((y0 - min_y) * inv_size)
|
||||
|
||||
x = (x | (x << 8)) & 0x00FF00FF
|
||||
x = (x | (x << 4)) & 0x0F0F0F0F
|
||||
x = (x | (x << 2)) & 0x33333333
|
||||
x = (x | (x << 1)) & 0x55555555
|
||||
|
||||
y = (y | (y << 8)) & 0x00FF00FF
|
||||
y = (y | (y << 4)) & 0x0F0F0F0F
|
||||
y = (y | (y << 2)) & 0x33333333
|
||||
y = (y | (y << 1)) & 0x55555555
|
||||
|
||||
return x | (y << 1)
|
||||
|
||||
|
||||
# Simon Tatham's linked list merge sort algorithm
|
||||
# http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
||||
cdef Node sort_linked(Node head):
|
||||
cdef:
|
||||
int in_size = 1
|
||||
int num_merges, p_size, q_size, i
|
||||
Node tail, p, q, e
|
||||
|
||||
while True:
|
||||
p = head
|
||||
head = None
|
||||
tail = None
|
||||
num_merges = 0
|
||||
while p:
|
||||
num_merges += 1
|
||||
q = p
|
||||
p_size = 0
|
||||
for i in range(in_size):
|
||||
p_size += 1
|
||||
q = q.next_z
|
||||
if not q:
|
||||
break
|
||||
q_size = in_size
|
||||
while p_size > 0 or (q_size > 0 and q):
|
||||
if p_size != 0 and (q_size == 0 or not q or p.z <= q.z):
|
||||
e = p
|
||||
p = p.next_z
|
||||
p_size -= 1
|
||||
else:
|
||||
e = q
|
||||
q = q.next_z
|
||||
q_size -= 1
|
||||
|
||||
if tail:
|
||||
tail.next_z = e
|
||||
else:
|
||||
head = e
|
||||
e.prev_z = tail
|
||||
tail = e
|
||||
p = q
|
||||
tail.next_z = None
|
||||
in_size *= 2
|
||||
if num_merges <= 1:
|
||||
break
|
||||
return head
|
||||
|
||||
|
||||
cdef Node split_polygon(Node a, Node b):
|
||||
"""Link two polygon vertices with a bridge.
|
||||
|
||||
If the vertices belong to the same ring, it splits polygon into two.
|
||||
If one belongs to the outer ring and another to a hole, it merges it into a
|
||||
single ring.
|
||||
"""
|
||||
cdef :
|
||||
Node a2 = Node(a.i, a.point)
|
||||
Node b2 = Node(b.i, b.point)
|
||||
Node an = a.next
|
||||
Node bp = b.prev
|
||||
|
||||
a.next = b
|
||||
b.prev = a
|
||||
|
||||
a2.next = an
|
||||
an.prev = a2
|
||||
|
||||
b2.next = a2
|
||||
a2.prev = b2
|
||||
|
||||
bp.next = b2
|
||||
b2.prev = bp
|
||||
|
||||
return b2
|
||||
|
||||
|
||||
# go through all polygon nodes and cure small local self-intersections
|
||||
cdef Node cure_local_intersections(Node start, list triangles):
|
||||
cdef:
|
||||
Node p = start
|
||||
Node a, b
|
||||
while True:
|
||||
a = p.prev
|
||||
b = p.next.next
|
||||
|
||||
if (
|
||||
not a.equals(b)
|
||||
and intersects(a, p, p.next, b)
|
||||
and locally_inside(a, b)
|
||||
and locally_inside(b, a)
|
||||
):
|
||||
triangles.append((a.point, p.point, b.point))
|
||||
# remove two nodes involved
|
||||
remove_node(p)
|
||||
remove_node(p.next)
|
||||
p = start = b
|
||||
|
||||
p = p.next
|
||||
if p is start:
|
||||
break
|
||||
return filter_points(p)
|
||||
|
||||
|
||||
cdef split_ear_cut(
|
||||
Node start,
|
||||
list triangles,
|
||||
double min_x,
|
||||
double min_y,
|
||||
double inv_size,
|
||||
):
|
||||
"""Try splitting polygon into two and triangulate them independently"""
|
||||
# look for a valid diagonal that divides the polygon into two
|
||||
cdef:
|
||||
Node a = start
|
||||
Node b, c
|
||||
|
||||
while True:
|
||||
b = a.next.next
|
||||
while b is not a.prev:
|
||||
if a.i != b.i and is_valid_diagonal(a, b):
|
||||
# split the polygon in two by the diagonal
|
||||
c = split_polygon(a, b)
|
||||
|
||||
# filter colinear points around the cuts
|
||||
a = filter_points(a, a.next)
|
||||
c = filter_points(c, c.next)
|
||||
|
||||
# run earcut on each half
|
||||
earcut_linked(a, triangles, min_x, min_y, inv_size, 0)
|
||||
earcut_linked(c, triangles, min_x, min_y, inv_size, 0)
|
||||
return
|
||||
b = b.next
|
||||
a = a.next
|
||||
if a is start:
|
||||
break
|
||||
|
||||
|
||||
# David Eberly's algorithm for finding a bridge between hole and outer polygon
|
||||
cdef Node find_hole_bridge(Node hole, Node outer_node):
|
||||
cdef:
|
||||
Node p = outer_node
|
||||
Node m = None
|
||||
Node stop
|
||||
double hx = hole.x
|
||||
double hy = hole.y
|
||||
double qx = -INFINITY
|
||||
double mx, my, tan_min, tan
|
||||
|
||||
# find a segment intersected by a ray from the hole's leftmost point to the left;
|
||||
# segment's endpoint with lesser x will be potential connection point
|
||||
while True:
|
||||
if p.y >= hy >= p.next.y != p.y:
|
||||
x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y)
|
||||
if hx >= x > qx:
|
||||
qx = x
|
||||
m = p if p.x < p.next.x else p.next
|
||||
if x == hx:
|
||||
# hole touches outer segment; pick leftmost endpoint
|
||||
return m
|
||||
p = p.next
|
||||
if p is outer_node:
|
||||
break
|
||||
|
||||
if m is None:
|
||||
return None
|
||||
|
||||
# look for points inside the triangle of hole point, segment intersection and endpoint;
|
||||
# if there are no points found, we have a valid connection;
|
||||
# otherwise choose the point of the minimum angle with the ray as connection point
|
||||
stop = m
|
||||
mx = m.x
|
||||
my = m.y
|
||||
tan_min = INFINITY
|
||||
p = m
|
||||
|
||||
while True:
|
||||
if (
|
||||
hx >= p.x >= mx
|
||||
and hx != p.x
|
||||
and point_in_triangle(
|
||||
hx if hy < my else qx,
|
||||
hy,
|
||||
mx,
|
||||
my,
|
||||
qx if hy < my else hx,
|
||||
hy,
|
||||
p.x,
|
||||
p.y,
|
||||
)
|
||||
):
|
||||
tan = fabs(hy - p.y) / (hx - p.x) # tangential
|
||||
if locally_inside(p, hole) and (
|
||||
tan < tan_min
|
||||
or (
|
||||
tan == tan_min
|
||||
and (
|
||||
p.x > m.x
|
||||
or (p.x == m.x and sector_contains_sector(m, p))
|
||||
)
|
||||
)
|
||||
):
|
||||
m = p
|
||||
tan_min = tan
|
||||
p = p.next
|
||||
if p is stop:
|
||||
break
|
||||
return m
|
||||
|
||||
|
||||
cdef bint locally_inside(Node a, Node b):
|
||||
"""Check if a polygon diagonal is locally inside the polygon"""
|
||||
return (
|
||||
area(a, b, a.next) >= 0 and area(a, a.prev, b) >= 0
|
||||
if area(a.prev, a, a.next) < 0
|
||||
else area(a, b, a.prev) < 0 or area(a, a.next, b) < 0
|
||||
)
|
||||
|
||||
|
||||
cdef bint middle_inside(Node a, Node b):
|
||||
"""Check if the middle point of a polygon diagonal is inside the polygon"""
|
||||
cdef:
|
||||
Node p = a
|
||||
bint inside = False
|
||||
double px = (a.x + b.x) / 2
|
||||
double py = (a.y + b.y) / 2
|
||||
|
||||
while True:
|
||||
if (
|
||||
((p.y > py) != (p.next.y > py))
|
||||
and p.next.y != p.y
|
||||
and (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)
|
||||
):
|
||||
inside = not inside
|
||||
p = p.next
|
||||
if p is a:
|
||||
break
|
||||
return inside
|
||||
45728
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.c
Normal file
45728
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.cpython-312-darwin.so
Executable file
Binary file not shown.
15
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.pxd
Normal file
15
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.pxd
Normal file
@@ -0,0 +1,15 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2020-2023, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from .vector cimport Vec3
|
||||
|
||||
cdef class Matrix44:
|
||||
cdef double m[16]
|
||||
cdef Vec3 get_ux(self: Matrix44)
|
||||
cdef Vec3 get_uy(self: Matrix44)
|
||||
cdef Vec3 get_uz(self: Matrix44)
|
||||
|
||||
cdef inline swap(double *a, double *b):
|
||||
cdef double tmp = a[0]
|
||||
a[0] = b[0]
|
||||
b[0] = tmp
|
||||
676
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.pyx
Normal file
676
.venv/lib/python3.12/site-packages/ezdxf/acc/matrix44.pyx
Normal file
@@ -0,0 +1,676 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2020-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing import Sequence, Iterable, Tuple, TYPE_CHECKING, Iterator
|
||||
from itertools import chain
|
||||
import math
|
||||
import numpy as np
|
||||
import cython
|
||||
|
||||
from .vector cimport (
|
||||
Vec2, Vec3, v3_normalize, v3_isclose, v3_cross, v3_dot,
|
||||
)
|
||||
from .vector import X_AXIS, Y_AXIS, Z_AXIS, NULLVEC
|
||||
|
||||
from libc.math cimport fabs, sin, cos, tan
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.math import UVec
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
|
||||
cdef double[16] IDENTITY = [
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0
|
||||
]
|
||||
|
||||
cdef void set_floats(double *m, object values) except *:
|
||||
cdef int i = 0
|
||||
for v in values:
|
||||
if i < 16: # Do not write beyond array bounds
|
||||
m[i] = v
|
||||
i += 1
|
||||
if i != 16:
|
||||
raise ValueError("invalid argument count")
|
||||
|
||||
cdef class Matrix44:
|
||||
def __cinit__(self, *args):
|
||||
cdef int nargs = len(args)
|
||||
if nargs == 0: # default constructor Matrix44(): fastest setup
|
||||
self.m = IDENTITY # memcopy!
|
||||
elif nargs == 1: # 16 numbers: slow setup
|
||||
set_floats(self.m, args[0])
|
||||
elif nargs == 4: # 4 rows of 4 numbers: slowest setup
|
||||
set_floats(self.m, chain(*args))
|
||||
else:
|
||||
raise ValueError("invalid argument count: 4 row vectors or "
|
||||
"iterable of 16 numbers")
|
||||
|
||||
def __reduce__(self):
|
||||
return Matrix44, (tuple(self),)
|
||||
|
||||
def __getitem__(self, tuple index) -> float:
|
||||
cdef int row = index[0]
|
||||
cdef int col = index[1]
|
||||
cdef int i = row * 4 + col
|
||||
|
||||
if 0 <= i < 16 and 0 <= col < 4:
|
||||
return self.m[i]
|
||||
else:
|
||||
raise IndexError(f'index out of range: {index}')
|
||||
|
||||
def __setitem__(self, tuple index, double value):
|
||||
cdef int row = index[0]
|
||||
cdef int col = index[1]
|
||||
cdef int i = row * 4 + col
|
||||
|
||||
if 0 <= i < 16 and 0 <= col < 4:
|
||||
self.m[i] = value
|
||||
else:
|
||||
raise IndexError(f'index out of range: {index}')
|
||||
|
||||
def __iter__(self):
|
||||
cdef int i
|
||||
for i in range(16):
|
||||
yield self.m[i]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
def format_row(row):
|
||||
return "(%s)" % ", ".join(str(value) for value in row)
|
||||
|
||||
return "Matrix44(%s)" % \
|
||||
", ".join(format_row(row) for row in self.rows())
|
||||
|
||||
def get_2d_transformation(self) -> Tuple[float, ...]:
|
||||
cdef double *m = self.m
|
||||
return m[0], m[1], 0.0, m[4], m[5], 0.0, m[12], m[13], 1.0
|
||||
|
||||
@staticmethod
|
||||
def from_2d_transformation(components: Sequence[float]) -> Matrix44:
|
||||
if len(components) != 6:
|
||||
raise ValueError(
|
||||
"First 2 columns of a 3x3 matrix required: m11, m12, m21, m22, m31, m32"
|
||||
)
|
||||
|
||||
m44 = Matrix44()
|
||||
m44.m[0] = components[0]
|
||||
m44.m[1] = components[1]
|
||||
m44.m[4] = components[2]
|
||||
m44.m[5] = components[3]
|
||||
m44.m[12] = components[4]
|
||||
m44.m[13] = components[5]
|
||||
return m44
|
||||
|
||||
def get_row(self, int row) -> Tuple[float, ...]:
|
||||
cdef int index = row * 4
|
||||
if 0 <= index < 13:
|
||||
return self.m[index], self.m[index + 1], self.m[index + 2], self.m[
|
||||
index + 3]
|
||||
else:
|
||||
raise IndexError(f'invalid row index: {row}')
|
||||
|
||||
def set_row(self, int row, values: Sequence[float]) -> None:
|
||||
cdef Py_ssize_t count = len(values)
|
||||
cdef Py_ssize_t start = row * 4
|
||||
cdef Py_ssize_t i
|
||||
if 0 <= row < 4:
|
||||
if count > 4:
|
||||
count = 4
|
||||
for i in range(count):
|
||||
self.m[start + i] = values[i]
|
||||
else:
|
||||
raise IndexError(f'invalid row index: {row}')
|
||||
|
||||
def get_col(self, int col) -> Tuple[float, ...]:
|
||||
if 0 <= col < 4:
|
||||
return self.m[col], self.m[col + 4], \
|
||||
self.m[col + 8], self.m[col + 12]
|
||||
else:
|
||||
raise IndexError(f'invalid col index: {col}')
|
||||
|
||||
def set_col(self, int col, values: Sequence[float]):
|
||||
cdef Py_ssize_t count = len(values)
|
||||
cdef Py_ssize_t i
|
||||
if 0 <= col < 4:
|
||||
if count > 4:
|
||||
count = 4
|
||||
for i in range(count):
|
||||
self.m[col + i * 4] = values[i]
|
||||
else:
|
||||
raise IndexError(f'invalid col index: {col}')
|
||||
|
||||
def rows(self) -> Iterator[Tuple[float, ...]]:
|
||||
return (self.get_row(index) for index in (0, 1, 2, 3))
|
||||
|
||||
def columns(self) -> Iterator[Tuple[float, ...]]:
|
||||
return (self.get_col(index) for index in (0, 1, 2, 3))
|
||||
|
||||
def copy(self) -> Matrix44:
|
||||
cdef Matrix44 _copy = Matrix44()
|
||||
_copy.m = self.m
|
||||
return _copy
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
@property
|
||||
def origin(self) -> Vec3:
|
||||
cdef Vec3 v = Vec3()
|
||||
v.x = self.m[12]
|
||||
v.y = self.m[13]
|
||||
v.z = self.m[14]
|
||||
return v
|
||||
|
||||
@origin.setter
|
||||
def origin(self, v: UVec) -> None:
|
||||
cdef Vec3 origin = Vec3(v)
|
||||
self.m[12] = origin.x
|
||||
self.m[13] = origin.y
|
||||
self.m[14] = origin.z
|
||||
|
||||
@property
|
||||
def ux(self) -> Vec3:
|
||||
return self.get_ux()
|
||||
|
||||
cdef Vec3 get_ux(self):
|
||||
cdef Vec3 v = Vec3()
|
||||
v.x = self.m[0]
|
||||
v.y = self.m[1]
|
||||
v.z = self.m[2]
|
||||
return v
|
||||
|
||||
@property
|
||||
def uy(self) -> Vec3:
|
||||
return self.get_uy()
|
||||
|
||||
cdef Vec3 get_uy(self):
|
||||
cdef Vec3 v = Vec3()
|
||||
v.x = self.m[4]
|
||||
v.y = self.m[5]
|
||||
v.z = self.m[6]
|
||||
return v
|
||||
|
||||
@property
|
||||
def uz(self) -> Vec3:
|
||||
return self.get_uz()
|
||||
|
||||
cdef Vec3 get_uz(self):
|
||||
cdef Vec3 v = Vec3()
|
||||
v.x = self.m[8]
|
||||
v.y = self.m[9]
|
||||
v.z = self.m[10]
|
||||
return v
|
||||
|
||||
@property
|
||||
def is_cartesian(self) -> bool:
|
||||
cdef Vec3 x_axis = v3_cross(self.get_uy(), self.get_uz())
|
||||
return v3_isclose(x_axis, self.get_ux(), REL_TOL, ABS_TOL)
|
||||
|
||||
@property
|
||||
def is_orthogonal(self) -> bool:
|
||||
cdef Vec3 ux = v3_normalize(self.get_ux(), 1.0)
|
||||
cdef Vec3 uy = v3_normalize(self.get_uy(), 1.0)
|
||||
cdef Vec3 uz = v3_normalize(self.get_uz(), 1.0)
|
||||
return fabs(v3_dot(ux, uy)) < 1e-9 and \
|
||||
fabs(v3_dot(ux, uz)) < 1e-9 and \
|
||||
fabs(v3_dot(uy, uz)) < 1e-9
|
||||
|
||||
@staticmethod
|
||||
def scale(double sx, sy = None, sz = None) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
mat.m[0] = sx
|
||||
mat.m[5] = sx if sy is None else sy
|
||||
mat.m[10] = sx if sz is None else sz
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def translate(double dx, double dy, double dz) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
mat.m[12] = dx
|
||||
mat.m[13] = dy
|
||||
mat.m[14] = dz
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def x_rotate(double angle) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
cdef double cos_a = cos(angle)
|
||||
cdef double sin_a = sin(angle)
|
||||
mat.m[5] = cos_a
|
||||
mat.m[6] = sin_a
|
||||
mat.m[9] = -sin_a
|
||||
mat.m[10] = cos_a
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def y_rotate(double angle) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
cdef double cos_a = cos(angle)
|
||||
cdef double sin_a = sin(angle)
|
||||
mat.m[0] = cos_a
|
||||
mat.m[2] = -sin_a
|
||||
mat.m[8] = sin_a
|
||||
mat.m[10] = cos_a
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def z_rotate(double angle) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
cdef double cos_a = cos(angle)
|
||||
cdef double sin_a = sin(angle)
|
||||
mat.m[0] = cos_a
|
||||
mat.m[1] = sin_a
|
||||
mat.m[4] = -sin_a
|
||||
mat.m[5] = cos_a
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def axis_rotate(axis: UVec, double angle) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
cdef double cos_a = cos(angle)
|
||||
cdef double sin_a = sin(angle)
|
||||
cdef double one_m_cos = 1.0 - cos_a
|
||||
cdef Vec3 _axis = Vec3(axis).normalize()
|
||||
cdef double x = _axis.x
|
||||
cdef double y = _axis.y
|
||||
cdef double z = _axis.z
|
||||
|
||||
mat.m[0] = x * x * one_m_cos + cos_a
|
||||
mat.m[1] = y * x * one_m_cos + z * sin_a
|
||||
mat.m[2] = x * z * one_m_cos - y * sin_a
|
||||
|
||||
mat.m[4] = x * y * one_m_cos - z * sin_a
|
||||
mat.m[5] = y * y * one_m_cos + cos_a
|
||||
mat.m[6] = y * z * one_m_cos + x * sin_a
|
||||
|
||||
mat.m[8] = x * z * one_m_cos + y * sin_a
|
||||
mat.m[9] = y * z * one_m_cos - x * sin_a
|
||||
mat.m[10] = z * z * one_m_cos + cos_a
|
||||
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def xyz_rotate(double angle_x, double angle_y,
|
||||
double angle_z) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
cdef double cx = cos(angle_x)
|
||||
cdef double sx = sin(angle_x)
|
||||
cdef double cy = cos(angle_y)
|
||||
cdef double sy = sin(angle_y)
|
||||
cdef double cz = cos(angle_z)
|
||||
cdef double sz = sin(angle_z)
|
||||
cdef double sxsy = sx * sy
|
||||
cdef double cxsy = cx * sy
|
||||
|
||||
mat.m[0] = cy * cz
|
||||
mat.m[1] = sxsy * cz + cx * sz
|
||||
mat.m[2] = -cxsy * cz + sx * sz
|
||||
mat.m[4] = -cy * sz
|
||||
mat.m[5] = -sxsy * sz + cx * cz
|
||||
mat.m[6] = cxsy * sz + sx * cz
|
||||
mat.m[8] = sy
|
||||
mat.m[9] = -sx * cy
|
||||
mat.m[10] = cx * cy
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def shear_xy(double angle_x = 0, double angle_y = 0) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
cdef double tx = tan(angle_x)
|
||||
cdef double ty = tan(angle_y)
|
||||
mat.m[1] = ty
|
||||
mat.m[4] = tx
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def perspective_projection(double left, double right, double top,
|
||||
double bottom, double near,
|
||||
double far) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
mat.m[0] = (2. * near) / (right - left)
|
||||
mat.m[5] = (2. * near) / (top - bottom)
|
||||
mat.m[8] = (right + left) / (right - left)
|
||||
mat.m[9] = (top + bottom) / (top - bottom)
|
||||
mat.m[10] = -((far + near) / (far - near))
|
||||
mat.m[11] = -1
|
||||
mat.m[14] = -((2. * far * near) / (far - near))
|
||||
return mat
|
||||
|
||||
@staticmethod
|
||||
def perspective_projection_fov(fov: float, aspect: float, near: float,
|
||||
far: float) -> Matrix44:
|
||||
vrange = near * math.tan(fov / 2.)
|
||||
left = -vrange * aspect
|
||||
right = vrange * aspect
|
||||
bottom = -vrange
|
||||
top = vrange
|
||||
return Matrix44.perspective_projection(left, right, bottom, top, near,
|
||||
far)
|
||||
|
||||
@staticmethod
|
||||
def chain(*matrices: Matrix44) -> Matrix44:
|
||||
cdef Matrix44 transformation = Matrix44()
|
||||
for matrix in matrices:
|
||||
transformation *= matrix
|
||||
return transformation
|
||||
|
||||
def __imul__(self, Matrix44 other) -> Matrix44:
|
||||
cdef double[16] m1 = self.m
|
||||
cdef double *m2 = other.m
|
||||
|
||||
self.m[0] = m1[0] * m2[0] + m1[1] * m2[4] + m1[2] * m2[8] + \
|
||||
m1[3] * m2[12]
|
||||
self.m[1] = m1[0] * m2[1] + m1[1] * m2[5] + m1[2] * m2[9] + \
|
||||
m1[3] * m2[13]
|
||||
self.m[2] = m1[0] * m2[2] + m1[1] * m2[6] + m1[2] * m2[10] + \
|
||||
m1[3] * m2[14]
|
||||
self.m[3] = m1[0] * m2[3] + m1[1] * m2[7] + m1[2] * m2[11] + \
|
||||
m1[3] * m2[15]
|
||||
|
||||
self.m[4] = m1[4] * m2[0] + m1[5] * m2[4] + m1[6] * m2[8] + \
|
||||
m1[7] * m2[12]
|
||||
self.m[5] = m1[4] * m2[1] + m1[5] * m2[5] + m1[6] * m2[9] + \
|
||||
m1[7] * m2[13]
|
||||
self.m[6] = m1[4] * m2[2] + m1[5] * m2[6] + m1[6] * m2[10] + \
|
||||
m1[7] * m2[14]
|
||||
self.m[7] = m1[4] * m2[3] + m1[5] * m2[7] + m1[6] * m2[11] + \
|
||||
m1[7] * m2[15]
|
||||
|
||||
self.m[8] = m1[8] * m2[0] + m1[9] * m2[4] + m1[10] * m2[8] + \
|
||||
m1[11] * m2[12]
|
||||
self.m[9] = m1[8] * m2[1] + m1[9] * m2[5] + m1[10] * m2[9] + \
|
||||
m1[11] * m2[13]
|
||||
self.m[10] = m1[8] * m2[2] + m1[9] * m2[6] + m1[10] * m2[10] + \
|
||||
m1[11] * m2[14]
|
||||
self.m[11] = m1[8] * m2[3] + m1[9] * m2[7] + m1[10] * m2[11] + \
|
||||
m1[11] * m2[15]
|
||||
|
||||
self.m[12] = m1[12] * m2[0] + m1[13] * m2[4] + m1[14] * m2[8] + \
|
||||
m1[15] * m2[12]
|
||||
self.m[13] = m1[12] * m2[1] + m1[13] * m2[5] + m1[14] * m2[9] + \
|
||||
m1[15] * m2[13]
|
||||
self.m[14] = m1[12] * m2[2] + m1[13] * m2[6] + m1[14] * m2[10] + \
|
||||
m1[15] * m2[14]
|
||||
self.m[15] = m1[12] * m2[3] + m1[13] * m2[7] + m1[14] * m2[11] + \
|
||||
m1[15] * m2[15]
|
||||
return self
|
||||
|
||||
def __mul__(self, Matrix44 other) -> Matrix44:
|
||||
cdef Matrix44 res_matrix = self.copy()
|
||||
return res_matrix.__imul__(other)
|
||||
|
||||
# __matmul__ = __mul__ does not work!
|
||||
|
||||
def __matmul__(self, Matrix44 other) -> Matrix44:
|
||||
cdef Matrix44 res_matrix = self.copy()
|
||||
return res_matrix.__imul__(other)
|
||||
|
||||
def transpose(self) -> None:
|
||||
swap(&self.m[1], &self.m[4])
|
||||
swap(&self.m[2], &self.m[8])
|
||||
swap(&self.m[3], &self.m[12])
|
||||
swap(&self.m[6], &self.m[9])
|
||||
swap(&self.m[7], &self.m[13])
|
||||
swap(&self.m[11], &self.m[14])
|
||||
|
||||
def determinant(self) -> float:
|
||||
cdef double *m = self.m
|
||||
return m[0] * m[5] * m[10] * m[15] - m[0] * m[5] * m[11] * m[14] + \
|
||||
m[0] * m[6] * m[11] * m[13] - m[0] * m[6] * m[9] * m[15] + \
|
||||
m[0] * m[7] * m[9] * m[14] - m[0] * m[7] * m[10] * m[13] - \
|
||||
m[1] * m[6] * m[11] * m[12] + m[1] * m[6] * m[8] * m[15] - \
|
||||
m[1] * m[7] * m[8] * m[14] + m[1] * m[7] * m[10] * m[12] - \
|
||||
m[1] * m[4] * m[10] * m[15] + m[1] * m[4] * m[11] * m[14] + \
|
||||
m[2] * m[7] * m[8] * m[13] - m[2] * m[7] * m[9] * m[12] + \
|
||||
m[2] * m[4] * m[9] * m[15] - m[2] * m[4] * m[11] * m[13] + \
|
||||
m[2] * m[5] * m[11] * m[12] - m[2] * m[5] * m[8] * m[15] - \
|
||||
m[3] * m[4] * m[9] * m[14] + m[3] * m[4] * m[10] * m[13] - \
|
||||
m[3] * m[5] * m[10] * m[12] + m[3] * m[5] * m[8] * m[14] - \
|
||||
m[3] * m[6] * m[8] * m[13] + m[3] * m[6] * m[9] * m[12]
|
||||
|
||||
def inverse(self) -> None:
|
||||
cdef double[16] m = self.m # memcopy
|
||||
cdef double det = self.determinant()
|
||||
cdef double f = 1. / det
|
||||
self.m[0] = (m[6] * m[11] * m[13] - m[7] * m[10] * m[13] + m[7] * m[9] *
|
||||
m[14] - m[5] * m[11] * m[14] - m[6] * m[9] * m[15] + m[5] *
|
||||
m[10] * m[15]) * f
|
||||
|
||||
self.m[1] = (m[3] * m[10] * m[13] - m[2] * m[11] * m[13] - m[3] * m[9] *
|
||||
m[14] + m[1] * m[11] * m[14] + m[2] * m[9] * m[15] -
|
||||
m[1] * m[10] * m[15]) * f
|
||||
self.m[2] = (m[2] * m[7] * m[13] - m[3] * m[6] * m[13] + m[3] * m[5] *
|
||||
m[14] - m[1] * m[7] * m[14] - m[2] * m[5] * m[15] +
|
||||
m[1] * m[6] * m[15]) * f
|
||||
|
||||
self.m[3] = (m[3] * m[6] * m[9] - m[2] * m[7] * m[9] - m[3] * m[5] *
|
||||
m[10] + m[1] * m[7] * m[10] + m[2] * m[5] * m[11] - m[1] *
|
||||
m[6] * m[11]) * f
|
||||
|
||||
self.m[4] = (m[7] * m[10] * m[12] - m[6] * m[11] * m[12] - m[7] * m[8] *
|
||||
m[14] + m[4] * m[11] * m[14] + m[6] * m[8] * m[15] -
|
||||
m[4] * m[10] * m[15]) * f
|
||||
|
||||
self.m[5] = (m[2] * m[11] * m[12] - m[3] * m[10] * m[12] + m[3] * m[8] *
|
||||
m[14] - m[0] * m[11] * m[14] - m[2] * m[8] * m[15] +
|
||||
m[0] * m[10] * m[15]) * f
|
||||
|
||||
self.m[6] = (m[3] * m[6] * m[12] - m[2] * m[7] * m[12] - m[3] * m[4] *
|
||||
m[14] + m[0] * m[7] * m[14] + m[2] * m[4] * m[15] -
|
||||
m[0] * m[6] * m[15]) * f
|
||||
|
||||
self.m[7] = (m[2] * m[7] * m[8] - m[3] * m[6] * m[8] + m[3] * m[4] *
|
||||
m[10] - m[0] * m[7] * m[10] - m[2] * m[4] * m[11] +
|
||||
m[0] * m[6] * m[11]) * f
|
||||
|
||||
self.m[8] = (m[5] * m[11] * m[12] - m[7] * m[9] * m[12] + m[7] * m[8] *
|
||||
m[13] - m[4] * m[11] * m[13] - m[5] * m[8] * m[15] +
|
||||
m[4] * m[9] * m[15]) * f
|
||||
|
||||
self.m[9] = (m[3] * m[9] * m[12] - m[1] * m[11] * m[12] - m[3] *
|
||||
m[8] * m[13] + m[0] * m[11] * m[13] + m[1] * m[8] *
|
||||
m[15] - m[0] * m[9] * m[15]) * f
|
||||
|
||||
self.m[10] = (m[1] * m[7] * m[12] - m[3] * m[5] * m[12] + m[3] *
|
||||
m[4] * m[13] - m[0] * m[7] * m[13] - m[1] * m[4] *
|
||||
m[15] + m[0] * m[5] * m[15]) * f
|
||||
|
||||
self.m[11] = (m[3] * m[5] * m[8] - m[1] * m[7] * m[8] - m[3] * m[4] *
|
||||
m[9] + m[0] * m[7] * m[9] + m[1] * m[4] * m[11] -
|
||||
m[0] * m[5] * m[11]) * f
|
||||
|
||||
self.m[12] = (m[6] * m[9] * m[12] - m[5] * m[10] * m[12] - m[6] *
|
||||
m[8] * m[13] + m[4] * m[10] * m[13] + m[5] * m[8] *
|
||||
m[14] - m[4] * m[9] * m[14]) * f
|
||||
|
||||
self.m[13] = (m[1] * m[10] * m[12] - m[2] * m[9] * m[12] + m[2] *
|
||||
m[8] * m[13] - m[0] * m[10] * m[13] - m[1] * m[8] *
|
||||
m[14] + m[0] * m[9] * m[14]) * f
|
||||
|
||||
self.m[14] = (m[2] * m[5] * m[12] - m[1] * m[6] * m[12] - m[2] *
|
||||
m[4] * m[13] + m[0] * m[6] * m[13] + m[1] * m[4] *
|
||||
m[14] - m[0] * m[5] * m[14]) * f
|
||||
|
||||
self.m[15] = (m[1] * m[6] * m[8] - m[2] * m[5] * m[8] + m[2] * m[4] *
|
||||
m[9] - m[0] * m[6] * m[9] - m[1] * m[4] * m[10] +
|
||||
m[0] * m[5] * m[10]) * f
|
||||
|
||||
@staticmethod
|
||||
def ucs(ux=X_AXIS, uy=Y_AXIS, uz=Z_AXIS, origin=NULLVEC) -> Matrix44:
|
||||
cdef Matrix44 mat = Matrix44()
|
||||
cdef Vec3 _ux = Vec3(ux)
|
||||
cdef Vec3 _uy = Vec3(uy)
|
||||
cdef Vec3 _uz = Vec3(uz)
|
||||
cdef Vec3 _origin = Vec3(origin)
|
||||
|
||||
mat.m[0] = _ux.x
|
||||
mat.m[1] = _ux.y
|
||||
mat.m[2] = _ux.z
|
||||
|
||||
mat.m[4] = _uy.x
|
||||
mat.m[5] = _uy.y
|
||||
mat.m[6] = _uy.z
|
||||
|
||||
mat.m[8] = _uz.x
|
||||
mat.m[9] = _uz.y
|
||||
mat.m[10] = _uz.z
|
||||
|
||||
mat.m[12] = _origin.x
|
||||
mat.m[13] = _origin.y
|
||||
mat.m[14] = _origin.z
|
||||
|
||||
return mat
|
||||
|
||||
def transform(self, vector: UVec) -> Vec3:
|
||||
cdef Vec3 res = Vec3(vector)
|
||||
cdef double x = res.x
|
||||
cdef double y = res.y
|
||||
cdef double z = res.z
|
||||
cdef double *m = self.m
|
||||
|
||||
res.x = x * m[0] + y * m[4] + z * m[8] + m[12]
|
||||
res.y = x * m[1] + y * m[5] + z * m[9] + m[13]
|
||||
res.z = x * m[2] + y * m[6] + z * m[10] + m[14]
|
||||
return res
|
||||
|
||||
def transform_direction(self, vector: UVec, normalize=False) -> Vec3:
|
||||
cdef Vec3 res = Vec3(vector)
|
||||
cdef double x = res.x
|
||||
cdef double y = res.y
|
||||
cdef double z = res.z
|
||||
cdef double *m = self.m
|
||||
|
||||
res.x = x * m[0] + y * m[4] + z * m[8]
|
||||
res.y = x * m[1] + y * m[5] + z * m[9]
|
||||
res.z = x * m[2] + y * m[6] + z * m[10]
|
||||
if normalize:
|
||||
return v3_normalize(res, 1.0)
|
||||
else:
|
||||
return res
|
||||
|
||||
ocs_to_wcs = transform_direction
|
||||
|
||||
def transform_vertices(self, vectors: Iterable[UVec]) -> Iterator[Vec3]:
|
||||
cdef double *m = self.m
|
||||
cdef Vec3 res
|
||||
cdef double x, y, z
|
||||
|
||||
for vector in vectors:
|
||||
res = Vec3(vector)
|
||||
x = res.x
|
||||
y = res.y
|
||||
z = res.z
|
||||
|
||||
res.x = x * m[0] + y * m[4] + z * m[8] + m[12]
|
||||
res.y = x * m[1] + y * m[5] + z * m[9] + m[13]
|
||||
res.z = x * m[2] + y * m[6] + z * m[10] + m[14]
|
||||
yield res
|
||||
|
||||
def fast_2d_transform(self, points: Iterable[UVec]) -> Iterator[Vec2]:
|
||||
cdef double m0 = self.m[0]
|
||||
cdef double m1 = self.m[1]
|
||||
cdef double m4 = self.m[4]
|
||||
cdef double m5 = self.m[5]
|
||||
cdef double m12 = self.m[12]
|
||||
cdef double m13 = self.m[13]
|
||||
cdef double x, y
|
||||
cdef Vec2 res
|
||||
|
||||
for pnt in points:
|
||||
res = Vec2(pnt)
|
||||
x = res.x
|
||||
y = res.y
|
||||
res.x = x * m0 + y * m4 + m12
|
||||
res.y = x * m1 + y * m5 + m13
|
||||
yield res
|
||||
|
||||
def transform_array_inplace(self, array: np.ndarray, ndim: int) -> None:
|
||||
"""Transforms a numpy array inplace, the argument `ndim` defines the dimensions
|
||||
to transform, this allows 2D/3D transformation on arrays with more columns
|
||||
e.g. a polyline array which stores points as (x, y, start_width, end_width,
|
||||
bulge) values.
|
||||
|
||||
"""
|
||||
cdef int _ndim = ndim
|
||||
if _ndim == 2:
|
||||
assert array.shape[1] > 1
|
||||
transform_2d_array_inplace(self.m, array, array.shape[0])
|
||||
elif _ndim == 3:
|
||||
assert array.shape[1] > 2
|
||||
transform_3d_array_inplace(self.m, array, array.shape[0])
|
||||
else:
|
||||
raise ValueError("ndim has to be 2 or 3")
|
||||
|
||||
|
||||
def transform_directions(
|
||||
self, vectors: Iterable[UVec], normalize=False
|
||||
) -> Iterator[Vec3]:
|
||||
cdef double *m = self.m
|
||||
cdef Vec3 res
|
||||
cdef double x, y, z
|
||||
cdef bint _normalize = normalize
|
||||
|
||||
for vector in vectors:
|
||||
res = Vec3(vector)
|
||||
x = res.x
|
||||
y = res.y
|
||||
z = res.z
|
||||
|
||||
res.x = x * m[0] + y * m[4] + z * m[8]
|
||||
res.y = x * m[1] + y * m[5] + z * m[9]
|
||||
res.z = x * m[2] + y * m[6] + z * m[10]
|
||||
yield v3_normalize(res, 1.0) if _normalize else res
|
||||
|
||||
def ucs_vertex_from_wcs(self, wcs: Vec3) -> Vec3:
|
||||
return self.ucs_direction_from_wcs(wcs - self.origin)
|
||||
|
||||
def ucs_direction_from_wcs(self, wcs: UVec) -> Vec3:
|
||||
cdef double *m = self.m
|
||||
cdef Vec3 res = Vec3(wcs)
|
||||
cdef double x = res.x
|
||||
cdef double y = res.y
|
||||
cdef double z = res.z
|
||||
|
||||
res.x = x * m[0] + y * m[1] + z * m[2]
|
||||
res.y = x * m[4] + y * m[5] + z * m[6]
|
||||
res.z = x * m[8] + y * m[9] + z * m[10]
|
||||
return res
|
||||
|
||||
ocs_from_wcs = ucs_direction_from_wcs
|
||||
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cdef void transform_2d_array_inplace(double *m, double [:, ::1] array, Py_ssize_t size):
|
||||
cdef double m0 = m[0]
|
||||
cdef double m1 = m[1]
|
||||
cdef double m4 = m[4]
|
||||
cdef double m5 = m[5]
|
||||
cdef double m12 = m[12]
|
||||
cdef double m13 = m[13]
|
||||
cdef double x, y
|
||||
cdef Py_ssize_t i
|
||||
|
||||
for i in range(size):
|
||||
x = array[i, 0]
|
||||
y = array[i, 1]
|
||||
array[i, 0] = x * m0 + y * m4 + m12
|
||||
array[i, 1] = x * m1 + y * m5 + m13
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cdef void transform_3d_array_inplace(double *m, double [:, ::1] array, Py_ssize_t size):
|
||||
cdef double x, y, z
|
||||
cdef Py_ssize_t i
|
||||
|
||||
for i in range(size):
|
||||
x = array[i, 0]
|
||||
y = array[i, 1]
|
||||
z = array[i, 2]
|
||||
|
||||
array[i, 0] = x * m[0] + y * m[4] + z * m[8] + m[12]
|
||||
array[i, 1] = x * m[1] + y * m[5] + z * m[9] + m[13]
|
||||
array[i, 2] = x * m[2] + y * m[6] + z * m[10] + m[14]
|
||||
29254
.venv/lib/python3.12/site-packages/ezdxf/acc/np_support.c
Normal file
29254
.venv/lib/python3.12/site-packages/ezdxf/acc/np_support.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/np_support.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/np_support.cpython-312-darwin.so
Executable file
Binary file not shown.
177
.venv/lib/python3.12/site-packages/ezdxf/acc/np_support.pyx
Normal file
177
.venv/lib/python3.12/site-packages/ezdxf/acc/np_support.pyx
Normal file
@@ -0,0 +1,177 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2023-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing_extensions import TypeAlias
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
import cython
|
||||
from .vector cimport isclose
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
|
||||
|
||||
NDArray: TypeAlias = npt.NDArray[np.float64]
|
||||
|
||||
|
||||
def has_clockwise_orientation(vertices: np.ndarray) -> bool:
|
||||
""" Returns True if 2D `vertices` have clockwise orientation. Ignores
|
||||
z-axis of all vertices.
|
||||
|
||||
Args:
|
||||
vertices: numpy array
|
||||
|
||||
Raises:
|
||||
ValueError: less than 3 vertices
|
||||
|
||||
"""
|
||||
if len(vertices) < 3:
|
||||
raise ValueError('At least 3 vertices required.')
|
||||
|
||||
return _has_clockwise_orientation(vertices, vertices.shape[0])
|
||||
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cdef bint _has_clockwise_orientation(double [:, ::1] vertices, Py_ssize_t size):
|
||||
cdef Py_ssize_t index
|
||||
cdef Py_ssize_t start
|
||||
cdef Py_ssize_t last = size - 1
|
||||
cdef double s = 0.0
|
||||
cdef double p1x = vertices[0][0]
|
||||
cdef double p1y = vertices[0][1]
|
||||
cdef double p2x = vertices[last][0]
|
||||
cdef double p2y = vertices[last][1]
|
||||
|
||||
# Using the same tolerance as the Python implementation:
|
||||
cdef bint x_is_close = isclose(p1x, p2x, REL_TOL, ABS_TOL)
|
||||
cdef bint y_is_close = isclose(p1y, p2y, REL_TOL, ABS_TOL)
|
||||
|
||||
if x_is_close and y_is_close:
|
||||
p1x = vertices[0][0]
|
||||
p1y = vertices[0][1]
|
||||
start = 1
|
||||
else:
|
||||
p1x = vertices[last][0]
|
||||
p1y = vertices[last][1]
|
||||
start = 0
|
||||
|
||||
for index in range(start, size):
|
||||
p2x = vertices[index][0]
|
||||
p2y = vertices[index][1]
|
||||
s += (p2x - p1x) * (p2y + p1y)
|
||||
p1x = p2x
|
||||
p1y = p2y
|
||||
return s > 0.0
|
||||
|
||||
|
||||
def lu_decompose(A: NDArray, m1: int, m2: int) -> tuple[NDArray, NDArray, NDArray]:
|
||||
upper: np.ndarray = np.array(A, dtype=np.float64)
|
||||
n: int = upper.shape[0]
|
||||
lower: np.ndarray = np.zeros((n, m1), dtype=np.float64)
|
||||
|
||||
# Is <np.int_> better to match <int> on all platforms?
|
||||
index: np.ndarray = np.zeros((n,), dtype=np.int32)
|
||||
_lu_decompose_cext(upper, lower, index, n, m1, m2)
|
||||
return upper, lower, index
|
||||
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cdef _lu_decompose_cext(
|
||||
double [:, ::1] upper,
|
||||
double [:, ::1] lower,
|
||||
int [::1] index,
|
||||
int n,
|
||||
int m1,
|
||||
int m2
|
||||
):
|
||||
cdef int mm = m1 + m2 + 1
|
||||
cdef int l = m1
|
||||
cdef int i, j, k
|
||||
cdef double dum
|
||||
|
||||
for i in range(m1):
|
||||
for j in range(m1 - i, mm):
|
||||
upper[i][j - l] = upper[i][j]
|
||||
l -= 1
|
||||
for j in range(mm - l - 1, mm):
|
||||
upper[i][j] = 0.0
|
||||
|
||||
l = m1
|
||||
for k in range(n):
|
||||
dum = upper[k][0]
|
||||
i = k
|
||||
if l < n:
|
||||
l += 1
|
||||
for j in range(k + 1, l):
|
||||
if abs(upper[j][0]) > abs(dum):
|
||||
dum = upper[j][0]
|
||||
i = j
|
||||
index[k] = i + 1
|
||||
if i != k:
|
||||
for j in range(mm):
|
||||
upper[k][j], upper[i][j] = upper[i][j], upper[k][j]
|
||||
|
||||
for i in range(k + 1, l):
|
||||
dum = upper[i][0] / upper[k][0]
|
||||
lower[k][i - k - 1] = dum
|
||||
for j in range(1, mm):
|
||||
upper[i][j - 1] = upper[i][j] - dum * upper[k][j]
|
||||
upper[i][mm - 1] = 0.0
|
||||
|
||||
|
||||
def solve_vector_banded_matrix(
|
||||
x: NDArray,
|
||||
upper: NDArray,
|
||||
lower: NDArray,
|
||||
index: NDArray,
|
||||
m1: int,
|
||||
m2: int,
|
||||
) -> NDArray:
|
||||
n: int = upper.shape[0]
|
||||
x = np.array(x) # copy x because array x gets modified
|
||||
if x.shape[0] != n:
|
||||
raise ValueError(
|
||||
"Item count of vector <x> has to match the row count of matrix <upper>."
|
||||
)
|
||||
_solve_vector_banded_matrix_cext(x, upper, lower, index, n, m1, m2)
|
||||
return x
|
||||
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cdef _solve_vector_banded_matrix_cext(
|
||||
double [::1] x,
|
||||
double [:, ::1] au,
|
||||
double [:, ::1] al,
|
||||
int [::1] index,
|
||||
int n,
|
||||
int m1,
|
||||
int m2,
|
||||
):
|
||||
cdef int mm = m1 + m2 + 1
|
||||
cdef int l = m1
|
||||
cdef int i, j, k
|
||||
cdef double dum
|
||||
|
||||
for k in range(n):
|
||||
j = index[k] - 1
|
||||
if j != k:
|
||||
x[k], x[j] = x[j], x[k]
|
||||
if l < n:
|
||||
l += 1
|
||||
for j in range(k + 1, l):
|
||||
x[j] -= al[k][j - k - 1] * x[k]
|
||||
|
||||
l = 1
|
||||
for i in range(n - 1, -1, -1):
|
||||
dum = x[i]
|
||||
for k in range(1, l):
|
||||
dum -= au[i][k] * x[k + i]
|
||||
x[i] = dum / au[i][0]
|
||||
if l < mm:
|
||||
l += 1
|
||||
return x
|
||||
32048
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.c
Normal file
32048
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.cpython-312-darwin.so
Executable file
BIN
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.cpython-312-darwin.so
Executable file
Binary file not shown.
49
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.pxd
Normal file
49
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.pxd
Normal file
@@ -0,0 +1,49 @@
|
||||
# cython: language_level=3
|
||||
# Copyright (c) 2020-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
|
||||
cdef bint isclose(double a, double b, double rel_tol, double abs_tol)
|
||||
cdef double normalize_rad_angle(double a)
|
||||
cdef double normalize_deg_angle(double a)
|
||||
|
||||
cdef class Vec2:
|
||||
cdef readonly double x, y
|
||||
|
||||
# Vec2 C-functions:
|
||||
cdef Vec2 v2_add(Vec2 a, Vec2 b)
|
||||
cdef Vec2 v2_sub(Vec2 a, Vec2 b)
|
||||
cdef Vec2 v2_mul(Vec2 a, double factor)
|
||||
cdef Vec2 v2_normalize(Vec2 a, double length)
|
||||
cdef double v2_dot(Vec2 a, Vec2 b)
|
||||
cdef double v2_det(Vec2 a, Vec2 b)
|
||||
cdef double v2_dist(Vec2 a, Vec2 b)
|
||||
cdef Vec2 v2_from_angle(double angle, double length)
|
||||
cdef double v2_angle_between(Vec2 a, Vec2 b) except -1000
|
||||
cdef Vec2 v2_lerp(Vec2 a, Vec2 b, double factor)
|
||||
cdef Vec2 v2_ortho(Vec2 a, bint ccw)
|
||||
cdef Vec2 v2_project(Vec2 a, Vec2 b)
|
||||
cdef bint v2_isclose(Vec2 a, Vec2 b, double rel_tol, double abs_tol)
|
||||
|
||||
|
||||
cdef class Vec3:
|
||||
cdef readonly double x, y, z
|
||||
|
||||
# Vec3 C-functions:
|
||||
cdef Vec3 v3_add(Vec3 a, Vec3 b)
|
||||
cdef Vec3 v3_sub(Vec3 a, Vec3 b)
|
||||
cdef Vec3 v3_mul(Vec3 a, double factor)
|
||||
cdef Vec3 v3_reverse(Vec3 a)
|
||||
cdef double v3_dot(Vec3 a, Vec3 b)
|
||||
cdef Vec3 v3_cross(Vec3 a, Vec3 b)
|
||||
cdef double v3_magnitude_sqr(Vec3 a)
|
||||
cdef double v3_magnitude(Vec3 a)
|
||||
cdef double v3_dist(Vec3 a, Vec3 b)
|
||||
cdef Vec3 v3_from_angle(double angle, double length)
|
||||
cdef double v3_angle_between(Vec3 a, Vec3 b) except -1000
|
||||
cdef double v3_angle_about(Vec3 a, Vec3 base, Vec3 target)
|
||||
cdef Vec3 v3_normalize(Vec3 a, double length)
|
||||
cdef Vec3 v3_lerp(Vec3 a, Vec3 b, double factor)
|
||||
cdef Vec3 v3_ortho(Vec3 a, bint ccw)
|
||||
cdef Vec3 v3_project(Vec3 a, Vec3 b)
|
||||
cdef bint v3_isclose(Vec3 a, Vec3 b, double rel_tol, double abs_tol)
|
||||
|
||||
838
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.pyx
Normal file
838
.venv/lib/python3.12/site-packages/ezdxf/acc/vector.pyx
Normal file
@@ -0,0 +1,838 @@
|
||||
# cython: language_level=3
|
||||
# cython: c_api_binop_methods=True
|
||||
# Copyright (c) 2020-2024, Manfred Moitzi
|
||||
# License: MIT License
|
||||
from typing import Iterable, List, Sequence, TYPE_CHECKING, Tuple, Iterator
|
||||
from libc.math cimport fabs, sin, cos, M_PI, hypot, atan2, acos, sqrt, fmod
|
||||
import random
|
||||
|
||||
cdef extern from "constants.h":
|
||||
const double ABS_TOL
|
||||
const double REL_TOL
|
||||
const double M_TAU
|
||||
|
||||
cdef double RAD2DEG = 180.0 / M_PI
|
||||
cdef double DEG2RAD = M_PI / 180.0
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.math import AnyVec, UVec
|
||||
|
||||
cdef bint isclose(double a, double b, double rel_tol, double abs_tol):
|
||||
# Has to match the Python implementation!
|
||||
cdef double diff = fabs(b - a)
|
||||
return diff <= fabs(rel_tol * b) or \
|
||||
diff <= fabs(rel_tol * a) or \
|
||||
diff <= abs_tol
|
||||
|
||||
|
||||
cdef double normalize_rad_angle(double a):
|
||||
# Emulate the Python behavior of (a % math.tau)
|
||||
cdef double res = fmod(a, M_TAU)
|
||||
if res < 0.0:
|
||||
res += M_TAU
|
||||
return res
|
||||
|
||||
def _normalize_rad_angle(double angle):
|
||||
# just for testing
|
||||
return normalize_rad_angle(angle)
|
||||
|
||||
cdef double normalize_deg_angle(double a):
|
||||
# Emulate the Python behavior of (a % 360)
|
||||
cdef double res = fmod(a, 360.0)
|
||||
if res < 0.0:
|
||||
res += 360.0
|
||||
return res
|
||||
|
||||
def _normalize_deg_angle(double angle):
|
||||
# just for testing
|
||||
return normalize_deg_angle(angle)
|
||||
|
||||
cdef class Vec2:
|
||||
""" Immutable 2D vector.
|
||||
|
||||
Init:
|
||||
|
||||
- Vec2(vec2)
|
||||
- Vec2(vec3)
|
||||
- Vec2((x, y))
|
||||
- Vec2((x, y, z)), ignore z-axis
|
||||
- Vec2(x, y)
|
||||
- Vec2(x, y, z), ignore z-axis
|
||||
|
||||
"""
|
||||
|
||||
def __cinit__(self, *args):
|
||||
cdef Py_ssize_t count = len(<tuple> args)
|
||||
|
||||
if count == 0: # fastest init - default constructor
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
return
|
||||
|
||||
if count == 1:
|
||||
arg = args[0]
|
||||
if isinstance(arg, Vec2):
|
||||
# fast init by Vec2()
|
||||
self.x = (<Vec2> arg).x
|
||||
self.y = (<Vec2> arg).y
|
||||
return
|
||||
if isinstance(arg, Vec3):
|
||||
# fast init by Vec3()
|
||||
self.x = (<Vec3> arg).x
|
||||
self.y = (<Vec3> arg).y
|
||||
return
|
||||
args = arg
|
||||
count = len(args)
|
||||
|
||||
# ignore z-axis but raise error for 4 or more arguments
|
||||
if count > 3:
|
||||
raise TypeError('invalid argument count')
|
||||
|
||||
# slow init by sequence
|
||||
self.x = args[0]
|
||||
self.y = args[1]
|
||||
|
||||
def __reduce__(self):
|
||||
return Vec2, (self.x, self.y)
|
||||
|
||||
@property
|
||||
def vec3(self) -> Vec3:
|
||||
return Vec3(self)
|
||||
|
||||
def round(self, ndigits=None) -> Vec2:
|
||||
# only used for testing
|
||||
return Vec2(round(self.x, ndigits), round(self.y, ndigits))
|
||||
|
||||
@staticmethod
|
||||
def list(items: Iterable[UVec]) -> List[Vec2]:
|
||||
return list(Vec2.generate(items))
|
||||
|
||||
@staticmethod
|
||||
def tuple(items: Iterable[UVec]) -> Sequence[Vec2]:
|
||||
return tuple(Vec2.generate(items))
|
||||
|
||||
@staticmethod
|
||||
def generate(items: Iterable[UVec]) -> Iterator[Vec2]:
|
||||
return (Vec2(item) for item in items)
|
||||
|
||||
@staticmethod
|
||||
def from_angle(double angle, double length = 1.0) -> Vec2:
|
||||
return v2_from_angle(angle, length)
|
||||
|
||||
@staticmethod
|
||||
def from_deg_angle(double angle, double length = 1.0) -> Vec2:
|
||||
return v2_from_angle(angle * DEG2RAD, length)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'({self.x}, {self.y})'
|
||||
|
||||
def __repr__(self)-> str:
|
||||
return "Vec2" + self.__str__()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return 2
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def copy(self) -> Vec2:
|
||||
return self # immutable
|
||||
|
||||
def __copy__(self) -> Vec2:
|
||||
return self # immutable
|
||||
|
||||
def __deepcopy__(self, memodict: dict) -> Vec2:
|
||||
return self # immutable
|
||||
|
||||
def __getitem__(self, int index) -> float:
|
||||
if index == 0:
|
||||
return self.x
|
||||
elif index == 1:
|
||||
return self.y
|
||||
else:
|
||||
raise IndexError(f'invalid index {index}')
|
||||
|
||||
def __iter__(self) -> Iterator[float]:
|
||||
yield self.x
|
||||
yield self.y
|
||||
|
||||
def __abs__(self) -> float:
|
||||
return hypot(self.x, self.y)
|
||||
|
||||
@property
|
||||
def magnitude(self) -> float:
|
||||
return hypot(self.x, self.y)
|
||||
|
||||
@property
|
||||
def is_null(self) -> bool:
|
||||
return fabs(self.x) <= ABS_TOL and fabs(self.y) <= ABS_TOL
|
||||
|
||||
@property
|
||||
def angle(self) -> float:
|
||||
return atan2(self.y, self.x)
|
||||
|
||||
@property
|
||||
def angle_deg(self) -> float:
|
||||
return atan2(self.y, self.x) * RAD2DEG
|
||||
|
||||
def orthogonal(self, ccw: bool = True) -> Vec2:
|
||||
return v2_ortho(self, ccw)
|
||||
|
||||
def lerp(self, other: "AnyVec", double factor = 0.5) -> Vec2:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
return v2_lerp(self, o, factor)
|
||||
|
||||
def normalize(self, double length = 1.) -> Vec2:
|
||||
return v2_normalize(self, length)
|
||||
|
||||
def project(self, other: AnyVec) -> Vec2:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
return v2_project(self, o)
|
||||
|
||||
def __neg__(self) -> Vec2:
|
||||
cdef Vec2 res = Vec2()
|
||||
res.x = -self.x
|
||||
res.y = -self.y
|
||||
return res
|
||||
|
||||
reversed = __neg__
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return self.x != 0 or self.y != 0
|
||||
|
||||
def isclose(self, other: UVec, *, double rel_tol=REL_TOL,
|
||||
double abs_tol = ABS_TOL) -> bool:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
return isclose(self.x, o.x, rel_tol, abs_tol) and \
|
||||
isclose(self.y, o.y, rel_tol, abs_tol)
|
||||
|
||||
def __eq__(self, other: UVec) -> bool:
|
||||
if not isinstance(other, Vec2):
|
||||
other = Vec2(other)
|
||||
return self.x == other.x and self.y == other.y
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
if self.x == o.x:
|
||||
return self.y < o.y
|
||||
else:
|
||||
return self.x < o.x
|
||||
|
||||
def __add__(self, other: AnyVec) -> Vec2:
|
||||
if not isinstance(other, Vec2):
|
||||
other = Vec2(other)
|
||||
return v2_add(self, <Vec2> other)
|
||||
|
||||
# __radd__ not supported for Vec2
|
||||
|
||||
__iadd__ = __add__ # immutable
|
||||
|
||||
def __sub__(self, other: AnyVec) -> Vec2:
|
||||
if not isinstance(other, Vec2):
|
||||
other = Vec2(other)
|
||||
return v2_sub(self, <Vec2> other)
|
||||
|
||||
# __rsub__ not supported for Vec2
|
||||
|
||||
__isub__ = __sub__ # immutable
|
||||
|
||||
def __mul__(self, factor) -> Vec2:
|
||||
if isinstance(self, Vec2):
|
||||
return v2_mul(self, factor)
|
||||
elif isinstance(factor, Vec2):
|
||||
return v2_mul(<Vec2> factor, <double> self)
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
# Special Cython <(3.0) feature: __rmul__ == __mul__(factor, self)
|
||||
|
||||
def __rmul__(self, double factor) -> Vec2:
|
||||
# for Cython >= 3.0
|
||||
return v2_mul(self, factor)
|
||||
|
||||
__imul__ = __mul__ # immutable
|
||||
|
||||
def __truediv__(self, double factor) -> Vec2:
|
||||
return v2_mul(self, 1.0 / factor)
|
||||
|
||||
# __rtruediv__ not supported -> TypeError
|
||||
|
||||
def dot(self, other: AnyVec) -> float:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
return v2_dot(self, o)
|
||||
|
||||
def det(self, other: AnyVec) -> float:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
return v2_det(self, o)
|
||||
|
||||
def distance(self, other: AnyVec) -> float:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
return v2_dist(self, o)
|
||||
|
||||
def angle_between(self, other: AnyVec) -> float:
|
||||
cdef Vec2 o = Vec2(other)
|
||||
return v2_angle_between(self, o)
|
||||
|
||||
def rotate(self, double angle) -> Vec2:
|
||||
cdef double self_angle = atan2(self.y, self.x)
|
||||
cdef double magnitude = hypot(self.x, self.y)
|
||||
return v2_from_angle(self_angle + angle, magnitude)
|
||||
|
||||
def rotate_deg(self, double angle) -> Vec2:
|
||||
return self.rotate(angle * DEG2RAD)
|
||||
|
||||
@staticmethod
|
||||
def sum(items: Iterable[Vec2]) -> Vec2:
|
||||
cdef Vec2 res = Vec2()
|
||||
cdef Vec2 tmp
|
||||
res.x = 0.0
|
||||
res.y = 0.0
|
||||
for v in items:
|
||||
tmp = v
|
||||
res.x += tmp.x
|
||||
res.y += tmp.y
|
||||
return res
|
||||
|
||||
|
||||
cdef Vec2 v2_add(Vec2 a, Vec2 b):
|
||||
res = Vec2()
|
||||
res.x = a.x + b.x
|
||||
res.y = a.y + b.y
|
||||
return res
|
||||
|
||||
cdef Vec2 v2_sub(Vec2 a, Vec2 b):
|
||||
res = Vec2()
|
||||
res.x = a.x - b.x
|
||||
res.y = a.y - b.y
|
||||
return res
|
||||
|
||||
cdef Vec2 v2_mul(Vec2 a, double factor):
|
||||
res = Vec2()
|
||||
res.x = a.x * factor
|
||||
res.y = a.y * factor
|
||||
return res
|
||||
|
||||
cdef double v2_dot(Vec2 a, Vec2 b):
|
||||
return a.x * b.x + a.y * b.y
|
||||
|
||||
cdef double v2_det(Vec2 a, Vec2 b):
|
||||
return a.x * b.y - a.y * b.x
|
||||
|
||||
cdef double v2_dist(Vec2 a, Vec2 b):
|
||||
return hypot(a.x - b.x, a.y - b.y)
|
||||
|
||||
cdef Vec2 v2_from_angle(double angle, double length):
|
||||
cdef Vec2 res = Vec2()
|
||||
res.x = cos(angle) * length
|
||||
res.y = sin(angle) * length
|
||||
return res
|
||||
|
||||
cdef double v2_angle_between(Vec2 a, Vec2 b) except -1000:
|
||||
cdef double cos_theta = v2_dot(v2_normalize(a, 1.0), v2_normalize(b, 1.0))
|
||||
# avoid domain errors caused by floating point imprecision:
|
||||
if cos_theta < -1.0:
|
||||
cos_theta = -1.0
|
||||
elif cos_theta > 1.0:
|
||||
cos_theta = 1.0
|
||||
return acos(cos_theta)
|
||||
|
||||
cdef Vec2 v2_normalize(Vec2 a, double length):
|
||||
cdef double factor = length / hypot(a.x, a.y)
|
||||
cdef Vec2 res = Vec2()
|
||||
res.x = a.x * factor
|
||||
res.y = a.y * factor
|
||||
return res
|
||||
|
||||
cdef Vec2 v2_lerp(Vec2 a, Vec2 b, double factor):
|
||||
cdef Vec2 res = Vec2()
|
||||
res.x = a.x + (b.x - a.x) * factor
|
||||
res.y = a.y + (b.y - a.y) * factor
|
||||
return res
|
||||
|
||||
cdef Vec2 v2_ortho(Vec2 a, bint ccw):
|
||||
cdef Vec2 res = Vec2()
|
||||
if ccw:
|
||||
res.x = -a.y
|
||||
res.y = a.x
|
||||
else:
|
||||
res.x = a.y
|
||||
res.y = -a.x
|
||||
return res
|
||||
|
||||
cdef Vec2 v2_project(Vec2 a, Vec2 b):
|
||||
cdef Vec2 uv = v2_normalize(a, 1.0)
|
||||
return v2_mul(uv, v2_dot(uv, b))
|
||||
|
||||
cdef bint v2_isclose(Vec2 a, Vec2 b, double rel_tol, double abs_tol):
|
||||
return isclose(a.x, b.x, rel_tol, abs_tol) and \
|
||||
isclose(a.y, b.y, rel_tol, abs_tol)
|
||||
|
||||
|
||||
cdef class Vec3:
|
||||
""" Immutable 3D vector.
|
||||
|
||||
Init:
|
||||
|
||||
- Vec3()
|
||||
- Vec3(vec3)
|
||||
- Vec3(vec2)
|
||||
- Vec3((x, y))
|
||||
- Vec3((x, y, z))
|
||||
- Vec3(x, y)
|
||||
- Vec3(x, y, z)
|
||||
|
||||
"""
|
||||
|
||||
def __cinit__(self, *args):
|
||||
cdef Py_ssize_t count = len(<tuple> args)
|
||||
if count == 0: # fastest init - default constructor
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.z = 0
|
||||
return
|
||||
|
||||
if count == 1:
|
||||
arg0 = args[0]
|
||||
if isinstance(arg0, Vec3):
|
||||
# fast init by Vec3()
|
||||
self.x = (<Vec3> arg0).x
|
||||
self.y = (<Vec3> arg0).y
|
||||
self.z = (<Vec3> arg0).z
|
||||
return
|
||||
if isinstance(arg0, Vec2):
|
||||
# fast init by Vec2()
|
||||
self.x = (<Vec2> arg0).x
|
||||
self.y = (<Vec2> arg0).y
|
||||
self.z = 0
|
||||
return
|
||||
args = arg0
|
||||
count = len(args)
|
||||
|
||||
if count > 3 or count < 2:
|
||||
raise TypeError('invalid argument count')
|
||||
|
||||
# slow init by sequence
|
||||
self.x = args[0]
|
||||
self.y = args[1]
|
||||
if count > 2:
|
||||
self.z = args[2]
|
||||
else:
|
||||
self.z = 0.0
|
||||
|
||||
def __reduce__(self):
|
||||
return Vec3, self.xyz
|
||||
|
||||
@property
|
||||
def xy(self) -> Vec3:
|
||||
cdef Vec3 res = Vec3()
|
||||
res.x = self.x
|
||||
res.y = self.y
|
||||
return res
|
||||
|
||||
@property
|
||||
def xyz(self) -> Tuple[float, float, float]:
|
||||
return self.x, self.y, self.z
|
||||
|
||||
@property
|
||||
def vec2(self) -> Vec2:
|
||||
cdef Vec2 res = Vec2()
|
||||
res.x = self.x
|
||||
res.y = self.y
|
||||
return res
|
||||
|
||||
def replace(self, x: float = None, y: float = None,
|
||||
z: float = None) -> Vec3:
|
||||
cdef Vec3 res = Vec3()
|
||||
res.x = self.x if x is None else x
|
||||
res.y = self.y if y is None else y
|
||||
res.z = self.z if z is None else z
|
||||
return res
|
||||
|
||||
def round(self, ndigits: int | None = None) -> Vec3:
|
||||
return Vec3(
|
||||
round(self.x, ndigits),
|
||||
round(self.y, ndigits),
|
||||
round(self.z, ndigits),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def list(items: Iterable[UVec]) -> List[Vec3]:
|
||||
return list(Vec3.generate(items))
|
||||
|
||||
@staticmethod
|
||||
def tuple(items: Iterable[UVec]) -> Sequence[Vec3]:
|
||||
return tuple(Vec3.generate(items))
|
||||
|
||||
@staticmethod
|
||||
def generate(items: Iterable[UVec]) -> Iterator[Vec3]:
|
||||
return (Vec3(item) for item in items)
|
||||
|
||||
@staticmethod
|
||||
def from_angle(double angle, double length = 1.0) -> Vec3:
|
||||
return v3_from_angle(angle, length)
|
||||
|
||||
@staticmethod
|
||||
def from_deg_angle(double angle, double length = 1.0) -> Vec3:
|
||||
return v3_from_angle(angle * DEG2RAD, length)
|
||||
|
||||
@staticmethod
|
||||
def random(double length = 1.0) -> Vec3:
|
||||
cdef Vec3 res = Vec3()
|
||||
uniform = random.uniform
|
||||
res.x = uniform(-1, 1)
|
||||
res.y = uniform(-1, 1)
|
||||
res.z = uniform(-1, 1)
|
||||
return v3_normalize(res, length)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'({self.x}, {self.y}, {self.z})'
|
||||
|
||||
def __repr__(self)-> str:
|
||||
return "Vec3" + self.__str__()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return 3
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.xyz)
|
||||
|
||||
def copy(self) -> Vec3:
|
||||
return self # immutable
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def __deepcopy__(self, memodict: dict) -> Vec3:
|
||||
return self # immutable!
|
||||
|
||||
def __getitem__(self, int index) -> float:
|
||||
if index == 0:
|
||||
return self.x
|
||||
elif index == 1:
|
||||
return self.y
|
||||
elif index == 2:
|
||||
return self.z
|
||||
else:
|
||||
raise IndexError(f'invalid index {index}')
|
||||
|
||||
def __iter__(self) -> Iterator[float]:
|
||||
yield self.x
|
||||
yield self.y
|
||||
yield self.z
|
||||
|
||||
def __abs__(self) -> float:
|
||||
return v3_magnitude(self)
|
||||
|
||||
@property
|
||||
def magnitude(self) -> float:
|
||||
return v3_magnitude(self)
|
||||
|
||||
@property
|
||||
def magnitude_xy(self) -> float:
|
||||
return hypot(self.x, self.y)
|
||||
|
||||
@property
|
||||
def magnitude_square(self) -> float:
|
||||
return v3_magnitude_sqr(self)
|
||||
|
||||
@property
|
||||
def is_null(self) -> bool:
|
||||
return fabs(self.x) <= ABS_TOL and fabs(self.y) <= ABS_TOL and \
|
||||
fabs(self.z) <= ABS_TOL
|
||||
|
||||
def is_parallel(self, other: UVec, *, double rel_tol=REL_TOL,
|
||||
double abs_tol = ABS_TOL) -> bool:
|
||||
cdef Vec3 o = Vec3(other)
|
||||
cdef Vec3 v1 = v3_normalize(self, 1.0)
|
||||
cdef Vec3 v2 = v3_normalize(o, 1.0)
|
||||
cdef Vec3 neg_v2 = v3_reverse(v2)
|
||||
return v3_isclose(v1, v2, rel_tol, abs_tol) or \
|
||||
v3_isclose(v1, neg_v2, rel_tol, abs_tol)
|
||||
|
||||
@property
|
||||
def spatial_angle(self) -> float:
|
||||
return acos(v3_dot(<Vec3> X_AXIS, v3_normalize(self, 1.0)))
|
||||
|
||||
@property
|
||||
def spatial_angle_deg(self) -> float:
|
||||
return self.spatial_angle * RAD2DEG
|
||||
|
||||
@property
|
||||
def angle(self) -> float:
|
||||
return atan2(self.y, self.x)
|
||||
|
||||
@property
|
||||
def angle_deg(self) -> float:
|
||||
return atan2(self.y, self.x) * RAD2DEG
|
||||
|
||||
def orthogonal(self, ccw: bool = True) -> Vec3:
|
||||
return v3_ortho(self, ccw)
|
||||
|
||||
def lerp(self, other: UVec, double factor=0.5) -> Vec3:
|
||||
if not isinstance(other, Vec3):
|
||||
other = Vec3(other)
|
||||
return v3_lerp(self, <Vec3> other, factor)
|
||||
|
||||
def project(self, other: UVec) -> Vec3:
|
||||
if not isinstance(other, Vec3):
|
||||
other = Vec3(other)
|
||||
return v3_project(self, <Vec3> other)
|
||||
|
||||
def normalize(self, double length = 1.) -> Vec3:
|
||||
return v3_normalize(self, length)
|
||||
|
||||
def reversed(self) -> Vec3:
|
||||
return v3_reverse(self)
|
||||
|
||||
def __neg__(self) -> Vec3:
|
||||
return v3_reverse(self)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return not self.is_null
|
||||
|
||||
def isclose(self, other: UVec, *, double rel_tol = REL_TOL,
|
||||
double abs_tol = ABS_TOL) -> bool:
|
||||
if not isinstance(other, Vec3):
|
||||
other = Vec3(other)
|
||||
return v3_isclose(self, <Vec3> other, rel_tol, abs_tol)
|
||||
|
||||
def __eq__(self, other: UVec) -> bool:
|
||||
if not isinstance(other, Vec3):
|
||||
other = Vec3(other)
|
||||
return self.x == other.x and self.y == other.y and self.z == other.z
|
||||
|
||||
def __lt__(self, other: UVec) -> bool:
|
||||
if not isinstance(other, Vec3):
|
||||
other = Vec3(other)
|
||||
if self.x == (<Vec3> other).x:
|
||||
return self.y < (<Vec3> other).y
|
||||
else:
|
||||
return self.x < (<Vec3> other).x
|
||||
|
||||
# Special Cython (<3.0) feature: __radd__ == __add__(other, self)
|
||||
def __add__(self, other) -> Vec3:
|
||||
if not isinstance(self, Vec3):
|
||||
# other is the real self
|
||||
return v3_add(Vec3(self), <Vec3> other)
|
||||
|
||||
if not isinstance(other, Vec3):
|
||||
other = Vec3(other)
|
||||
return v3_add(<Vec3> self, <Vec3> other)
|
||||
|
||||
__radd__ = __add__ # Cython >= 3.0
|
||||
|
||||
__iadd__ = __add__ # immutable
|
||||
|
||||
# Special Cython (<3.0) feature: __rsub__ == __sub__(other, self)
|
||||
def __sub__(self, other) -> Vec3:
|
||||
if not isinstance(self, Vec3):
|
||||
# other is the real self
|
||||
return v3_sub(Vec3(self), <Vec3> other)
|
||||
|
||||
if not isinstance(other, Vec3):
|
||||
other = Vec3(other)
|
||||
return v3_sub(<Vec3> self, <Vec3> other)
|
||||
|
||||
def __rsub__(self, other) -> Vec3:
|
||||
# for Cython >= 3.0
|
||||
return v3_sub(Vec3(other), <Vec3> self)
|
||||
|
||||
__isub__ = __sub__ # immutable
|
||||
|
||||
# Special Cython <(3.0) feature: __rmul__ == __mul__(factor, self)
|
||||
def __mul__(self, factor) -> Vec3:
|
||||
if isinstance(factor, Vec3):
|
||||
return v3_mul(<Vec3> factor, self)
|
||||
return v3_mul(<Vec3> self, factor)
|
||||
|
||||
def __rmul__(self, double factor) -> Vec3:
|
||||
# for Cython >= 3.0
|
||||
return v3_mul(self, factor)
|
||||
|
||||
__imul__ = __mul__ # immutable
|
||||
|
||||
def __truediv__(self, double factor) -> Vec3:
|
||||
return v3_mul(self, 1.0 / factor)
|
||||
|
||||
# __rtruediv__ not supported -> TypeError
|
||||
|
||||
@staticmethod
|
||||
def sum(items: Iterable[UVec]) -> Vec3:
|
||||
cdef Vec3 res = Vec3()
|
||||
cdef Vec3 tmp
|
||||
for v in items:
|
||||
tmp = Vec3(v)
|
||||
res.x += tmp.x
|
||||
res.y += tmp.y
|
||||
res.z += tmp.z
|
||||
return res
|
||||
|
||||
def dot(self, other: UVec) -> float:
|
||||
cdef Vec3 o = Vec3(other)
|
||||
return v3_dot(self, o)
|
||||
|
||||
def cross(self, other: UVec) -> Vec3:
|
||||
cdef Vec3 o = Vec3(other)
|
||||
return v3_cross(self, o)
|
||||
|
||||
def distance(self, other: UVec) -> float:
|
||||
cdef Vec3 o = Vec3(other)
|
||||
return v3_dist(self, o)
|
||||
|
||||
def angle_between(self, other: UVec) -> float:
|
||||
cdef Vec3 o = Vec3(other)
|
||||
return v3_angle_between(self, o)
|
||||
|
||||
def angle_about(self, base: UVec, target: UVec) -> float:
|
||||
cdef Vec3 b = Vec3(base)
|
||||
cdef Vec3 t = Vec3(target)
|
||||
return v3_angle_about(self, b, t)
|
||||
|
||||
def rotate(self, double angle) -> Vec3:
|
||||
cdef double angle_ = atan2(self.y, self.x) + angle
|
||||
cdef double magnitude_ = hypot(self.x, self.y)
|
||||
cdef Vec3 res = Vec3.from_angle(angle_, magnitude_)
|
||||
res.z = self.z
|
||||
return res
|
||||
|
||||
def rotate_deg(self, double angle) -> Vec3:
|
||||
return self.rotate(angle * DEG2RAD)
|
||||
|
||||
|
||||
X_AXIS = Vec3(1, 0, 0)
|
||||
Y_AXIS = Vec3(0, 1, 0)
|
||||
Z_AXIS = Vec3(0, 0, 1)
|
||||
NULLVEC = Vec3(0, 0, 0)
|
||||
|
||||
cdef Vec3 v3_add(Vec3 a, Vec3 b):
|
||||
res = Vec3()
|
||||
res.x = a.x + b.x
|
||||
res.y = a.y + b.y
|
||||
res.z = a.z + b.z
|
||||
return res
|
||||
|
||||
cdef Vec3 v3_sub(Vec3 a, Vec3 b):
|
||||
res = Vec3()
|
||||
res.x = a.x - b.x
|
||||
res.y = a.y - b.y
|
||||
res.z = a.z - b.z
|
||||
return res
|
||||
|
||||
|
||||
cdef Vec3 v3_mul(Vec3 a, double factor):
|
||||
res = Vec3()
|
||||
res.x = a.x * factor
|
||||
res.y = a.y * factor
|
||||
res.z = a.z * factor
|
||||
return res
|
||||
|
||||
|
||||
cdef Vec3 v3_reverse(Vec3 a):
|
||||
cdef Vec3 res = Vec3()
|
||||
res.x = -a.x
|
||||
res.y = -a.y
|
||||
res.z = -a.z
|
||||
return res
|
||||
|
||||
|
||||
cdef double v3_dot(Vec3 a, Vec3 b):
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z
|
||||
|
||||
|
||||
cdef Vec3 v3_cross(Vec3 a, Vec3 b):
|
||||
res = Vec3()
|
||||
res.x = a.y * b.z - a.z * b.y
|
||||
res.y = a.z * b.x - a.x * b.z
|
||||
res.z = a.x * b.y - a.y * b.x
|
||||
return res
|
||||
|
||||
|
||||
cdef inline double v3_magnitude_sqr(Vec3 a):
|
||||
return a.x * a.x + a.y * a.y + a.z * a.z
|
||||
|
||||
|
||||
cdef inline double v3_magnitude(Vec3 a):
|
||||
return sqrt(v3_magnitude_sqr(a))
|
||||
|
||||
|
||||
cdef double v3_dist(Vec3 a, Vec3 b):
|
||||
cdef double dx = a.x - b.x
|
||||
cdef double dy = a.y - b.y
|
||||
cdef double dz = a.z - b.z
|
||||
return sqrt(dx * dx + dy * dy + dz * dz)
|
||||
|
||||
|
||||
cdef Vec3 v3_from_angle(double angle, double length):
|
||||
cdef Vec3 res = Vec3()
|
||||
res.x = cos(angle) * length
|
||||
res.y = sin(angle) * length
|
||||
return res
|
||||
|
||||
|
||||
cdef double v3_angle_between(Vec3 a, Vec3 b) except -1000:
|
||||
cdef double cos_theta = v3_dot(v3_normalize(a, 1.0), v3_normalize(b, 1.0))
|
||||
# avoid domain errors caused by floating point imprecision:
|
||||
if cos_theta < -1.0:
|
||||
cos_theta = -1.0
|
||||
elif cos_theta > 1.0:
|
||||
cos_theta = 1.0
|
||||
return acos(cos_theta)
|
||||
|
||||
|
||||
cdef double v3_angle_about(Vec3 a, Vec3 base, Vec3 target):
|
||||
cdef Vec3 x_axis = v3_normalize(v3_sub(base, v3_project(a, base)), 1.0)
|
||||
cdef Vec3 y_axis = v3_normalize(v3_cross(a, x_axis), 1.0)
|
||||
cdef double target_projected_x = v3_dot(x_axis, target)
|
||||
cdef double target_projected_y = v3_dot(y_axis, target)
|
||||
return normalize_rad_angle(atan2(target_projected_y, target_projected_x))
|
||||
|
||||
|
||||
cdef Vec3 v3_normalize(Vec3 a, double length):
|
||||
cdef double factor = length / v3_magnitude(a)
|
||||
cdef Vec3 res = Vec3()
|
||||
res.x = a.x * factor
|
||||
res.y = a.y * factor
|
||||
res.z = a.z * factor
|
||||
return res
|
||||
|
||||
|
||||
cdef Vec3 v3_lerp(Vec3 a, Vec3 b, double factor):
|
||||
cdef Vec3 res = Vec3()
|
||||
res.x = a.x + (b.x - a.x) * factor
|
||||
res.y = a.y + (b.y - a.y) * factor
|
||||
res.z = a.z + (b.z - a.z) * factor
|
||||
return res
|
||||
|
||||
|
||||
cdef Vec3 v3_ortho(Vec3 a, bint ccw):
|
||||
cdef Vec3 res = Vec3()
|
||||
res.z = a.z
|
||||
if ccw:
|
||||
res.x = -a.y
|
||||
res.y = a.x
|
||||
else:
|
||||
res.x = a.y
|
||||
res.y = -a.x
|
||||
return res
|
||||
|
||||
|
||||
cdef Vec3 v3_project(Vec3 a, Vec3 b):
|
||||
cdef Vec3 uv = v3_normalize(a, 1.0)
|
||||
return v3_mul(uv, v3_dot(uv, b))
|
||||
|
||||
|
||||
cdef bint v3_isclose(Vec3 a, Vec3 b, double rel_tol, double abs_tol):
|
||||
return isclose(a.x, b.x, rel_tol, abs_tol) and \
|
||||
isclose(a.y, b.y, rel_tol, abs_tol) and \
|
||||
isclose(a.z, b.z, rel_tol, abs_tol)
|
||||
|
||||
|
||||
def distance(p1: UVec, p2: UVec) -> float:
|
||||
cdef Vec3 a = Vec3(p1)
|
||||
cdef Vec3 b = Vec3(p2)
|
||||
return v3_dist(a, b)
|
||||
|
||||
|
||||
def lerp(p1: UVec, p2: UVec, double factor = 0.5) -> Vec3:
|
||||
cdef Vec3 a = Vec3(p1)
|
||||
cdef Vec3 b = Vec3(p2)
|
||||
return v3_lerp(a, b, factor)
|
||||
Reference in New Issue
Block a user