refactor: replace angle calculations with radians for consistency

This commit is contained in:
Hakan Seven committed 2026-05-03 21:58:38 +03:00
1 parent 8be415213a
commit 603d07412e
11 files changed
+98 -99

No files matched your search

+4 -4
View File
@@ -183,7 +183,7 @@ fn point_at_distance(entity: &EntityType, d: f64) -> Option<Vector3> {
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<Vector3> {
}
}
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 }
}
+2 -2
View File
@@ -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 {
+5 -6
View File
@@ -235,21 +235,20 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
// ── 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
+7 -8
View File
@@ -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<EntityType> {
+35 -40
View File
@@ -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)));
+5 -5
View File
@@ -152,7 +152,7 @@ fn try_join_arcs(arcs: &[&(Handle, &EntityType)]) -> Option<(Vec<Handle>, Vec<En
if (a.radius - r).abs() > 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<Handle>, Vec<En
let merged_start = intervals[0][0];
let mut merged_end = intervals[0][1];
for &[s, e] in &intervals[1..] {
let span = ((e - merged_end) + 360.0) % 360.0;
let gap = ((s - merged_end) + 360.0) % 360.0;
let span = (e - merged_end).rem_euclid(std::f32::consts::TAU);
let gap = (s - merged_end).rem_euclid(std::f32::consts::TAU);
let _ = span;
if gap > 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<Handle> = 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();
+11 -11
View File
@@ -144,16 +144,16 @@ fn lengthen_arc(arc: &ArcEnt, pick_pt: Vec3, mode: &LenMode) -> Option<EntityTyp
let cy = arc.center.z as f32; // Y-up: DXF Y → world Z
// Current arc span
let span = arc_span_deg(arc.start_angle, arc.end_angle);
let current_arc_len = arc.radius * span.to_radians();
let span = arc_span_rad(arc.start_angle, arc.end_angle);
let current_arc_len = arc.radius * span;
let new_arc_len = apply_mode(current_arc_len, mode)?;
if new_arc_len < 1e-10 { return None; }
let new_span_deg = (new_arc_len / arc.radius).to_degrees();
let new_span = new_arc_len / arc.radius;
// Which end (start or end angle) is closer to pick?
let start_rad = arc.start_angle.to_radians();
let end_rad = arc.end_angle.to_radians();
let start_rad = arc.start_angle;
let end_rad = arc.end_angle;
let start_pt = Vec3::new(
cx + arc.radius as f32 * start_rad.cos() as f32,
@@ -168,17 +168,17 @@ fn lengthen_arc(arc: &ArcEnt, pick_pt: Vec3, mode: &LenMode) -> Option<EntityTyp
let dist_start = (pick_pt - start_pt).length();
let dist_end = (pick_pt - end_pt).length();
let delta_span = new_span_deg - span;
let delta_span = new_span - span;
let mut result = arc.clone();
result.common.handle = Handle::NULL;
if dist_end <= dist_start {
// Extend end angle
result.end_angle = arc.start_angle + new_span_deg;
result.end_angle = arc.start_angle + new_span;
} else {
// Extend start angle (move start backwards)
result.start_angle = arc.end_angle - new_span_deg;
result.start_angle = arc.end_angle - new_span;
}
let _ = delta_span;
Some(EntityType::Arc(result))
@@ -257,9 +257,9 @@ fn apply_mode(current: f64, mode: &LenMode) -> Option<f64> {
}
}
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 {
+2 -2
View File
@@ -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 {
+12 -12
View File
@@ -260,8 +260,8 @@ fn build_geos(entities: &[EntityType]) -> Vec<Geo> {
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<EntityType> {
/// Trim an Arc entity. Returns the surviving arc segments.
fn trim_arc(orig: &ArcEnt, ts: &[f64], t_click: f64) -> Vec<EntityType> {
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<EntityType> {
}
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![];