Another project
0

Configure Feed

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

at main 13 kB View raw
1use bone_types::{Angle, Length, Point2}; 2use uom::si::angle::radian; 3use uom::si::length::millimeter; 4use wgpu::util::DeviceExt; 5 6use crate::RenderTargets; 7use crate::camera::Camera2; 8use crate::gpu::{Gpu, PICK_FORMAT}; 9use crate::pick::PickId; 10use crate::pipelines::{CONSTRUCTION_BIT, FRAME_UNIFORM_SIZE, build_frame_uniform}; 11use crate::preview::{PreviewArc, PreviewCircle, SketchPreview}; 12use crate::scene::{SceneArc, SceneCircle, SketchScene}; 13use crate::snapshot::Style; 14 15#[repr(C)] 16#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 17struct ArcInstance { 18 center: [f32; 2], 19 radius_mm: f32, 20 half_width_px: f32, 21 start_rad: f32, 22 sweep_rad: f32, 23 aabb_min_mm: [f32; 2], 24 aabb_max_mm: [f32; 2], 25 pick_id: u32, 26 style_bits: u32, 27} 28 29const INSTANCE_STRIDE: u64 = core::mem::size_of::<ArcInstance>() as u64; 30 31pub struct ArcPipeline { 32 device: wgpu::Device, 33 queue: wgpu::Queue, 34 pipeline: wgpu::RenderPipeline, 35 uniform_buffer: wgpu::Buffer, 36 bind_group: wgpu::BindGroup, 37} 38 39impl ArcPipeline { 40 #[must_use] 41 pub fn new(gpu: &Gpu, color_format: wgpu::TextureFormat) -> Self { 42 let device = gpu.device().clone(); 43 let queue = gpu.queue().clone(); 44 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { 45 label: Some("bone-render:arc-shader"), 46 source: wgpu::ShaderSource::Wgsl(include_str!("arc.wgsl").into()), 47 }); 48 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 49 label: Some("bone-render:arc-bgl"), 50 entries: &[wgpu::BindGroupLayoutEntry { 51 binding: 0, 52 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, 53 ty: wgpu::BindingType::Buffer { 54 ty: wgpu::BufferBindingType::Uniform, 55 has_dynamic_offset: false, 56 min_binding_size: wgpu::BufferSize::new(FRAME_UNIFORM_SIZE), 57 }, 58 count: None, 59 }], 60 }); 61 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 62 label: Some("bone-render:arc-layout"), 63 bind_group_layouts: &[Some(&bind_group_layout)], 64 immediate_size: 0, 65 }); 66 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 67 label: Some("bone-render:arc-pipeline"), 68 layout: Some(&pipeline_layout), 69 vertex: wgpu::VertexState { 70 module: &shader, 71 entry_point: Some("vs"), 72 compilation_options: wgpu::PipelineCompilationOptions::default(), 73 buffers: &[wgpu::VertexBufferLayout { 74 array_stride: INSTANCE_STRIDE, 75 step_mode: wgpu::VertexStepMode::Instance, 76 attributes: &INSTANCE_ATTRS, 77 }], 78 }, 79 fragment: Some(wgpu::FragmentState { 80 module: &shader, 81 entry_point: Some("fs"), 82 compilation_options: wgpu::PipelineCompilationOptions::default(), 83 targets: &[ 84 Some(wgpu::ColorTargetState { 85 format: color_format, 86 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), 87 write_mask: wgpu::ColorWrites::ALL, 88 }), 89 Some(wgpu::ColorTargetState { 90 format: PICK_FORMAT, 91 blend: None, 92 write_mask: wgpu::ColorWrites::ALL, 93 }), 94 ], 95 }), 96 primitive: wgpu::PrimitiveState { 97 topology: wgpu::PrimitiveTopology::TriangleList, 98 strip_index_format: None, 99 front_face: wgpu::FrontFace::Ccw, 100 cull_mode: None, 101 polygon_mode: wgpu::PolygonMode::Fill, 102 conservative: false, 103 unclipped_depth: false, 104 }, 105 depth_stencil: None, 106 multisample: wgpu::MultisampleState::default(), 107 multiview_mask: None, 108 cache: None, 109 }); 110 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { 111 label: Some("bone-render:arc-uniform"), 112 size: FRAME_UNIFORM_SIZE, 113 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 114 mapped_at_creation: false, 115 }); 116 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 117 label: Some("bone-render:arc-bg"), 118 layout: &bind_group_layout, 119 entries: &[wgpu::BindGroupEntry { 120 binding: 0, 121 resource: uniform_buffer.as_entire_binding(), 122 }], 123 }); 124 Self { 125 device, 126 queue, 127 pipeline, 128 uniform_buffer, 129 bind_group, 130 } 131 } 132 133 pub fn draw( 134 &self, 135 encoder: &mut wgpu::CommandEncoder, 136 targets: RenderTargets<'_>, 137 camera: Camera2, 138 style: &Style, 139 scene: &SketchScene, 140 preview: &SketchPreview, 141 ) { 142 let instances = build_instances(scene, preview, style); 143 if instances.is_empty() { 144 return; 145 } 146 let uniform = build_frame_uniform(camera, style); 147 self.queue 148 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform)); 149 let instance_buffer = self 150 .device 151 .create_buffer_init(&wgpu::util::BufferInitDescriptor { 152 label: Some("bone-render:arc-instances"), 153 contents: bytemuck::cast_slice(&instances), 154 usage: wgpu::BufferUsages::VERTEX, 155 }); 156 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 157 label: Some("bone-render:arc-pass"), 158 color_attachments: &[ 159 Some(wgpu::RenderPassColorAttachment { 160 view: targets.color, 161 resolve_target: None, 162 depth_slice: None, 163 ops: wgpu::Operations { 164 load: wgpu::LoadOp::Load, 165 store: wgpu::StoreOp::Store, 166 }, 167 }), 168 Some(wgpu::RenderPassColorAttachment { 169 view: targets.pick, 170 resolve_target: None, 171 depth_slice: None, 172 ops: wgpu::Operations { 173 load: wgpu::LoadOp::Load, 174 store: wgpu::StoreOp::Store, 175 }, 176 }), 177 ], 178 depth_stencil_attachment: None, 179 timestamp_writes: None, 180 occlusion_query_set: None, 181 multiview_mask: None, 182 }); 183 pass.set_pipeline(&self.pipeline); 184 pass.set_bind_group(0, &self.bind_group, &[]); 185 pass.set_vertex_buffer(0, instance_buffer.slice(..)); 186 let len = instances.len(); 187 let Ok(count) = u32::try_from(len) else { 188 panic!("arc instance count {len} exceeds u32::MAX"); 189 }; 190 pass.draw(0..6, 0..count); 191 } 192} 193 194impl core::fmt::Debug for ArcPipeline { 195 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 196 f.debug_struct("ArcPipeline").finish_non_exhaustive() 197 } 198} 199 200const INSTANCE_ATTRS: [wgpu::VertexAttribute; 9] = wgpu::vertex_attr_array![ 201 0 => Float32x2, 202 1 => Float32, 203 2 => Float32, 204 3 => Float32, 205 4 => Float32, 206 5 => Float32x2, 207 6 => Float32x2, 208 7 => Uint32, 209 8 => Uint32, 210]; 211 212const FULL_CIRCLE_SWEEP: f32 = core::f32::consts::TAU; 213 214fn build_instances( 215 scene: &SketchScene, 216 preview: &SketchPreview, 217 style: &Style, 218) -> Vec<ArcInstance> { 219 let arcs = scene.arcs().iter().map(|a| arc_instance(*a, style)); 220 let circles = scene.circles().iter().map(|c| circle_instance(*c, style)); 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() 230} 231 232#[allow( 233 clippy::cast_possible_truncation, 234 reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 235)] 236fn arc_instance(arc: SceneArc, style: &Style) -> ArcInstance { 237 let (cx, cy) = arc.center().coords_mm(); 238 let radius_mm = arc.radius().get::<millimeter>() as f32; 239 let start_rad = arc.start_angle().get::<radian>() as f32; 240 let sweep_rad = arc.sweep_angle().get::<radian>() as f32; 241 let (aabb_min_mm, aabb_max_mm) = arc_aabb_offsets_mm( 242 arc.center(), 243 arc.radius(), 244 arc.start_angle(), 245 arc.sweep_angle(), 246 ); 247 let bits = if arc.for_construction() { 248 CONSTRUCTION_BIT 249 } else { 250 0 251 }; 252 ArcInstance { 253 center: [cx as f32, cy as f32], 254 radius_mm, 255 half_width_px: style.strokes().stroke_width_px() * 0.5, 256 start_rad, 257 sweep_rad, 258 aabb_min_mm, 259 aabb_max_mm, 260 pick_id: arc.pick().raw(), 261 style_bits: bits, 262 } 263} 264 265#[allow( 266 clippy::cast_possible_truncation, 267 reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 268)] 269fn circle_instance(circle: SceneCircle, style: &Style) -> ArcInstance { 270 let (cx, cy) = circle.center().coords_mm(); 271 let radius_mm = circle.radius().get::<millimeter>() as f32; 272 let bits = if circle.for_construction() { 273 CONSTRUCTION_BIT 274 } else { 275 0 276 }; 277 ArcInstance { 278 center: [cx as f32, cy as f32], 279 radius_mm, 280 half_width_px: style.strokes().stroke_width_px() * 0.5, 281 start_rad: 0.0, 282 sweep_rad: FULL_CIRCLE_SWEEP, 283 aabb_min_mm: [-radius_mm, -radius_mm], 284 aabb_max_mm: [radius_mm, radius_mm], 285 pick_id: circle.pick().raw(), 286 style_bits: bits, 287 } 288} 289 290#[allow( 291 clippy::cast_possible_truncation, 292 reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 293)] 294fn preview_circle_instance(circle: PreviewCircle, style: &Style) -> ArcInstance { 295 let (cx, cy) = circle.center.coords_mm(); 296 let radius_mm = circle.radius.get::<millimeter>() as f32; 297 ArcInstance { 298 center: [cx as f32, cy as f32], 299 radius_mm, 300 half_width_px: style.strokes().stroke_width_px() * 0.5, 301 start_rad: 0.0, 302 sweep_rad: FULL_CIRCLE_SWEEP, 303 aabb_min_mm: [-radius_mm, -radius_mm], 304 aabb_max_mm: [radius_mm, radius_mm], 305 pick_id: PickId::NONE.raw(), 306 style_bits: 0, 307 } 308} 309 310#[allow( 311 clippy::cast_possible_truncation, 312 reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 313)] 314fn preview_arc_instance(arc: PreviewArc, style: &Style) -> ArcInstance { 315 let (cx, cy) = arc.center.coords_mm(); 316 let radius_mm = arc.radius.get::<millimeter>() as f32; 317 let start_rad = arc.start_angle.get::<radian>() as f32; 318 let sweep_rad = arc.sweep_angle.get::<radian>() as f32; 319 let (aabb_min_mm, aabb_max_mm) = 320 arc_aabb_offsets_mm(arc.center, arc.radius, arc.start_angle, arc.sweep_angle); 321 ArcInstance { 322 center: [cx as f32, cy as f32], 323 radius_mm, 324 half_width_px: style.strokes().stroke_width_px() * 0.5, 325 start_rad, 326 sweep_rad, 327 aabb_min_mm, 328 aabb_max_mm, 329 pick_id: PickId::NONE.raw(), 330 style_bits: 0, 331 } 332} 333 334#[allow( 335 clippy::cast_possible_truncation, 336 reason = "mm coordinates and radians fit f32 mantissa at CAD scales" 337)] 338fn arc_aabb_offsets_mm( 339 center: Point2, 340 radius: Length, 341 start: Angle, 342 sweep: Angle, 343) -> ([f32; 2], [f32; 2]) { 344 let aabb = bone_kernel::arc_bounding_box(center, radius, start, sweep); 345 let (cx, cy) = center.coords_mm(); 346 let (mnx, mny) = aabb.min().coords_mm(); 347 let (mxx, mxy) = aabb.max().coords_mm(); 348 ( 349 [(mnx - cx) as f32, (mny - cy) as f32], 350 [(mxx - cx) as f32, (mxy - cy) as f32], 351 ) 352} 353 354#[cfg(test)] 355mod tests { 356 use super::arc_aabb_offsets_mm; 357 use bone_types::{Angle, Length, Point2}; 358 use core::f64::consts::FRAC_PI_2; 359 use uom::si::angle::radian; 360 use uom::si::length::millimeter; 361 362 #[test] 363 fn quarter_arc_at_origin_offsets_to_unit_quadrant() { 364 let (min, max) = arc_aabb_offsets_mm( 365 Point2::from_mm(0.0, 0.0), 366 Length::new::<millimeter>(1.0), 367 Angle::new::<radian>(0.0), 368 Angle::new::<radian>(FRAC_PI_2), 369 ); 370 assert!(min[0].abs() < 1e-5 && min[1].abs() < 1e-5); 371 assert!((max[0] - 1.0).abs() < 1e-5 && (max[1] - 1.0).abs() < 1e-5); 372 } 373 374 #[test] 375 fn translated_center_yields_the_same_relative_offsets() { 376 let (min, max) = arc_aabb_offsets_mm( 377 Point2::from_mm(10.0, -5.0), 378 Length::new::<millimeter>(1.0), 379 Angle::new::<radian>(0.0), 380 Angle::new::<radian>(FRAC_PI_2), 381 ); 382 assert!(min[0].abs() < 1e-5 && min[1].abs() < 1e-5); 383 assert!((max[0] - 1.0).abs() < 1e-5 && (max[1] - 1.0).abs() < 1e-5); 384 } 385}