fix(grips): compute grip positions in f64 so they sit on the wire

Grip markers drew visibly offset from the entity on large-coordinate
(UTM-scale) drawings: every Grippable impl cast the f64 entity coordinates
to f32 BEFORE the world-offset subtraction, and at 1e7 magnitude an f32
holds ~0.9 drawing units of rounding error. The wires subtract the offset
in f64 first (precision-preserving), so the grips landed up to ~1 unit
away from the geometry they belong to.

GripDef.world is now DVec3: producers fill it straight from the f64
entity data, refresh_selected_grips subtracts the world offset in f64,
and only the small offset-local result is cast to f32 for screen-space
projection (grips_to_screen / find_hit_grip) and drag state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-11 00:04:37 +03:00
commit 25715d1b03
33 changed files with 898 additions and 817 deletions

View file

@ -1,5 +1,4 @@
use acadrust::entities::Spline;
use glam::Vec3;
use truck_modeling::{
base::{BoundedCurve, ParametricCurve, Vector4},
builder, BSplineCurve, Curve, Edge, KnotVec, NurbsCurve, Point3, Wire,
@ -64,10 +63,7 @@ fn to_truck(spl: &Spline) -> TruckEntity {
} else {
&spl.control_points
};
let key_vertices: Vec<[f64; 3]> = snap_source
.iter()
.map(|p| [p.x, p.y, p.z])
.collect();
let key_vertices: Vec<[f64; 3]> = snap_source.iter().map(|p| [p.x, p.y, p.z]).collect();
let is_closed = spl.flags.closed || spl.flags.periodic;
let gap = {
@ -106,7 +102,7 @@ fn grips(spline: &Spline) -> Vec<GripDef> {
.control_points
.iter()
.enumerate()
.map(|(i, p)| square_grip(i, Vec3::new(p.x as f32, p.y as f32, p.z as f32)))
.map(|(i, p)| square_grip(i, glam::DVec3::new(p.x, p.y, p.z)))
.collect()
}
@ -168,23 +164,28 @@ impl crate::entities::traits::Grippable for Spline {
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
fn grip_menu(&self, _grip_id: usize) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Add Vertex", action: GripMenuAction::AddVertex },
GripMenuItem { label: "Remove Vertex", action: GripMenuAction::RemoveVertex },
GripMenuItem { label: "Refine Vertices", action: GripMenuAction::RefineVertices },
GripMenuItem {
label: "Stretch",
action: GripMenuAction::Stretch,
},
GripMenuItem {
label: "Add Vertex",
action: GripMenuAction::AddVertex,
},
GripMenuItem {
label: "Remove Vertex",
action: GripMenuAction::RemoveVertex,
},
GripMenuItem {
label: "Refine Vertices",
action: GripMenuAction::RefineVertices,
},
]
}
fn apply_grip_menu(
&mut self,
grip_id: usize,
action: crate::scene::object::GripMenuAction,
) {
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::object::GripMenuAction) {
use crate::scene::object::GripMenuAction as A;
let n = self.control_points.len();
let min_cv = (self.degree as usize).saturating_add(1).max(2);
@ -203,7 +204,8 @@ impl crate::entities::traits::Grippable for Spline {
);
self.control_points.insert(i1, mid);
if !self.weights.is_empty() && self.weights.len() == n {
let w = (self.weights[grip_id] + self.weights[i1.min(self.weights.len() - 1)]) * 0.5;
let w = (self.weights[grip_id] + self.weights[i1.min(self.weights.len() - 1)])
* 0.5;
self.weights.insert(i1, w);
}
// Clear knots so to_truck rebuilds a uniform knot vector
@ -255,10 +257,7 @@ impl crate::entities::traits::Grippable for Spline {
}
impl crate::entities::traits::PropertyEditable for Spline {
fn geometry_properties(
&self,
_text_style_names: &[String],
) -> PropSection {
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
properties(self)
}
fn apply_geom_prop(&mut self, field: &str, value: &str) {
@ -271,4 +270,3 @@ impl crate::entities::traits::Transformable for Spline {
apply_transform(self, t);
}
}