Another project
0

Configure Feed

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

feat(render): preview anchors, segments, circles, arcs

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

author
Lewis
date (May 10, 2026, 10:39 AM +0300) commit 2d3d75e4 parent 65f345eb change-id olpqvmus
+140 -26
+15 -4
crates/bone-render/src/lib.rs
··· 16 16 ArcPipeline, ChromeInstance, ChromePipeline, ChromeTextPipeline, GlyphPipeline, GridPipeline, 17 17 LinesPipeline, SdfGlyphInstance, TextPipeline, 18 18 }; 19 - pub use preview::SketchPreview; 19 + pub use preview::{PreviewArc, PreviewCircle, SketchPreview}; 20 20 pub use scene::{ 21 21 RelationGlyphKind, SceneArc, SceneCircle, SceneDimension, SceneLine, ScenePoint, 22 22 SceneRelationGlyph, SketchScene, ··· 115 115 ) { 116 116 self.grid.draw(encoder, targets.color, camera, style); 117 117 gpu::clear_pick_attachment(encoder, targets.pick); 118 - self.arcs.draw(encoder, targets, camera, style, scene); 118 + self.arcs 119 + .draw(encoder, targets, camera, style, scene, preview); 119 120 self.lines 120 121 .draw(encoder, targets, camera, style, scene, preview); 121 122 self.glyphs.draw(encoder, targets, camera, style, scene); ··· 129 130 camera: Camera2, 130 131 style: &Style, 131 132 ) -> Result<SnapshotFrame> { 133 + self.render_with_preview(ctx, scene, &SketchPreview::empty(), camera, style) 134 + } 135 + 136 + pub fn render_with_preview( 137 + &mut self, 138 + ctx: &OffscreenContext, 139 + scene: &SketchScene, 140 + preview: &SketchPreview, 141 + camera: Camera2, 142 + style: &Style, 143 + ) -> Result<SnapshotFrame> { 132 144 debug_assert_eq!( 133 145 camera.extent(), 134 146 ctx.extent(), 135 147 "camera extent must match offscreen context extent", 136 148 ); 137 149 self.prepare(scene, style); 138 - let preview = SketchPreview::empty(); 139 150 ctx.render(|encoder, color_view, pick_view| { 140 151 self.encode_passes( 141 152 encoder, 142 153 RenderTargets::new(color_view, pick_view), 143 154 scene, 144 - &preview, 155 + preview, 145 156 camera, 146 157 style, 147 158 );
+56 -3
crates/bone-render/src/pipelines/arc.rs
··· 6 6 use crate::RenderTargets; 7 7 use crate::camera::Camera2; 8 8 use crate::gpu::{Gpu, PICK_FORMAT}; 9 + use crate::pick::PickId; 9 10 use crate::pipelines::{CONSTRUCTION_BIT, FRAME_UNIFORM_SIZE, build_frame_uniform}; 11 + use crate::preview::{PreviewArc, PreviewCircle, SketchPreview}; 10 12 use crate::scene::{SceneArc, SceneCircle, SketchScene}; 11 13 use crate::snapshot::Style; 12 14 ··· 135 137 camera: Camera2, 136 138 style: &Style, 137 139 scene: &SketchScene, 140 + preview: &SketchPreview, 138 141 ) { 139 - let instances = build_instances(scene, style); 142 + let instances = build_instances(scene, preview, style); 140 143 if instances.is_empty() { 141 144 return; 142 145 } ··· 208 211 209 212 const FULL_CIRCLE_SWEEP: f32 = core::f32::consts::TAU; 210 213 211 - fn build_instances(scene: &SketchScene, style: &Style) -> Vec<ArcInstance> { 214 + fn build_instances( 215 + scene: &SketchScene, 216 + preview: &SketchPreview, 217 + style: &Style, 218 + ) -> Vec<ArcInstance> { 212 219 let arcs = scene.arcs().iter().map(|a| arc_instance(*a, style)); 213 220 let circles = scene.circles().iter().map(|c| circle_instance(*c, style)); 214 - arcs.chain(circles).collect() 221 + let preview_circles = preview 222 + .circles 223 + .iter() 224 + .map(|c| preview_circle_instance(*c, style)); 225 + let preview_arcs = preview.arcs.iter().map(|a| preview_arc_instance(*a, style)); 226 + arcs.chain(circles) 227 + .chain(preview_circles) 228 + .chain(preview_arcs) 229 + .collect() 215 230 } 216 231 217 232 #[allow(clippy::cast_possible_truncation)] ··· 263 278 aabb_max_mm: [radius_mm, radius_mm], 264 279 pick_id: circle.pick().raw(), 265 280 style_bits: bits, 281 + } 282 + } 283 + 284 + #[allow(clippy::cast_possible_truncation)] 285 + fn preview_circle_instance(circle: PreviewCircle, style: &Style) -> ArcInstance { 286 + let (cx, cy) = circle.center.coords_mm(); 287 + let radius_mm = circle.radius.get::<millimeter>() as f32; 288 + ArcInstance { 289 + center: [cx as f32, cy as f32], 290 + radius_mm, 291 + half_width_px: style.strokes().stroke_width_px() * 0.5, 292 + start_rad: 0.0, 293 + sweep_rad: FULL_CIRCLE_SWEEP, 294 + aabb_min_mm: [-radius_mm, -radius_mm], 295 + aabb_max_mm: [radius_mm, radius_mm], 296 + pick_id: PickId::NONE.raw(), 297 + style_bits: 0, 298 + } 299 + } 300 + 301 + #[allow(clippy::cast_possible_truncation)] 302 + fn preview_arc_instance(arc: PreviewArc, style: &Style) -> ArcInstance { 303 + let (cx, cy) = arc.center.coords_mm(); 304 + let radius_mm = arc.radius.get::<millimeter>() as f32; 305 + let start_rad = arc.start_angle.get::<radian>() as f32; 306 + let sweep_rad = arc.sweep_angle.get::<radian>() as f32; 307 + let (aabb_min_mm, aabb_max_mm) = 308 + arc_aabb_offsets_mm(arc.center, arc.radius, arc.start_angle, arc.sweep_angle); 309 + ArcInstance { 310 + center: [cx as f32, cy as f32], 311 + radius_mm, 312 + half_width_px: style.strokes().stroke_width_px() * 0.5, 313 + start_rad, 314 + sweep_rad, 315 + aabb_min_mm, 316 + aabb_max_mm, 317 + pick_id: PickId::NONE.raw(), 318 + style_bits: 0, 266 319 } 267 320 } 268 321
+6 -6
crates/bone-render/src/pipelines/lines.rs
··· 206 206 ) -> Vec<LineInstance> { 207 207 let lines = scene.lines().iter().map(|l| line_instance(*l, style)); 208 208 let points = scene.points().iter().map(|p| point_instance(*p, style)); 209 - let preview_line = preview 210 - .rubber_band 209 + let preview_segments = preview 210 + .segments 211 211 .iter() 212 212 .map(|(a, b)| preview_line_instance(*a, *b, style)); 213 - let preview_anchor = preview 214 - .anchor 213 + let preview_anchors = preview 214 + .anchors 215 215 .iter() 216 216 .map(|p| preview_point_instance(*p, style)); 217 217 let preview_snap = preview ··· 220 220 .map(|p| preview_snap_instance(*p, style)); 221 221 lines 222 222 .chain(points) 223 - .chain(preview_line) 224 - .chain(preview_anchor) 223 + .chain(preview_segments) 224 + .chain(preview_anchors) 225 225 .chain(preview_snap) 226 226 .collect() 227 227 }
+63 -13
crates/bone-render/src/preview.rs
··· 1 - use bone_types::Point2; 1 + use bone_types::{Angle, Length, Point2}; 2 + 3 + #[derive(Copy, Clone, Debug, PartialEq)] 4 + pub struct PreviewCircle { 5 + pub center: Point2, 6 + pub radius: Length, 7 + } 8 + 9 + #[derive(Copy, Clone, Debug, PartialEq)] 10 + pub struct PreviewArc { 11 + pub center: Point2, 12 + pub radius: Length, 13 + pub start_angle: Angle, 14 + pub sweep_angle: Angle, 15 + } 2 16 3 - #[derive(Copy, Clone, Debug, Default, PartialEq)] 17 + #[derive(Clone, Debug, Default, PartialEq)] 4 18 pub struct SketchPreview { 5 - pub anchor: Option<Point2>, 6 - pub rubber_band: Option<(Point2, Point2)>, 19 + pub anchors: Vec<Point2>, 20 + pub segments: Vec<(Point2, Point2)>, 21 + pub circles: Vec<PreviewCircle>, 22 + pub arcs: Vec<PreviewArc>, 7 23 pub snap: Option<Point2>, 8 24 } 9 25 ··· 11 27 #[must_use] 12 28 pub const fn empty() -> Self { 13 29 Self { 14 - anchor: None, 15 - rubber_band: None, 30 + anchors: Vec::new(), 31 + segments: Vec::new(), 32 + circles: Vec::new(), 33 + arcs: Vec::new(), 16 34 snap: None, 17 35 } 18 36 } 19 37 20 38 #[must_use] 21 - pub const fn is_empty(&self) -> bool { 22 - self.anchor.is_none() && self.rubber_band.is_none() && self.snap.is_none() 39 + pub fn is_empty(&self) -> bool { 40 + self.anchors.is_empty() 41 + && self.segments.is_empty() 42 + && self.circles.is_empty() 43 + && self.arcs.is_empty() 44 + && self.snap.is_none() 23 45 } 24 46 } 25 47 26 48 #[cfg(test)] 27 49 mod tests { 28 - use super::SketchPreview; 29 - use bone_types::Point2; 50 + use super::{PreviewArc, PreviewCircle, SketchPreview}; 51 + use bone_types::{Angle, Length, Point2}; 52 + use uom::si::angle::radian; 53 + use uom::si::length::millimeter; 30 54 31 55 #[test] 32 56 fn empty_has_no_overlay() { ··· 37 61 #[test] 38 62 fn anchor_only_is_non_empty() { 39 63 let preview = SketchPreview { 40 - anchor: Some(Point2::from_mm(1.0, 2.0)), 64 + anchors: vec![Point2::from_mm(1.0, 2.0)], 41 65 ..SketchPreview::empty() 42 66 }; 43 67 assert!(!preview.is_empty()); 44 68 } 45 69 46 70 #[test] 47 - fn rubber_band_only_is_non_empty() { 71 + fn segment_only_is_non_empty() { 48 72 let preview = SketchPreview { 49 - rubber_band: Some((Point2::from_mm(0.0, 0.0), Point2::from_mm(1.0, 1.0))), 73 + segments: vec![(Point2::from_mm(0.0, 0.0), Point2::from_mm(1.0, 1.0))], 50 74 ..SketchPreview::empty() 51 75 }; 52 76 assert!(!preview.is_empty()); ··· 56 80 fn snap_only_is_non_empty() { 57 81 let preview = SketchPreview { 58 82 snap: Some(Point2::from_mm(2.0, 3.0)), 83 + ..SketchPreview::empty() 84 + }; 85 + assert!(!preview.is_empty()); 86 + } 87 + 88 + #[test] 89 + fn circle_only_is_non_empty() { 90 + let preview = SketchPreview { 91 + circles: vec![PreviewCircle { 92 + center: Point2::from_mm(0.0, 0.0), 93 + radius: Length::new::<millimeter>(3.0), 94 + }], 95 + ..SketchPreview::empty() 96 + }; 97 + assert!(!preview.is_empty()); 98 + } 99 + 100 + #[test] 101 + fn arc_only_is_non_empty() { 102 + let preview = SketchPreview { 103 + arcs: vec![PreviewArc { 104 + center: Point2::from_mm(0.0, 0.0), 105 + radius: Length::new::<millimeter>(2.0), 106 + start_angle: Angle::new::<radian>(0.0), 107 + sweep_angle: Angle::new::<radian>(1.0), 108 + }], 59 109 ..SketchPreview::empty() 60 110 }; 61 111 assert!(!preview.is_empty());