Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(app): tools geometry hamburger helpers, preview ops

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (May 10, 2026, 5:06 PM +0300) commit ca9d70ca parent b161edf0 change-id kswuvnuu
+656
+238
crates/bone-app/src/tools/geometry.rs
··· 1 + use bone_document::{Sketch, SketchEntity}; 2 + use bone_types::{Point2, SketchEntityId, Vec2}; 3 + 4 + use crate::snap::{SnapHit, SnapKind}; 5 + 6 + pub(super) fn distance(a: Point2, b: Point2) -> f64 { 7 + let (ax, ay) = a.coords_mm(); 8 + let (bx, by) = b.coords_mm(); 9 + (ax - bx).hypot(ay - by) 10 + } 11 + 12 + pub(super) fn project_to_circle(center: Point2, radius_mm: f64, point: Point2) -> Point2 { 13 + let (cx, cy) = center.coords_mm(); 14 + let (px, py) = point.coords_mm(); 15 + let dx = px - cx; 16 + let dy = py - cy; 17 + let len = (dx * dx + dy * dy).sqrt(); 18 + if !(len.is_finite() && len > 0.0) { 19 + return Point2::from_mm(cx + radius_mm, cy); 20 + } 21 + let scale = radius_mm / len; 22 + Point2::from_mm(cx + dx * scale, cy + dy * scale) 23 + } 24 + 25 + pub(super) fn circumcircle(p1: Point2, p2: Point2, p3: Point2) -> Option<(Point2, f64)> { 26 + let (ax, ay) = p1.coords_mm(); 27 + let (bx, by) = p2.coords_mm(); 28 + let (cx, cy) = p3.coords_mm(); 29 + let d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)); 30 + if !(d.is_finite()) || d.abs() < 1e-12 { 31 + return None; 32 + } 33 + let a_sq = ax * ax + ay * ay; 34 + let b_sq = bx * bx + by * by; 35 + let c_sq = cx * cx + cy * cy; 36 + let ux = (a_sq * (by - cy) + b_sq * (cy - ay) + c_sq * (ay - by)) / d; 37 + let uy = (a_sq * (cx - bx) + b_sq * (ax - cx) + c_sq * (bx - ax)) / d; 38 + if !(ux.is_finite() && uy.is_finite()) { 39 + return None; 40 + } 41 + let center = Point2::from_mm(ux, uy); 42 + let radius = distance(center, p1); 43 + (radius.is_finite() && radius > 0.0).then_some((center, radius)) 44 + } 45 + 46 + pub(super) fn minor_arc_ccw(center: Point2, start: Point2, end: Point2) -> bool { 47 + let (cx, cy) = center.coords_mm(); 48 + let (sx, sy) = start.coords_mm(); 49 + let (ex, ey) = end.coords_mm(); 50 + let cross = (sx - cx) * (ey - cy) - (sy - cy) * (ex - cx); 51 + cross >= 0.0 52 + } 53 + 54 + pub(super) fn on_circle_endpoint( 55 + snap: Option<SnapHit>, 56 + center: Point2, 57 + radius_mm: f64, 58 + ) -> Option<SketchEntityId> { 59 + let hit = snap?; 60 + let SnapKind::Endpoint(id) = hit.kind else { 61 + return None; 62 + }; 63 + ((distance(center, hit.world) - radius_mm).abs() < 1e-9).then_some(id) 64 + } 65 + 66 + pub(super) fn ccw_through(center: Point2, start: Point2, end: Point2, via: Point2) -> bool { 67 + let (cx, cy) = center.coords_mm(); 68 + let (sx, sy) = start.coords_mm(); 69 + let (ex, ey) = end.coords_mm(); 70 + let (vx, vy) = via.coords_mm(); 71 + let start_angle = (sy - cy).atan2(sx - cx); 72 + let end_angle = (ey - cy).atan2(ex - cx); 73 + let via_angle = (vy - cy).atan2(vx - cx); 74 + let tau = std::f64::consts::TAU; 75 + let delta_via = (via_angle - start_angle).rem_euclid(tau); 76 + let delta_end = (end_angle - start_angle).rem_euclid(tau); 77 + delta_via < delta_end 78 + } 79 + 80 + pub(super) fn axes_distinct(a: (f64, f64), b: (f64, f64)) -> bool { 81 + let dx = b.0 - a.0; 82 + let dy = b.1 - a.1; 83 + dx.is_finite() && dy.is_finite() && dx.abs() > 1e-12 && dy.abs() > 1e-12 84 + } 85 + 86 + pub(super) fn tangent_at_point( 87 + sketch: &Sketch, 88 + point: SketchEntityId, 89 + ) -> Option<(SketchEntityId, Vec2)> { 90 + sketch.entity_order().iter().copied().find_map(|id| { 91 + let tangent = match sketch.entities().get(id)? { 92 + SketchEntity::Line(l) => { 93 + let other = if l.a() == point { 94 + l.b() 95 + } else if l.b() == point { 96 + l.a() 97 + } else { 98 + return None; 99 + }; 100 + let (hx, hy) = endpoint_position(sketch, point)?.coords_mm(); 101 + let (tx, ty) = endpoint_position(sketch, other)?.coords_mm(); 102 + unit_vec((tx - hx, ty - hy))? 103 + } 104 + SketchEntity::Arc(a) => { 105 + let center = endpoint_position(sketch, a.center())?; 106 + let here = endpoint_position(sketch, point)?; 107 + let (cx, cy) = center.coords_mm(); 108 + let (px, py) = here.coords_mm(); 109 + let ccw_perp = (-(py - cy), px - cx); 110 + let into_host = if a.start() == point { 111 + ccw_perp 112 + } else if a.end() == point { 113 + (-ccw_perp.0, -ccw_perp.1) 114 + } else { 115 + return None; 116 + }; 117 + unit_vec(into_host)? 118 + } 119 + _ => return None, 120 + }; 121 + Some((id, tangent)) 122 + }) 123 + } 124 + 125 + pub(super) fn endpoint_position(sketch: &Sketch, id: SketchEntityId) -> Option<Point2> { 126 + match sketch.entities().get(id)? { 127 + SketchEntity::Point(p) => Some(p.at()), 128 + _ => None, 129 + } 130 + } 131 + 132 + fn unit_vec(v: (f64, f64)) -> Option<Vec2> { 133 + let len = (v.0 * v.0 + v.1 * v.1).sqrt(); 134 + (len.is_finite() && len > 0.0).then(|| Vec2::from_mm(v.0 / len, v.1 / len)) 135 + } 136 + 137 + pub(super) fn tangent_arc_ccw_natural(start: Point2, center: Point2, tangent: Vec2) -> bool { 138 + let (sx, sy) = start.coords_mm(); 139 + let (cx, cy) = center.coords_mm(); 140 + let radial_x = sx - cx; 141 + let radial_y = sy - cy; 142 + let ccw_init_x = -radial_y; 143 + let ccw_init_y = radial_x; 144 + let (tx, ty) = tangent.coords_mm(); 145 + ccw_init_x * (-tx) + ccw_init_y * (-ty) > 0.0 146 + } 147 + 148 + pub(super) fn tangent_arc_center(start: Point2, end: Point2, tangent: Vec2) -> Option<Point2> { 149 + let (sx, sy) = start.coords_mm(); 150 + let (ex, ey) = end.coords_mm(); 151 + let (tx, ty) = tangent.coords_mm(); 152 + let (nx, ny) = (-ty, tx); 153 + let dx = sx - ex; 154 + let dy = sy - ey; 155 + let denom = nx * dx + ny * dy; 156 + if !denom.is_finite() || denom.abs() < 1e-9 { 157 + return None; 158 + } 159 + let alpha = -(dx * dx + dy * dy) / (2.0 * denom); 160 + if !alpha.is_finite() { 161 + return None; 162 + } 163 + Some(Point2::from_mm(sx + alpha * nx, sy + alpha * ny)) 164 + } 165 + 166 + pub(super) fn three_point_corner_extents( 167 + p1: Point2, 168 + p2: Point2, 169 + click3: Point2, 170 + ) -> Option<(Point2, Point2)> { 171 + let (p1x, p1y) = p1.coords_mm(); 172 + let (p2x, p2y) = p2.coords_mm(); 173 + let (c3x, c3y) = click3.coords_mm(); 174 + let edge = (p2x - p1x, p2y - p1y); 175 + let len = (edge.0 * edge.0 + edge.1 * edge.1).sqrt(); 176 + if !(len.is_finite() && len > 0.0) { 177 + return None; 178 + } 179 + let u = (edge.0 / len, edge.1 / len); 180 + let n = (-u.1, u.0); 181 + let from_p2 = (c3x - p2x, c3y - p2y); 182 + let extent = from_p2.0 * n.0 + from_p2.1 * n.1; 183 + if !extent.is_finite() || extent.abs() < 1e-9 { 184 + return None; 185 + } 186 + Some(( 187 + Point2::from_mm(p2x + n.0 * extent, p2y + n.1 * extent), 188 + Point2::from_mm(p1x + n.0 * extent, p1y + n.1 * extent), 189 + )) 190 + } 191 + 192 + pub(super) fn three_point_center_corners( 193 + center: Point2, 194 + mid: Point2, 195 + click3: Point2, 196 + ) -> Option<(Point2, Point2, Point2, Point2)> { 197 + let (cx, cy) = center.coords_mm(); 198 + let (mx, my) = mid.coords_mm(); 199 + let (kx, ky) = click3.coords_mm(); 200 + let mid_v = (mx - cx, my - cy); 201 + let mid_len = (mid_v.0 * mid_v.0 + mid_v.1 * mid_v.1).sqrt(); 202 + if !(mid_len.is_finite() && mid_len > 0.0) { 203 + return None; 204 + } 205 + let u = (mid_v.0 / mid_len, mid_v.1 / mid_len); 206 + let n = (-u.1, u.0); 207 + let from_center = (kx - cx, ky - cy); 208 + let perp_extent = from_center.0 * n.0 + from_center.1 * n.1; 209 + if !perp_extent.is_finite() || perp_extent.abs() < 1e-9 { 210 + return None; 211 + } 212 + let along = mid_len; 213 + let p1 = Point2::from_mm( 214 + cx + along * u.0 + perp_extent * n.0, 215 + cy + along * u.1 + perp_extent * n.1, 216 + ); 217 + let p2 = Point2::from_mm( 218 + cx - along * u.0 + perp_extent * n.0, 219 + cy - along * u.1 + perp_extent * n.1, 220 + ); 221 + let p3 = Point2::from_mm( 222 + cx - along * u.0 - perp_extent * n.0, 223 + cy - along * u.1 - perp_extent * n.1, 224 + ); 225 + let p4 = Point2::from_mm( 226 + cx + along * u.0 - perp_extent * n.0, 227 + cy + along * u.1 - perp_extent * n.1, 228 + ); 229 + Some((p1, p2, p3, p4)) 230 + } 231 + 232 + pub(super) fn degenerate_triangle(p1: Point2, p2: Point2, p3: Point2) -> bool { 233 + let (ax, ay) = p1.coords_mm(); 234 + let (bx, by) = p2.coords_mm(); 235 + let (cx, cy) = p3.coords_mm(); 236 + let cross = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax); 237 + !cross.is_finite() || cross.abs() < 1e-9 238 + }
+418
crates/bone-app/src/tools/preview.rs
··· 1 + use bone_document::Sketch; 2 + use bone_render::{PreviewArc, PreviewCircle, SketchPreview}; 3 + use bone_types::{Angle, Length, Point2}; 4 + use uom::si::angle::radian; 5 + use uom::si::length::millimeter; 6 + 7 + use crate::sketch_mode::{ClickAnchor, Pending, SketchTool}; 8 + use crate::snap::SnapHit; 9 + 10 + use super::click_position; 11 + use super::geometry::{ 12 + ccw_through, circumcircle, degenerate_triangle, distance, minor_arc_ccw, project_to_circle, 13 + tangent_arc_ccw_natural, tangent_arc_center, tangent_at_point, three_point_center_corners, 14 + three_point_corner_extents, 15 + }; 16 + 17 + pub(super) fn preview( 18 + sketch: &Sketch, 19 + tool: SketchTool, 20 + cursor: Point2, 21 + pending: Option<Pending>, 22 + snap: Option<SnapHit>, 23 + ) -> SketchPreview { 24 + let snap_dot = snap.map(|s| s.world); 25 + let effective = snap_dot.unwrap_or(cursor); 26 + match tool { 27 + SketchTool::Point => snap_only(snap_dot), 28 + SketchTool::Line => preview_line(sketch, effective, pending, snap_dot), 29 + SketchTool::Circle => preview_circle(sketch, effective, pending, snap_dot), 30 + SketchTool::PerimeterCircle => { 31 + preview_perimeter_circle(sketch, effective, pending, snap_dot) 32 + } 33 + SketchTool::CenterpointArc => preview_centerpoint_arc(sketch, effective, pending, snap_dot), 34 + SketchTool::TangentArc => preview_tangent_arc(sketch, effective, pending, snap_dot), 35 + SketchTool::ThreePointArc => preview_three_point_arc(sketch, effective, pending, snap_dot), 36 + SketchTool::CornerRectangle => { 37 + preview_corner_rectangle(sketch, effective, pending, snap_dot) 38 + } 39 + SketchTool::CenterRectangle => { 40 + preview_center_rectangle(sketch, effective, pending, snap_dot) 41 + } 42 + SketchTool::ThreePointCornerRectangle => { 43 + preview_three_point_corner_rectangle(sketch, effective, pending, snap_dot) 44 + } 45 + SketchTool::ThreePointCenterRectangle => { 46 + preview_three_point_center_rectangle(sketch, effective, pending, snap_dot) 47 + } 48 + SketchTool::Parallelogram => preview_parallelogram(sketch, effective, pending, snap_dot), 49 + SketchTool::SmartDimension => SketchPreview::empty(), 50 + } 51 + } 52 + 53 + fn snap_only(snap_dot: Option<Point2>) -> SketchPreview { 54 + SketchPreview { 55 + snap: snap_dot, 56 + ..SketchPreview::empty() 57 + } 58 + } 59 + 60 + fn preview_dispatch_two<F>( 61 + sketch: &Sketch, 62 + pending: Option<Pending>, 63 + effective: Point2, 64 + snap_dot: Option<Point2>, 65 + show_first: F, 66 + ) -> SketchPreview 67 + where 68 + F: FnOnce(Point2, Point2) -> SketchPreview, 69 + { 70 + match pending { 71 + None | Some(Pending::Second(_, _)) => snap_only(snap_dot), 72 + Some(Pending::First(a)) => click_position(sketch, a).map_or_else( 73 + || snap_only(snap_dot), 74 + |p| SketchPreview { 75 + anchors: vec![p], 76 + snap: snap_dot, 77 + ..show_first(p, effective) 78 + }, 79 + ), 80 + } 81 + } 82 + 83 + fn preview_dispatch_three<F>( 84 + sketch: &Sketch, 85 + pending: Option<Pending>, 86 + effective: Point2, 87 + snap_dot: Option<Point2>, 88 + second_render: F, 89 + ) -> SketchPreview 90 + where 91 + F: FnOnce(Point2, Point2, Point2) -> SketchPreview, 92 + { 93 + match pending { 94 + None => snap_only(snap_dot), 95 + Some(Pending::First(_)) => { 96 + preview_dispatch_two(sketch, pending, effective, snap_dot, rubber_band) 97 + } 98 + Some(Pending::Second(a, b)) => { 99 + match (click_position(sketch, a), click_position(sketch, b)) { 100 + (Some(p1), Some(p2)) => SketchPreview { 101 + anchors: vec![p1, p2], 102 + snap: snap_dot, 103 + ..second_render(p1, p2, effective) 104 + }, 105 + _ => snap_only(snap_dot), 106 + } 107 + } 108 + } 109 + } 110 + 111 + fn rubber_band(p: Point2, effective: Point2) -> SketchPreview { 112 + SketchPreview { 113 + segments: vec![(p, effective)], 114 + ..SketchPreview::empty() 115 + } 116 + } 117 + 118 + fn preview_line( 119 + sketch: &Sketch, 120 + effective: Point2, 121 + pending: Option<Pending>, 122 + snap_dot: Option<Point2>, 123 + ) -> SketchPreview { 124 + preview_dispatch_two(sketch, pending, effective, snap_dot, rubber_band) 125 + } 126 + 127 + fn preview_circle( 128 + sketch: &Sketch, 129 + effective: Point2, 130 + pending: Option<Pending>, 131 + snap_dot: Option<Point2>, 132 + ) -> SketchPreview { 133 + preview_dispatch_two(sketch, pending, effective, snap_dot, |center_pos, eff| { 134 + let radius_mm = distance(center_pos, eff); 135 + if !(radius_mm.is_finite() && radius_mm > 0.0) { 136 + return SketchPreview::empty(); 137 + } 138 + SketchPreview { 139 + circles: vec![PreviewCircle { 140 + center: center_pos, 141 + radius: Length::new::<millimeter>(radius_mm), 142 + }], 143 + ..SketchPreview::empty() 144 + } 145 + }) 146 + } 147 + 148 + fn preview_perimeter_circle( 149 + sketch: &Sketch, 150 + effective: Point2, 151 + pending: Option<Pending>, 152 + snap_dot: Option<Point2>, 153 + ) -> SketchPreview { 154 + preview_dispatch_three( 155 + sketch, 156 + pending, 157 + effective, 158 + snap_dot, 159 + |p1, p2, eff| match circumcircle(p1, p2, eff) { 160 + Some((center, radius_mm)) => SketchPreview { 161 + circles: vec![PreviewCircle { 162 + center, 163 + radius: Length::new::<millimeter>(radius_mm), 164 + }], 165 + ..SketchPreview::empty() 166 + }, 167 + None => SketchPreview { 168 + segments: vec![(p1, p2)], 169 + ..SketchPreview::empty() 170 + }, 171 + }, 172 + ) 173 + } 174 + 175 + fn preview_centerpoint_arc( 176 + sketch: &Sketch, 177 + effective: Point2, 178 + pending: Option<Pending>, 179 + snap_dot: Option<Point2>, 180 + ) -> SketchPreview { 181 + preview_dispatch_three( 182 + sketch, 183 + pending, 184 + effective, 185 + snap_dot, 186 + |center_pos, start_pos, eff| { 187 + let radius_mm = distance(center_pos, start_pos); 188 + if !(radius_mm.is_finite() && radius_mm > 0.0) { 189 + return SketchPreview::empty(); 190 + } 191 + let end_on_circle = project_to_circle(center_pos, radius_mm, eff); 192 + let (arc_start, arc_end) = if minor_arc_ccw(center_pos, start_pos, end_on_circle) { 193 + (start_pos, end_on_circle) 194 + } else { 195 + (end_on_circle, start_pos) 196 + }; 197 + match ghost_arc_ccw(center_pos, radius_mm, arc_start, arc_end) { 198 + Some(arc) => SketchPreview { 199 + arcs: vec![arc], 200 + ..SketchPreview::empty() 201 + }, 202 + None => SketchPreview::empty(), 203 + } 204 + }, 205 + ) 206 + } 207 + 208 + fn preview_tangent_arc( 209 + sketch: &Sketch, 210 + effective: Point2, 211 + pending: Option<Pending>, 212 + snap_dot: Option<Point2>, 213 + ) -> SketchPreview { 214 + let Some(Pending::First(start)) = pending else { 215 + return snap_only(snap_dot); 216 + }; 217 + let ClickAnchor::Endpoint(start_id) = start else { 218 + return snap_only(snap_dot); 219 + }; 220 + let Some(start_pos) = click_position(sketch, start) else { 221 + return snap_only(snap_dot); 222 + }; 223 + let arc = tangent_at_point(sketch, start_id).and_then(|(_host, tangent)| { 224 + let center_pos = tangent_arc_center(start_pos, effective, tangent)?; 225 + let radius_mm = distance(center_pos, start_pos); 226 + let (arc_start, arc_end) = if tangent_arc_ccw_natural(start_pos, center_pos, tangent) { 227 + (start_pos, effective) 228 + } else { 229 + (effective, start_pos) 230 + }; 231 + ghost_arc_ccw(center_pos, radius_mm, arc_start, arc_end) 232 + }); 233 + SketchPreview { 234 + anchors: vec![start_pos], 235 + arcs: arc.into_iter().collect(), 236 + snap: snap_dot, 237 + ..SketchPreview::empty() 238 + } 239 + } 240 + 241 + fn preview_three_point_arc( 242 + sketch: &Sketch, 243 + effective: Point2, 244 + pending: Option<Pending>, 245 + snap_dot: Option<Point2>, 246 + ) -> SketchPreview { 247 + preview_dispatch_three( 248 + sketch, 249 + pending, 250 + effective, 251 + snap_dot, 252 + |start_pos, end_pos, eff| match circumcircle(start_pos, end_pos, eff) { 253 + Some((center, radius_mm)) => { 254 + let (s, e) = if ccw_through(center, start_pos, end_pos, eff) { 255 + (start_pos, end_pos) 256 + } else { 257 + (end_pos, start_pos) 258 + }; 259 + match ghost_arc_ccw(center, radius_mm, s, e) { 260 + Some(arc) => SketchPreview { 261 + arcs: vec![arc], 262 + ..SketchPreview::empty() 263 + }, 264 + None => SketchPreview::empty(), 265 + } 266 + } 267 + None => SketchPreview { 268 + segments: vec![(start_pos, end_pos)], 269 + ..SketchPreview::empty() 270 + }, 271 + }, 272 + ) 273 + } 274 + 275 + fn preview_corner_rectangle( 276 + sketch: &Sketch, 277 + effective: Point2, 278 + pending: Option<Pending>, 279 + snap_dot: Option<Point2>, 280 + ) -> SketchPreview { 281 + preview_dispatch_two(sketch, pending, effective, snap_dot, |corner_pos, eff| { 282 + let (cx, cy) = corner_pos.coords_mm(); 283 + let (ox, oy) = eff.coords_mm(); 284 + SketchPreview { 285 + segments: rect_edges( 286 + corner_pos, 287 + Point2::from_mm(ox, cy), 288 + Point2::from_mm(ox, oy), 289 + Point2::from_mm(cx, oy), 290 + ), 291 + ..SketchPreview::empty() 292 + } 293 + }) 294 + } 295 + 296 + fn preview_center_rectangle( 297 + sketch: &Sketch, 298 + effective: Point2, 299 + pending: Option<Pending>, 300 + snap_dot: Option<Point2>, 301 + ) -> SketchPreview { 302 + preview_dispatch_two(sketch, pending, effective, snap_dot, |center_pos, eff| { 303 + let (cx, cy) = center_pos.coords_mm(); 304 + let (ox, oy) = eff.coords_mm(); 305 + let dx = ox - cx; 306 + let dy = oy - cy; 307 + SketchPreview { 308 + segments: rect_edges( 309 + Point2::from_mm(cx + dx, cy + dy), 310 + Point2::from_mm(cx - dx, cy + dy), 311 + Point2::from_mm(cx - dx, cy - dy), 312 + Point2::from_mm(cx + dx, cy - dy), 313 + ), 314 + ..SketchPreview::empty() 315 + } 316 + }) 317 + } 318 + 319 + fn preview_three_point_corner_rectangle( 320 + sketch: &Sketch, 321 + effective: Point2, 322 + pending: Option<Pending>, 323 + snap_dot: Option<Point2>, 324 + ) -> SketchPreview { 325 + preview_dispatch_three(sketch, pending, effective, snap_dot, |p1, p2, eff| { 326 + match three_point_corner_extents(p1, p2, eff) { 327 + Some((p3, p4)) => SketchPreview { 328 + segments: rect_edges(p1, p2, p3, p4), 329 + ..SketchPreview::empty() 330 + }, 331 + None => SketchPreview { 332 + segments: vec![(p1, p2)], 333 + ..SketchPreview::empty() 334 + }, 335 + } 336 + }) 337 + } 338 + 339 + fn preview_three_point_center_rectangle( 340 + sketch: &Sketch, 341 + effective: Point2, 342 + pending: Option<Pending>, 343 + snap_dot: Option<Point2>, 344 + ) -> SketchPreview { 345 + preview_dispatch_three( 346 + sketch, 347 + pending, 348 + effective, 349 + snap_dot, 350 + |center_pos, mid_pos, eff| match three_point_center_corners(center_pos, mid_pos, eff) { 351 + Some((p1, p2, p3, p4)) => SketchPreview { 352 + segments: rect_edges(p1, p2, p3, p4), 353 + ..SketchPreview::empty() 354 + }, 355 + None => SketchPreview { 356 + segments: vec![(center_pos, mid_pos)], 357 + ..SketchPreview::empty() 358 + }, 359 + }, 360 + ) 361 + } 362 + 363 + fn preview_parallelogram( 364 + sketch: &Sketch, 365 + effective: Point2, 366 + pending: Option<Pending>, 367 + snap_dot: Option<Point2>, 368 + ) -> SketchPreview { 369 + preview_dispatch_three(sketch, pending, effective, snap_dot, |p1, p2, eff| { 370 + if degenerate_triangle(p1, p2, eff) { 371 + return SketchPreview { 372 + segments: vec![(p1, p2)], 373 + ..SketchPreview::empty() 374 + }; 375 + } 376 + let (p1x, p1y) = p1.coords_mm(); 377 + let (p2x, p2y) = p2.coords_mm(); 378 + let (p3x, p3y) = eff.coords_mm(); 379 + let p4 = Point2::from_mm(p1x + (p3x - p2x), p1y + (p3y - p2y)); 380 + SketchPreview { 381 + segments: rect_edges(p1, p2, eff, p4), 382 + ..SketchPreview::empty() 383 + } 384 + }) 385 + } 386 + 387 + fn rect_edges(p1: Point2, p2: Point2, p3: Point2, p4: Point2) -> Vec<(Point2, Point2)> { 388 + vec![(p1, p2), (p2, p3), (p3, p4), (p4, p1)] 389 + } 390 + 391 + fn ghost_arc_ccw( 392 + center: Point2, 393 + radius_mm: f64, 394 + start: Point2, 395 + end: Point2, 396 + ) -> Option<PreviewArc> { 397 + if !(radius_mm.is_finite() && radius_mm > 0.0) { 398 + return None; 399 + } 400 + let (cx, cy) = center.coords_mm(); 401 + let (sx, sy) = start.coords_mm(); 402 + let (ex, ey) = end.coords_mm(); 403 + let start_angle = (sy - cy).atan2(sx - cx); 404 + let end_angle = (ey - cy).atan2(ex - cx); 405 + if !(start_angle.is_finite() && end_angle.is_finite()) { 406 + return None; 407 + } 408 + let mut sweep = (end_angle - start_angle).rem_euclid(std::f64::consts::TAU); 409 + if sweep == 0.0 { 410 + sweep = std::f64::consts::TAU; 411 + } 412 + Some(PreviewArc { 413 + center, 414 + radius: Length::new::<millimeter>(radius_mm), 415 + start_angle: Angle::new::<radian>(start_angle), 416 + sweep_angle: Angle::new::<radian>(sweep), 417 + }) 418 + }