diff --git a/src/entities/hatch.rs b/src/entities/hatch.rs index deb8d1351..0dbcb665a 100644 --- a/src/entities/hatch.rs +++ b/src/entities/hatch.rs @@ -87,7 +87,7 @@ fn apply_transform(h: &mut Hatch, t: &EntityTransform) { if len2 < 1e-12 { return; } - let line_angle_deg = dy.atan2(dx).to_degrees(); + let line_angle = dy.atan2(dx); for path in &mut entity.paths { for edge in &mut path.edges { match edge { @@ -113,8 +113,8 @@ fn apply_transform(h: &mut Hatch, t: &EntityTransform) { p2, ); let tmp = a.start_angle; - a.start_angle = 2.0 * line_angle_deg - a.end_angle; - a.end_angle = 2.0 * line_angle_deg - tmp; + a.start_angle = 2.0 * line_angle - a.end_angle; + a.end_angle = 2.0 * line_angle - tmp; } BoundaryEdge::EllipticArc(e) => { crate::scene::transform::reflect_xy_point( @@ -131,8 +131,8 @@ fn apply_transform(h: &mut Hatch, t: &EntityTransform) { e.major_axis_endpoint.x = 2.0 * dot * ax / len2 - rx; e.major_axis_endpoint.y = 2.0 * dot * ay / len2 - ry; let tmp = e.start_angle; - e.start_angle = 2.0 * line_angle_deg - e.end_angle; - e.end_angle = 2.0 * line_angle_deg - tmp; + e.start_angle = 2.0 * line_angle - e.end_angle; + e.end_angle = 2.0 * line_angle - tmp; } BoundaryEdge::Spline(s) => { for cp in &mut s.control_points { diff --git a/src/modules/home/inquiry/divide.rs b/src/modules/home/inquiry/divide.rs index 6a7356d7a..cdaaab6b9 100644 --- a/src/modules/home/inquiry/divide.rs +++ b/src/modules/home/inquiry/divide.rs @@ -183,7 +183,7 @@ fn point_at_distance(entity: &EntityType, d: f64) -> Option { let span = arc_span_rad(a.start_angle, a.end_angle); let t = d / a.radius; // arc_length = r * theta if t > span { return None; } - let angle = a.start_angle.to_radians() + t; + let angle = a.start_angle + t; Some(Vector3::new( a.center.x + a.radius * angle.cos(), a.center.y + a.radius * angle.sin(), @@ -227,7 +227,7 @@ fn point_at_distance(entity: &EntityType, d: f64) -> Option { } } -fn arc_span_rad(start_deg: f64, end_deg: f64) -> f64 { - let span = ((end_deg - start_deg) + 360.0) % 360.0; - if span < 1e-6 { 2.0 * PI } else { span.to_radians() } +fn arc_span_rad(start: f64, end: f64) -> f64 { + let span = (end - start).rem_euclid(2.0 * PI); + if span < 1e-6 { 2.0 * PI } else { span } } diff --git a/src/modules/home/modify/array.rs b/src/modules/home/modify/array.rs index 9ff14ca08..8678a3cb0 100644 --- a/src/modules/home/modify/array.rs +++ b/src/modules/home/modify/array.rs @@ -531,8 +531,8 @@ impl ArrayPathCommand { } EntityType::Arc(a) => { let (cx, cy, r) = (a.center.x as f32, a.center.y as f32, a.radius as f32); - let a0 = a.start_angle.to_radians() as f32; - let a1 = a.end_angle.to_radians() as f32; + let a0 = a.start_angle as f32; + let a1 = a.end_angle as f32; let span = { let s = fn_norm(a1) - fn_norm(a0); if s <= 0.0 { diff --git a/src/modules/home/modify/break_cmd.rs b/src/modules/home/modify/break_cmd.rs index 3c615ac49..c6e07f769 100644 --- a/src/modules/home/modify/break_cmd.rs +++ b/src/modules/home/modify/break_cmd.rs @@ -235,21 +235,20 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec { // ── Small utilities ──────────────────────────────────────────────────────── -/// Returns the angle (degrees, 0-360) of `pt` viewed from (cx, cy) in the XZ plane. +/// Returns the angle (radians, 0-2π) of `pt` viewed from (cx, cy) in the XZ plane. fn angle_on_arc(cx: f32, cy: f32, pt: Vec3) -> f32 { let dx = pt.x - cx; let dy = pt.z - cy; // XZ plane - let a = dy.atan2(dx).to_degrees(); - (a + 360.0) % 360.0 + dy.atan2(dx).rem_euclid(std::f32::consts::TAU) } /// Clamp angle `a` to within the arc's angular range (CCW from `start` to `end`). fn clamp_to_arc(a: f32, start: f32, end: f32) -> f32 { - let span = ((end - start) + 360.0) % 360.0; - let rel = ((a - start) + 360.0) % 360.0; + let span = (end - start).rem_euclid(std::f32::consts::TAU); + let rel = (a - start).rem_euclid(std::f32::consts::TAU); if rel <= span { a - } else if rel < span + (360.0 - span) / 2.0 { + } else if rel < span + (std::f32::consts::TAU - span) / 2.0 { end } else { start diff --git a/src/modules/home/modify/explode.rs b/src/modules/home/modify/explode.rs index d2697c677..e76a7cf28 100644 --- a/src/modules/home/modify/explode.rs +++ b/src/modules/home/modify/explode.rs @@ -273,10 +273,10 @@ fn bulge_to_arc( // acadrust arcs are always CCW: positive bulge → CCW from p0 to p1 // Negative bulge → CW from p0 to p1 = CCW from p1 to p0 - let (start_deg, end_deg) = if b > 0.0 { - (norm_deg(a0_rad.to_degrees()), norm_deg(a1_rad.to_degrees())) + let (start_angle, end_angle) = if b > 0.0 { + (norm_angle(a0_rad), norm_angle(a1_rad)) } else { - (norm_deg(a1_rad.to_degrees()), norm_deg(a0_rad.to_degrees())) + (norm_angle(a1_rad), norm_angle(a0_rad)) }; let mut common = common_src.clone(); @@ -286,16 +286,15 @@ fn bulge_to_arc( common, center: Vector3::new(cx, cy, elevation), radius: r, - start_angle: start_deg, - end_angle: end_deg, + start_angle, + end_angle, ..ArcEnt::new() }; Some(EntityType::Arc(arc)) } -fn norm_deg(a: f64) -> f64 { - let t = TAU.to_degrees(); - ((a % t) + t) % t +fn norm_angle(a: f64) -> f64 { + ((a % TAU) + TAU) % TAU } fn explode_mline(ml: &MLine) -> Vec { diff --git a/src/modules/home/modify/fillet.rs b/src/modules/home/modify/fillet.rs index 62cd03281..75acd2ffa 100644 --- a/src/modules/home/modify/fillet.rs +++ b/src/modules/home/modify/fillet.rs @@ -149,16 +149,15 @@ fn compute_fillet( let arc_cx = px + arc_dist * bx / blen; let arc_cy = py + arc_dist * by / blen; - // Arc angles in degrees - let a_start_deg = (t1[1] - arc_cy).atan2(t1[0] - arc_cx).to_degrees(); - let a_end_deg = (t2[1] - arc_cy).atan2(t2[0] - arc_cx).to_degrees(); + let a_start = (t1[1] - arc_cy).atan2(t1[0] - arc_cx); + let a_end = (t2[1] - arc_cy).atan2(t2[0] - arc_cx); // Pick CCW direction that fills the concave corner let cross = dir1[0] * dir2[1] - dir1[1] * dir2[0]; - let (start_deg, end_deg) = if cross <= 0.0 { - (a_start_deg, a_end_deg) + let (start_angle, end_angle) = if cross <= 0.0 { + (a_start, a_end) } else { - (a_end_deg, a_start_deg) + (a_end, a_start) }; // Trim l1 to T1 and l2 to T2 @@ -171,8 +170,8 @@ fn compute_fillet( arc.common.handle = Handle::NULL; arc.center = Vector3::new(arc_cx, arc_cy, z); arc.radius = radius; - arc.start_angle = start_deg; - arc.end_angle = end_deg; + arc.start_angle = norm_angle(start_angle); + arc.end_angle = norm_angle(end_angle); Some(( EntityType::Line(new_l1), @@ -265,13 +264,9 @@ fn line_pts(l: &LineEnt) -> Vec<[f32; 3]> { ] } -fn arc_pts(cx: f64, cy: f64, r: f64, a0_deg: f64, a1_deg: f64, z: f64) -> Vec<[f32; 3]> { - use std::f64::consts::TAU; - let fn_norm = |a: f64| -> f64 { ((a % TAU) + TAU) % TAU }; - let a0 = a0_deg.to_radians(); - let a1 = a1_deg.to_radians(); +fn arc_pts(cx: f64, cy: f64, r: f64, a0: f64, a1: f64, z: f64) -> Vec<[f32; 3]> { let span = { - let s = fn_norm(a1) - fn_norm(a0); + let s = norm_angle(a1) - norm_angle(a0); if s <= 0.0 { s + TAU } else { @@ -281,7 +276,7 @@ fn arc_pts(cx: f64, cy: f64, r: f64, a0_deg: f64, a1_deg: f64, z: f64) -> Vec<[f let steps = (span.abs() * 20.0).ceil().max(4.0) as usize; (0..=steps) .map(|i| { - let ang = fn_norm(a0) + span * (i as f64 / steps as f64); + let ang = norm_angle(a0) + span * (i as f64 / steps as f64); [ (cx + r * ang.cos()) as f32, (cy + r * ang.sin()) as f32, @@ -309,41 +304,41 @@ fn entity_pts(e: &EntityType) -> Vec<[f32; 3]> { // ── Arc geometry helpers ─────────────────────────────────────────────────── -/// Extract center, radius, start/end angle (degrees), elevation from an arc. +/// Extract center, radius, start/end angle (radians), elevation from an arc. fn arc_geom(a: &ArcEnt) -> ([f64; 2], f64, f64, f64, f64) { ([a.center.x, a.center.y], a.radius, a.start_angle, a.end_angle, a.center.z) } -/// Normalize angle to [0, 360). -fn norm360(a: f64) -> f64 { ((a % 360.0) + 360.0) % 360.0 } +/// Normalize angle to [0, 2π). +fn norm_angle(a: f64) -> f64 { ((a % TAU) + TAU) % TAU } /// Return the CCW angular span from `start` to `end`. fn arc_span(start: f64, end: f64) -> f64 { - let s = (end - start + 360.0) % 360.0; - if s < 1e-6 { 360.0 } else { s } + let s = (end - start).rem_euclid(TAU); + if s < 1e-6 { TAU } else { s } } -/// Project a pick point onto an arc: return the parameter t ∈ [0, 360) = the angle. +/// Project a pick point onto an arc: return the angle in radians. fn arc_angle_at(center: [f64; 2], pt: [f64; 2]) -> f64 { - norm360((pt[1] - center[1]).atan2(pt[0] - center[0]).to_degrees()) + norm_angle((pt[1] - center[1]).atan2(pt[0] - center[0])) } /// Clamp angle `a` into the arc range (CCW from `start` to `end`). /// Returns the nearer endpoint if `a` is outside. fn clamp_angle_to_arc(a: f64, start: f64, end: f64) -> f64 { let span = arc_span(start, end); - let rel = (a - start + 360.0) % 360.0; + let rel = (a - start).rem_euclid(TAU); if rel <= span { a } - else if rel < span + (360.0 - span) / 2.0 { end } + else if rel < span + (TAU - span) / 2.0 { end } else { start } } -/// Trim an arc so it goes from `new_start` to `new_end` (both in degrees). +/// Trim an arc so it goes from `new_start` to `new_end` (both in radians). fn trim_arc(orig: &ArcEnt, new_start: f64, new_end: f64) -> ArcEnt { let mut a = orig.clone(); a.common.handle = Handle::NULL; - a.start_angle = norm360(new_start); - a.end_angle = norm360(new_end); + a.start_angle = norm_angle(new_start); + a.end_angle = norm_angle(new_end); a } @@ -795,7 +790,7 @@ fn fillet_line_arc( // The tangent point on the arc must be within the arc's angular range let tp_arc_angle = arc_angle_at(ac, tp_arc); let tp_arc_clamped = clamp_angle_to_arc(tp_arc_angle, a_start, a_end); - if (norm360(tp_arc_angle) - norm360(tp_arc_clamped)).abs() > 0.5 { continue; } + if (norm_angle(tp_arc_angle) - norm_angle(tp_arc_clamped)).abs() > 0.01 { continue; } // The tangent point on the line must be on the correct side of the click // (prefer the intersection closest to the click) @@ -809,8 +804,8 @@ fn fillet_line_arc( // Build trimmed arc let arc_click_angle = arc_angle_at(ac, click_arc); - let arc_click_rel = (arc_click_angle - a_start + 360.0) % 360.0; - let tp_arc_rel = (tp_arc_clamped - a_start + 360.0) % 360.0; + let arc_click_rel = (arc_click_angle - a_start).rem_euclid(TAU); + let tp_arc_rel = (tp_arc_clamped - a_start).rem_euclid(TAU); let new_arc = if tp_arc_rel <= arc_click_rel { trim_arc(arc, tp_arc_clamped, a_end) } else { @@ -827,8 +822,8 @@ fn fillet_line_arc( fillet_arc.common.handle = Handle::NULL; fillet_arc.center = Vector3::new(fc[0], fc[1], z); fillet_arc.radius = radius; - fillet_arc.start_angle = norm360(fstart); - fillet_arc.end_angle = norm360(fend); + fillet_arc.start_angle = norm_angle(fstart); + fillet_arc.end_angle = norm_angle(fend); best_dist = dist_total; best = Some((EntityType::Line(new_line), EntityType::Arc(new_arc), EntityType::Arc(fillet_arc))); @@ -870,12 +865,12 @@ fn fillet_arc_arc( let ca1 = clamp_angle_to_arc(arc_angle_at(c1, click1), s1, e1); let ca2 = clamp_angle_to_arc(arc_angle_at(c2, click2), s2, e2); - let new_a1 = if (ic1 - s1 + 360.0) % 360.0 <= (ca1 - s1 + 360.0) % 360.0 { + let new_a1 = if (ic1 - s1).rem_euclid(TAU) <= (ca1 - s1).rem_euclid(TAU) { trim_arc(a1, ic1, e1) } else { trim_arc(a1, s1, ic1) }; - let new_a2 = if (ic2 - s2 + 360.0) % 360.0 <= (ca2 - s2 + 360.0) % 360.0 { + let new_a2 = if (ic2 - s2).rem_euclid(TAU) <= (ca2 - s2).rem_euclid(TAU) { trim_arc(a2, ic2, e2) } else { trim_arc(a2, s2, ic2) @@ -909,8 +904,8 @@ fn fillet_arc_arc( let tp2a = arc_angle_at(c2, tp2); let tc1 = clamp_angle_to_arc(tp1a, s1, e1); let tc2 = clamp_angle_to_arc(tp2a, s2, e2); - if (norm360(tp1a) - norm360(tc1)).abs() > 0.5 { continue; } - if (norm360(tp2a) - norm360(tc2)).abs() > 0.5 { continue; } + if (norm_angle(tp1a) - norm_angle(tc1)).abs() > 0.01 { continue; } + if (norm_angle(tp2a) - norm_angle(tc2)).abs() > 0.01 { continue; } let dist_total = (tp1[0]-click1[0]).hypot(tp1[1]-click1[1]) + (tp2[0]-click2[0]).hypot(tp2[1]-click2[1]); @@ -919,12 +914,12 @@ fn fillet_arc_arc( let ca1 = clamp_angle_to_arc(arc_angle_at(c1, click1), s1, e1); let ca2 = clamp_angle_to_arc(arc_angle_at(c2, click2), s2, e2); - let new_a1 = if (tc1 - s1 + 360.0) % 360.0 <= (ca1 - s1 + 360.0) % 360.0 { + let new_a1 = if (tc1 - s1).rem_euclid(TAU) <= (ca1 - s1).rem_euclid(TAU) { trim_arc(a1, tc1, e1) } else { trim_arc(a1, s1, tc1) }; - let new_a2 = if (tc2 - s2 + 360.0) % 360.0 <= (ca2 - s2 + 360.0) % 360.0 { + let new_a2 = if (tc2 - s2).rem_euclid(TAU) <= (ca2 - s2).rem_euclid(TAU) { trim_arc(a2, tc2, e2) } else { trim_arc(a2, s2, tc2) @@ -939,8 +934,8 @@ fn fillet_arc_arc( fillet_arc.common.handle = Handle::NULL; fillet_arc.center = Vector3::new(fc[0], fc[1], z); fillet_arc.radius = radius; - fillet_arc.start_angle = norm360(fstart); - fillet_arc.end_angle = norm360(fend); + fillet_arc.start_angle = norm_angle(fstart); + fillet_arc.end_angle = norm_angle(fend); best_dist = dist_total; best = Some((EntityType::Arc(new_a1), EntityType::Arc(new_a2), EntityType::Arc(fillet_arc))); diff --git a/src/modules/home/modify/join.rs b/src/modules/home/modify/join.rs index 4d5ef5f31..183c1fd3e 100644 --- a/src/modules/home/modify/join.rs +++ b/src/modules/home/modify/join.rs @@ -152,7 +152,7 @@ fn try_join_arcs(arcs: &[&(Handle, &EntityType)]) -> Option<(Vec, Vec 1e-3 { return None; } } - // Collect (start_deg, end_deg) — normalize CCW into [0, 360) + // Collect (start_angle, end_angle) in radians. let mut intervals: Vec<[f32; 2]> = segs.iter().map(|(_, a)| { [a.start_angle as f32, a.end_angle as f32] }).collect(); @@ -164,17 +164,17 @@ fn try_join_arcs(arcs: &[&(Handle, &EntityType)]) -> Option<(Vec, Vec 1e-3 { return None; } // discontinuous merged_end = e; } - let span = ((merged_end - merged_start) + 360.0) % 360.0; + let span = merged_end - merged_start; let handles: Vec = arcs.iter().map(|(h, _)| *h).collect(); - if (span - 360.0).abs() < 0.5 { + if (span - std::f32::consts::TAU).abs() < 0.01 { // Full circle let mut circle = acadrust::entities::Circle::new(); circle.common = first.common.clone(); diff --git a/src/modules/home/modify/lengthen.rs b/src/modules/home/modify/lengthen.rs index cbcd26994..f2be7dd66 100644 --- a/src/modules/home/modify/lengthen.rs +++ b/src/modules/home/modify/lengthen.rs @@ -144,16 +144,16 @@ fn lengthen_arc(arc: &ArcEnt, pick_pt: Vec3, mode: &LenMode) -> Option Option Option { } } -fn arc_span_deg(start: f64, end: f64) -> f64 { - let span = ((end - start) + 360.0) % 360.0; - if span < 1e-6 { 360.0 } else { span } +fn arc_span_rad(start: f64, end: f64) -> f64 { + let span = (end - start).rem_euclid(std::f64::consts::TAU); + if span < 1e-6 { std::f64::consts::TAU } else { span } } fn xz_to_v3(v: Vec3, z: f64) -> Vector3 { diff --git a/src/modules/home/modify/offset.rs b/src/modules/home/modify/offset.rs index 1e555f104..5235bb2e0 100644 --- a/src/modules/home/modify/offset.rs +++ b/src/modules/home/modify/offset.rs @@ -394,8 +394,8 @@ fn entity_wire_pts(e: &EntityType) -> Vec<[f32; 3]> { .collect() } EntityType::Arc(a) => { - let a0 = norm_rad(a.start_angle.to_radians()); - let a1 = norm_rad(a.end_angle.to_radians()); + let a0 = norm_rad(a.start_angle); + let a1 = norm_rad(a.end_angle); let span = { let s = a1 - a0; if s <= 0.0 { diff --git a/src/modules/home/modify/trim.rs b/src/modules/home/modify/trim.rs index dec7de3f9..8c1b24299 100644 --- a/src/modules/home/modify/trim.rs +++ b/src/modules/home/modify/trim.rs @@ -260,8 +260,8 @@ fn build_geos(entities: &[EntityType]) -> Vec { cx: a.center.x, cy: a.center.y, r: a.radius, - a0: a.start_angle.to_radians(), - a1: a.end_angle.to_radians(), + a0: a.start_angle, + a1: a.end_angle, }), EntityType::Circle(c) => Some(Geo::Circle { handle: h, @@ -949,8 +949,8 @@ fn trim_line(orig: &LineEnt, ts: &[f64], t_click: f64) -> Vec { /// Trim an Arc entity. Returns the surviving arc segments. fn trim_arc(orig: &ArcEnt, ts: &[f64], t_click: f64) -> Vec { - let a0 = orig.start_angle.to_radians(); - let a1 = orig.end_angle.to_radians(); + let a0 = orig.start_angle; + let a1 = orig.end_angle; let span = { let s = norm(a1) - norm(a0); if s <= 0.0 { @@ -969,8 +969,8 @@ fn trim_arc(orig: &ArcEnt, ts: &[f64], t_click: f64) -> Vec { } let mut a = orig.clone(); a.common.handle = Handle::NULL; - a.start_angle = angle_at(ta).to_degrees(); - a.end_angle = angle_at(tb).to_degrees(); + a.start_angle = angle_at(ta); + a.end_angle = angle_at(tb); Some(EntityType::Arc(a)) }) .collect() @@ -1362,8 +1362,8 @@ fn entity_pts(e: &EntityType) -> Vec<[f32; 3]> { a.center.x, a.center.y, a.radius, - a.start_angle.to_radians(), - a.end_angle.to_radians(), + a.start_angle, + a.end_angle, a.center.y, ), EntityType::Ellipse(e) => { @@ -1466,8 +1466,8 @@ impl CadCommand for TrimCommand { Some(EntityType::Arc(a)) => { let cx = a.center.x; let cy = a.center.y; - let a0 = a.start_angle.to_radians(); - let a1 = a.end_angle.to_radians(); + let a0 = a.start_angle; + let a1 = a.end_angle; let ts = arc_seg_ts(cx, cy, a.radius, a0, a1, handle, &self.geos); if ts.is_empty() { return CmdResult::NeedPoint; @@ -1630,8 +1630,8 @@ impl CadCommand for TrimCommand { Some(EntityType::Arc(a)) => { let cx = a.center.x; let cy = a.center.y; - let a0 = a.start_angle.to_radians(); - let a1 = a.end_angle.to_radians(); + let a0 = a.start_angle; + let a1 = a.end_angle; let ts = arc_seg_ts(cx, cy, a.radius, a0, a1, handle, &self.geos); if ts.is_empty() { return vec![]; diff --git a/src/scene/tessellate.rs b/src/scene/tessellate.rs index 70b53653b..fb841a02f 100644 --- a/src/scene/tessellate.rs +++ b/src/scene/tessellate.rs @@ -659,8 +659,11 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry { key_verts.push(p1); } acadrust::entities::BoundaryEdge::CircularArc(arc) => { - let sa = (arc.start_angle as f32).to_radians(); - let ea = (arc.end_angle as f32).to_radians(); + let (sa, ea) = if arc.counter_clockwise { + (arc.start_angle as f32, arc.end_angle as f32) + } else { + (arc.end_angle as f32, arc.start_angle as f32) + }; let span = if ea > sa { ea - sa } else { @@ -688,8 +691,11 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry { let r_min = r_maj * ell.minor_axis_ratio as f32; let rot = (ell.major_axis_endpoint.y as f32) .atan2(ell.major_axis_endpoint.x as f32); - let sa = ell.start_angle as f32; - let ea = ell.end_angle as f32; + let (sa, ea) = if ell.counter_clockwise { + (ell.start_angle as f32, ell.end_angle as f32) + } else { + (ell.end_angle as f32, ell.start_angle as f32) + }; let span = if ea > sa { ea - sa } else {