From e537a495c655ec1edd11d8e881abd667d6931400 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 21 Sep 2026 22:21:19 +0300 Subject: [PATCH] fix(constraints): point symmetry converges from any start Symmetric held the connecting direction perpendicular to the axis with the kernel's angle-based Perpendicular, which locks its turn side at construction: a point starting far from its mirror image had to swing the pair through the axis direction, the residual wrapped, and the solver stalled with the point unmoved. The perpendicularity is now the projection of the pair onto the axis direction being zero, a polynomial residual the solver follows from anywhere. Circle centers and ellipse centers use the same equation. tests/parametric_constraints_solve.rs symmetric_constraint_mirrors_one_circles_center_across_the_axis_line passes again. Co-Authored-By: Claude Fable 5.1 --- src/scene/parametric_solve.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/scene/parametric_solve.rs b/src/scene/parametric_solve.rs index 59a5d30ca..eb800105c 100644 --- a/src/scene/parametric_solve.rs +++ b/src/scene/parametric_solve.rs @@ -1466,13 +1466,23 @@ fn build_constraint( let Some(mirror) = whole_line(sys, cache, *m) else { return Vec::new(); }; + // The connecting direction is held perpendicular to the axis as a + // projection onto the axis direction being zero: a polynomial + // residual the solver can follow from any start. The angle form + // (`Perpendicular`) locks its turn side at construction and stalls + // when the pair has to swing through the axis direction — a point + // starting far from its mirror image never arrived. + let across_axis = |sys: &mut System, pa: GPoint, pb: GPoint| -> Rc { + let zero = sys.add_param(0.0, true); + Rc::new(ProjectedDistanceAlongLine::new(pa, pb, zero, mirror, false)) + }; if let (Some(pa), Some(pb)) = (point_ref(sys, cache, *a), point_ref(sys, cache, *b)) { let pair = GLine { p1: pa, p2: pb }; return vec![ Rc::new(MidpointOnLine::new(pair, mirror)), - Rc::new(PerpendicularConstraint::new(sys.store(), pair, mirror)), + across_axis(sys, pa, pb), ]; } let (Some(first), Some(second)) = ( @@ -1500,11 +1510,7 @@ fn build_constraint( }; vec![ Rc::new(MidpointOnLine::new(centers, mirror)), - Rc::new(PerpendicularConstraint::new( - sys.store(), - centers, - mirror, - )), + across_axis(sys, first.center, second.center), Rc::new(Equal::new(second.rad, first.rad, 1.0)), ] } @@ -1523,11 +1529,7 @@ fn build_constraint( }; vec![ Rc::new(MidpointOnLine::new(centers, mirror)), - Rc::new(PerpendicularConstraint::new( - sys.store(), - centers, - mirror, - )), + across_axis(sys, first.center, second.center), Rc::new(SymmetricLineDirections::new( sys.store(), first_axis,