From 3da71698b1d1d44a1c3e2765498818ca1e811702 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 24 Jun 2026 03:32:11 +0300 Subject: [PATCH] wip(f64): convert command layer to DVec3 (in progress) Migrate the command/snap/grip/cursor chain from f32 Vec3 to f64 DVec3 so picked points, snaps and grip drags stay precise at UTM-scale coordinates (f32 quantizes to ~0.5 m there). Done so far: - command/mod.rs: EntityTransform, CmdResult points, CadCommand point methods, TangentObject already DVec3 (radius f64). - snap: SnapResult.world DVec3; world_to_screen/try_pt DVec3. - GripApply -> DVec3; grip find/state (GripEdit) f64; camera pick_on_plane_f64 / pick_on_target_plane_f64; scene paper_to_model_f64. - update.rs click + mouse-move + grip-drag pipelines route precise DVec3 to on_point/on_preview_wires/on_entity_pick; ortho/polar cast at the UcsXform boundary only. - cmd_result dispatch, transform helpers, dimension/arc/circle/multileader apply_grip, circle/arc/ellipse/shapes draw commands, array modify. Tree does NOT compile yet (~236 errors remain across modify/annotate/ insert command files + entity apply_grip impls); committed at user request to checkpoint progress. Co-Authored-By: Claude Opus 4.8 --- src/app/cmd_result.rs | 31 ++-- src/app/update.rs | 127 ++++++++-------- src/command/mod.rs | 66 ++++----- src/entities/arc.rs | 40 ++--- src/entities/circle.rs | 8 +- src/entities/dimension.rs | 41 +++--- src/entities/multileader.rs | 8 +- src/modules/draw/draw/arc.rs | 245 ++++++++++++++++--------------- src/modules/draw/draw/circle.rs | 164 +++++++++++---------- src/modules/draw/draw/ellipse.rs | 115 ++++++++------- src/modules/draw/draw/shapes.rs | 173 +++++++++++----------- src/modules/draw/modify/array.rs | 48 +++--- src/scene/mod.rs | 18 ++- src/scene/model/object.rs | 6 +- src/scene/pick/grip.rs | 18 +-- src/scene/view/camera.rs | 17 ++- src/scene/view/transform.rs | 22 +-- src/snap/mod.rs | 66 ++++----- 18 files changed, 626 insertions(+), 587 deletions(-) diff --git a/src/app/cmd_result.rs b/src/app/cmd_result.rs index e0c87fac2..e16e8acc6 100644 --- a/src/app/cmd_result.rs +++ b/src/app/cmd_result.rs @@ -210,7 +210,7 @@ impl OpenCADStudio { self.push_undo_snapshot(i, "BLOCK"); match self.tabs[i] .scene - .create_block_from_entities(&handles, &name, base) + .create_block_from_entities(&handles, &name, base.as_vec3()) { Ok(insert_handle) => { self.tabs[i].dirty = true; @@ -611,12 +611,13 @@ impl OpenCADStudio { // offset gap. Zero for a same-drawing paste. (#135) let src_wo = self.clipboard_world_offset; let tgt_wo = [0.0_f64; 3]; - let wo_corr = glam::Vec3::new( - (tgt_wo[0] - src_wo[0]) as f32, - (tgt_wo[1] - src_wo[1]) as f32, - (tgt_wo[2] - src_wo[2]) as f32, + let wo_corr = glam::DVec3::new( + tgt_wo[0] - src_wo[0], + tgt_wo[1] - src_wo[1], + tgt_wo[2] - src_wo[2], ); - let delta = base_pt - self.clipboard_centroid + wo_corr; + let delta = + base_pt - self.clipboard_centroid.as_dvec3() + wo_corr; let translate = crate::command::EntityTransform::Translate(delta); self.push_undo_snapshot(i, "PASTECLIP"); // Recreate any layer / linetype / style the copied entities @@ -783,7 +784,7 @@ impl OpenCADStudio { self.tabs[i].active_cmd = None; self.tabs[i].snap_result = None; self.tabs[i].scene.clear_preview_wire(); - self.tabs[i].scene.zoom_to_window(p1, p2); + self.tabs[i].scene.zoom_to_window(p1.as_vec3(), p2.as_vec3()); self.command_line.push_output("Zoom Window"); } CmdResult::Measurement(msg) => { @@ -818,7 +819,7 @@ impl OpenCADStudio { self.tabs[i].scene.transform_entities( &handles, &crate::command::EntityTransform::Scale { - center: glam::Vec3::ZERO, + center: glam::DVec3::ZERO, factor: scale, }, ); @@ -828,7 +829,7 @@ impl OpenCADStudio { self.tabs[i].scene.transform_entities( &handles, &crate::command::EntityTransform::Rotate { - center: glam::Vec3::ZERO, + center: glam::DVec3::ZERO, angle_rad, }, ); @@ -861,7 +862,7 @@ impl OpenCADStudio { .scene .document .get_entity(handle) - .and_then(|e| lengthen_entity(e, pick_pt, &mode)); + .and_then(|e| lengthen_entity(e, pick_pt.as_vec3(), &mode)); match result { Some(new_entity) => { let label = self.history_label_from_active_cmd(i, "LENGTHEN"); @@ -1001,7 +1002,7 @@ impl OpenCADStudio { .scene .document .get_entity(handle) - .and_then(|e| break_entity(e, p1, p2)); + .and_then(|e| break_entity(e, p1.as_vec3(), p2.as_vec3())); match replacement { Some(frags) => { let label = self.history_label_from_active_cmd(i, "BREAK"); @@ -1091,9 +1092,7 @@ impl OpenCADStudio { // Helper: is DXF point (x, y) inside the world-space window? // Drawing plane is world XY (= DXF XY). let in_win = |x: f64, y: f64| -> bool { - let wx = x as f32; - let wy = y as f32; - wx >= win_min.x && wx <= win_max.x && wy >= win_min.y && wy <= win_max.y + x >= win_min.x && x <= win_max.x && y >= win_min.y && y <= win_max.y }; let dx = delta.x as f64; @@ -1687,7 +1686,7 @@ impl OpenCADStudio { } => { self.tabs[i].active_cmd = None; self.tabs[i].snap_result = None; - self.open_mtext_editor(pos, handle, &initial, height); + self.open_mtext_editor(pos.as_vec3(), handle, &initial, height); } CmdResult::OpenTextEditor { pos, @@ -1698,7 +1697,7 @@ impl OpenCADStudio { self.tabs[i].active_cmd = None; self.tabs[i].snap_result = None; self.open_text_inline( - pos, + pos.as_vec3(), handle, &initial, height, diff --git a/src/app/update.rs b/src/app/update.rs index b18fdbe8c..0b6557401 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -1688,7 +1688,7 @@ impl OpenCADStudio { self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt)); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3())); if let Some(r) = result { let task = self.apply_cmd_result(r); self.refresh_active_cmd_preview(i); @@ -1730,7 +1730,7 @@ impl OpenCADStudio { self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(wcs_pt)); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(wcs_pt.as_dvec3())); if let Some(r) = result { let task = self.apply_cmd_result(r); // The rubber-band preview that the command @@ -2617,10 +2617,10 @@ impl OpenCADStudio { height: vh, }; let cam = self.tabs[i].scene.camera.borrow(); - let raw_paper = cam.pick_on_target_plane(p, bounds); + let raw_paper = cam.pick_on_target_plane_f64(p, bounds); let view_rot = cam.view_proj_rte(bounds); let eye = cam.eye_f64(); drop(cam); - let raw = self.tabs[i].scene.paper_to_model(raw_paper); + let raw = self.tabs[i].scene.paper_to_model_f64(raw_paper); // First move of this drag: hide the edited entity from the // base tessellation (one re-tess) so subsequent moves only @@ -2648,7 +2648,7 @@ impl OpenCADStudio { self.snapper.from_point = None; let (go, gr) = self.tabs[i].ucs_grid_basis(); let snap_hit = - self.snapper.snap(raw, p, &all_wires[..], view_rot, eye, bounds, go, gr); + self.snapper.snap(raw.as_vec3(), p, &all_wires[..], view_rot, eye, bounds, go, gr); let mut snapped = snap_hit.map(|s| s.world).unwrap_or(raw); self.tabs[i].snap_result = snap_hit; if let Some(s) = self.tabs[i].snap_result.as_mut() { @@ -2657,13 +2657,13 @@ impl OpenCADStudio { } if snap_hit.is_none() { - let base = grip.origin_world; + let base = grip.origin_world.as_vec3(); let ucs_xf = self.tabs[i].ucs_xform(); if self.ortho_mode { - snapped = ortho_constrain(snapped, base, &ucs_xf); + snapped = ortho_constrain(snapped.as_vec3(), base, &ucs_xf).as_dvec3(); } else if self.polar_mode { snapped = polar_constrain_near( - snapped, + snapped.as_vec3(), base, self.polar_increment_deg, view_rot, @@ -2671,7 +2671,8 @@ impl OpenCADStudio { bounds, self.snapper.osnap_radius_px, &ucs_xf, - ); + ) + .as_dvec3(); } } @@ -2679,9 +2680,9 @@ impl OpenCADStudio { // Only add world_offset when converting local → DXF space in model space. let wo_vec = if self.tabs[i].scene.current_layout == "Model" { let wo = [0.0_f64; 3]; - glam::Vec3::new(wo[0] as f32, wo[1] as f32, wo[2] as f32) + glam::DVec3::new(wo[0], wo[1], wo[2]) } else { - glam::Vec3::ZERO + glam::DVec3::ZERO }; let apply = if grip.is_translate { GripApply::Translate(snapped - grip.last_world) @@ -2765,23 +2766,19 @@ impl OpenCADStudio { height: vh, }; let cursor_paper = if let Some(ref ucs) = self.tabs[i].active_ucs { - let origin = glam::Vec3::new( - ucs.origin.x as f32, - ucs.origin.y as f32, - ucs.origin.z as f32, - ); + let origin = glam::DVec3::new(ucs.origin.x, ucs.origin.y, ucs.origin.z); let normal = ucs_z_axis(ucs); self.tabs[i] .scene .camera .borrow() - .pick_on_plane(p, bounds, normal, origin) + .pick_on_plane_f64(p, bounds, normal, origin) } else { self.tabs[i] .scene .camera .borrow() - .pick_on_target_plane(p, bounds) + .pick_on_target_plane_f64(p, bounds) }; let (view_rot, eye) = { let cam = self.tabs[i].scene.camera.borrow(); @@ -2792,7 +2789,7 @@ impl OpenCADStudio { crate::ui::overlay::compute_grid_step(view_rot, bounds); // In MSPACE, map paper-space cursor to model space so that // command previews and snapping work in the correct coordinate space. - let cursor_world = self.tabs[i].scene.paper_to_model(cursor_paper); + let cursor_world = self.tabs[i].scene.paper_to_model_f64(cursor_paper); let all_wires = self.tabs[i].scene.hit_test_wires(); let needs_entity = self.tabs[i] @@ -2819,7 +2816,7 @@ impl OpenCADStudio { None } else if needs_tan { self.snapper.snap_tangent_only( - cursor_world, + cursor_world.as_vec3(), p, &all_wires[..], view_rot, @@ -2830,14 +2827,14 @@ impl OpenCADStudio { let (go, gr) = self.tabs[i].ucs_grid_basis(); self.snapper.from_point = self.last_point; self.snapper - .snap(cursor_world, p, &all_wires[..], view_rot, eye, bounds, go, gr) + .snap(cursor_world.as_vec3(), p, &all_wires[..], view_rot, eye, bounds, go, gr) }; // Object Snap Tracking: update dwell, then align the cursor // to a tracking ray (and store the alignment so a typed // distance can place a point along it — issue #69). let otrack_hit = { - let snap_world = self.tabs[i].snap_result.map(|s| s.world); + let snap_world = self.tabs[i].snap_result.map(|s| s.world.as_vec3()); self.snapper.update_otrack_dwell( snap_world, view_rot, @@ -2853,7 +2850,7 @@ impl OpenCADStudio { }; let ucs = self.tabs[i].scene.viewcube_ucs_mat(); self.snapper.otrack_snap( - cursor_world, + cursor_world.as_vec3(), view_rot, eye, bounds, @@ -2868,16 +2865,16 @@ impl OpenCADStudio { self.otrack_active = otrack_hit.map(|h| (h.base, h.dir)); let effective = { - let mut pt = if let Some(h) = otrack_hit { + let mut pt: glam::DVec3 = if let Some(h) = otrack_hit { // Tracking alignment wins over the free cursor; // ortho/polar don't re-constrain it. - h.aligned + h.aligned.as_dvec3() } else { // snap.world is paper-space for viewport-projected // wires; convert to model-space for previews. let mut pt = self.tabs[i] .snap_result - .map(|s| self.tabs[i].scene.paper_to_model(s.world)) + .map(|s| self.tabs[i].scene.paper_to_model_f64(s.world)) .unwrap_or(cursor_world); // Object snap wins over ortho/polar: a snapped point // is taken as-is so it isn't pulled onto the ortho @@ -2890,10 +2887,11 @@ impl OpenCADStudio { if let Some(base) = self.last_point { let ucs_xf = self.tabs[i].ucs_xform(); if self.ortho_mode { - pt = ortho_constrain(pt, base, &ucs_xf); + pt = ortho_constrain(pt.as_vec3(), base, &ucs_xf) + .as_dvec3(); } else if self.polar_mode { pt = polar_constrain_near( - pt, + pt.as_vec3(), base, self.polar_increment_deg, view_rot, @@ -2901,7 +2899,8 @@ impl OpenCADStudio { bounds, self.snapper.osnap_radius_px, &ucs_xf, - ); + ) + .as_dvec3(); } } } @@ -2914,7 +2913,7 @@ impl OpenCADStudio { } pt }; - self.tabs[i].last_cursor_world = effective; + self.tabs[i].last_cursor_world = effective.as_vec3(); self.tabs[i].last_cursor_screen = p_full; // Project the step anchor (an explicit `dyn_anchor` or the // last point) so the dynamic-input overlay can place its @@ -2941,7 +2940,7 @@ impl OpenCADStudio { } else { [0.0; 3] }; - let wo_f = glam::Vec3::new(wo_local[0] as f32, wo_local[1] as f32, wo_local[2] as f32); + let wo_f = glam::DVec3::new(wo_local[0], wo_local[1], wo_local[2]); let effective_wcs = effective + wo_f; // Orange object snap (plugin commands implement resolve_object_pick). @@ -2955,8 +2954,8 @@ impl OpenCADStudio { ) }); if let Some(pick) = pick { - let world = glam::Vec3::new(pick.x as f32, pick.y as f32, effective.z); - let ndc = view_rot.project_point3((world.as_dvec3() - eye).as_vec3()); + let world = glam::DVec3::new(pick.x, pick.y, effective.z); + let ndc = view_rot.project_point3((world - eye).as_vec3()); let screen = iced::Point::new( (ndc.x + 1.0) * 0.5 * bounds.width, (1.0 - ndc.y) * 0.5 * bounds.height, @@ -3008,12 +3007,12 @@ impl OpenCADStudio { // on_hover_entity returns WCS wires; shift to the // offset-relative frame so the preview lands on the // geometry on large-coordinate drawings. - if wo_f != glam::Vec3::ZERO { + if wo_f != glam::DVec3::ZERO { for w in p.iter_mut() { for pt in w.points.iter_mut() { - pt[0] -= wo_f.x; - pt[1] -= wo_f.y; - pt[2] -= wo_f.z; + pt[0] -= wo_f.x as f32; + pt[1] -= wo_f.y as f32; + pt[2] -= wo_f.z as f32; } } } @@ -3042,6 +3041,7 @@ impl OpenCADStudio { // the snapped angle direction, extending across the drawing. if self.polar_mode && !needs_entity { if let Some(base) = self.last_point { + let effective = effective.as_vec3(); let dx = effective.x - base.x; let dy = effective.y - base.y; // Only show the guide while POLAR is actually engaged @@ -3428,26 +3428,22 @@ impl OpenCADStudio { // Project screen point onto the active UCS XY plane (or world XY when // no UCS is active). let raw_paper = if let Some(ref ucs) = self.tabs[i].active_ucs { - let origin = glam::Vec3::new( - ucs.origin.x as f32, - ucs.origin.y as f32, - ucs.origin.z as f32, - ); + let origin = glam::DVec3::new(ucs.origin.x, ucs.origin.y, ucs.origin.z); let normal = ucs_z_axis(ucs); self.tabs[i] .scene .camera .borrow() - .pick_on_plane(p, bounds, normal, origin) + .pick_on_plane_f64(p, bounds, normal, origin) } else { self.tabs[i] .scene .camera .borrow() - .pick_on_target_plane(p, bounds) + .pick_on_target_plane_f64(p, bounds) }; // Convert paper-space → model-space when inside a viewport. - let raw = self.tabs[i].scene.paper_to_model(raw_paper); + let raw = self.tabs[i].scene.paper_to_model_f64(raw_paper); let (view_rot, eye) = { let c = self.tabs[i].scene.camera.borrow(); (c.view_proj_rte(bounds), c.eye_f64()) }; let all_wires = self.tabs[i].scene.hit_test_wires(); let needs_tan = self.tabs[i] @@ -3464,16 +3460,16 @@ impl OpenCADStudio { None } else if needs_tan { self.snapper - .snap_tangent_only(raw, p, &all_wires[..], view_rot, eye, bounds) + .snap_tangent_only(raw.as_vec3(), p, &all_wires[..], view_rot, eye, bounds) } else { let (go, gr) = self.tabs[i].ucs_grid_basis(); self.snapper.from_point = self.last_point; - self.snapper.snap(raw, p, &all_wires[..], view_rot, eye, bounds, go, gr) + self.snapper.snap(raw.as_vec3(), p, &all_wires[..], view_rot, eye, bounds, go, gr) }; // snap.world is in paper-space (projected wire coords in MSPACE); // convert to model-space so commands receive consistent coordinates. let mut pt = snap_hit - .map(|s| self.tabs[i].scene.paper_to_model(s.world)) + .map(|s| self.tabs[i].scene.paper_to_model_f64(s.world)) .unwrap_or(raw); // When no UCS is active clamp to world XY; with a UCS the point is // already constrained to that plane by the ray–plane intersection. @@ -3490,12 +3486,12 @@ impl OpenCADStudio { }; let ucs = self.tabs[i].scene.viewcube_ucs_mat(); self.snapper - .otrack_snap(raw, view_rot, eye, bounds, step, self.last_point, ucs) + .otrack_snap(raw.as_vec3(), view_rot, eye, bounds, step, self.last_point, ucs) } else { None }; if let Some(h) = otrack { - pt = h.aligned; + pt = h.aligned.as_dvec3(); if self.tabs[i].active_ucs.is_none() { pt.z = 0.0; } @@ -3507,10 +3503,10 @@ impl OpenCADStudio { if let Some(base) = self.last_point { let ucs_xf = self.tabs[i].ucs_xform(); if self.ortho_mode { - pt = ortho_constrain(pt, base, &ucs_xf); + pt = ortho_constrain(pt.as_vec3(), base, &ucs_xf).as_dvec3(); } else if self.polar_mode { pt = polar_constrain_near( - pt, + pt.as_vec3(), base, self.polar_increment_deg, view_rot, @@ -3518,7 +3514,8 @@ impl OpenCADStudio { bounds, self.snapper.osnap_radius_px, &ucs_xf, - ); + ) + .as_dvec3(); } } } @@ -3538,7 +3535,7 @@ impl OpenCADStudio { } else { [0.0; 3] }; - world_pt + glam::Vec3::new(wo[0] as f32, wo[1] as f32, wo[2] as f32) + world_pt + glam::DVec3::new(wo[0], wo[1], wo[2]) }; let result = if self.tabs[i] @@ -3555,7 +3552,7 @@ impl OpenCADStudio { ) }); if let Some(pick) = pick { - let center = glam::Vec3::new(pick.x as f32, pick.y as f32, pick_wcs.z); + let center = glam::DVec3::new(pick.x, pick.y, pick_wcs.z); let result = self.tabs[i] .active_cmd .as_mut() @@ -3701,7 +3698,7 @@ impl OpenCADStudio { self.tabs[i].dyn_active = 0; } } - self.last_point = Some(world_pt); + self.last_point = Some(world_pt.as_vec3()); self.dyn_user_reshaped = false; self.sync_dyn_fields(); self.reset_tracking_after_point(); @@ -4510,8 +4507,8 @@ impl OpenCADStudio { handle: popup.handle, grip_id, is_translate, - origin_world: g.world.as_vec3(), - last_world: g.world.as_vec3(), + origin_world: g.world, + last_world: g.world, }); } return Task::none(); @@ -4572,8 +4569,8 @@ impl OpenCADStudio { handle: popup.handle, grip_id: new_gid, is_translate: false, - origin_world: g.world.as_vec3(), - last_world: g.world.as_vec3(), + origin_world: g.world, + last_world: g.world, }); self.grip_add_provisional = Some((popup.handle, new_gid)); } @@ -8215,9 +8212,9 @@ impl OpenCADStudio { self.tabs[i].dyn_guide = spec.guide; self.tabs[i].dyn_anchor = match spec.anchor { crate::command::DynAnchor::LastPoint => self.last_point, - crate::command::DynAnchor::Point(p) => Some(p), + crate::command::DynAnchor::Point(p) => Some(p.as_vec3()), }; - self.tabs[i].dyn_ref = spec.ref_point; + self.tabs[i].dyn_ref = spec.ref_point.map(|v| v.as_vec3()); } /// Track cursor dwell over a selected entity's grip. Sets @@ -8368,7 +8365,7 @@ impl OpenCADStudio { let previews = self.tabs[i] .active_cmd .as_mut() - .map(|c| c.on_preview_wires(cur)) + .map(|c| c.on_preview_wires(cur.as_dvec3())) .unwrap_or_default(); self.tabs[i].scene.set_preview_wires(previews); } @@ -8647,7 +8644,7 @@ impl OpenCADStudio { self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt)); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3())); let task = result.map(|r| self.apply_cmd_result(r))?; self.refresh_active_cmd_preview(i); return Some(task); @@ -8706,7 +8703,7 @@ impl OpenCADStudio { self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt)); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3())); for f in self.tabs[i].dyn_fields.iter_mut() { f.buffer = None; } diff --git a/src/command/mod.rs b/src/command/mod.rs index 46f74264c..b2bc52694 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -9,7 +9,7 @@ use crate::scene::model::hatch_model::HatchModel; use crate::scene::model::wire_model::WireModel; use crate::scene::Scene; use acadrust::{EntityType, Handle}; -use glam::Vec3; +use glam::DVec3; /// Domain object resolved under the cursor for ObjectPick snapping. #[derive(Clone, Copy, Debug)] @@ -26,13 +26,13 @@ pub struct ObjectPickHit { #[derive(Clone)] pub enum EntityTransform { /// Move every point by the given world-space delta (world XY plane). - Translate(Vec3), + Translate(DVec3), /// Rotate around `center` by `angle_rad` in the world XY plane. - Rotate { center: Vec3, angle_rad: f32 }, + Rotate { center: DVec3, angle_rad: f32 }, /// Uniform scale from `center` by `factor`. - Scale { center: Vec3, factor: f32 }, + Scale { center: DVec3, factor: f32 }, /// Mirror across the line through `p1`→`p2` in the world XY plane. - Mirror { p1: Vec3, p2: Vec3 }, + Mirror { p1: DVec3, p2: DVec3 }, } // ── Tangent object ───────────────────────────────────────────────────────── @@ -41,9 +41,9 @@ pub enum EntityTransform { #[derive(Clone, Copy, Debug)] pub enum TangentObject { /// Infinite line through two world-space XZ-plane points. - Line { p1: Vec3, p2: Vec3 }, + Line { p1: DVec3, p2: DVec3 }, /// Circle in the world XY plane. - Circle { center: Vec3, radius: f32 }, + Circle { center: DVec3, radius: f64 }, } // ── Result token ────────────────────────────────────────────────────────── @@ -82,7 +82,7 @@ pub enum CmdResult { CreateBlock { handles: Vec, name: String, - base: Vec3, + base: DVec3, }, /// Apply a transform to selected entities and end the command. TransformSelected(Vec, EntityTransform), @@ -117,13 +117,13 @@ pub enum CmdResult { thaw: Vec, }, /// Paste clipboard entities translated so their centroid lands at `base_pt`; end command. - PasteClipboard { base_pt: Vec3 }, + PasteClipboard { base_pt: DVec3 }, /// Zoom the model-space camera to fit the given corner points; end command. - ZoomToWindow { p1: Vec3, p2: Vec3 }, + ZoomToWindow { p1: DVec3, p2: DVec3 }, /// Print a measurement result to the command line and end the command. Measurement(String), /// Break `handle` at points `p1` and `p2`; replace with computed fragments. - BreakEntity { handle: Handle, p1: Vec3, p2: Vec3 }, + BreakEntity { handle: Handle, p1: DVec3, p2: DVec3 }, /// Attempt to join the given entities into fewer merged entities. JoinEntities(Vec), /// Apply a polyline-edit operation to one entity; keep command active. @@ -138,19 +138,19 @@ pub enum CmdResult { /// Extend/trim a Line or Arc by the given mode; end command. LengthenEntity { handle: Handle, - pick_pt: Vec3, + pick_pt: DVec3, mode: crate::modules::draw::modify::lengthen::LenMode, }, /// Align selected entities: translate to dst1, rotate by angle_rad, optional scale. AlignSelected { handles: Vec, - src1: Vec3, - dst1: Vec3, + src1: DVec3, + dst1: DVec3, angle_rad: f32, scale: f32, }, /// Set the plot window on the active layout's PlotSettings. - SetPlotWindow { p1: Vec3, p2: Vec3 }, + SetPlotWindow { p1: DVec3, p2: DVec3 }, /// Replace the text content of a Text/MText entity in-place. DdeditEntity { handle: Handle, new_text: String }, /// Open the in-place editor (plain box or rich MText editor, per type) for @@ -160,7 +160,7 @@ pub enum CmdResult { /// area with live viewport preview). `handle` is `Some` when editing an /// existing MText, `None` when creating a new one at `pos`. OpenMTextEditor { - pos: Vec3, + pos: DVec3, handle: Option, initial: String, height: f64, @@ -169,7 +169,7 @@ pub enum CmdResult { /// formatting toolbar). `handle` is `Some` when editing an existing Text, /// `None` when creating a new one at `pos`. OpenTextEditor { - pos: Vec3, + pos: DVec3, handle: Option, initial: String, height: f64, @@ -185,11 +185,11 @@ pub enum CmdResult { StretchEntities { handles: Vec, /// Min corner of the crossing window in world XZ (= DXF XY). - win_min: Vec3, + win_min: DVec3, /// Max corner of the crossing window in world XZ (= DXF XY). - win_max: Vec3, + win_max: DVec3, /// Translation vector to apply to vertices inside the window. - delta: Vec3, + delta: DVec3, }, /// Create a Solid3D placeholder entity + associated MeshModel. /// `mesh_fn` is called with the entity's handle string to build the mesh. @@ -205,8 +205,8 @@ pub enum CmdResult { /// Revolve the profile entity `handle` around the given axis by `angle_deg`. RevolveEntity { handle: Handle, - axis_start: glam::Vec3, - axis_end: glam::Vec3, + axis_start: glam::DVec3, + axis_end: glam::DVec3, angle_deg: f32, color: [f32; 4], }, @@ -377,7 +377,7 @@ pub enum DynAnchor { #[allow(dead_code)] LastPoint, /// An explicit world point. - Point(Vec3), + Point(DVec3), } /// One value box in a [`DynSpec`]. @@ -403,7 +403,7 @@ pub struct DynSpec { pub guide: DynGuide, /// Far end of a reference line through `anchor` (only used by /// [`DynGuide::Perp`]); `None` otherwise. - pub ref_point: Option, + pub ref_point: Option, } // ── Trait ───────────────────────────────────────────────────────────────── @@ -424,7 +424,7 @@ pub trait CadCommand: Send { fn set_ucs(&mut self, _ucs: glam::Mat4) {} /// Called when the user left-clicks in the viewport (point pick). - fn on_point(&mut self, pt: Vec3) -> CmdResult; + fn on_point(&mut self, pt: DVec3) -> CmdResult; /// Called when the user presses Enter (finalize / next option). fn on_enter(&mut self) -> CmdResult; @@ -447,7 +447,7 @@ pub trait CadCommand: Send { /// Called when the user clicks and `needs_entity_pick()` is true. /// `handle` is the nearest wire's entity handle (Handle::NULL if nothing found). - fn on_entity_pick(&mut self, _handle: Handle, _pt: Vec3) -> CmdResult { + fn on_entity_pick(&mut self, _handle: Handle, _pt: DVec3) -> CmdResult { CmdResult::Cancel } @@ -467,7 +467,7 @@ pub trait CadCommand: Send { } /// Preview wires while hovering during object-point pick. - fn object_pick_hover_previews(&self, _scene: &Scene, _cursor: Vec3) -> Vec { + fn object_pick_hover_previews(&self, _scene: &Scene, _cursor: DVec3) -> Vec { vec![] } @@ -477,7 +477,7 @@ pub trait CadCommand: Send { } /// Called when `needs_structure_point_pick()` is true and a structure is found near the click. - fn on_structure_pick(&mut self, _handle: Handle, _pt: Vec3) -> CmdResult { + fn on_structure_pick(&mut self, _handle: Handle, _pt: DVec3) -> CmdResult { CmdResult::Cancel } @@ -502,19 +502,19 @@ pub trait CadCommand: Send { /// Called on every mouse-move when `needs_entity_pick()` is true. /// Return preview wires showing the operation result under the cursor. /// Default: empty (no preview). - fn on_hover_entity(&mut self, _handle: Handle, _pt: Vec3) -> Vec { + fn on_hover_entity(&mut self, _handle: Handle, _pt: DVec3) -> Vec { vec![] } /// Called on every mouse-move in the viewport. /// Return `Some(WireModel)` to update the rubber-band preview, `None` to skip. - fn on_mouse_move(&mut self, _pt: Vec3) -> Option { + fn on_mouse_move(&mut self, _pt: DVec3) -> Option { None } /// Called on every mouse-move; return all preview wires to show (object ghosts + rubber-band). /// Default: forwards to `on_mouse_move` for backwards compatibility. - fn on_preview_wires(&mut self, pt: Vec3) -> Vec { + fn on_preview_wires(&mut self, pt: DVec3) -> Vec { self.on_mouse_move(pt).into_iter().collect() } @@ -599,7 +599,7 @@ pub trait CadCommand: Send { /// Called instead of `on_point` when the command needs a tangent pick /// and the snap system found a tangent object. - fn on_tangent_point(&mut self, obj: TangentObject, hit: Vec3) -> CmdResult { + fn on_tangent_point(&mut self, obj: TangentObject, hit: DVec3) -> CmdResult { let _ = obj; self.on_point(hit) } @@ -647,7 +647,7 @@ pub trait CadCommand: Send { /// Returns `None` when the value can only be typed (a count, or a /// distance with no reference yet). The string the host commits is this /// value formatted; the command's own `on_text_input` parses it back. - fn dyn_live_value(&self, _cursor: Vec3) -> Option { + fn dyn_live_value(&self, _cursor: DVec3) -> Option { None } } diff --git a/src/entities/arc.rs b/src/entities/arc.rs index b9aea0c15..25a211f01 100644 --- a/src/entities/arc.rs +++ b/src/entities/arc.rs @@ -157,37 +157,37 @@ fn apply_geom_prop(arc: &mut Arc, field: &str, value: &str) { fn apply_grip(arc: &mut Arc, grip_id: usize, apply: GripApply) { match (grip_id, apply) { (0, GripApply::Translate(d)) => { - arc.center.x += d.x as f64; - arc.center.y += d.y as f64; - arc.center.z += d.z as f64; + arc.center.x += d.x; + arc.center.y += d.y; + arc.center.z += d.z; } (0, GripApply::Absolute(p)) => { - arc.center.x = p.x as f64; - arc.center.y = p.y as f64; - arc.center.z = p.z as f64; + arc.center.x = p.x; + arc.center.y = p.y; + arc.center.z = p.z; } (1, GripApply::Absolute(p)) => { - let dx = p.x - arc.center.x as f32; - let dy = p.y - arc.center.y as f32; - arc.start_angle = (dy as f64).atan2(dx as f64); + let dx = p.x - arc.center.x; + let dy = p.y - arc.center.y; + arc.start_angle = dy.atan2(dx); } (2, GripApply::Absolute(p)) => { - let dx = p.x - arc.center.x as f32; - let dy = p.y - arc.center.y as f32; - arc.end_angle = (dy as f64).atan2(dx as f64); + let dx = p.x - arc.center.x; + let dy = p.y - arc.center.y; + arc.end_angle = dy.atan2(dx); } (3, GripApply::Translate(d)) => { - let sa = arc.start_angle as f32; - let ea = arc.end_angle as f32; - let span = angle_span(sa, ea); + let sa = arc.start_angle; + let ea = arc.end_angle; + let span = angle_span(sa as f32, ea as f32) as f64; let mid_a = sa + span * 0.5; - let current_mid_x = arc.center.x as f32 + arc.radius as f32 * mid_a.cos(); - let current_mid_y = arc.center.y as f32 + arc.radius as f32 * mid_a.sin(); + let current_mid_x = arc.center.x + arc.radius * mid_a.cos(); + let current_mid_y = arc.center.y + arc.radius * mid_a.sin(); let new_mid_x = current_mid_x + d.x; let new_mid_y = current_mid_y + d.y; - let dx = new_mid_x - arc.center.x as f32; - let dy = new_mid_y - arc.center.y as f32; - let new_r = (dx * dx + dy * dy).sqrt() as f64; + let dx = new_mid_x - arc.center.x; + let dy = new_mid_y - arc.center.y; + let new_r = (dx * dx + dy * dy).sqrt(); if new_r > 1e-6 { arc.radius = new_r; } diff --git a/src/entities/circle.rs b/src/entities/circle.rs index be0928846..beb3f9241 100644 --- a/src/entities/circle.rs +++ b/src/entities/circle.rs @@ -166,11 +166,9 @@ fn apply_grip(circle: &mut Circle, grip_id: usize, apply: GripApply) { circle.center.z += d.z as f64; } (1..=4, GripApply::Absolute(p)) => { - let cx = circle.center.x as f32; - let cy = circle.center.y as f32; - let dx = p.x - cx; - let dy = p.y - cy; - circle.radius = ((dx * dx + dy * dy) as f64).sqrt(); + let dx = p.x - circle.center.x; + let dy = p.y - circle.center.y; + circle.radius = (dx * dx + dy * dy).sqrt(); } _ => {} } diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index e88295f9d..c49078e73 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -3,7 +3,7 @@ use acadrust::entities::{ DimensionDiameter, DimensionLinear, DimensionOrdinate, DimensionRadius, }; use acadrust::Entity; -use glam::Vec3; +use glam::{DVec3, Vec3}; use crate::command::EntityTransform; use crate::entities::common::{ @@ -540,21 +540,22 @@ where } } -fn rotate_point(p: &mut acadrust::types::Vector3, center: Vec3, angle_rad: f32) { - let dx = p.x as f32 - center.x; - let dy = p.y as f32 - center.y; - let (s, c) = angle_rad.sin_cos(); - p.x = (center.x + dx * c - dy * s) as f64; - p.y = (center.y + dx * s + dy * c) as f64; +fn rotate_point(p: &mut acadrust::types::Vector3, center: DVec3, angle_rad: f32) { + let dx = p.x - center.x; + let dy = p.y - center.y; + let (s, c) = (angle_rad as f64).sin_cos(); + p.x = center.x + dx * c - dy * s; + p.y = center.y + dx * s + dy * c; } -fn scale_point(p: &mut acadrust::types::Vector3, center: Vec3, factor: f32) { - p.x = (center.x + (p.x as f32 - center.x) * factor) as f64; - p.y = (center.y + (p.y as f32 - center.y) * factor) as f64; - p.z = (center.z + (p.z as f32 - center.z) * factor) as f64; +fn scale_point(p: &mut acadrust::types::Vector3, center: DVec3, factor: f32) { + let f = factor as f64; + p.x = center.x + (p.x - center.x) * f; + p.y = center.y + (p.y - center.y) * f; + p.z = center.z + (p.z - center.z) * f; } -fn mirror_point(p: &mut acadrust::types::Vector3, p1: Vec3, p2: Vec3) { +fn mirror_point(p: &mut acadrust::types::Vector3, p1: DVec3, p2: DVec3) { crate::scene::view::transform::reflect_xy_point(&mut p.x, &mut p.y, p1, p2); } @@ -582,16 +583,16 @@ fn dv3(v: &acadrust::types::Vector3) -> glam::DVec3 { glam::DVec3::new(v.x, v.y, v.z) } -fn set_v3(target: &mut acadrust::types::Vector3, p: Vec3) { - target.x = p.x as f64; - target.y = p.y as f64; - target.z = p.z as f64; +fn set_v3(target: &mut acadrust::types::Vector3, p: DVec3) { + target.x = p.x; + target.y = p.y; + target.z = p.z; } -fn translate_v3(target: &mut acadrust::types::Vector3, d: Vec3) { - target.x += d.x as f64; - target.y += d.y as f64; - target.z += d.z as f64; +fn translate_v3(target: &mut acadrust::types::Vector3, d: DVec3) { + target.x += d.x; + target.y += d.y; + target.z += d.z; } fn apply_to_v3(target: &mut acadrust::types::Vector3, apply: &GripApply) { diff --git a/src/entities/multileader.rs b/src/entities/multileader.rs index c2a8222e8..5086db01f 100644 --- a/src/entities/multileader.rs +++ b/src/entities/multileader.rs @@ -24,7 +24,7 @@ pub(crate) fn mleader_v_anchor(attach: TextAttachmentType) -> MTextVAnchor { | TextAttachmentType::BottomOfTopLineUnderlineAll => MTextVAnchor::BottomOfTopLine, } } -use glam::Vec3; +use glam::{DVec3, Vec3}; use crate::command::EntityTransform; use crate::entities::common::{ @@ -920,9 +920,9 @@ fn apply_transform(ml: &mut MultiLeader, t: &EntityTransform) { /// Reflect a direction (not position) vector across the mirror line p1→p2. /// Reflecting a direction is the same as reflecting `p1 + dir` then subtracting /// the reflection of `p1`, which simplifies to reflecting around the origin. -fn reflect_xy_direction(dx: &mut f64, dy: &mut f64, p1: Vec3, p2: Vec3) { - let zero = Vec3::ZERO; - let p2_rel = Vec3::new(p2.x - p1.x, p2.y - p1.y, 0.0); +fn reflect_xy_direction(dx: &mut f64, dy: &mut f64, p1: DVec3, p2: DVec3) { + let zero = DVec3::ZERO; + let p2_rel = DVec3::new(p2.x - p1.x, p2.y - p1.y, 0.0); let mut tip_x = *dx; let mut tip_y = *dy; crate::scene::view::transform::reflect_xy_point(&mut tip_x, &mut tip_y, zero, p2_rel); diff --git a/src/modules/draw/draw/arc.rs b/src/modules/draw/draw/arc.rs index 79b1b4ba2..b48cd464e 100644 --- a/src/modules/draw/draw/arc.rs +++ b/src/modules/draw/draw/arc.rs @@ -18,9 +18,9 @@ use acadrust::{Arc as CadArc, EntityType}; use crate::command::{CadCommand, CmdResult}; use crate::modules::IconKind; use crate::scene::model::wire_model::WireModel; -use glam::Vec3; +use glam::DVec3; -const TAU: f32 = std::f32::consts::TAU; +const TAU: f64 = std::f64::consts::TAU; // ── Per-method SVG icons ─────────────────────────────────────────────────── @@ -67,12 +67,12 @@ pub const ICON: IconKind = ICON_CSE; // ── Shared math helpers ──────────────────────────────────────────────────── /// Angle in radians from `center` to `pt`. -fn angle_xy(center: Vec3, pt: Vec3) -> f32 { +fn angle_xy(center: DVec3, pt: DVec3) -> f64 { (pt.y - center.y).atan2(pt.x - center.x) } /// Build a CCW arc polyline from `start_angle` to `end_angle` (radians). -fn arc_preview(center: Vec3, radius: f32, start_angle: f32, end_angle: f32) -> WireModel { +fn arc_preview(center: DVec3, radius: f64, start_angle: f64, end_angle: f64) -> WireModel { let mut ea = end_angle; while ea < start_angle { ea += TAU; @@ -81,23 +81,23 @@ fn arc_preview(center: Vec3, radius: f32, start_angle: f32, end_angle: f32) -> W let segs = ((span / TAU) * 64.0).ceil().max(4.0) as u32; let pts: Vec<[f32; 3]> = (0..=segs) .map(|i| { - let a = start_angle + span * (i as f32 / segs as f32); + let a = start_angle + span * (i as f64 / segs as f64); [ - center.x + radius * a.cos(), - center.y + radius * a.sin(), - center.z, + (center.x + radius * a.cos()) as f32, + (center.y + radius * a.sin()) as f32, + center.z as f32, ] }) .collect(); WireModel::solid("rubber_band".into(), pts, WireModel::CYAN, false) } -fn make_arc(center: Vec3, radius: f32, start_angle: f32, end_angle: f32) -> EntityType { +fn make_arc(center: DVec3, radius: f64, start_angle: f64, end_angle: f64) -> EntityType { EntityType::Arc(CadArc { - center: Vector3::new(center.x as f64, center.y as f64, center.z as f64), - radius: radius as f64, - start_angle: start_angle as f64, - end_angle: end_angle as f64, + center: Vector3::new(center.x, center.y, center.z), + radius, + start_angle, + end_angle, ..Default::default() }) } @@ -105,7 +105,7 @@ fn make_arc(center: Vec3, radius: f32, start_angle: f32, end_angle: f32) -> Enti /// Signed rotation from `prev` to `curr` around `center`. /// Positive = CCW, negative = CW. /// Signed angle (radians) swept from `prev` to `curr` about `center`. -fn rot_delta(center: Vec3, prev: Vec3, curr: Vec3) -> f32 { +fn rot_delta(center: DVec3, prev: DVec3, curr: DVec3) -> f64 { let p = prev - center; let c = curr - center; (p.x * c.y - p.y * c.x).atan2(p.x * c.x + p.y * c.y) @@ -113,19 +113,22 @@ fn rot_delta(center: Vec3, prev: Vec3, curr: Vec3) -> f32 { /// Minimum swept angle before the previewed arc may flip CW/CCW. Filters the /// per-frame cursor jitter that otherwise reverses the sweep on tiny moves. -const DIR_TOL: f32 = 0.1745; // ~10° +const DIR_TOL: f64 = 0.1745; // ~10° -fn line_wire(a: Vec3, b: Vec3) -> WireModel { +fn line_wire(a: DVec3, b: DVec3) -> WireModel { WireModel::solid( "rubber_band".into(), - vec![[a.x, a.y, a.z], [b.x, b.y, b.z]], + vec![ + [a.x as f32, a.y as f32, a.z as f32], + [b.x as f32, b.y as f32, b.z as f32], + ], WireModel::CYAN, false, ) } /// Circumscribed circle through three points. -fn circumcircle(a: Vec3, b: Vec3, c: Vec3) -> Option<(Vec3, f32)> { +fn circumcircle(a: DVec3, b: DVec3, c: DVec3) -> Option<(DVec3, f64)> { let d = 2.0 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)); if d.abs() < 1e-9 { return None; @@ -138,12 +141,12 @@ fn circumcircle(a: Vec3, b: Vec3, c: Vec3) -> Option<(Vec3, f32)> { + (b.x * b.x + b.y * b.y) * (a.x - c.x) + (c.x * c.x + c.y * c.y) * (b.x - a.x)) / d; - let center = Vec3::new(ux, uy, a.z); + let center = DVec3::new(ux, uy, a.z); Some((center, center.distance(a))) } /// True if `angle` lies inside the CCW arc [start..end] (radians). -fn ccw_contains(angle: f32, start: f32, end: f32) -> bool { +fn ccw_contains(angle: f64, start: f64, end: f64) -> bool { let a = angle.rem_euclid(TAU); let s = start.rem_euclid(TAU); let e = end.rem_euclid(TAU); @@ -155,14 +158,14 @@ fn ccw_contains(angle: f32, start: f32, end: f32) -> bool { } /// Arc center+radius from two endpoints and a cursor (sagitta / bow-toward-cursor). -fn arc_from_sagitta(s: Vec3, e: Vec3, cursor: Vec3) -> Option<(Vec3, f32)> { +fn arc_from_sagitta(s: DVec3, e: DVec3, cursor: DVec3) -> Option<(DVec3, f64)> { let chord_vec = e - s; let chord_len = (chord_vec.x * chord_vec.x + chord_vec.y * chord_vec.y).sqrt(); if chord_len < 1e-6 { return None; } - let unit_chord = Vec3::new(chord_vec.x / chord_len, chord_vec.y / chord_len, 0.0); - let perp = Vec3::new(-unit_chord.y, unit_chord.x, 0.0); + let unit_chord = DVec3::new(chord_vec.x / chord_len, chord_vec.y / chord_len, 0.0); + let perp = DVec3::new(-unit_chord.y, unit_chord.x, 0.0); let mid = (s + e) * 0.5; let h = (cursor - mid).dot(perp); // signed sagitta if h.abs() < 1e-3 { @@ -176,14 +179,14 @@ fn arc_from_sagitta(s: Vec3, e: Vec3, cursor: Vec3) -> Option<(Vec3, f32)> { } /// Arc center+radius from start, end, and a radius-magnitude point (dist = dist(pt, start)). -fn arc_from_se_radius(s: Vec3, e: Vec3, radius_pt: Vec3) -> Option<(Vec3, f32)> { +fn arc_from_se_radius(s: DVec3, e: DVec3, radius_pt: DVec3) -> Option<(DVec3, f64)> { let r = s.distance(radius_pt).max(1e-3); let chord_len = s.distance(e); if r < chord_len * 0.5 { return None; } let unit_chord = (e - s) / chord_len; - let perp = Vec3::new(-unit_chord.y, 0.0, unit_chord.x); + let perp = DVec3::new(-unit_chord.y, 0.0, unit_chord.x); let mid = (s + e) * 0.5; let h_sign = (radius_pt - mid).dot(perp).signum(); let d = (r * r - (chord_len * 0.5) * (chord_len * 0.5)) @@ -193,12 +196,12 @@ fn arc_from_se_radius(s: Vec3, e: Vec3, radius_pt: Vec3) -> Option<(Vec3, f32)> } /// Arc center+radius from start, end, and a tangent-direction point at start. -fn arc_from_direction(s: Vec3, e: Vec3, dir_pt: Vec3) -> Option<(Vec3, f32)> { +fn arc_from_direction(s: DVec3, e: DVec3, dir_pt: DVec3) -> Option<(DVec3, f64)> { let t = (dir_pt - s).normalize_or_zero(); if t.length_squared() < 1e-12 { return None; } - let perp_t = Vec3::new(-t.y, t.x, 0.0); // rotate tangent 90° CCW + let perp_t = DVec3::new(-t.y, t.x, 0.0); // rotate tangent 90° CCW let chord = e - s; let denom = perp_t.x * chord.x + perp_t.y * chord.y; if denom.abs() < 1e-9 { @@ -211,7 +214,7 @@ fn arc_from_direction(s: Vec3, e: Vec3, dir_pt: Vec3) -> Option<(Vec3, f32)> { /// Compute end_angle from a chord-length pick (SCL / CSL semantics). /// `chord_len` is clamped to [0, 2r]. -fn end_angle_from_chord_len(start_angle: f32, chord: f32, r: f32) -> f32 { +fn end_angle_from_chord_len(start_angle: f64, chord: f64, r: f64) -> f64 { let half = (chord.min(2.0 * r) / (2.0 * r)).asin(); start_angle + 2.0 * half } @@ -220,10 +223,10 @@ fn end_angle_from_chord_len(start_angle: f32, chord: f32, r: f32) -> f32 { pub struct ArcCommand { step: u8, - c: Vec3, - r: f32, - sa: f32, - prev_pt: Option, + c: DVec3, + r: f64, + sa: f64, + prev_pt: Option, cw: bool, } @@ -231,7 +234,7 @@ impl ArcCommand { pub fn new() -> Self { Self { step: 0, - c: Vec3::ZERO, + c: DVec3::ZERO, r: 0.0, sa: 0.0, prev_pt: None, @@ -254,7 +257,7 @@ impl CadCommand for ArcCommand { ), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.c = pt; @@ -284,7 +287,7 @@ impl CadCommand for ArcCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.c, pt)), 2 => { @@ -315,7 +318,7 @@ impl CadCommand for ArcCommand { // ── Command 2: 3-Point (ARC_3P) ────────────────────────────────────────── pub struct Arc3PCommand { - pts: Vec, + pts: Vec, } impl Arc3PCommand { @@ -335,7 +338,7 @@ impl CadCommand for Arc3PCommand { _ => "ARC 3P Specify end point:".into(), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { self.pts.push(pt); if self.pts.len() < 3 { return CmdResult::NeedPoint; @@ -366,7 +369,7 @@ impl CadCommand for Arc3PCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.pts.len() { 0 => None, 1 => Some(line_wire(self.pts[0], pt)), @@ -385,7 +388,11 @@ impl CadCommand for Arc3PCommand { } else { Some(WireModel::solid( "rubber_band".into(), - vec![[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z], [pt.x, pt.y, pt.z]], + vec![ + [p1.x as f32, p1.y as f32, p1.z as f32], + [p2.x as f32, p2.y as f32, p2.z as f32], + [pt.x as f32, pt.y as f32, pt.z as f32], + ], WireModel::CYAN, false, )) @@ -399,11 +406,11 @@ impl CadCommand for Arc3PCommand { pub struct ArcSCECommand { step: u8, - s: Vec3, - c: Vec3, - r: f32, - sa: f32, - prev_pt: Option, + s: DVec3, + c: DVec3, + r: f64, + sa: f64, + prev_pt: Option, cw: bool, } @@ -411,8 +418,8 @@ impl ArcSCECommand { pub fn new() -> Self { Self { step: 0, - s: Vec3::ZERO, - c: Vec3::ZERO, + s: DVec3::ZERO, + c: DVec3::ZERO, r: 0.0, sa: 0.0, prev_pt: None, @@ -432,7 +439,7 @@ impl CadCommand for ArcSCECommand { _ => format!("ARC SCE Specify end point [r={:.3}]:", self.r), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.s = pt; @@ -463,7 +470,7 @@ impl CadCommand for ArcSCECommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.s, pt)), 2 => { @@ -496,11 +503,11 @@ impl CadCommand for ArcSCECommand { pub struct ArcSCACommand { step: u8, - s: Vec3, - c: Vec3, - r: f32, - sa: f32, - prev_pt: Option, + s: DVec3, + c: DVec3, + r: f64, + sa: f64, + prev_pt: Option, cw: bool, } @@ -508,8 +515,8 @@ impl ArcSCACommand { pub fn new() -> Self { Self { step: 0, - s: Vec3::ZERO, - c: Vec3::ZERO, + s: DVec3::ZERO, + c: DVec3::ZERO, r: 0.0, sa: 0.0, prev_pt: None, @@ -532,7 +539,7 @@ impl CadCommand for ArcSCACommand { ), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.s = pt; @@ -565,7 +572,7 @@ impl CadCommand for ArcSCACommand { } fn on_text_input(&mut self, text: &str) -> Option { if self.step == 2 { - let span: f32 = text.trim().replace(',', ".").parse().ok()?; + let span: f64 = text.trim().replace(',', ".").parse().ok()?; // Negative span = CW; positive = CCW. let ea = self.sa + span.to_radians(); return Some(CmdResult::CommitAndExit(make_arc( @@ -582,17 +589,18 @@ impl CadCommand for ArcSCACommand { anchor: DynAnchor::Point(self.c), fields: vec![DynFieldSpec::new(DynRole::Angle)], guide: DynGuide::Polar, - ref_point: Some(self.c + Vec3::new(self.sa.cos(), self.sa.sin(), 0.0)), + ref_point: Some(self.c + DVec3::new(self.sa.cos(), self.sa.sin(), 0.0)), }) } fn dyn_commit_as_text(&self) -> bool { self.step == 2 } - fn dyn_live_value(&self, cursor: Vec3) -> Option { - (self.step == 2) - .then(|| crate::command::dyn_display_angle_deg(angle_xy(self.c, cursor) - self.sa) as f64) + fn dyn_live_value(&self, cursor: DVec3) -> Option { + (self.step == 2).then(|| { + crate::command::dyn_display_angle_deg((angle_xy(self.c, cursor) - self.sa) as f32) as f64 + }) } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.s, pt)), 2 => { @@ -626,18 +634,18 @@ impl CadCommand for ArcSCACommand { pub struct ArcSCLCommand { step: u8, - s: Vec3, - c: Vec3, - r: f32, - sa: f32, + s: DVec3, + c: DVec3, + r: f64, + sa: f64, } impl ArcSCLCommand { pub fn new() -> Self { Self { step: 0, - s: Vec3::ZERO, - c: Vec3::ZERO, + s: DVec3::ZERO, + c: DVec3::ZERO, r: 0.0, sa: 0.0, } @@ -658,7 +666,7 @@ impl CadCommand for ArcSCLCommand { ), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.s = pt; @@ -687,7 +695,7 @@ impl CadCommand for ArcSCLCommand { } fn on_text_input(&mut self, text: &str) -> Option { if self.step == 2 { - let chord: f32 = text.trim().replace(',', ".").parse().ok()?; + let chord: f64 = text.trim().replace(',', ".").parse().ok()?; if chord > 0.0 { let ea = end_angle_from_chord_len(self.sa, chord, self.r); return Some(CmdResult::CommitAndExit(make_arc( @@ -710,10 +718,10 @@ impl CadCommand for ArcSCLCommand { fn dyn_commit_as_text(&self) -> bool { self.step == 2 } - fn dyn_live_value(&self, cursor: Vec3) -> Option { - (self.step == 2).then(|| self.s.distance(cursor) as f64) + fn dyn_live_value(&self, cursor: DVec3) -> Option { + (self.step == 2).then(|| self.s.distance(cursor)) } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.s, pt)), 2 => { @@ -731,16 +739,16 @@ impl CadCommand for ArcSCLCommand { pub struct ArcSEACommand { step: u8, - s: Vec3, - e: Vec3, + s: DVec3, + e: DVec3, } impl ArcSEACommand { pub fn new() -> Self { Self { step: 0, - s: Vec3::ZERO, - e: Vec3::ZERO, + s: DVec3::ZERO, + e: DVec3::ZERO, } } } @@ -756,7 +764,7 @@ impl CadCommand for ArcSEACommand { _ => "ARC SEA Specify angle (move cursor perpendicular to chord):".into(), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.s = pt; @@ -784,7 +792,7 @@ impl CadCommand for ArcSEACommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.s, pt)), 2 => { @@ -806,16 +814,16 @@ impl CadCommand for ArcSEACommand { pub struct ArcSERCommand { step: u8, - s: Vec3, - e: Vec3, + s: DVec3, + e: DVec3, } impl ArcSERCommand { pub fn new() -> Self { Self { step: 0, - s: Vec3::ZERO, - e: Vec3::ZERO, + s: DVec3::ZERO, + e: DVec3::ZERO, } } } @@ -831,7 +839,7 @@ impl CadCommand for ArcSERCommand { _ => "ARC SER Click radius point or type radius value:".into(), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.s = pt; @@ -861,13 +869,13 @@ impl CadCommand for ArcSERCommand { } fn on_text_input(&mut self, text: &str) -> Option { if self.step == 2 { - let r: f32 = text.trim().replace(',', ".").parse().ok()?; + let r: f64 = text.trim().replace(',', ".").parse().ok()?; let chord = self.s.distance(self.e); if r > 0.0 && r >= chord / 2.0 { let mid = (self.s + self.e) * 0.5; let perp = { let cv = (self.e - self.s) / chord; - Vec3::new(-cv.y, cv.x, 0.0) + DVec3::new(-cv.y, cv.x, 0.0) }; let d = (r * r - (chord / 2.0) * (chord / 2.0)).max(0.0).sqrt(); let center = mid - perp * d; @@ -891,13 +899,13 @@ impl CadCommand for ArcSERCommand { fn dyn_commit_as_text(&self) -> bool { self.step == 2 } - fn dyn_live_value(&self, cursor: Vec3) -> Option { + fn dyn_live_value(&self, cursor: DVec3) -> Option { if self.step != 2 { return None; } - arc_from_se_radius(self.s, self.e, cursor).map(|(_, r)| r as f64) + arc_from_se_radius(self.s, self.e, cursor).map(|(_, r)| r) } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.s, pt)), 2 => { @@ -919,16 +927,16 @@ impl CadCommand for ArcSERCommand { pub struct ArcSEDCommand { step: u8, - s: Vec3, - e: Vec3, + s: DVec3, + e: DVec3, } impl ArcSEDCommand { pub fn new() -> Self { Self { step: 0, - s: Vec3::ZERO, - e: Vec3::ZERO, + s: DVec3::ZERO, + e: DVec3::ZERO, } } } @@ -944,7 +952,7 @@ impl CadCommand for ArcSEDCommand { _ => "ARC SED Specify tangent direction at start:".into(), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.s = pt; @@ -972,7 +980,7 @@ impl CadCommand for ArcSEDCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.s, pt)), 2 => { @@ -994,10 +1002,10 @@ impl CadCommand for ArcSEDCommand { pub struct ArcCSACommand { step: u8, - c: Vec3, - r: f32, - sa: f32, - prev_pt: Option, + c: DVec3, + r: f64, + sa: f64, + prev_pt: Option, cw: bool, } @@ -1005,7 +1013,7 @@ impl ArcCSACommand { pub fn new() -> Self { Self { step: 0, - c: Vec3::ZERO, + c: DVec3::ZERO, r: 0.0, sa: 0.0, prev_pt: None, @@ -1028,7 +1036,7 @@ impl CadCommand for ArcCSACommand { ), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.c = pt; @@ -1060,7 +1068,7 @@ impl CadCommand for ArcCSACommand { } fn on_text_input(&mut self, text: &str) -> Option { if self.step == 2 { - let span: f32 = text.trim().replace(',', ".").parse().ok()?; + let span: f64 = text.trim().replace(',', ".").parse().ok()?; let ea = self.sa + span.to_radians(); return Some(CmdResult::CommitAndExit(make_arc( self.c, self.r, self.sa, ea, @@ -1074,17 +1082,18 @@ impl CadCommand for ArcCSACommand { anchor: DynAnchor::Point(self.c), fields: vec![DynFieldSpec::new(DynRole::Angle)], guide: DynGuide::Polar, - ref_point: Some(self.c + Vec3::new(self.sa.cos(), self.sa.sin(), 0.0)), + ref_point: Some(self.c + DVec3::new(self.sa.cos(), self.sa.sin(), 0.0)), }) } fn dyn_commit_as_text(&self) -> bool { self.step == 2 } - fn dyn_live_value(&self, cursor: Vec3) -> Option { - (self.step == 2) - .then(|| crate::command::dyn_display_angle_deg(angle_xy(self.c, cursor) - self.sa) as f64) + fn dyn_live_value(&self, cursor: DVec3) -> Option { + (self.step == 2).then(|| { + crate::command::dyn_display_angle_deg((angle_xy(self.c, cursor) - self.sa) as f32) as f64 + }) } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.c, pt)), 2 => { @@ -1117,18 +1126,18 @@ impl CadCommand for ArcCSACommand { pub struct ArcCSLCommand { step: u8, - c: Vec3, - s: Vec3, - r: f32, - sa: f32, + c: DVec3, + s: DVec3, + r: f64, + sa: f64, } impl ArcCSLCommand { pub fn new() -> Self { Self { step: 0, - c: Vec3::ZERO, - s: Vec3::ZERO, + c: DVec3::ZERO, + s: DVec3::ZERO, r: 0.0, sa: 0.0, } @@ -1149,7 +1158,7 @@ impl CadCommand for ArcCSLCommand { ), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.c = pt; @@ -1178,7 +1187,7 @@ impl CadCommand for ArcCSLCommand { } fn on_text_input(&mut self, text: &str) -> Option { if self.step == 2 { - let chord: f32 = text.trim().replace(',', ".").parse().ok()?; + let chord: f64 = text.trim().replace(',', ".").parse().ok()?; if chord > 0.0 { let ea = end_angle_from_chord_len(self.sa, chord, self.r); return Some(CmdResult::CommitAndExit(make_arc( @@ -1201,10 +1210,10 @@ impl CadCommand for ArcCSLCommand { fn dyn_commit_as_text(&self) -> bool { self.step == 2 } - fn dyn_live_value(&self, cursor: Vec3) -> Option { - (self.step == 2).then(|| self.s.distance(cursor) as f64) + fn dyn_live_value(&self, cursor: DVec3) -> Option { + (self.step == 2).then(|| self.s.distance(cursor)) } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(line_wire(self.c, pt)), 2 => { diff --git a/src/modules/draw/draw/circle.rs b/src/modules/draw/draw/circle.rs index 8e4c41104..af26cadee 100644 --- a/src/modules/draw/draw/circle.rs +++ b/src/modules/draw/draw/circle.rs @@ -15,9 +15,9 @@ use crate::command::{CadCommand, CmdResult, DynField, TangentObject}; use crate::modules::draw::defaults; use crate::modules::IconKind; use crate::scene::model::wire_model::WireModel; -use glam::Vec3; +use glam::DVec3; -const TAU: f32 = std::f32::consts::TAU; +const TAU: f64 = std::f64::consts::TAU; // ── Per-method SVG icons ─────────────────────────────────────────────────── @@ -58,15 +58,15 @@ pub const ICON: IconKind = ICON_CR; // ── Shared geometry ──────────────────────────────────────────────────────── -fn circle_wire(center: Vec3, radius: f32) -> WireModel { +fn circle_wire(center: DVec3, radius: f64) -> WireModel { let segs = 64u32; let mut pts: Vec<[f32; 3]> = (0..=segs) .map(|i| { - let a = (i as f32) * TAU / segs as f32; + let a = (i as f64) * TAU / segs as f64; [ - center.x + radius * a.cos(), - center.y + radius * a.sin(), - center.z, + (center.x + radius * a.cos()) as f32, + (center.y + radius * a.sin()) as f32, + center.z as f32, ] }) .collect(); @@ -76,9 +76,9 @@ fn circle_wire(center: Vec3, radius: f32) -> WireModel { WireModel::solid("rubber_band".into(), pts, WireModel::CYAN, false) } -fn make_circle(center: Vec3, radius: f64) -> EntityType { +fn make_circle(center: DVec3, radius: f64) -> EntityType { EntityType::Circle(Circle { - center: Vector3::new(center.x as f64, center.y as f64, center.z as f64), + center: Vector3::new(center.x, center.y, center.z), radius, ..Default::default() }) @@ -86,7 +86,7 @@ fn make_circle(center: Vec3, radius: f64) -> EntityType { /// Circumscribed circle through three points. /// Returns `None` if the points are collinear. -fn circumcircle(a: Vec3, b: Vec3, c: Vec3) -> Option<(Vec3, f32)> { +fn circumcircle(a: DVec3, b: DVec3, c: DVec3) -> Option<(DVec3, f64)> { let ax = a.x; let ay = a.y; let bx = b.x; @@ -105,7 +105,7 @@ fn circumcircle(a: Vec3, b: Vec3, c: Vec3) -> Option<(Vec3, f32)> { + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d; - let center = Vec3::new(ux, uy, a.z); + let center = DVec3::new(ux, uy, a.z); Some((center, center.distance(a))) } @@ -117,7 +117,7 @@ pub struct CircleCommand { } enum StepCR { Center, - Radius(Vec3), + Radius(DVec3), } impl CircleCommand { @@ -142,7 +142,7 @@ impl CadCommand for CircleCommand { ), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.step { StepCR::Center => { self.step = StepCR::Radius(pt); @@ -150,8 +150,8 @@ impl CadCommand for CircleCommand { } StepCR::Radius(c) => { let r = c.distance(pt); - defaults::set_circle_radius(r); - CmdResult::CommitAndExit(make_circle(*c, r as f64)) + defaults::set_circle_radius(r as f32); + CmdResult::CommitAndExit(make_circle(*c, r)) } } } @@ -166,7 +166,7 @@ impl CadCommand for CircleCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if let StepCR::Radius(c) = &self.step { Some(circle_wire(*c, c.distance(pt))) } else { @@ -175,10 +175,10 @@ impl CadCommand for CircleCommand { } fn on_text_input(&mut self, text: &str) -> Option { if let StepCR::Radius(c) = &self.step { - let r: f32 = text.trim().replace(',', ".").parse().ok()?; + let r: f64 = text.trim().replace(',', ".").parse().ok()?; if r > 0.0 { - defaults::set_circle_radius(r); - return Some(CmdResult::CommitAndExit(make_circle(*c, r as f64))); + defaults::set_circle_radius(r as f32); + return Some(CmdResult::CommitAndExit(make_circle(*c, r))); } } None @@ -236,7 +236,7 @@ impl CadCommand for CircleCDCommand { ), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.step { StepCR::Center => { self.step = StepCR::Radius(pt); @@ -244,8 +244,8 @@ impl CadCommand for CircleCDCommand { } StepCR::Radius(c) => { let d = c.distance(pt) * 2.0; - defaults::set_circle_diam(d); - CmdResult::CommitAndExit(make_circle(*c, (d / 2.0) as f64)) + defaults::set_circle_diam(d as f32); + CmdResult::CommitAndExit(make_circle(*c, d / 2.0)) } } } @@ -260,7 +260,7 @@ impl CadCommand for CircleCDCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { // Preview radius = distance to cursor; on commit that distance becomes the radius (diameter = 2x). if let StepCR::Radius(c) = &self.step { Some(circle_wire(*c, c.distance(pt))) @@ -270,10 +270,10 @@ impl CadCommand for CircleCDCommand { } fn on_text_input(&mut self, text: &str) -> Option { if let StepCR::Radius(c) = &self.step { - let d: f32 = text.trim().replace(',', ".").parse().ok()?; + let d: f64 = text.trim().replace(',', ".").parse().ok()?; if d > 0.0 { - defaults::set_circle_diam(d); - return Some(CmdResult::CommitAndExit(make_circle(*c, (d / 2.0) as f64))); + defaults::set_circle_diam(d as f32); + return Some(CmdResult::CommitAndExit(make_circle(*c, d / 2.0))); } } None @@ -298,7 +298,7 @@ impl CadCommand for CircleCDCommand { // ── Command: 2-Point ────────────────────────────────────────────────────── pub struct Circle2PCommand { - p1: Option, + p1: Option, } impl Circle2PCommand { @@ -318,7 +318,7 @@ impl CadCommand for Circle2PCommand { "CIRCLE 2P Specify second end of diameter:".into() } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.p1 { None => { self.p1 = Some(pt); @@ -326,7 +326,7 @@ impl CadCommand for Circle2PCommand { } Some(p1) => { let center = (p1 + pt) * 0.5; - let radius = p1.distance(pt) as f64 / 2.0; + let radius = p1.distance(pt) / 2.0; CmdResult::CommitAndExit(make_circle(center, radius)) } } @@ -337,7 +337,7 @@ impl CadCommand for Circle2PCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let p1 = self.p1?; let center = (p1 + pt) * 0.5; let radius = p1.distance(pt) / 2.0; @@ -348,7 +348,7 @@ impl CadCommand for Circle2PCommand { // ── Command: 3-Point ────────────────────────────────────────────────────── pub struct Circle3PCommand { - pts: Vec, + pts: Vec, } impl Circle3PCommand { @@ -368,14 +368,14 @@ impl CadCommand for Circle3PCommand { _ => "CIRCLE 3P Specify third point:".into(), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { self.pts.push(pt); if self.pts.len() < 3 { return CmdResult::NeedPoint; } let (a, b, c) = (self.pts[0], self.pts[1], self.pts[2]); match circumcircle(a, b, c) { - Some((center, radius)) => CmdResult::CommitAndExit(make_circle(center, radius as f64)), + Some((center, radius)) => CmdResult::CommitAndExit(make_circle(center, radius)), None => { self.pts.pop(); CmdResult::NeedPoint @@ -388,7 +388,7 @@ impl CadCommand for Circle3PCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.pts.len() { 0 => None, 1 => { @@ -406,7 +406,11 @@ impl CadCommand for Circle3PCommand { } else { Some(WireModel::solid( "rubber_band".into(), - vec![[a.x, a.y, a.z], [b.x, b.y, b.z], [pt.x, pt.y, pt.z]], + vec![ + [a.x as f32, a.y as f32, a.z as f32], + [b.x as f32, b.y as f32, b.z as f32], + [pt.x as f32, pt.y as f32, pt.z as f32], + ], WireModel::CYAN, false, )) @@ -420,13 +424,13 @@ impl CadCommand for Circle3PCommand { #[derive(Clone, Copy)] struct Line2D { - a: f32, - b: f32, - c: f32, + a: f64, + b: f64, + c: f64, } // ax + by + c = 0, a²+b²=1 impl Line2D { - fn from_obj(p1: Vec3, p2: Vec3) -> Self { + fn from_obj(p1: DVec3, p2: DVec3) -> Self { let dx = p2.x - p1.x; let dy = p2.y - p1.y; let len = (dx * dx + dy * dy).sqrt(); @@ -447,17 +451,17 @@ impl Line2D { } } -fn line_line_isect(l1: Line2D, l2: Line2D) -> Option { +fn line_line_isect(l1: Line2D, l2: Line2D) -> Option { let det = l1.a * l2.b - l2.a * l1.b; if det.abs() < 1e-9 { return None; } let x = (-l1.c * l2.b + l2.c * l1.b) / det; let y = (-l1.a * l2.c + l2.a * l1.c) / det; - Some(Vec3::new(x, y, 0.0)) + Some(DVec3::new(x, y, 0.0)) } -fn solve_quadratic(a: f32, b: f32, c: f32) -> Vec { +fn solve_quadratic(a: f64, b: f64, c: f64) -> Vec { if a.abs() < 1e-9 { if b.abs() < 1e-9 { return vec![]; @@ -472,7 +476,7 @@ fn solve_quadratic(a: f32, b: f32, c: f32) -> Vec { vec![(-b - sq) / (2.0 * a), (-b + sq) / (2.0 * a)] } -fn best_of(candidates: &[Vec3], hint: Vec3) -> Option { +fn best_of(candidates: &[DVec3], hint: DVec3) -> Option { candidates.iter().copied().min_by(|a, b| { a.distance(hint) .partial_cmp(&b.distance(hint)) @@ -480,7 +484,7 @@ fn best_of(candidates: &[Vec3], hint: Vec3) -> Option { }) } -fn best_circle_of(candidates: &[(Vec3, f32)], hint: Vec3) -> Option<(Vec3, f32)> { +fn best_circle_of(candidates: &[(DVec3, f64)], hint: DVec3) -> Option<(DVec3, f64)> { candidates .iter() .copied() @@ -493,7 +497,7 @@ fn best_circle_of(candidates: &[(Vec3, f32)], hint: Vec3) -> Option<(Vec3, f32)> } /// All candidate circle centers tangent to two objects with radius r. -fn ttr_candidates(obj1: TangentObject, obj2: TangentObject, r: f32) -> Vec { +fn ttr_candidates(obj1: TangentObject, obj2: TangentObject, r: f64) -> Vec { match (obj1, obj2) { (TangentObject::Line { p1: a, p2: b }, TangentObject::Line { p1: c, p2: d }) => { let l1 = Line2D::from_obj(a, b); @@ -540,10 +544,10 @@ fn ttr_candidates(obj1: TangentObject, obj2: TangentObject, r: f32) -> Vec } } -fn ttr_lc_candidates(l: Line2D, cp: Vec3, cr: f32, r: f32) -> Vec { +fn ttr_lc_candidates(l: Line2D, cp: DVec3, cr: f64, r: f64) -> Vec { let mut out = Vec::new(); - for s1 in [-1.0f32, 1.0] { - for s2 in [-1.0f32, 1.0] { + for s1 in [-1.0f64, 1.0] { + for s2 in [-1.0f64, 1.0] { let rho = cr + s2 * r; if rho < 0.0 { continue; @@ -563,23 +567,23 @@ fn ttr_lc_candidates(l: Line2D, cp: Vec3, cr: f32, r: f32) -> Vec { // (-l.b*t + u)^2 + (l.a*t + v)^2 = rho^2 // (l.b^2 + l.a^2)*t^2 + 2*(-l.b*u + l.a*v)*t + (u^2 + v^2 - rho^2) = 0 // Since a^2 + b^2 = 1: - let qa = 1.0f32; + let qa = 1.0f64; let qb = 2.0 * (-l.b * u + l.a * v); let qc = u * u + v * v - rho * rho; for t in solve_quadratic(qa, qb, qc) { let cx = -l.b * t - l.a * c_off; let cy = l.a * t - l.b * c_off; - out.push(Vec3::new(cx, cy, 0.0)); + out.push(DVec3::new(cx, cy, 0.0)); } } } out } -fn ttr_cc_candidates(c1: Vec3, r1: f32, c2: Vec3, r2: f32, r: f32) -> Vec { +fn ttr_cc_candidates(c1: DVec3, r1: f64, c2: DVec3, r2: f64, r: f64) -> Vec { let mut out = Vec::new(); - for s1 in [-1.0f32, 1.0] { - for s2 in [-1.0f32, 1.0] { + for s1 in [-1.0f64, 1.0] { + for s2 in [-1.0f64, 1.0] { let rho1 = r1 + s1 * r; let rho2 = r2 + s2 * r; if rho1 < 0.0 || rho2 < 0.0 { @@ -605,10 +609,10 @@ fn ttr_cc_candidates(c1: Vec3, r1: f32, c2: Vec3, r2: f32, r: f32) -> Vec if disc < 0.0 { continue; } - for sign in [-1.0f32, 1.0] { + for sign in [-1.0f64, 1.0] { let cx = (-qb + sign * disc.sqrt()) / (2.0 * qa); let cy = (k - ax * cx) * iy; - out.push(Vec3::new(cx, cy, 0.0)); + out.push(DVec3::new(cx, cy, 0.0)); } } else { let ix = 1.0 / ax; @@ -620,10 +624,10 @@ fn ttr_cc_candidates(c1: Vec3, r1: f32, c2: Vec3, r2: f32, r: f32) -> Vec if disc < 0.0 { continue; } - for sign in [-1.0f32, 1.0] { + for sign in [-1.0f64, 1.0] { let cy = (-qb + sign * disc.sqrt()) / (2.0 * qa); let cx = (k - ay * cy) * ix; - out.push(Vec3::new(cx, cy, 0.0)); + out.push(DVec3::new(cx, cy, 0.0)); } } } @@ -636,10 +640,10 @@ fn ttt_candidates( obj1: TangentObject, obj2: TangentObject, obj3: TangentObject, -) -> Vec<(Vec3, f32)> { +) -> Vec<(DVec3, f64)> { let objs = [obj1, obj2, obj3]; let mut results = Vec::new(); - let sign_combos: [[f32; 3]; 8] = [ + let sign_combos: [[f64; 3]; 8] = [ [-1., -1., -1.], [-1., -1., 1.], [-1., 1., -1.], @@ -661,13 +665,13 @@ fn ttt_candidates( // LinEq: lx*cx + ly*cy + lr*r = k struct LinEq { - lx: f32, - ly: f32, - lr: f32, - k: f32, + lx: f64, + ly: f64, + lr: f64, + k: f64, } -fn ttt_solve_sign(objs: &[TangentObject; 3], eps: &[f32; 3]) -> Vec<(Vec3, f32)> { +fn ttt_solve_sign(objs: &[TangentObject; 3], eps: &[f64; 3]) -> Vec<(DVec3, f64)> { let mut lin_eqs: Vec = Vec::new(); let mut circle_idx: Vec = Vec::new(); @@ -728,7 +732,7 @@ fn ttt_solve_sign(objs: &[TangentObject; 3], eps: &[f32; 3]) -> Vec<(Vec3, f32)> let a_cy = (e0.lx * e1.k - e1.lx * e0.k) / det; let b_cy = -(e0.lx * e1.lr - e1.lx * e0.lr) / det; - let r_vals: Vec = if lin_eqs.len() >= 3 { + let r_vals: Vec = if lin_eqs.len() >= 3 { let e2 = &lin_eqs[2]; let r_coeff = b_cx * e2.lx + b_cy * e2.ly + e2.lr; let r_const = e2.k - a_cx * e2.lx - a_cy * e2.ly; @@ -766,7 +770,7 @@ fn ttt_solve_sign(objs: &[TangentObject; 3], eps: &[f32; 3]) -> Vec<(Vec3, f32)> } let cx = a_cx + b_cx * r; let cy = a_cy + b_cy * r; - Some((Vec3::new(cx, cy, 0.0), r)) + Some((DVec3::new(cx, cy, 0.0), r)) }) .collect() } @@ -781,13 +785,13 @@ enum StepTTR { First, Second { obj1: TangentObject, - hit1: Vec3, + hit1: DVec3, }, Radius { obj1: TangentObject, obj2: TangentObject, - hit1: Vec3, - hit2: Vec3, + hit1: DVec3, + hit2: DVec3, }, } @@ -828,11 +832,11 @@ impl CadCommand for CircleTTRCommand { } } - fn on_point(&mut self, _pt: Vec3) -> CmdResult { + fn on_point(&mut self, _pt: DVec3) -> CmdResult { CmdResult::NeedPoint } - fn on_tangent_point(&mut self, obj: TangentObject, hit: Vec3) -> CmdResult { + fn on_tangent_point(&mut self, obj: TangentObject, hit: DVec3) -> CmdResult { match &self.step { StepTTR::First => { self.step = StepTTR::Second { @@ -856,7 +860,7 @@ impl CadCommand for CircleTTRCommand { } fn on_text_input(&mut self, text: &str) -> Option { - let r: f32 = text.trim().parse().ok()?; + let r: f64 = text.trim().parse().ok()?; if r <= 0.0 { return Some(CmdResult::Cancel); } @@ -870,7 +874,7 @@ impl CadCommand for CircleTTRCommand { let hint = (*hit1 + *hit2) * 0.5; let candidates = ttr_candidates(*obj1, *obj2, r); if let Some(center) = best_of(&candidates, hint) { - Some(CmdResult::CommitAndExit(make_circle(center, r as f64))) + Some(CmdResult::CommitAndExit(make_circle(center, r))) } else { Some(CmdResult::Cancel) } @@ -885,7 +889,7 @@ impl CadCommand for CircleTTRCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, _pt: Vec3) -> Option { + fn on_mouse_move(&mut self, _pt: DVec3) -> Option { None } } @@ -894,7 +898,7 @@ impl CadCommand for CircleTTRCommand { pub struct CircleTTTCommand { objs: Vec, - hits: Vec, + hits: Vec, } impl CircleTTTCommand { @@ -923,20 +927,20 @@ impl CadCommand for CircleTTTCommand { } } - fn on_point(&mut self, _pt: Vec3) -> CmdResult { + fn on_point(&mut self, _pt: DVec3) -> CmdResult { CmdResult::NeedPoint } - fn on_tangent_point(&mut self, obj: TangentObject, hit: Vec3) -> CmdResult { + fn on_tangent_point(&mut self, obj: TangentObject, hit: DVec3) -> CmdResult { self.objs.push(obj); self.hits.push(hit); if self.objs.len() < 3 { return CmdResult::NeedPoint; } - let hint = self.hits.iter().fold(Vec3::ZERO, |a, &b| a + b) / 3.0; + let hint = self.hits.iter().fold(DVec3::ZERO, |a, &b| a + b) / 3.0; let candidates = ttt_candidates(self.objs[0], self.objs[1], self.objs[2]); match best_circle_of(&candidates, hint) { - Some((center, r)) => CmdResult::CommitAndExit(make_circle(center, r as f64)), + Some((center, r)) => CmdResult::CommitAndExit(make_circle(center, r)), None => { self.objs.pop(); self.hits.pop(); @@ -951,7 +955,7 @@ impl CadCommand for CircleTTTCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, _pt: Vec3) -> Option { + fn on_mouse_move(&mut self, _pt: DVec3) -> Option { None } } diff --git a/src/modules/draw/draw/ellipse.rs b/src/modules/draw/draw/ellipse.rs index 022b7765b..63204913f 100644 --- a/src/modules/draw/draw/ellipse.rs +++ b/src/modules/draw/draw/ellipse.rs @@ -11,17 +11,17 @@ use acadrust::{Ellipse, EntityType}; use crate::command::{CadCommand, CmdResult}; use crate::modules::IconKind; use crate::scene::model::wire_model::WireModel; -use glam::Vec3; +use glam::DVec3; -fn parse_f32(text: &str) -> Option { +fn parse_num(text: &str) -> Option { text.trim().replace(',', ".").parse().ok() } -const TAU: f32 = std::f32::consts::TAU; +const TAU: f64 = std::f64::consts::TAU; /// Minimum swept angle before the previewed arc may flip CW/CCW — filters the /// per-frame cursor jitter that otherwise reverses the sweep on tiny moves. -const DIR_TOL: f32 = 0.1745; // ~10° +const DIR_TOL: f64 = 0.1745; // ~10° // ── Icons ───────────────────────────────────────────────────────────────── @@ -51,33 +51,33 @@ pub const ICON: IconKind = ICON_CTR; /// Preview wire for a full or partial ellipse. fn ellipse_wire( - center: Vec3, - major: Vec3, // vector from center to major-axis endpoint - ratio: f32, // minor/major - t_start: f32, - t_end: f32, + center: DVec3, + major: DVec3, // vector from center to major-axis endpoint + ratio: f64, // minor/major + t_start: f64, + t_end: f64, ) -> WireModel { let r_major = major.length(); if r_major < 1e-9 { return WireModel::solid("rubber_band".into(), vec![], WireModel::CYAN, false); } let major_dir = major / r_major; - let v = Vec3::Z.cross(major_dir).normalize(); + let v = DVec3::Z.cross(major_dir).normalize(); let segs = 64u32; // Unwrap t_end so the arc goes counter-clockwise. let t_e = if t_end <= t_start { t_end + TAU } else { t_end }; let pts: Vec<[f32; 3]> = (0..=segs) .map(|i| { - let t = t_start + (t_e - t_start) * (i as f32 / segs as f32); + let t = t_start + (t_e - t_start) * (i as f64 / segs as f64); let p = center + t.cos() * r_major * major_dir + t.sin() * r_major * ratio * v; - [p.x, p.y, p.z] + [p.x as f32, p.y as f32, p.z as f32] }) .collect(); WireModel::solid("rubber_band".into(), pts, WireModel::CYAN, false) } /// Convert a world point to the parametric angle on the ellipse. -fn param_angle(center: Vec3, major_dir: Vec3, v: Vec3, pt: Vec3, ratio: f32) -> f32 { +fn param_angle(center: DVec3, major_dir: DVec3, v: DVec3, pt: DVec3, ratio: f64) -> f64 { let d = pt - center; let u_proj = d.dot(major_dir); let v_proj = d.dot(v); @@ -87,13 +87,13 @@ fn param_angle(center: Vec3, major_dir: Vec3, v: Vec3, pt: Vec3, ratio: f32) -> } /// Build the final Ellipse entity. -fn make_ellipse(center: Vec3, major: Vec3, ratio: f32, t_start: f32, t_end: f32) -> Ellipse { +fn make_ellipse(center: DVec3, major: DVec3, ratio: f64, t_start: f64, t_end: f64) -> Ellipse { Ellipse { - center: Vector3::new(center.x as f64, center.y as f64, center.z as f64), - major_axis: Vector3::new(major.x as f64, major.y as f64, major.z as f64), - minor_axis_ratio: ratio as f64, - start_parameter: t_start as f64, - end_parameter: t_end as f64, + center: Vector3::new(center.x, center.y, center.z), + major_axis: Vector3::new(major.x, major.y, major.z), + minor_axis_ratio: ratio, + start_parameter: t_start, + end_parameter: t_end, ..Default::default() } } @@ -103,8 +103,8 @@ fn make_ellipse(center: Vec3, major: Vec3, ratio: f32, t_start: f32, t_end: f32) enum CtrStep { Center, - MajorAxis { center: Vec3 }, - MinorRatio { center: Vec3, major: Vec3 }, + MajorAxis { center: DVec3 }, + MinorRatio { center: DVec3, major: DVec3 }, } pub struct EllipseCommand { @@ -135,7 +135,7 @@ impl CadCommand for EllipseCommand { } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.step { CtrStep::Center => { self.step = CtrStep::MajorAxis { center: pt }; @@ -165,7 +165,7 @@ impl CadCommand for EllipseCommand { fn on_text_input(&mut self, text: &str) -> Option { if let CtrStep::MinorRatio { center, major } = &self.step { - let r_minor = parse_f32(text)?; + let r_minor = parse_num(text)?; if r_minor > 0.0 { let ratio = (r_minor / major.length()).clamp(1e-6, 1.0); return Some(CmdResult::CommitAndExit(EntityType::Ellipse(make_ellipse( @@ -176,7 +176,7 @@ impl CadCommand for EllipseCommand { None } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match &self.step { CtrStep::MajorAxis { center } => Some(line_wire(*center, pt)), CtrStep::MinorRatio { center, major } => { @@ -204,9 +204,9 @@ impl CadCommand for EllipseCommand { } } - fn dyn_live_value(&self, cursor: Vec3) -> Option { + fn dyn_live_value(&self, cursor: DVec3) -> Option { if let CtrStep::MinorRatio { center, major } = &self.step { - Some((minor_ratio(*center, *major, cursor) * major.length()) as f64) + Some(minor_ratio(*center, *major, cursor) * major.length()) } else { None } @@ -218,8 +218,8 @@ impl CadCommand for EllipseCommand { enum AxisStep { Pt1, - Pt2 { p1: Vec3 }, - MinorRatio { center: Vec3, major: Vec3 }, + Pt2 { p1: DVec3 }, + MinorRatio { center: DVec3, major: DVec3 }, } pub struct EllipseAxisCommand { @@ -250,7 +250,7 @@ impl CadCommand for EllipseAxisCommand { } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.step { AxisStep::Pt1 => { self.step = AxisStep::Pt2 { p1: pt }; @@ -278,7 +278,7 @@ impl CadCommand for EllipseAxisCommand { fn on_text_input(&mut self, text: &str) -> Option { if let AxisStep::MinorRatio { center, major } = &self.step { - let r_minor = parse_f32(text)?; + let r_minor = parse_num(text)?; if r_minor > 0.0 { let ratio = (r_minor / major.length()).clamp(1e-6, 1.0); return Some(CmdResult::CommitAndExit(EntityType::Ellipse(make_ellipse( @@ -289,7 +289,7 @@ impl CadCommand for EllipseAxisCommand { None } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match &self.step { AxisStep::Pt1 => None, AxisStep::Pt2 { p1 } => Some(line_wire(*p1, pt)), @@ -316,9 +316,9 @@ impl CadCommand for EllipseAxisCommand { } } - fn dyn_live_value(&self, cursor: Vec3) -> Option { + fn dyn_live_value(&self, cursor: DVec3) -> Option { if let AxisStep::MinorRatio { center, major } = &self.step { - Some((minor_ratio(*center, *major, cursor) * major.length()) as f64) + Some(minor_ratio(*center, *major, cursor) * major.length()) } else { None } @@ -331,28 +331,28 @@ impl CadCommand for EllipseAxisCommand { enum ArcStep { Center, MajorAxis { - center: Vec3, + center: DVec3, }, MinorRatio { - center: Vec3, - major: Vec3, + center: DVec3, + major: DVec3, }, StartAngle { - center: Vec3, - major: Vec3, - ratio: f32, + center: DVec3, + major: DVec3, + ratio: f64, }, EndAngle { - center: Vec3, - major: Vec3, - ratio: f32, - t_start: f32, + center: DVec3, + major: DVec3, + ratio: f64, + t_start: f64, }, } pub struct EllipseArcCommand { step: ArcStep, - prev_pt: Option, + prev_pt: Option, cw: bool, } @@ -389,7 +389,7 @@ impl CadCommand for EllipseArcCommand { } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.step { ArcStep::Center => { self.step = ArcStep::MajorAxis { center: pt }; @@ -452,7 +452,7 @@ impl CadCommand for EllipseArcCommand { } fn on_text_input(&mut self, text: &str) -> Option { - let val = parse_f32(text)?; + let val = parse_num(text)?; match &self.step { ArcStep::MinorRatio { center, major } => { if val > 0.0 { @@ -499,7 +499,7 @@ impl CadCommand for EllipseArcCommand { None } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match &self.step { ArcStep::MajorAxis { center } => Some(line_wire(*center, pt)), ArcStep::MinorRatio { center, major } => { @@ -528,10 +528,10 @@ impl CadCommand for EllipseArcCommand { let t_prev = angle_from_point(*center, *major, *ratio, prev); let t_cur = angle_from_point(*center, *major, *ratio, pt); let mut d = t_cur - t_prev; - while d > std::f32::consts::PI { + while d > std::f64::consts::PI { d -= TAU; } - while d <= -std::f32::consts::PI { + while d <= -std::f64::consts::PI { d += TAU; } if d.abs() > DIR_TOL { @@ -576,9 +576,9 @@ impl CadCommand for EllipseArcCommand { } } - fn dyn_live_value(&self, cursor: Vec3) -> Option { + fn dyn_live_value(&self, cursor: DVec3) -> Option { if let ArcStep::MinorRatio { center, major } = &self.step { - Some((minor_ratio(*center, *major, cursor) * major.length()) as f64) + Some(minor_ratio(*center, *major, cursor) * major.length()) } else { None } @@ -587,7 +587,7 @@ impl CadCommand for EllipseArcCommand { // ── Internal helpers ────────────────────────────────────────────────────── -fn minor_ratio(center: Vec3, major: Vec3, pt: Vec3) -> f32 { +fn minor_ratio(center: DVec3, major: DVec3, pt: DVec3) -> f64 { let r_major = major.length(); if r_major < 1e-9 { return 0.5; @@ -599,20 +599,23 @@ fn minor_ratio(center: Vec3, major: Vec3, pt: Vec3) -> f32 { (r_minor / r_major).clamp(1e-6, 1.0) } -fn angle_from_point(center: Vec3, major: Vec3, ratio: f32, pt: Vec3) -> f32 { +fn angle_from_point(center: DVec3, major: DVec3, ratio: f64, pt: DVec3) -> f64 { let r_major = major.length(); if r_major < 1e-9 { return 0.0; } let major_dir = major / r_major; - let v = Vec3::Z.cross(major_dir).normalize(); + let v = DVec3::Z.cross(major_dir).normalize(); param_angle(center, major_dir, v, pt, ratio) } -fn line_wire(from: Vec3, to: Vec3) -> WireModel { +fn line_wire(from: DVec3, to: DVec3) -> WireModel { WireModel { name: "rubber_band".into(), - points: vec![[from.x, from.y, from.z], [to.x, to.y, to.z]], + points: vec![ + [from.x as f32, from.y as f32, from.z as f32], + [to.x as f32, to.y as f32, to.z as f32], + ], points_low: Vec::new(), color: WireModel::CYAN, selected: false, diff --git a/src/modules/draw/draw/shapes.rs b/src/modules/draw/draw/shapes.rs index 281a678da..40a17cc38 100644 --- a/src/modules/draw/draw/shapes.rs +++ b/src/modules/draw/draw/shapes.rs @@ -17,35 +17,37 @@ use acadrust::{EntityType, LwPolyline}; use crate::command::{CadCommand, CmdResult}; use crate::modules::IconKind; use crate::scene::model::wire_model::WireModel; -use glam::{Mat4, Vec3}; +use glam::{DVec3, Mat4}; /// Build the four corners of an axis-aligned box between opposite corners `a` /// and `b`, axis-aligned in the active UCS (`ucs` = UCS→wire affine, identity = /// world). The two given corners stay put; the other two are placed square to /// the UCS axes instead of the world axes. -fn ucs_box_corners(a: Vec3, b: Vec3, ucs: Mat4) -> [Vec3; 4] { +fn ucs_box_corners(a: DVec3, b: DVec3, ucs: Mat4) -> [DVec3; 4] { + let ucs = ucs.as_dmat4(); let inv = ucs.inverse(); let au = inv.transform_point3(a); let bu = inv.transform_point3(b); [ - ucs.transform_point3(Vec3::new(au.x, au.y, au.z)), - ucs.transform_point3(Vec3::new(bu.x, au.y, au.z)), - ucs.transform_point3(Vec3::new(bu.x, bu.y, au.z)), - ucs.transform_point3(Vec3::new(au.x, bu.y, au.z)), + ucs.transform_point3(DVec3::new(au.x, au.y, au.z)), + ucs.transform_point3(DVec3::new(bu.x, au.y, au.z)), + ucs.transform_point3(DVec3::new(bu.x, bu.y, au.z)), + ucs.transform_point3(DVec3::new(au.x, bu.y, au.z)), ] } /// Four corners of a box centred at `c` with half-extents taken from `corner`, /// axis-aligned in the active UCS (`ucs` = UCS→wire affine, identity = world). -fn ucs_box_around_center(c: Vec3, corner: Vec3, ucs: Mat4) -> [Vec3; 4] { +fn ucs_box_around_center(c: DVec3, corner: DVec3, ucs: Mat4) -> [DVec3; 4] { + let ucs = ucs.as_dmat4(); let d = ucs.inverse().transform_vector3(corner - c); - let rx = ucs.transform_vector3(Vec3::new(d.x.abs(), 0.0, 0.0)); - let ry = ucs.transform_vector3(Vec3::new(0.0, d.y.abs(), 0.0)); + let rx = ucs.transform_vector3(DVec3::new(d.x.abs(), 0.0, 0.0)); + let ry = ucs.transform_vector3(DVec3::new(0.0, d.y.abs(), 0.0)); [c - rx - ry, c + rx - ry, c + rx + ry, c - rx + ry] } -const TAU: f32 = std::f32::consts::TAU; -const PI: f32 = std::f32::consts::PI; +const TAU: f64 = std::f64::consts::TAU; +const PI: f64 = std::f64::consts::PI; // ── Icons ────────────────────────────────────────────────────────────────── @@ -100,10 +102,13 @@ fn wire_loop(pts: Vec<[f32; 3]>) -> WireModel { WireModel::solid("rubber_band".into(), p, WireModel::CYAN, false) } -fn wire_seg(a: Vec3, b: Vec3) -> WireModel { +fn wire_seg(a: DVec3, b: DVec3) -> WireModel { WireModel::solid( "rubber_band".into(), - vec![[a.x, a.y, a.z], [b.x, b.y, b.z]], + vec![ + [a.x as f32, a.y as f32, a.z as f32], + [b.x as f32, b.y as f32, b.z as f32], + ], WireModel::CYAN, false, ) @@ -111,40 +116,40 @@ fn wire_seg(a: Vec3, b: Vec3) -> WireModel { // ── Polygon geometry ─────────────────────────────────────────────────────── -fn poly_verts_xy(center: Vec3, vertex_r: f32, sides: u32, start_angle: f32) -> Vec<[f64; 2]> { +fn poly_verts_xy(center: DVec3, vertex_r: f64, sides: u32, start_angle: f64) -> Vec<[f64; 2]> { (0..sides) .map(|i| { - let a = start_angle + (i as f32) * TAU / sides as f32; + let a = start_angle + (i as f64) * TAU / sides as f64; [ - (center.x + vertex_r * a.cos()) as f64, - (center.y + vertex_r * a.sin()) as f64, + center.x + vertex_r * a.cos(), + center.y + vertex_r * a.sin(), ] }) .collect() } -fn poly_wire(center: Vec3, vertex_r: f32, sides: u32, start_angle: f32) -> WireModel { +fn poly_wire(center: DVec3, vertex_r: f64, sides: u32, start_angle: f64) -> WireModel { let pts: Vec<[f32; 3]> = (0..sides) .map(|i| { - let a = start_angle + (i as f32) * TAU / sides as f32; + let a = start_angle + (i as f64) * TAU / sides as f64; [ - center.x + vertex_r * a.cos(), - center.y + vertex_r * a.sin(), - center.z, + (center.x + vertex_r * a.cos()) as f32, + (center.y + vertex_r * a.sin()) as f32, + center.z as f32, ] }) .collect(); wire_loop(pts) } -fn angle_xy(from: Vec3, to: Vec3) -> f32 { +fn angle_xy(from: DVec3, to: DVec3) -> f64 { (to.y - from.y).atan2(to.x - from.x) } // ── Command: Rectangle — Two Corners (RECT) ────────────────────────────── pub struct RectCommand { - a: Option, + a: Option, ucs: Mat4, } @@ -168,7 +173,7 @@ impl CadCommand for RectCommand { "RECT Specify opposite corner:".into() } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.a { None => { self.a = Some(pt); @@ -177,10 +182,10 @@ impl CadCommand for RectCommand { Some(a) => { let c = ucs_box_corners(a, pt, self.ucs); CmdResult::CommitAndExit(make_pline(&[ - [c[0].x as f64, c[0].y as f64], - [c[1].x as f64, c[1].y as f64], - [c[2].x as f64, c[2].y as f64], - [c[3].x as f64, c[3].y as f64], + [c[0].x, c[0].y], + [c[1].x, c[1].y], + [c[2].x, c[2].y], + [c[3].x, c[3].y], ])) } } @@ -191,14 +196,14 @@ impl CadCommand for RectCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let a = self.a?; let c = ucs_box_corners(a, pt, self.ucs); Some(wire_loop(vec![ - [c[0].x, c[0].y, c[0].z], - [c[1].x, c[1].y, c[1].z], - [c[2].x, c[2].y, c[2].z], - [c[3].x, c[3].y, c[3].z], + [c[0].x as f32, c[0].y as f32, c[0].z as f32], + [c[1].x as f32, c[1].y as f32, c[1].z as f32], + [c[2].x as f32, c[2].y as f32, c[2].z as f32], + [c[3].x as f32, c[3].y as f32, c[3].z as f32], ])) } fn dyn_spec(&self) -> Option { @@ -225,16 +230,16 @@ impl CadCommand for RectCommand { pub struct RectRotCommand { step: u8, - a: Vec3, - b: Vec3, + a: DVec3, + b: DVec3, } impl RectRotCommand { pub fn new() -> Self { Self { step: 0, - a: Vec3::ZERO, - b: Vec3::ZERO, + a: DVec3::ZERO, + b: DVec3::ZERO, } } } @@ -250,7 +255,7 @@ impl CadCommand for RectRotCommand { _ => "RECT ROT Specify height (perpendicular pick):".into(), } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.a = pt; @@ -265,15 +270,15 @@ impl CadCommand for RectRotCommand { _ => { let (a, b) = (self.a, self.b); let dir = (b - a).normalize_or_zero(); - let perp = Vec3::new(-dir.y, dir.x, 0.0); + let perp = DVec3::new(-dir.y, dir.x, 0.0); let h = (pt - b).dot(perp); // signed height let c = b + perp * h; let d = a + perp * h; CmdResult::CommitAndExit(make_pline(&[ - [a.x as f64, a.y as f64], - [b.x as f64, b.y as f64], - [c.x as f64, c.y as f64], - [d.x as f64, d.y as f64], + [a.x, a.y], + [b.x, b.y], + [c.x, c.y], + [d.x, d.y], ])) } } @@ -284,21 +289,21 @@ impl CadCommand for RectRotCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { 1 => Some(wire_seg(self.a, pt)), 2 => { let (a, b) = (self.a, self.b); let dir = (b - a).normalize_or_zero(); - let perp = Vec3::new(-dir.y, dir.x, 0.0); + let perp = DVec3::new(-dir.y, dir.x, 0.0); let h = (pt - b).dot(perp); let c = b + perp * h; let d = a + perp * h; Some(wire_loop(vec![ - [a.x, a.y, a.z], - [b.x, b.y, b.z], - [c.x, c.y, c.z], - [d.x, d.y, d.z], + [a.x as f32, a.y as f32, a.z as f32], + [b.x as f32, b.y as f32, b.z as f32], + [c.x as f32, c.y as f32, c.z as f32], + [d.x as f32, d.y as f32, d.z as f32], ])) } _ => None, @@ -319,12 +324,12 @@ impl CadCommand for RectRotCommand { }) } - fn dyn_live_value(&self, cursor: Vec3) -> Option { + fn dyn_live_value(&self, cursor: DVec3) -> Option { // Live height = perpendicular distance from the cursor to the base edge. (self.step == 2).then(|| { let dir = (self.b - self.a).normalize_or_zero(); - let perp = Vec3::new(-dir.y, dir.x, 0.0); - (cursor - self.b).dot(perp).abs() as f64 + let perp = DVec3::new(-dir.y, dir.x, 0.0); + (cursor - self.b).dot(perp).abs() }) } } @@ -334,7 +339,7 @@ impl CadCommand for RectRotCommand { // Step 1: pick any corner (half-width = |cx|, half-height = |cy|) pub struct RectCenCommand { - center: Option, + center: Option, ucs: Mat4, } @@ -358,7 +363,7 @@ impl CadCommand for RectCenCommand { "RECT CEN Specify corner point:".into() } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.center { None => { self.center = Some(pt); @@ -367,10 +372,10 @@ impl CadCommand for RectCenCommand { Some(c) => { let q = ucs_box_around_center(c, pt, self.ucs); CmdResult::CommitAndExit(make_pline(&[ - [q[0].x as f64, q[0].y as f64], - [q[1].x as f64, q[1].y as f64], - [q[2].x as f64, q[2].y as f64], - [q[3].x as f64, q[3].y as f64], + [q[0].x, q[0].y], + [q[1].x, q[1].y], + [q[2].x, q[2].y], + [q[3].x, q[3].y], ])) } } @@ -381,14 +386,14 @@ impl CadCommand for RectCenCommand { fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let c = self.center?; let q = ucs_box_around_center(c, pt, self.ucs); Some(wire_loop(vec![ - [q[0].x, q[0].y, q[0].z], - [q[1].x, q[1].y, q[1].z], - [q[2].x, q[2].y, q[2].z], - [q[3].x, q[3].y, q[3].z], + [q[0].x as f32, q[0].y as f32, q[0].z as f32], + [q[1].x as f32, q[1].y as f32, q[1].z as f32], + [q[2].x as f32, q[2].y as f32, q[2].z as f32], + [q[3].x as f32, q[3].y as f32, q[3].z as f32], ])) } fn dyn_spec(&self) -> Option { @@ -414,7 +419,7 @@ impl CadCommand for RectCenCommand { pub struct PolyCommand { sides: u32, step: u8, - center: Vec3, + center: DVec3, } impl PolyCommand { @@ -422,7 +427,7 @@ impl PolyCommand { Self { sides: 6, step: 0, - center: Vec3::ZERO, + center: DVec3::ZERO, } } } @@ -479,7 +484,7 @@ impl CadCommand for PolyCommand { } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { // User clicked without typing sides: use default, treat click as center. @@ -511,7 +516,7 @@ impl CadCommand for PolyCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if self.step < 2 { return None; } @@ -528,7 +533,7 @@ impl CadCommand for PolyCommand { pub struct PolyCCommand { sides: u32, step: u8, - center: Vec3, + center: DVec3, } impl PolyCCommand { @@ -536,7 +541,7 @@ impl PolyCCommand { Self { sides: 6, step: 0, - center: Vec3::ZERO, + center: DVec3::ZERO, } } } @@ -593,7 +598,7 @@ impl CadCommand for PolyCCommand { } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.center = pt; @@ -607,11 +612,11 @@ impl CadCommand for PolyCCommand { } _ => { let inradius = self.center.distance(pt); - let vr = inradius / (PI / self.sides as f32).cos(); + let vr = inradius / (PI / self.sides as f64).cos(); // The picked pt is at the midpoint of an edge; the vertex is // offset by half a sector (π/N) from that direction. let edge_angle = angle_xy(self.center, pt); - let sa = edge_angle + PI / self.sides as f32; + let sa = edge_angle + PI / self.sides as f64; CmdResult::CommitAndExit(make_pline(&poly_verts_xy( self.center, vr, @@ -633,13 +638,13 @@ impl CadCommand for PolyCCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if self.step < 2 { return None; } let inradius = self.center.distance(pt); - let vr = inradius / (PI / self.sides as f32).cos(); - let sa = angle_xy(self.center, pt) + PI / self.sides as f32; + let vr = inradius / (PI / self.sides as f64).cos(); + let sa = angle_xy(self.center, pt) + PI / self.sides as f64; Some(poly_wire(self.center, vr, self.sides, sa)) } } @@ -651,7 +656,7 @@ impl CadCommand for PolyCCommand { pub struct PolyECommand { sides: u32, step: u8, - a: Vec3, + a: DVec3, } impl PolyECommand { @@ -659,7 +664,7 @@ impl PolyECommand { Self { sides: 6, step: 0, - a: Vec3::ZERO, + a: DVec3::ZERO, } } } @@ -705,7 +710,7 @@ impl CadCommand for PolyECommand { } } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { 0 => { self.a = pt; @@ -738,7 +743,7 @@ impl CadCommand for PolyECommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: Vec3) -> Option { + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if self.step < 2 { return None; } @@ -752,18 +757,18 @@ impl CadCommand for PolyECommand { /// Compute polygon center, vertex-radius and start-angle from two edge endpoints. /// The polygon is placed on the left side of A→B (CCW convention). -fn edge_poly_params(a: Vec3, b: Vec3, sides: u32) -> Option<(Vec3, f32, f32)> { +fn edge_poly_params(a: DVec3, b: DVec3, sides: u32) -> Option<(DVec3, f64, f64)> { let edge_len = a.distance(b); if edge_len < 1e-6 { return None; } // vertex_radius = edge_len / (2 * sin(π/N)) - let vr = edge_len / (2.0 * (PI / sides as f32).sin()); + let vr = edge_len / (2.0 * (PI / sides as f64).sin()); // inradius = vr * cos(π/N) = edge_len / (2 * tan(π/N)) - let inradius = vr * (PI / sides as f32).cos(); + let inradius = vr * (PI / sides as f64).cos(); // Center: on the left perpendicular bisector of A→B let dir = (b - a) / edge_len; - let perp = Vec3::new(-dir.y, dir.x, 0.0); // CCW left + let perp = DVec3::new(-dir.y, dir.x, 0.0); // CCW left let mid = (a + b) * 0.5; let center = mid + perp * inradius; // First vertex = A diff --git a/src/modules/draw/modify/array.rs b/src/modules/draw/modify/array.rs index 8461fe525..9bea9927d 100644 --- a/src/modules/draw/modify/array.rs +++ b/src/modules/draw/modify/array.rs @@ -14,7 +14,7 @@ // 1. Center point → 2. Item count (text) → 3. Total angle in degrees (text) use acadrust::Handle; -use glam::{Mat4, Vec3}; +use glam::{DVec3, Mat4, Vec3}; use crate::command::{CadCommand, CmdResult, EntityTransform}; use crate::modules::draw::defaults; @@ -94,8 +94,8 @@ impl ArrayRectCommand { if r == 0 && c == 0 { continue; } - t.push(EntityTransform::Translate(ucs.transform_vector3( - Vec3::new(col_sp * c as f32, row_sp * r as f32, 0.0), + t.push(EntityTransform::Translate(ucs.as_dmat4().transform_vector3( + glam::DVec3::new((col_sp * c as f32) as f64, (row_sp * r as f32) as f64, 0.0), ))); } } @@ -194,7 +194,7 @@ impl CadCommand for ArrayRectCommand { } } - fn on_preview_wires(&mut self, _pt: Vec3) -> Vec { + fn on_preview_wires(&mut self, _pt: DVec3) -> Vec { let (rows, cols, row_sp, col_sp) = match self.step { RectStep::Rows => ( self.default_rows, @@ -219,7 +219,7 @@ impl CadCommand for ArrayRectCommand { if let EntityTransform::Translate(delta) = t { self.wire_models .iter() - .map(|w| w.translated(*delta)) + .map(|w| w.translated(delta.as_vec3())) .collect::>() } else { vec![] @@ -228,7 +228,7 @@ impl CadCommand for ArrayRectCommand { .collect() } - fn on_point(&mut self, _pt: Vec3) -> CmdResult { + fn on_point(&mut self, _pt: DVec3) -> CmdResult { CmdResult::NeedPoint } @@ -331,6 +331,7 @@ impl CadCommand for ArrayPolarCommand { v }; let step_rad = total_deg.to_radians() / count as f32; + let center = center.as_dvec3(); let transforms = (1..count) .map(|n| EntityTransform::Rotate { center, @@ -343,7 +344,8 @@ impl CadCommand for ArrayPolarCommand { } } - fn on_preview_wires(&mut self, pt: Vec3) -> Vec { + fn on_preview_wires(&mut self, pt: DVec3) -> Vec { + let pt = pt.as_vec3(); let (center, count, total_deg) = match &self.step { PolarStep::Center => (pt, self.default_count, self.default_angle), PolarStep::Count { center } => (*center, self.default_count, self.default_angle), @@ -370,7 +372,8 @@ impl CadCommand for ArrayPolarCommand { out } - fn on_point(&mut self, pt: Vec3) -> CmdResult { + fn on_point(&mut self, pt: DVec3) -> CmdResult { + let pt = pt.as_vec3(); if let PolarStep::Center = self.step { self.step = PolarStep::Count { center: pt }; } @@ -655,13 +658,13 @@ impl ArrayPathCommand { .map(|(i, &p)| { let dth = tans[i] - t0; if dth.abs() < 1e-5 { - EntityTransform::Translate(p - p0) + EntityTransform::Translate((p - p0).as_dvec3()) } else { // The aligned copy is the rigid motion x' = p + R(x - p0), // which is a pure rotation by `dth` about its fixed point. let center = Self::rigid_center(p0, p, dth); EntityTransform::Rotate { - center, + center: center.as_dvec3(), angle_rad: dth, } } @@ -720,7 +723,8 @@ impl CadCommand for ArrayPathCommand { } } - fn on_entity_pick(&mut self, handle: Handle, pt: Vec3) -> CmdResult { + fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult { + let pt = pt.as_vec3(); if handle.is_null() || self.handles.contains(&handle) { return CmdResult::NeedPoint; } @@ -738,7 +742,7 @@ impl CadCommand for ArrayPathCommand { CmdResult::NeedPoint } - fn on_hover_entity(&mut self, handle: Handle, _pt: Vec3) -> Vec { + fn on_hover_entity(&mut self, handle: Handle, _pt: DVec3) -> Vec { if handle.is_null() || self.handles.contains(&handle) { return vec![]; } @@ -784,7 +788,7 @@ impl CadCommand for ArrayPathCommand { Some(CmdResult::BatchCopy(self.handles.clone(), transforms)) } - fn on_preview_wires(&mut self, _pt: Vec3) -> Vec { + fn on_preview_wires(&mut self, _pt: DVec3) -> Vec { let PathStep::Count { path_entity } = &self.step else { return vec![]; }; @@ -796,19 +800,19 @@ impl CadCommand for ArrayPathCommand { EntityTransform::Translate(delta) => self .wire_models .iter() - .map(|w| w.translated(*delta)) + .map(|w| w.translated(delta.as_vec3())) .collect::>(), EntityTransform::Rotate { center, angle_rad } => self .wire_models .iter() - .map(|w| w.rotated(*center, *angle_rad)) + .map(|w| w.rotated(center.as_vec3(), *angle_rad)) .collect::>(), _ => vec![], }) .collect() } - fn on_point(&mut self, _pt: Vec3) -> CmdResult { + fn on_point(&mut self, _pt: DVec3) -> CmdResult { CmdResult::NeedPoint } @@ -885,10 +889,10 @@ impl Array3DCommand { } // Drawing plane is world XY: X = col dir, Y = row dir, // Z = level (elevation). - t.push(EntityTransform::Translate(Vec3::new( - col_sp * c as f32, - row_sp * r as f32, - lvl_sp * l as f32, + t.push(EntityTransform::Translate(DVec3::new( + (col_sp * c as f32) as f64, + (row_sp * r as f32) as f64, + (lvl_sp * l as f32) as f64, ))); } } @@ -1025,7 +1029,7 @@ impl CadCommand for Array3DCommand { } } - fn on_point(&mut self, _pt: Vec3) -> CmdResult { + fn on_point(&mut self, _pt: DVec3) -> CmdResult { CmdResult::NeedPoint } @@ -1037,7 +1041,7 @@ impl CadCommand for Array3DCommand { CmdResult::Cancel } - fn on_preview_wires(&mut self, _pt: Vec3) -> Vec { + fn on_preview_wires(&mut self, _pt: DVec3) -> Vec { vec![] } } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index b60b3836f..75b3f716a 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -3594,6 +3594,11 @@ impl Scene { /// geometry of the currently active viewport. Returns the input unchanged /// when there is no active viewport. pub fn paper_to_model(&self, paper_pt: glam::Vec3) -> glam::Vec3 { + self.paper_to_model_f64(paper_pt.as_dvec3()).as_vec3() + } + + /// f64 variant of [`paper_to_model`] — preserves UTM-scale precision. + pub fn paper_to_model_f64(&self, paper_pt: glam::DVec3) -> glam::DVec3 { let vp_handle = match self.active_viewport { Some(h) => h, None => return paper_pt, @@ -3602,16 +3607,15 @@ impl Scene { Some(acadrust::EntityType::Viewport(vp)) => vp, _ => return paper_pt, }; - let scale = - vp_effective_scale(vp.custom_scale, vp.view_height, vp.height) as f32; + let scale = vp_effective_scale(vp.custom_scale, vp.view_height, vp.height); if scale.abs() < 1e-9 { return paper_pt; } - let tx = vp.view_target.x as f32; - let ty = vp.view_target.y as f32; - let pcx = vp.center.x as f32; - let pcy = vp.center.y as f32; - glam::Vec3::new( + let tx = vp.view_target.x; + let ty = vp.view_target.y; + let pcx = vp.center.x; + let pcy = vp.center.y; + glam::DVec3::new( (paper_pt.x - pcx) / scale + tx, (paper_pt.y - pcy) / scale + ty, paper_pt.z, diff --git a/src/scene/model/object.rs b/src/scene/model/object.rs index 18d8e86f6..d1575f01d 100644 --- a/src/scene/model/object.rs +++ b/src/scene/model/object.rs @@ -1,7 +1,7 @@ // Shared value types used by the dispatch and grip systems. use acadrust::types::{Color as AcadColor, LineWeight}; -use glam::Vec3; +use glam::{DVec3, Vec3}; /// The kind of value held by a property row. #[derive(Clone, Debug, PartialEq)] @@ -96,9 +96,9 @@ pub struct GripDef { #[derive(Clone, Debug)] pub enum GripApply { /// Move a specific vertex to this absolute world position. - Absolute(Vec3), + Absolute(DVec3), /// Translate the whole object by this delta vector. - Translate(Vec3), + Translate(DVec3), } /// One entry in the hover-popup menu that opens when the cursor dwells diff --git a/src/scene/pick/grip.rs b/src/scene/pick/grip.rs index 306d55ad7..2f2bae2cf 100644 --- a/src/scene/pick/grip.rs +++ b/src/scene/pick/grip.rs @@ -1,7 +1,7 @@ //! OpenCADStudio-style grip editing. use acadrust::Handle; -use glam::Vec3; +use glam::{DVec3, Vec3}; use iced::{Point, Rectangle}; use crate::scene::model::object::{GripDef, GripShape}; @@ -23,9 +23,9 @@ pub struct GripEdit { /// `true` → midpoint / translate grip; `false` → endpoint / absolute grip. pub is_translate: bool, /// World-space position of the grip when the drag started (ortho/polar base). - pub origin_world: Vec3, + pub origin_world: DVec3, /// Last world-space cursor position (needed for incremental delta on translate drags). - pub last_world: Vec3, + pub last_world: DVec3, } // ── Screen-space helpers ─────────────────────────────────────────────────── @@ -83,9 +83,9 @@ pub fn find_hit_grip_paper( half_w: f32, half_h: f32, bounds: Rectangle, -) -> Option<(usize, bool, Vec3)> { +) -> Option<(usize, bool, DVec3)> { let mut best_dist = GRIP_THRESHOLD_PX; - let mut best: Option<(usize, bool, Vec3)> = None; + let mut best: Option<(usize, bool, DVec3)> = None; for g in grips { let screen = Point::new( @@ -97,7 +97,7 @@ pub fn find_hit_grip_paper( let d = (dx * dx + dy * dy).sqrt(); if d < best_dist { best_dist = d; - best = Some((g.id, g.is_midpoint, g.world.as_vec3())); + best = Some((g.id, g.is_midpoint, g.world)); } } best @@ -110,9 +110,9 @@ pub fn find_hit_grip( grips: &[GripDef], camera: &crate::scene::view::camera::Camera, bounds: Rectangle, -) -> Option<(usize, bool, Vec3)> { +) -> Option<(usize, bool, DVec3)> { let mut best_dist = GRIP_THRESHOLD_PX; - let mut best: Option<(usize, bool, Vec3)> = None; + let mut best: Option<(usize, bool, DVec3)> = None; for g in grips { let Some(screen) = camera.project_f64(g.world, bounds) else { @@ -124,7 +124,7 @@ pub fn find_hit_grip( let d = (dx * dx + dy * dy).sqrt(); if d < best_dist { best_dist = d; - best = Some((g.id, g.is_midpoint, g.world.as_vec3())); + best = Some((g.id, g.is_midpoint, g.world)); } } best diff --git a/src/scene/view/camera.rs b/src/scene/view/camera.rs index 8fbac370c..b5303c74a 100644 --- a/src/scene/view/camera.rs +++ b/src/scene/view/camera.rs @@ -233,9 +233,24 @@ impl Camera { } pub fn pick_on_target_plane(&self, screen: Point, bounds: Rectangle) -> Vec3 { + self.pick_on_target_plane_f64(screen, bounds).as_vec3() + } + + /// f64 variant of [`pick_on_plane`] — keeps the cursor precise at UTM-scale. + pub fn pick_on_plane_f64( + &self, + screen: Point, + bounds: Rectangle, + plane_normal: Vec3, + plane_point: glam::DVec3, + ) -> glam::DVec3 { + self.unproject_on_plane_f64(screen, bounds, plane_normal, plane_point) + } + + /// f64 variant of [`pick_on_target_plane`]. + pub fn pick_on_target_plane_f64(&self, screen: Point, bounds: Rectangle) -> glam::DVec3 { let forward = (self.target.as_vec3() - self.eye()).normalize_or(Vec3::NEG_Z); self.unproject_on_plane_f64(screen, bounds, forward, self.target) - .as_vec3() } diff --git a/src/scene/view/transform.rs b/src/scene/view/transform.rs index 965838993..8f3853833 100644 --- a/src/scene/view/transform.rs +++ b/src/scene/view/transform.rs @@ -1,14 +1,14 @@ use acadrust::types::{Transform, Vector3}; -use glam::Vec3; +use glam::DVec3; use crate::command::EntityTransform; #[inline] -fn to_v3(v: Vec3) -> Vector3 { - Vector3::new(v.x as f64, v.y as f64, v.z as f64) +fn to_v3(v: DVec3) -> Vector3 { + Vector3::new(v.x, v.y, v.z) } -pub fn apply_standard_transform(entity: &mut T, center: Vec3, angle_rad: f32) +pub fn apply_standard_transform(entity: &mut T, center: DVec3, angle_rad: f32) where T: acadrust::Entity, { @@ -19,7 +19,7 @@ where entity.apply_transform(&t); } -pub fn apply_standard_scale(entity: &mut T, center: Vec3, factor: f32) +pub fn apply_standard_scale(entity: &mut T, center: DVec3, factor: f32) where T: acadrust::Entity, { @@ -31,7 +31,7 @@ where pub fn apply_standard_entity_transform(entity: &mut T, t: &EntityTransform, mirror: F) where T: acadrust::Entity, - F: FnOnce(&mut T, Vec3, Vec3), + F: FnOnce(&mut T, DVec3, DVec3), { match t { EntityTransform::Translate(d) => entity.translate(to_v3(*d)), @@ -47,7 +47,7 @@ where /// `Transform`, for delegating MIRROR to acadrust's entity-aware /// `apply_transform` paths (which handle direction flags, stored-angle /// conventions and bulges themselves). Degenerate line → identity. -pub fn reflection_about_xy_line(p1: Vec3, p2: Vec3) -> acadrust::types::Transform { +pub fn reflection_about_xy_line(p1: DVec3, p2: DVec3) -> acadrust::types::Transform { use acadrust::types::{Matrix4, Transform}; let dx = (p2.x - p1.x) as f64; let dy = (p2.y - p1.y) as f64; @@ -63,7 +63,7 @@ pub fn reflection_about_xy_line(p1: Vec3, p2: Vec3) -> acadrust::types::Transfor Transform::from_matrix(m) } -pub fn reflect_xy_point(x: &mut f64, y: &mut f64, p1: Vec3, p2: Vec3) { +pub fn reflect_xy_point(x: &mut f64, y: &mut f64, p1: DVec3, p2: DVec3) { let ax = (p2.x - p1.x) as f64; let ay = (p2.y - p1.y) as f64; let len2 = ax * ax + ay * ay; @@ -79,7 +79,7 @@ pub fn reflect_xy_point(x: &mut f64, y: &mut f64, p1: Vec3, p2: Vec3) { *y = p1.y as f64 + my; } -pub fn mirror_xy_line(line: &mut acadrust::entities::Line, p1: Vec3, p2: Vec3) { +pub fn mirror_xy_line(line: &mut acadrust::entities::Line, p1: DVec3, p2: DVec3) { reflect_xy_point(&mut line.start.x, &mut line.start.y, p1, p2); reflect_xy_point(&mut line.end.x, &mut line.end.y, p1, p2); } @@ -171,8 +171,8 @@ mod mirror_delegation_tests { // Mirror across the vertical line x = 5. h.apply_transform(&EntityTransform::Mirror { - p1: Vec3::new(5.0, 0.0, 0.0), - p2: Vec3::new(5.0, 1.0, 0.0), + p1: DVec3::new(5.0, 0.0, 0.0), + p2: DVec3::new(5.0, 1.0, 0.0), }); let edges = &h.paths[0].edges; diff --git a/src/snap/mod.rs b/src/snap/mod.rs index 331601140..ab5ab9cb5 100644 --- a/src/snap/mod.rs +++ b/src/snap/mod.rs @@ -57,7 +57,7 @@ pub const ALL_SNAP_MODES: &[(SnapType, &str, &str)] = &[ #[derive(Debug, Clone, Copy)] pub struct SnapResult { - pub world: Vec3, + pub world: glam::DVec3, pub screen: Point, pub snap_type: SnapType, /// Set when `snap_type == Tangent`; provides entity geometry for TTR/TTT. @@ -203,8 +203,8 @@ impl Snapper { Some(p) => { // Convert to screen to measure pixel distance. let is_same = if let Some(prev) = self.last_snap_world { - let dp = world_to_screen(p, view_rot, eye, bounds); - let dp2 = world_to_screen(prev, view_rot, eye, bounds); + let dp = world_to_screen(p.as_dvec3(), view_rot, eye, bounds); + let dp2 = world_to_screen(prev.as_dvec3(), view_rot, eye, bounds); let dx = dp.x - dp2.x; let dy = dp.y - dp2.y; (dx * dx + dy * dy).sqrt() < DWELL_PX @@ -273,11 +273,11 @@ impl Snapper { return None; } - let cursor_screen = world_to_screen(cursor_world, view_rot, eye, bounds); + let cursor_screen = world_to_screen(cursor_world.as_dvec3(), view_rot, eye, bounds); // Use the same aperture as OSNAP so the catch distance is uniform. let r = self.osnap_radius_px; let screen_dist = |w: Vec3| { - let s = world_to_screen(w, view_rot, eye, bounds); + let s = world_to_screen(w.as_dvec3(), view_rot, eye, bounds); ((s.x - cursor_screen.x).powi(2) + (s.y - cursor_screen.y).powi(2)).sqrt() }; @@ -499,7 +499,7 @@ impl Snapper { && cursor_world.y - r <= wire.aabb[3] }; - let mut try_pt = |world: Vec3, snap_type: SnapType| { + let mut try_pt = |world: glam::DVec3, snap_type: SnapType| { let screen = world_to_screen(world, view_rot, eye, bounds); let d2 = dist2(screen, cursor_screen); // `!(d2 < radius2)` (not `d2 >= radius2`) so a NaN distance from @@ -533,7 +533,7 @@ impl Snapper { SnapHint::Midpoint => SnapType::Midpoint, }; if self.is_on(snap_type) { - try_pt(world.as_vec3(), snap_type); + try_pt(world, snap_type); } } } @@ -548,18 +548,18 @@ impl Snapper { // Use explicit vertices (Line, LwPolyline): every vertex is an endpoint. for &p in &wire.key_vertices { try_pt( - Vec3::new(p[0] as f32, p[1] as f32, p[2] as f32), + glam::DVec3::new(p[0], p[1], p[2]), SnapType::Endpoint, ); } } else { // Tessellated curves (Circle, Arc, Ellipse): only arc endpoints. if let Some(&p) = wire.points.first() { - try_pt(Vec3::from(p), SnapType::Endpoint); + try_pt(glam::DVec3::new(p[0] as f64, p[1] as f64, p[2] as f64), SnapType::Endpoint); } if wire.points.len() > 1 { if let Some(&p) = wire.points.last() { - try_pt(Vec3::from(p), SnapType::Endpoint); + try_pt(glam::DVec3::new(p[0] as f64, p[1] as f64, p[2] as f64), SnapType::Endpoint); } } } @@ -582,7 +582,7 @@ impl Snapper { let a = Vec3::new(seg[0][0] as f32, seg[0][1] as f32, seg[0][2] as f32); let b = Vec3::new(seg[1][0] as f32, seg[1][1] as f32, seg[1][2] as f32); if a.distance_squared(b) > 1e-12 { - try_pt((a + b) * 0.5, SnapType::Midpoint); + try_pt(((a + b) * 0.5).as_dvec3(), SnapType::Midpoint); } } } @@ -598,7 +598,7 @@ impl Snapper { for seg in wire.points.windows(2) { let p = nearest_on_segment(cursor_world, Vec3::from(seg[0]), Vec3::from(seg[1])); - try_pt(p, SnapType::Nearest); + try_pt(p.as_dvec3(), SnapType::Nearest); } } } @@ -618,7 +618,7 @@ impl Snapper { } for seg in wire.points.windows(2) { if let Some(foot) = perp_foot(q, Vec3::from(seg[0]), Vec3::from(seg[1])) { - try_pt(foot, SnapType::Perpendicular); + try_pt(foot.as_dvec3(), SnapType::Perpendicular); } } } @@ -654,7 +654,7 @@ impl Snapper { continue; } if let Some(pt) = seg_intersect_xy(a0, a1, b0, b1) { - try_pt(pt, SnapType::Intersection); + try_pt(pt.as_dvec3(), SnapType::Intersection); } } } @@ -682,7 +682,7 @@ impl Snapper { bounds, self.osnap_radius_px, ) { - try_pt(ext, SnapType::Extension); + try_pt(ext.as_dvec3(), SnapType::Extension); } } // Extend beyond the last point. @@ -698,7 +698,7 @@ impl Snapper { bounds, self.osnap_radius_px, ) { - try_pt(ext, SnapType::Extension); + try_pt(ext.as_dvec3(), SnapType::Extension); } } } @@ -716,7 +716,7 @@ impl Snapper { Some( w.points .iter() - .map(|&p| world_to_screen(Vec3::from(p), view_rot, eye, bounds)) + .map(|&p| world_to_screen(glam::DVec3::new(p[0] as f64, p[1] as f64, p[2] as f64), view_rot, eye, bounds)) .collect::>(), ) }) @@ -739,7 +739,7 @@ impl Snapper { if let Some((ta, _)) = seg_intersect_2d(sa0, sa1, sb0, sb1) { let wa0 = Vec3::from(seg_a[0]); let wa1 = Vec3::from(seg_a[1]); - try_pt(wa0 + ta * (wa1 - wa0), SnapType::ApparentIntersection); + try_pt((wa0 + ta * (wa1 - wa0)).as_dvec3(), SnapType::ApparentIntersection); } } } @@ -758,7 +758,7 @@ impl Snapper { let ux = (rel.dot(ax) / s).round() * s; let uy = (rel.dot(ay) / s).round() * s; let uz = (rel.dot(az) / s).round() * s; - try_pt(grid_origin + ax * ux + ay * uy + az * uz, SnapType::Grid); + try_pt((grid_origin + ax * ux + ay * uy + az * uz).as_dvec3(), SnapType::Grid); } // ── Tangent ──────────────────────────────────────────────────────── @@ -769,8 +769,8 @@ impl Snapper { for tg in &wire.tangent_geoms { let (world_pt, d2) = match tg { TangentGeom::Line { p1, p2 } => { - let sp0 = world_to_screen(Vec3::from(*p1), view_rot, eye, bounds); - let sp1 = world_to_screen(Vec3::from(*p2), view_rot, eye, bounds); + let sp0 = world_to_screen(glam::DVec3::new(p1[0] as f64, p1[1] as f64, p1[2] as f64), view_rot, eye, bounds); + let sp1 = world_to_screen(glam::DVec3::new(p2[0] as f64, p2[1] as f64, p2[2] as f64), view_rot, eye, bounds); let d2 = dist2_to_segment(cursor_screen, sp0, sp1); let t = t_on_segment(cursor_screen, sp0, sp1); let w = Vec3::from(*p1) + t * (Vec3::from(*p2) - Vec3::from(*p1)); @@ -778,9 +778,9 @@ impl Snapper { } TangentGeom::Circle { center, radius } => { let cv = Vec3::from(*center); - let sc = world_to_screen(cv, view_rot, eye, bounds); + let sc = world_to_screen(cv.as_dvec3(), view_rot, eye, bounds); let rim = world_to_screen( - Vec3::new(cv.x + radius, cv.y, cv.z), + glam::DVec3::new((cv.x + radius) as f64, cv.y as f64, cv.z as f64), view_rot, eye, bounds, @@ -805,19 +805,19 @@ impl Snapper { if d2 < radius2 && (rank < best_rank || (rank == best_rank && d2 < best_d2)) { best_rank = rank; best_d2 = d2; - let screen_pt = world_to_screen(world_pt, view_rot, eye, bounds); + let screen_pt = world_to_screen(world_pt.as_dvec3(), view_rot, eye, bounds); let tangent_obj = match tg { TangentGeom::Line { p1, p2 } => TangentObject::Line { - p1: Vec3::from(*p1), - p2: Vec3::from(*p2), + p1: glam::DVec3::new(p1[0] as f64, p1[1] as f64, p1[2] as f64), + p2: glam::DVec3::new(p2[0] as f64, p2[1] as f64, p2[2] as f64), }, TangentGeom::Circle { center, radius } => TangentObject::Circle { - center: Vec3::from(*center), - radius: *radius, + center: glam::DVec3::new(center[0] as f64, center[1] as f64, center[2] as f64), + radius: *radius as f64, }, }; best = Some(SnapResult { - world: world_pt, + world: world_pt.as_dvec3(), screen: screen_pt, snap_type: SnapType::Tangent, tangent_obj: Some(tangent_obj), @@ -962,8 +962,8 @@ fn extension_snap( return None; } // only beyond the endpoint let world_pt = Vec3::new(origin.x + t * dir.x, origin.y + t * dir.y, origin.z); - let screen_pt = world_to_screen(world_pt, view_rot, eye, bounds); - let cursor_screen = world_to_screen(cursor_world, view_rot, eye, bounds); + let screen_pt = world_to_screen(world_pt.as_dvec3(), view_rot, eye, bounds); + let cursor_screen = world_to_screen(cursor_world.as_dvec3(), view_rot, eye, bounds); if dist2(screen_pt, cursor_screen) > radius_px * radius_px { return None; } @@ -976,8 +976,8 @@ fn extension_snap( /// so the result is precise at UTM-scale absolute coordinates (a full /// view-projection with a ~1e7 translation cancels catastrophically in f32). /// `view_rot` is the rotation-only view-projection (Camera::view_proj_rte). -fn world_to_screen(world: Vec3, view_rot: Mat4, eye: glam::DVec3, bounds: Rectangle) -> Point { - let rel = (world.as_dvec3() - eye).as_vec3(); +fn world_to_screen(world: glam::DVec3, view_rot: Mat4, eye: glam::DVec3, bounds: Rectangle) -> Point { + let rel = (world - eye).as_vec3(); let ndc = view_rot.project_point3(rel); Point::new( (ndc.x + 1.0) * 0.5 * bounds.width,