diff --git a/crates/bone-render/src/lib.rs b/crates/bone-render/src/lib.rs index bcf89de..a2a8342 100644 --- a/crates/bone-render/src/lib.rs +++ b/crates/bone-render/src/lib.rs @@ -16,7 +16,7 @@ pub use pipelines::{ ArcPipeline, ChromeInstance, ChromePipeline, ChromeTextPipeline, GlyphPipeline, GridPipeline, LinesPipeline, SdfGlyphInstance, TextPipeline, }; -pub use preview::SketchPreview; +pub use preview::{PreviewArc, PreviewCircle, SketchPreview}; pub use scene::{ RelationGlyphKind, SceneArc, SceneCircle, SceneDimension, SceneLine, ScenePoint, SceneRelationGlyph, SketchScene, @@ -115,7 +115,8 @@ impl SketchRenderer { ) { self.grid.draw(encoder, targets.color, camera, style); gpu::clear_pick_attachment(encoder, targets.pick); - self.arcs.draw(encoder, targets, camera, style, scene); + self.arcs + .draw(encoder, targets, camera, style, scene, preview); self.lines .draw(encoder, targets, camera, style, scene, preview); self.glyphs.draw(encoder, targets, camera, style, scene); @@ -128,6 +129,17 @@ impl SketchRenderer { scene: &SketchScene, camera: Camera2, style: &Style, + ) -> Result { + self.render_with_preview(ctx, scene, &SketchPreview::empty(), camera, style) + } + + pub fn render_with_preview( + &mut self, + ctx: &OffscreenContext, + scene: &SketchScene, + preview: &SketchPreview, + camera: Camera2, + style: &Style, ) -> Result { debug_assert_eq!( camera.extent(), @@ -135,13 +147,12 @@ impl SketchRenderer { "camera extent must match offscreen context extent", ); self.prepare(scene, style); - let preview = SketchPreview::empty(); ctx.render(|encoder, color_view, pick_view| { self.encode_passes( encoder, RenderTargets::new(color_view, pick_view), scene, - &preview, + preview, camera, style, ); diff --git a/crates/bone-render/src/pipelines/arc.rs b/crates/bone-render/src/pipelines/arc.rs index b53938d..08c32f3 100644 --- a/crates/bone-render/src/pipelines/arc.rs +++ b/crates/bone-render/src/pipelines/arc.rs @@ -6,7 +6,9 @@ use wgpu::util::DeviceExt; use crate::RenderTargets; use crate::camera::Camera2; use crate::gpu::{Gpu, PICK_FORMAT}; +use crate::pick::PickId; use crate::pipelines::{CONSTRUCTION_BIT, FRAME_UNIFORM_SIZE, build_frame_uniform}; +use crate::preview::{PreviewArc, PreviewCircle, SketchPreview}; use crate::scene::{SceneArc, SceneCircle, SketchScene}; use crate::snapshot::Style; @@ -135,8 +137,9 @@ impl ArcPipeline { camera: Camera2, style: &Style, scene: &SketchScene, + preview: &SketchPreview, ) { - let instances = build_instances(scene, style); + let instances = build_instances(scene, preview, style); if instances.is_empty() { return; } @@ -208,10 +211,22 @@ const INSTANCE_ATTRS: [wgpu::VertexAttribute; 9] = wgpu::vertex_attr_array![ const FULL_CIRCLE_SWEEP: f32 = core::f32::consts::TAU; -fn build_instances(scene: &SketchScene, style: &Style) -> Vec { +fn build_instances( + scene: &SketchScene, + preview: &SketchPreview, + style: &Style, +) -> Vec { let arcs = scene.arcs().iter().map(|a| arc_instance(*a, style)); let circles = scene.circles().iter().map(|c| circle_instance(*c, style)); - arcs.chain(circles).collect() + let preview_circles = preview + .circles + .iter() + .map(|c| preview_circle_instance(*c, style)); + let preview_arcs = preview.arcs.iter().map(|a| preview_arc_instance(*a, style)); + arcs.chain(circles) + .chain(preview_circles) + .chain(preview_arcs) + .collect() } #[allow(clippy::cast_possible_truncation)] @@ -266,6 +281,44 @@ fn circle_instance(circle: SceneCircle, style: &Style) -> ArcInstance { } } +#[allow(clippy::cast_possible_truncation)] +fn preview_circle_instance(circle: PreviewCircle, style: &Style) -> ArcInstance { + let (cx, cy) = circle.center.coords_mm(); + let radius_mm = circle.radius.get::() as f32; + ArcInstance { + center: [cx as f32, cy as f32], + radius_mm, + half_width_px: style.strokes().stroke_width_px() * 0.5, + start_rad: 0.0, + sweep_rad: FULL_CIRCLE_SWEEP, + aabb_min_mm: [-radius_mm, -radius_mm], + aabb_max_mm: [radius_mm, radius_mm], + pick_id: PickId::NONE.raw(), + style_bits: 0, + } +} + +#[allow(clippy::cast_possible_truncation)] +fn preview_arc_instance(arc: PreviewArc, style: &Style) -> ArcInstance { + let (cx, cy) = arc.center.coords_mm(); + let radius_mm = arc.radius.get::() as f32; + let start_rad = arc.start_angle.get::() as f32; + let sweep_rad = arc.sweep_angle.get::() as f32; + let (aabb_min_mm, aabb_max_mm) = + arc_aabb_offsets_mm(arc.center, arc.radius, arc.start_angle, arc.sweep_angle); + ArcInstance { + center: [cx as f32, cy as f32], + radius_mm, + half_width_px: style.strokes().stroke_width_px() * 0.5, + start_rad, + sweep_rad, + aabb_min_mm, + aabb_max_mm, + pick_id: PickId::NONE.raw(), + style_bits: 0, + } +} + #[allow(clippy::cast_possible_truncation)] fn arc_aabb_offsets_mm( center: Point2, diff --git a/crates/bone-render/src/pipelines/lines.rs b/crates/bone-render/src/pipelines/lines.rs index e172616..924acc8 100644 --- a/crates/bone-render/src/pipelines/lines.rs +++ b/crates/bone-render/src/pipelines/lines.rs @@ -206,12 +206,12 @@ fn build_instances( ) -> Vec { let lines = scene.lines().iter().map(|l| line_instance(*l, style)); let points = scene.points().iter().map(|p| point_instance(*p, style)); - let preview_line = preview - .rubber_band + let preview_segments = preview + .segments .iter() .map(|(a, b)| preview_line_instance(*a, *b, style)); - let preview_anchor = preview - .anchor + let preview_anchors = preview + .anchors .iter() .map(|p| preview_point_instance(*p, style)); let preview_snap = preview @@ -220,8 +220,8 @@ fn build_instances( .map(|p| preview_snap_instance(*p, style)); lines .chain(points) - .chain(preview_line) - .chain(preview_anchor) + .chain(preview_segments) + .chain(preview_anchors) .chain(preview_snap) .collect() } diff --git a/crates/bone-render/src/preview.rs b/crates/bone-render/src/preview.rs index 820d4d8..d2dee3c 100644 --- a/crates/bone-render/src/preview.rs +++ b/crates/bone-render/src/preview.rs @@ -1,9 +1,25 @@ -use bone_types::Point2; +use bone_types::{Angle, Length, Point2}; -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct PreviewCircle { + pub center: Point2, + pub radius: Length, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct PreviewArc { + pub center: Point2, + pub radius: Length, + pub start_angle: Angle, + pub sweep_angle: Angle, +} + +#[derive(Clone, Debug, Default, PartialEq)] pub struct SketchPreview { - pub anchor: Option, - pub rubber_band: Option<(Point2, Point2)>, + pub anchors: Vec, + pub segments: Vec<(Point2, Point2)>, + pub circles: Vec, + pub arcs: Vec, pub snap: Option, } @@ -11,22 +27,30 @@ impl SketchPreview { #[must_use] pub const fn empty() -> Self { Self { - anchor: None, - rubber_band: None, + anchors: Vec::new(), + segments: Vec::new(), + circles: Vec::new(), + arcs: Vec::new(), snap: None, } } #[must_use] - pub const fn is_empty(&self) -> bool { - self.anchor.is_none() && self.rubber_band.is_none() && self.snap.is_none() + pub fn is_empty(&self) -> bool { + self.anchors.is_empty() + && self.segments.is_empty() + && self.circles.is_empty() + && self.arcs.is_empty() + && self.snap.is_none() } } #[cfg(test)] mod tests { - use super::SketchPreview; - use bone_types::Point2; + use super::{PreviewArc, PreviewCircle, SketchPreview}; + use bone_types::{Angle, Length, Point2}; + use uom::si::angle::radian; + use uom::si::length::millimeter; #[test] fn empty_has_no_overlay() { @@ -37,16 +61,16 @@ mod tests { #[test] fn anchor_only_is_non_empty() { let preview = SketchPreview { - anchor: Some(Point2::from_mm(1.0, 2.0)), + anchors: vec![Point2::from_mm(1.0, 2.0)], ..SketchPreview::empty() }; assert!(!preview.is_empty()); } #[test] - fn rubber_band_only_is_non_empty() { + fn segment_only_is_non_empty() { let preview = SketchPreview { - rubber_band: Some((Point2::from_mm(0.0, 0.0), Point2::from_mm(1.0, 1.0))), + segments: vec![(Point2::from_mm(0.0, 0.0), Point2::from_mm(1.0, 1.0))], ..SketchPreview::empty() }; assert!(!preview.is_empty()); @@ -60,4 +84,30 @@ mod tests { }; assert!(!preview.is_empty()); } + + #[test] + fn circle_only_is_non_empty() { + let preview = SketchPreview { + circles: vec![PreviewCircle { + center: Point2::from_mm(0.0, 0.0), + radius: Length::new::(3.0), + }], + ..SketchPreview::empty() + }; + assert!(!preview.is_empty()); + } + + #[test] + fn arc_only_is_non_empty() { + let preview = SketchPreview { + arcs: vec![PreviewArc { + center: Point2::from_mm(0.0, 0.0), + radius: Length::new::(2.0), + start_angle: Angle::new::(0.0), + sweep_angle: Angle::new::(1.0), + }], + ..SketchPreview::empty() + }; + assert!(!preview.is_empty()); + } }