This commit is contained in:
Keavon Chambers 2025-02-14 18:21:15 -08:00
parent fc60ea5bab
commit f217edef3d

View file

@ -113,31 +113,56 @@ impl Bezier {
match self.handles { match self.handles {
BezierHandles::Linear => Vec::new(), BezierHandles::Linear => Vec::new(),
BezierHandles::Quadratic { handle } => { BezierHandles::Quadratic { handle } => {
// Represent the quadratic in standard form: // For quadratic Bezier:
// p(t) = p0 + 2(p1 - p0)t + (p0 - 2*p1 + p2)t² // Let P0 = self.start, P1 = handle, P2 = self.end.
let a = self.start - 2. * handle + self.end; // Express the curve as p(t) = P0 + 2t*(P1P0) + t²*(P2 2P1 + P0)
let b = 2. * (handle - self.start); // Its derivative is p(t) = 2*(P1P0) + 2t*(P2 2P1 + P0).
let c = self.start - point; // A point lies on the tangent at p(t) if (p(t) point)×p(t) = 0.
// After algebra, we can write the equation as:
// Our polynomial is: (a.cross(b)) t² - 2*(d.cross(a)) t - (d.cross(b)) = 0. //c0 + c1*t + c2*t² = 0,
let c2 = a.perp_dot(b); // where:
let c1 = -2. * c.perp_dot(a); //c0 = (P0 point)×(P1P0)
let c0 = b.perp_dot(c); //c1 = (P0 point)×(P2 2*P1 + P0)
//c2 = (P1P0)×(P2 2*P1 + P0)
let a = self.end - 2. * handle + self.start; // (P2 2P1 + P0)
let c = handle - self.start; // (P1 P0)
let q = self.start - point;
let c0 = q.perp_dot(c);
let c1 = q.perp_dot(a);
let c2 = c.perp_dot(a);
crate::quartic_solver2::solve_quadratic(c0, c1, c2).iter().copied().flatten().filter(|t| *t >= 0. && *t <= 1.).collect() crate::quartic_solver2::solve_quadratic(c0, c1, c2).iter().copied().flatten().filter(|t| *t >= 0. && *t <= 1.).collect()
} }
BezierHandles::Cubic { handle_start, handle_end } => { BezierHandles::Cubic { handle_start, handle_end } => {
let d = self.start - point; // For cubic Bezier:
let c = (handle_start - self.start) * 3.; // Let P0 = self.start, P1 = handle_start, P2 = handle_end, P3 = self.end.
let b = (handle_end - handle_start) * 3. - c; // Write the curve in power form:
let a = self.end - self.start - c - b; //p(t) = P0 + 3t*(P1P0) + 3t²*(P2 2*P1 + P0) + t³*(P3 3*P2 + 3*P1 P0)
// and its derivative:
// coefficients of x(t) \cross x'(t) //p(t) = 3*(P1P0) + 6t*(P2 2*P1 + P0) + 3t²*(P3 3*P2 + 3*P1 P0).
let c0 = d.perp_dot(c); // The condition for an external point to lie on the tangent is:
let c1 = 2. * d.perp_dot(b); //(p(t) point)×p(t) = 0.
let c2 = c.perp_dot(b) + 3. * d.perp_dot(a); // After expanding and collecting like powers, we express the equation as:
let c3 = 2. * c.perp_dot(a); //c0 + c1*t + c2*t² + c3*t³ + c4*t⁴ = 0,
let c4 = b.perp_dot(a); // where:
//Let D = P1P0,
//E = P2 2·P1 + P0,
//F = P3 3·P2 + 3·P1 P0,
// then
//c0 = 3·( (P0 point)×D )
//c1 = 6·((P0 point)×E)
//c2 = 3·( (P0 point)×F + 5·(D×E) )
//c3 = 6·(D×F)
//c4 = 3·(E×F)
let d = handle_start - self.start;
let e = handle_end - 2. * handle_start + self.start;
let f = self.end - 3. * handle_end + 3. * handle_start - self.start;
let q = self.start - point;
let c0 = 3. * q.perp_dot(d);
let c1 = 6. * q.perp_dot(e);
let c2 = 3. * (q.perp_dot(f) + 3. * d.perp_dot(e));
let c3 = 6. * d.perp_dot(f);
let c4 = 3. * e.perp_dot(f);
crate::quartic_solver2::solve_quartic(c0, c1, c2, c3, c4) crate::quartic_solver2::solve_quartic(c0, c1, c2, c3, c4)
.iter() .iter()
@ -168,37 +193,58 @@ impl Bezier {
} }
let t = point_a.dot(point_b) / point_b.length_squared(); let t = point_a.dot(point_b) / point_b.length_squared();
if !(0.0..=1.).contains(&t) { std::iter::once(t).filter(|t| *t >= 0. && *t <= 1.).collect()
return Vec::new();
}
vec![t]
} }
BezierHandles::Quadratic { handle } => { BezierHandles::Quadratic { handle } => {
let a = self.start - 2. * handle + self.end; // Replace the todo!() block in the Quadratic branch of normals_to_point with:
let b = 2. * (handle - self.start);
let c = self.start - point;
let c2 = a.dot(b); let A = handle - self.start;
let c1 = -2. * c.dot(a); let B = self.end - 2. * handle + self.start;
let c0 = b.dot(c); let q = point - self.start;
let c0 = q.dot(A);
let c1 = q.dot(B) - 2. * A.dot(A);
let c2 = -3. * A.dot(B);
let c3 = -B.dot(B);
crate::quartic_solver2::solve_quadratic(c0, c1, c2).iter().copied().flatten().filter(|t| *t >= 0. && *t <= 1.).collect() crate::quartic_solver2::solve_cubic(c0, c1, c2, c3).iter().copied().flatten().filter(|t| *t >= 0. && *t <= 1.).collect()
} }
BezierHandles::Cubic { handle_start, handle_end } => { BezierHandles::Cubic { handle_start, handle_end } => {
let d = self.start - point; // For a cubic Bézier with:
let c = (handle_start - self.start) * 3.; // P0 = self.start, P1 = handle_start, P2 = handle_end, P3 = self.end,
let b = (handle_end - handle_start) * 3. - c; // we can express the curve as:
let a = self.end - self.start - c - b; //p(t) = P0 + 3t*(P1 - P0) + 3t²*(P2 - 2P1 + P0) + t³*(P3 - 3P2 + 3P1 - P0)
// and its derivative as:
//p(t) = 3*(P1 - P0) + 6t*(P2 - 2P1 + P0) + 3t²*(P3 - 3P2 + 3P1 - P0)
//
// The normal condition is (p(t) - point) · p(t) = 0.
// Let:
//d = handle_start - self.start
//e = handle_end - 2. * handle_start + self.start
//f = self.end - 3. * handle_end + 3. * handle_start - self.start
//q = point - self.start
//
// Expanding (p(t)-point)·p'(t)=0 yields a quintic:
//c₀ + c₁*t + c₂*t² + c₃*t³ + c₄*t⁴ + c₅*t⁵ = 0,
// with:
//c₀ = -3. * q.dot(d)
//c₁ = 9. * d.dot(d) - 6. * q.dot(e)
//c₂ = 27. * d.dot(e) - 3. * q.dot(f)
//c₃ = 12. * d.dot(f) + 18. * e.dot(e)
//c₄ = 15. * e.dot(f)
//c₅ = 3. * f.dot(f)
let d = handle_start - self.start;
let e = handle_end - 2. * handle_start + self.start;
let f = self.end - 3. * handle_end + 3. * handle_start - self.start;
let q = point - self.start;
// coefficients of x(t) \cdot x'(t) let c0 = -3. * q.dot(d);
let c0 = d.dot(c); let c1 = 9. * d.dot(d) - 6. * q.dot(e);
let c1 = 2. * d.dot(b); let c2 = 27. * d.dot(e) - 3. * q.dot(f);
let c2 = c.dot(b) + 3. * d.dot(a); let c3 = 12. * d.dot(f) + 18. * e.dot(e);
let c3 = 2. * c.dot(a); let c4 = 15. * e.dot(f);
let c4 = b.dot(a); let c5 = 3. * f.dot(f);
crate::quartic_solver2::solve_quartic(c0, c1, c2, c3, c4) crate::quartic_solver2::solve_quintic(c0, c1, c2, c3, c4, c5)
.iter() .iter()
.copied() .copied()
.flatten() .flatten()