Coverage for trimesh/path/intersections.py: 96%
23 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
1import numpy as np
3from .. import util
4from ..constants import tol_path as tol
7def line_line(origins, directions, plane_normal=None):
8 """
9 Find the intersection between two lines.
10 Uses terminology from:
11 http://geomalgorithms.com/a05-_intersect-1.html
13 line 1: P(s) = p_0 + sU
14 line 2: Q(t) = q_0 + tV
16 Parameters
17 ---------
18 origins : (2, d) float
19 Points on lines (d in [2,3])
20 directions : (2, d) float
21 Direction vectors
22 plane_normal : (3, ) float
23 If not passed computed from cross
25 Returns
26 ---------
27 intersects : bool
28 Whether the lines intersect.
29 In 2D, false if the lines are parallel
30 In 3D, false if lines are not coplanar
31 intersection : (d,) float or None
32 Point of intersection
33 """
34 # check so we can accept 2D or 3D points
35 origins, is_2D = util.stack_3D(origins, return_2D=True)
36 directions, is_2D = util.stack_3D(directions, return_2D=True)
38 # unitize direction vectors
39 directions /= util.row_norm(directions).reshape((-1, 1))
41 # exit if values are parallel
42 if np.sum(np.abs(np.diff(directions, axis=0))) < tol.zero:
43 return False, None
45 # using notation from docstring
46 q_0, p_0 = origins
47 v, u = directions
48 w = p_0 - q_0
50 # recompute plane normal if not passed
51 if plane_normal is None:
52 # the normal of the plane given by the two direction vectors
53 plane_normal = np.cross(u, v)
54 plane_normal /= np.linalg.norm(plane_normal)
56 # vectors perpendicular to the two lines
57 v_perp = np.cross(v, plane_normal)
58 v_perp /= np.linalg.norm(v_perp)
60 # if the vector from origin to origin is on the plane given by
61 # the direction vector, the dot product with the plane normal
62 # should be within floating point error of zero
63 w_norm = np.linalg.norm(w)
64 if w_norm > tol.zero and abs(np.dot(plane_normal, w / w_norm)) > tol.zero:
65 # not coplanar
66 return False, None
68 # value of parameter s where intersection occurs
69 s_I = np.dot(-v_perp, w) / np.dot(v_perp, u)
70 # plug back into the equation of the line to find the point
71 intersection = p_0 + s_I * u
73 return True, intersection[: (3 - is_2D)]