Skip to main content

tumo_path/geom/
line.rs

1use crate::LinearCmd;
2
3use super::{Cubic, Curve, CurveDasher, Point};
4
5#[repr(C)]
6#[derive(Clone, Copy, Debug, PartialEq, Hash)]
7pub struct Line {
8	pub p0: Point,
9	pub to: Point,
10}
11impl Line {
12	pub fn new(p0: Point, to: Point) -> Self {
13		Self { p0, to }
14	}
15	pub fn linearize(self, tolerance: f32) -> LineIter {
16		LineIter::new(self, tolerance)
17	}
18	pub fn dash(self, tolerance: f32) -> CurveDasher<Self> {
19		CurveDasher::new(self, tolerance)
20	}
21	pub fn its(&self, other: &Line) -> Option<LineIts> {
22		let d1 = self.to - self.p0;
23		let d2 = other.to - other.p0;
24		let cross = d1.x * d2.y - d1.y * d2.x;
25		if cross.abs() < f32::EPSILON {
26			return None;
27		}
28		let ac = other.p0 - self.p0;
29		let t = (ac.x * d2.y - ac.y * d2.x) / cross;
30		let s = (ac.x * d1.y - ac.y * d1.x) / cross;
31		let point = self.p0 + d1 * t;
32		Some(LineIts {
33			point,
34			cross,
35			t,
36			s,
37			is_on_this: t >= 0.0 && t <= 1.0,
38			is_on_other: s >= 0.0 && s <= 1.0,
39		})
40	}
41}
42impl Curve for Line {
43	fn start(&self) -> Point {
44		self.p0
45	}
46	fn end(&self) -> Point {
47		self.to
48	}
49	fn linear(&self, _tolerance: f32) -> bool {
50		true
51	}
52	fn split(self, at: f32) -> (Self, Self) {
53		let p01 = self.p0.lerp(self.to, at);
54		(Self::new(self.p0, p01), Self::new(p01, self.to))
55	}
56	/// Slices the line into a sub-line from parameter `start` to `end`.
57	/// `start` and `end` should be between 0.0 and 1.0.
58	fn slice(&self, start: f32, end: f32) -> Self {
59		let p0 = self.eval(start);
60		let to = self.eval(end);
61		Line { p0, to }
62	}
63	fn shift(&self, offset: f32) -> (Self, Self) {
64		if offset.abs() < f32::EPSILON {
65			return (*self, *self);
66		}
67		let normal = self.p0.normal_to(self.to).normalized();
68		let p0 = self.p0 + normal * offset;
69		let to = self.to + normal * offset;
70		let ccw = Line { p0, to };
71		let p0 = self.p0 + normal * -offset;
72		let to = self.to + normal * -offset;
73		let cw = Line { p0, to };
74		(ccw, cw)
75	}
76	fn reverse(&self) -> Self {
77		Self { p0: self.to, to: self.p0 }
78	}
79	/// Returns the length of the curve.
80	fn length(&self) -> f32 {
81		if self.p0 == self.to {
82			return 0.0;
83		}
84		(self.to - self.p0).length()
85	}
86	/// Evaluates the line at the specified time `t`.
87	/// `t` should be between 0.0 and 1.0, where 0.0 corresponds to `p0` and 1.0 corresponds to `to`.
88	fn eval(&self, at: f32) -> Point {
89		// Linear interpolation: P(t) = (1 - t) * p0 + t * to
90		self.p0 + (self.to - self.p0) * at
91	}
92	/// Evaluates the derivative (constant tangent vector) of the line.
93	/// The derivative is constant along the entire line and equals the vector from `p0` to `to`.
94	fn derivative(&self, _at: f32) -> Point {
95		self.to - self.p0
96	}
97	/// Evaluates the normal vector of the line.
98	fn normal(&self, at: f32) -> Point {
99		if self.p0 == self.to {
100			return Point::ZERO;
101		}
102		// Gets `to - p0`
103		self.derivative(at).normal().normalized()
104	}
105	/// Returns the time parameter for the specified linear distance along the line.
106	/// This function finds the parameter `t` such that the arc length from the start
107	/// of the line up to `t` is approximately equal to `distance`.
108	fn at(&self, distance: f32, _tolerance: f32) -> f32 {
109		if distance <= 0.0 {
110			return 0.0;
111		}
112		let total_length = self.length();
113		if distance >= total_length {
114			return 1.0;
115		}
116		distance / total_length
117	}
118}
119impl From<Line> for Cubic {
120	fn from(value: Line) -> Self {
121		Cubic::new(value.p0, value.p0, value.to, value.to)
122	}
123}
124impl From<Line> for LinearCmd {
125	fn from(value: Line) -> Self {
126		LinearCmd::LineTo(value.to)
127	}
128}
129
130#[derive(Debug, Clone, PartialEq)]
131pub struct LineIter {
132	to: Option<Point>,
133}
134impl LineIter {
135	pub fn empty(_tolerance: f32) -> Self {
136		Self { to: None }
137	}
138	pub fn new(value: Line, tolerance: f32) -> Self {
139		let mut this = Self::empty(tolerance);
140		this.to = Some(value.to);
141		this
142	}
143}
144impl Iterator for LineIter {
145	type Item = LinearCmd;
146	fn next(&mut self) -> Option<Self::Item> {
147		Some(LinearCmd::LineTo(self.to.take()?))
148	}
149}
150
151#[repr(C)]
152#[derive(Debug, Copy, Clone, PartialEq, Hash)]
153pub enum LineCmd {
154	Move(Point),
155	Line(Line),
156	Close,
157}
158impl From<Line> for LineCmd {
159	fn from(value: Line) -> Self {
160		LineCmd::Line(value)
161	}
162}
163impl From<LineCmd> for Option<Line> {
164	fn from(value: LineCmd) -> Self {
165		match value {
166			LineCmd::Line(line) => Some(line),
167			_ => None,
168		}
169	}
170}
171impl From<LineCmd> for LinearCmd {
172	fn from(value: LineCmd) -> Self {
173		match value {
174			LineCmd::Move(to) => LinearCmd::MoveTo(to),
175			LineCmd::Line(line) => LinearCmd::LineTo(line.to),
176			LineCmd::Close => LinearCmd::Close,
177		}
178	}
179}
180
181#[derive(Debug, Clone, Copy, PartialEq)]
182pub struct LineIts {
183	/// 交点坐标
184	pub point: Point,
185	/// 叉乘结果
186	pub cross: f32,
187	/// 在第一条线段上的参数 t (P = A + t*(B-A))
188	pub t: f32,
189	/// 在第二条线段上的参数 s (P = C + s*(D-C))
190	pub s: f32,
191	/// 交点是否在第一条线段上(包括端点)
192	pub is_on_this: bool,
193	/// 交点是否在第二条线段上(包括端点)
194	pub is_on_other: bool,
195}