forked from 1b-agent/opencadstudio-upstream
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
96aaf2a293
commit
3da71698b1
18 files changed
+626
-587
No files matched your search
+127
-118
@@ -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<Vec3>,
|
||||
c: DVec3,
|
||||
r: f64,
|
||||
sa: f64,
|
||||
prev_pt: Option<DVec3>,
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<Vec3>,
|
||||
pts: Vec<DVec3>,
|
||||
}
|
||||
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<Vec3>,
|
||||
s: DVec3,
|
||||
c: DVec3,
|
||||
r: f64,
|
||||
sa: f64,
|
||||
prev_pt: Option<DVec3>,
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<Vec3>,
|
||||
s: DVec3,
|
||||
c: DVec3,
|
||||
r: f64,
|
||||
sa: f64,
|
||||
prev_pt: Option<DVec3>,
|
||||
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<CmdResult> {
|
||||
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<f64> {
|
||||
(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<f64> {
|
||||
(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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<CmdResult> {
|
||||
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<f64> {
|
||||
(self.step == 2).then(|| self.s.distance(cursor) as f64)
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
(self.step == 2).then(|| self.s.distance(cursor))
|
||||
}
|
||||
fn on_mouse_move(&mut self, pt: Vec3) -> Option<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<CmdResult> {
|
||||
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<f64> {
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<Vec3>,
|
||||
c: DVec3,
|
||||
r: f64,
|
||||
sa: f64,
|
||||
prev_pt: Option<DVec3>,
|
||||
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<CmdResult> {
|
||||
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<f64> {
|
||||
(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<f64> {
|
||||
(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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<CmdResult> {
|
||||
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<f64> {
|
||||
(self.step == 2).then(|| self.s.distance(cursor) as f64)
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
(self.step == 2).then(|| self.s.distance(cursor))
|
||||
}
|
||||
fn on_mouse_move(&mut self, pt: Vec3) -> Option<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
match self.step {
|
||||
1 => Some(line_wire(self.c, pt)),
|
||||
2 => {
|
||||
|
||||
@@ -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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<CmdResult> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
// 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<CmdResult> {
|
||||
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<Vec3>,
|
||||
p1: Option<DVec3>,
|
||||
}
|
||||
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<Vec3>,
|
||||
pts: Vec<DVec3>,
|
||||
}
|
||||
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<Vec3> {
|
||||
fn line_line_isect(l1: Line2D, l2: Line2D) -> Option<DVec3> {
|
||||
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<f32> {
|
||||
fn solve_quadratic(a: f64, b: f64, c: f64) -> Vec<f64> {
|
||||
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<f32> {
|
||||
vec![(-b - sq) / (2.0 * a), (-b + sq) / (2.0 * a)]
|
||||
}
|
||||
|
||||
fn best_of(candidates: &[Vec3], hint: Vec3) -> Option<Vec3> {
|
||||
fn best_of(candidates: &[DVec3], hint: DVec3) -> Option<DVec3> {
|
||||
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<Vec3> {
|
||||
})
|
||||
}
|
||||
|
||||
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<Vec3> {
|
||||
fn ttr_candidates(obj1: TangentObject, obj2: TangentObject, r: f64) -> Vec<DVec3> {
|
||||
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<Vec3>
|
||||
}
|
||||
}
|
||||
|
||||
fn ttr_lc_candidates(l: Line2D, cp: Vec3, cr: f32, r: f32) -> Vec<Vec3> {
|
||||
fn ttr_lc_candidates(l: Line2D, cp: DVec3, cr: f64, r: f64) -> Vec<DVec3> {
|
||||
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<Vec3> {
|
||||
// (-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<Vec3> {
|
||||
fn ttr_cc_candidates(c1: DVec3, r1: f64, c2: DVec3, r2: f64, r: f64) -> Vec<DVec3> {
|
||||
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<Vec3>
|
||||
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<Vec3>
|
||||
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<LinEq> = Vec::new();
|
||||
let mut circle_idx: Vec<usize> = 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<f32> = if lin_eqs.len() >= 3 {
|
||||
let r_vals: Vec<f64> = 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<CmdResult> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, _pt: DVec3) -> Option<WireModel> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -894,7 +898,7 @@ impl CadCommand for CircleTTRCommand {
|
||||
|
||||
pub struct CircleTTTCommand {
|
||||
objs: Vec<TangentObject>,
|
||||
hits: Vec<Vec3>,
|
||||
hits: Vec<DVec3>,
|
||||
}
|
||||
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, _pt: DVec3) -> Option<WireModel> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<f32> {
|
||||
fn parse_num(text: &str) -> Option<f64> {
|
||||
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<CmdResult> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<f64> {
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
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<CmdResult> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<f64> {
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
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<Vec3>,
|
||||
prev_pt: Option<DVec3>,
|
||||
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<CmdResult> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<f64> {
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
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,
|
||||
|
||||
@@ -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<Vec3>,
|
||||
a: Option<DVec3>,
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<crate::command::DynSpec> {
|
||||
@@ -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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<f64> {
|
||||
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
|
||||
// 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<Vec3>,
|
||||
center: Option<DVec3>,
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<crate::command::DynSpec> {
|
||||
@@ -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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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<WireModel> {
|
||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||
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
|
||||
|
||||
@@ -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<WireModel> {
|
||||
fn on_preview_wires(&mut self, _pt: DVec3) -> Vec<WireModel> {
|
||||
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::<Vec<_>>()
|
||||
} 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<WireModel> {
|
||||
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||
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<WireModel> {
|
||||
fn on_hover_entity(&mut self, handle: Handle, _pt: DVec3) -> Vec<WireModel> {
|
||||
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<WireModel> {
|
||||
fn on_preview_wires(&mut self, _pt: DVec3) -> Vec<WireModel> {
|
||||
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::<Vec<_>>(),
|
||||
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<_>>(),
|
||||
_ => 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<WireModel> {
|
||||
fn on_preview_wires(&mut self, _pt: DVec3) -> Vec<WireModel> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user