1use super::*;
2
3impl RenderCore {
4 pub fn render_to_view(
8 &mut self,
9 gpu_scene: &mut GpuScene,
10 scene: &RenderScene,
11 params: &FrameParams,
12 resolve_view: &wgpu::TextureView,
13 ) {
14 let width = params.width.max(1);
15 let height = params.height.max(1);
16 self.ensure_targets(width, height);
17 self.write_global_styles(params.settings);
18
19 let globals = Globals {
20 view_proj: params.camera.view_proj,
21 viewport: [width as f32, height as f32, params.dpr.max(1e-3), 0.0],
22 forward: [
23 params.camera.forward[0],
24 params.camera.forward[1],
25 params.camera.forward[2],
26 0.0,
27 ],
28 };
29 self.queue
30 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
31
32 let axis_len = params.settings.axis_length_px as f64 * params.world_per_pixel;
34 let draw_axes = params.settings.axis_length_px > 0.0 && axis_len.is_finite() && axis_len > 0.0;
35 if draw_axes {
36 let l = axis_len as f32;
37 let segments = [
38 EdgeInstance { p0: [0.0; 3], p1: [l, 0.0, 0.0] },
39 EdgeInstance { p0: [0.0; 3], p1: [0.0, l, 0.0] },
40 EdgeInstance { p0: [0.0; 3], p1: [0.0, 0.0, l] },
41 ];
42 match &gpu_scene.axis_buf {
43 Some(buf) => self.queue.write_buffer(buf, 0, bytemuck::cast_slice(&segments)),
44 None => {
45 gpu_scene.axis_buf = Some(self.device.create_buffer_init(
46 &wgpu::util::BufferInitDescriptor {
47 label: Some("world axes"),
48 contents: bytemuck::cast_slice(&segments),
49 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
50 },
51 ));
52 }
53 }
54 }
55
56 let has_emphasis = !params.emphasis.is_empty();
58 for solid in scene.solids() {
59 if let Some(gpu_solid) = gpu_scene.solids.get_mut(&solid.name) {
60 self.sync_boundary(gpu_solid, solid, params.emphasis);
61 }
62 }
63
64 let bg = params.settings.background;
65 let targets = self.targets.as_ref().expect("targets ensured");
66 let mut encoder = self
67 .device
68 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
69 label: Some("brep-render frame"),
70 });
71 {
72 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
73 label: Some("scene"),
74 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
75 view: &targets.msaa_view,
76 depth_slice: None,
77 resolve_target: Some(resolve_view),
78 ops: wgpu::Operations {
79 load: wgpu::LoadOp::Clear(wgpu::Color {
80 r: bg[0] as f64,
81 g: bg[1] as f64,
82 b: bg[2] as f64,
83 a: 1.0,
84 }),
85 store: wgpu::StoreOp::Store,
86 },
87 })],
88 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
89 view: &targets.depth_view,
90 depth_ops: Some(wgpu::Operations {
91 load: wgpu::LoadOp::Clear(1.0),
92 store: wgpu::StoreOp::Discard,
93 }),
94 stencil_ops: None,
95 }),
96 timestamp_writes: None,
97 occlusion_query_set: None,
98 multiview_mask: None,
99 });
100 pass.set_bind_group(0, &self.globals_bind, &[]);
101
102 let visible_solids: Vec<(&SolidDisplay, &GpuSolid)> = gpu_scene
103 .order
104 .iter()
105 .filter_map(|name| {
106 let solid = scene.solid(name)?;
107 if !solid.visible {
108 return None;
109 }
110 Some((solid, gpu_scene.solids.get(name)?))
111 })
112 .collect();
113
114 if params.settings.wireframe {
121 pass.set_pipeline(&self.wire_pipeline);
122 for (solid, gpu_solid) in &visible_solids {
123 if gpu_solid.wire_index_count == 0 {
124 continue;
125 }
126 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
127 pass.set_index_buffer(
128 gpu_solid.wire_index_buf.slice(..),
129 wgpu::IndexFormat::Uint32,
130 );
131 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
132 if !solid.visibility.any_face_hidden() {
134 pass.draw_indexed(0..gpu_solid.wire_index_count, 0, 0..1);
135 continue;
136 }
137 let mut run: Option<(u32, u32)> = None; let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<(u32, u32)>| {
145 if let Some((first, count)) = run.take() {
146 if count > 0 {
147 pass.draw_indexed(first..first + count, 0, 0..1);
148 }
149 }
150 };
151 for (index, face) in solid.faces.iter().enumerate() {
152 if face.tri_count == 0 {
153 continue;
154 }
155 let first = face.tri_start * 6;
156 let count = face.tri_count * 6;
157 if !solid.visibility.is_face_visible(index) {
158 flush(&mut pass, &mut run);
159 continue;
160 }
161 match &mut run {
162 Some((run_first, run_count)) if *run_first + *run_count == first => {
163 *run_count += count;
164 }
165 _ => {
166 flush(&mut pass, &mut run);
167 run = Some((first, count));
168 }
169 }
170 }
171 flush(&mut pass, &mut run);
172 }
173 }
174
175 if !params.settings.wireframe {
180 pass.set_pipeline(&self.mesh_pipeline);
181 for (solid, gpu_solid) in &visible_solids {
182 if solid.mesh.indices.is_empty() {
183 continue;
184 }
185 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
186 pass.set_index_buffer(gpu_solid.index_buf.slice(..), wgpu::IndexFormat::Uint32);
187 let any_face_hidden = solid.visibility.any_face_hidden();
192 if !has_emphasis && !any_face_hidden {
193 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
194 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
195 continue;
196 }
197 let style_for = |state: EmphasisState| match state {
198 EmphasisState::Base => &gpu_solid.base_style.bind,
199 EmphasisState::Selected => &self.styles.face_selected.bind,
200 EmphasisState::Hovered => &self.styles.face_hovered.bind,
201 };
202 let mut run: Option<(EmphasisState, u32, u32)> = None; let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<(EmphasisState, u32, u32)>| {
204 if let Some((state, first, count)) = run.take() {
205 if count > 0 {
206 pass.set_bind_group(1, style_for(state), &[]);
207 pass.draw_indexed(first..first + count, 0, 0..1);
208 }
209 }
210 };
211 for (index, face) in solid.faces.iter().enumerate() {
212 let range = &gpu_solid.faces[index];
213 if range.index_count == 0 {
214 continue;
215 }
216 if !solid.visibility.is_face_visible(index) {
219 flush(&mut pass, &mut run);
220 continue;
221 }
222 let state = if has_emphasis {
223 params.emphasis.face_state(&solid.name, &face.name)
224 } else {
225 EmphasisState::Base
226 };
227 match &mut run {
228 Some((run_state, first, count))
229 if *run_state == state && *first + *count == range.first_index =>
230 {
231 *count += range.index_count;
232 }
233 _ => {
234 flush(&mut pass, &mut run);
235 run = Some((state, range.first_index, range.index_count));
236 }
237 }
238 }
239 flush(&mut pass, &mut run);
240 }
241 }
242
243 if params.settings.hidden_edge_alpha > 0.0 {
246 pass.set_pipeline(&self.edge_hidden_pipeline);
247 pass.set_bind_group(1, &self.styles.edge_hidden.bind, &[]);
248 for (solid, gpu_solid) in &visible_solids {
249 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
250 if gpu_solid.edge_instances == 0 {
251 continue;
252 }
253 pass.set_vertex_buffer(0, edge_buf.slice(..));
254 if solid.visibility.any_edge_hidden() {
255 draw_visible_edge_ranges(&mut pass, solid, gpu_solid);
257 } else {
258 pass.draw(0..6, 0..gpu_solid.edge_instances);
259 }
260 }
261 }
262
263 pass.set_pipeline(&self.edge_visible_pipeline);
265 for (solid, gpu_solid) in &visible_solids {
266 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
267 if gpu_solid.edge_instances == 0 {
268 continue;
269 }
270 pass.set_vertex_buffer(0, edge_buf.slice(..));
271 let any_edge_hidden = solid.visibility.any_edge_hidden();
272 if !has_emphasis {
273 pass.set_bind_group(1, &self.styles.edge_base.bind, &[]);
274 if any_edge_hidden {
275 draw_visible_edge_ranges(&mut pass, solid, gpu_solid);
276 } else {
277 pass.draw(0..6, 0..gpu_solid.edge_instances);
278 }
279 continue;
280 }
281 let style_for = |state: EmphasisState| match state {
282 EmphasisState::Base => &self.styles.edge_base.bind,
283 EmphasisState::Selected => &self.styles.edge_selected.bind,
284 EmphasisState::Hovered => &self.styles.edge_hovered.bind,
285 };
286 let mut run: Option<(EmphasisState, u32, u32)> = None;
287 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<(EmphasisState, u32, u32)>| {
288 if let Some((state, first, count)) = run.take() {
289 if count > 0 {
290 pass.set_bind_group(1, style_for(state), &[]);
291 pass.draw(0..6, first..first + count);
292 }
293 }
294 };
295 for (index, edge) in solid.edges.iter().enumerate() {
296 let range = &gpu_solid.edges[index];
297 if range.instance_count == 0 {
298 continue;
299 }
300 if !solid.visibility.is_edge_visible(index) {
302 flush(&mut pass, &mut run);
303 continue;
304 }
305 let state = params.emphasis.edge_state(&solid.name, &edge.name);
306 match &mut run {
307 Some((run_state, first, count))
308 if *run_state == state
309 && *first + *count == range.first_instance =>
310 {
311 *count += range.instance_count;
312 }
313 _ => {
314 flush(&mut pass, &mut run);
315 run = Some((state, range.first_instance, range.instance_count));
316 }
317 }
318 }
319 flush(&mut pass, &mut run);
320 }
321
322 if has_emphasis {
324 pass.set_bind_group(1, &self.styles.boundary.bind, &[]);
325 for (_, gpu_solid) in &visible_solids {
326 let Some(boundary) = &gpu_solid.boundary else { continue };
327 let Some(buf) = &boundary.buf else { continue };
328 if boundary.count == 0 {
329 continue;
330 }
331 pass.set_vertex_buffer(0, buf.slice(..));
332 pass.draw(0..6, 0..boundary.count);
333 }
334 }
335
336 if params.settings.vertex_size_px > 0.0 {
338 pass.set_pipeline(&self.point_pipeline);
339 for (solid, gpu_solid) in &visible_solids {
340 let Some(point_buf) = &gpu_solid.point_buf else { continue };
341 if gpu_solid.point_count == 0 {
342 continue;
343 }
344 if solid.visibility.all_vertices_hidden(solid.vertices.len()) {
350 continue;
351 }
352 pass.set_vertex_buffer(0, point_buf.slice(..));
353 let any_vertex_hidden = solid.visibility.any_vertex_hidden();
354 if has_emphasis || any_vertex_hidden {
355 let tol = 1e-9_f64.max(params.world_per_pixel * 1e-3);
356 let mut base_run: Option<(u32, u32)> = None;
357 let mut emphasized: Vec<(EmphasisState, u32)> = Vec::new();
358 for (index, vertex) in solid.vertices.iter().enumerate() {
359 if !solid.visibility.is_vertex_visible(index) {
361 if let Some((first, count)) = base_run.take() {
362 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
363 pass.draw(0..6, first..first + count);
364 }
365 continue;
366 }
367 let state = if has_emphasis {
368 params.emphasis.vertex_state(&solid.name, vertex.position, tol)
369 } else {
370 EmphasisState::Base
371 };
372 if state == EmphasisState::Base {
373 match &mut base_run {
374 Some((first, count)) if *first + *count == index as u32 => {
375 *count += 1
376 }
377 _ => {
378 if let Some((first, count)) = base_run.take() {
379 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
380 pass.draw(0..6, first..first + count);
381 }
382 base_run = Some((index as u32, 1));
383 }
384 }
385 } else {
386 emphasized.push((state, index as u32));
387 }
388 }
389 if let Some((first, count)) = base_run {
390 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
391 pass.draw(0..6, first..first + count);
392 }
393 for (state, index) in emphasized {
394 let style = match state {
395 EmphasisState::Selected => &self.styles.point_selected.bind,
396 _ => &self.styles.point_hovered.bind,
397 };
398 pass.set_bind_group(1, style, &[]);
399 pass.draw(0..6, index..index + 1);
400 }
401 } else {
402 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
403 pass.draw(0..6, 0..gpu_solid.point_count);
404 }
405 }
406 }
407
408 if draw_axes {
410 if let Some(axis_buf) = &gpu_scene.axis_buf {
411 pass.set_pipeline(&self.edge_visible_pipeline);
412 pass.set_vertex_buffer(0, axis_buf.slice(..));
413 for (index, style) in [
414 &self.styles.axis_x,
415 &self.styles.axis_y,
416 &self.styles.axis_z,
417 ]
418 .iter()
419 .enumerate()
420 {
421 pass.set_bind_group(1, &style.bind, &[]);
422 let i = index as u32;
423 pass.draw(0..6, i..i + 1);
424 }
425 }
426 }
427 }
428
429 if let Some(overlay) = params.overlay {
434 let make_tris = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
435 let verts = overlay_tri_verts(ov);
436 (!verts.is_empty()).then(|| {
437 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
438 label: Some("overlay tris"),
439 contents: bytemuck::cast_slice(&verts),
440 usage: wgpu::BufferUsages::VERTEX,
441 })
442 })
443 };
444 let make_lines = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
445 let insts = overlay_line_insts(ov);
446 (!insts.is_empty()).then(|| {
447 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
448 label: Some("overlay lines"),
449 contents: bytemuck::cast_slice(&insts),
450 usage: wgpu::BufferUsages::VERTEX,
451 })
452 })
453 };
454
455 let main_tri_count = overlay.main.tris.len() as u32;
456 let main_line_count = (overlay.main.lines.len() / 2) as u32;
457 let main_tri_buf = make_tris(&overlay.main);
458 let main_line_buf = make_lines(&overlay.main);
459 if main_tri_buf.is_some() || main_line_buf.is_some() {
460 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
461 label: Some("overlay-main"),
462 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
463 view: &targets.msaa_view,
464 depth_slice: None,
465 resolve_target: Some(resolve_view),
466 ops: wgpu::Operations {
467 load: wgpu::LoadOp::Load,
468 store: wgpu::StoreOp::Store,
469 },
470 })],
471 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
472 view: &targets.depth_view,
473 depth_ops: Some(wgpu::Operations {
474 load: wgpu::LoadOp::Clear(1.0),
475 store: wgpu::StoreOp::Discard,
476 }),
477 stencil_ops: None,
478 }),
479 timestamp_writes: None,
480 occlusion_query_set: None,
481 multiview_mask: None,
482 });
483 pass.set_bind_group(0, &self.globals_bind, &[]);
484 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
485 if let Some(buf) = &main_tri_buf {
486 pass.set_pipeline(&self.overlay_tri_pipeline);
487 pass.set_vertex_buffer(0, buf.slice(..));
488 pass.draw(0..main_tri_count, 0..1);
489 }
490 if let Some(buf) = &main_line_buf {
491 pass.set_pipeline(&self.overlay_line_pipeline);
492 pass.set_vertex_buffer(0, buf.slice(..));
493 pass.draw(0..6, 0..main_line_count);
494 }
495 }
496
497 if let Some(vc) = &overlay.viewcube {
498 let dpr = params.dpr.max(1e-3);
499 let mut x = (vc.rect_css[0] * dpr).max(0.0);
500 let mut y = (vc.rect_css[1] * dpr).max(0.0);
501 let mut w = (vc.rect_css[2] * dpr).max(1.0);
502 let mut h = (vc.rect_css[3] * dpr).max(1.0);
503 w = w.min(width as f32 - x).max(1.0);
505 h = h.min(height as f32 - y).max(1.0);
506 x = x.min(width as f32 - w).max(0.0);
507 y = y.min(height as f32 - h).max(0.0);
508
509 let vc_globals = Globals {
510 view_proj: vc.view_proj,
511 viewport: [w, h, dpr, 0.0],
512 forward: [vc.forward[0], vc.forward[1], vc.forward[2], 0.0],
513 };
514 self.queue
515 .write_buffer(&self.vc_globals_buf, 0, bytemuck::bytes_of(&vc_globals));
516
517 let vc_tri_count = vc.overlay.tris.len() as u32;
518 let vc_line_count = (vc.overlay.lines.len() / 2) as u32;
519 let vc_tri_buf = make_tris(&vc.overlay);
520 let vc_line_buf = make_lines(&vc.overlay);
521 if vc_tri_buf.is_some() || vc_line_buf.is_some() {
522 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
523 label: Some("overlay-viewcube"),
524 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
525 view: &targets.msaa_view,
526 depth_slice: None,
527 resolve_target: Some(resolve_view),
528 ops: wgpu::Operations {
529 load: wgpu::LoadOp::Load,
530 store: wgpu::StoreOp::Store,
531 },
532 })],
533 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
534 view: &targets.depth_view,
535 depth_ops: Some(wgpu::Operations {
536 load: wgpu::LoadOp::Clear(1.0),
537 store: wgpu::StoreOp::Discard,
538 }),
539 stencil_ops: None,
540 }),
541 timestamp_writes: None,
542 occlusion_query_set: None,
543 multiview_mask: None,
544 });
545 pass.set_viewport(x, y, w, h, 0.0, 1.0);
546 pass.set_scissor_rect(x as u32, y as u32, w as u32, h as u32);
547 pass.set_bind_group(0, &self.vc_globals_bind, &[]);
548 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
549 if let Some(buf) = &vc_tri_buf {
550 pass.set_pipeline(&self.overlay_tri_pipeline);
551 pass.set_vertex_buffer(0, buf.slice(..));
552 pass.draw(0..vc_tri_count, 0..1);
553 }
554 if let Some(buf) = &vc_line_buf {
555 pass.set_pipeline(&self.overlay_line_pipeline);
556 pass.set_vertex_buffer(0, buf.slice(..));
557 pass.draw(0..6, 0..vc_line_count);
558 }
559 }
560 }
561 }
562
563 self.queue.submit([encoder.finish()]);
564 }
565
566 #[cfg(not(target_arch = "wasm32"))]
569 pub fn render_to_png(
570 &mut self,
571 scene: &RenderScene,
572 camera: &Camera,
573 width: u32,
574 height: u32,
575 ) -> Result<Vec<u8>, String> {
576 let settings = RenderSettings::artifact();
577 let emphasis = Emphasis::default();
578 let mut gpu_scene = self.upload_scene_with(scene, &settings);
579 let params = FrameParams {
580 camera,
581 width,
582 height,
583 dpr: 1.0,
584 settings: &settings,
585 emphasis: &emphasis,
586 world_per_pixel: 0.0,
587 overlay: None,
588 };
589 let resolve = self.device.create_texture(&wgpu::TextureDescriptor {
590 label: Some("resolve"),
591 size: wgpu::Extent3d {
592 width,
593 height,
594 depth_or_array_layers: 1,
595 },
596 mip_level_count: 1,
597 sample_count: 1,
598 dimension: wgpu::TextureDimension::D2,
599 format: self.format,
600 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
601 view_formats: &[],
602 });
603 let resolve_view = resolve.create_view(&Default::default());
604 self.render_to_view(&mut gpu_scene, scene, ¶ms, &resolve_view);
605
606 let bytes_per_row = (width * 4).div_ceil(256) * 256;
608 let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
609 label: Some("readback"),
610 size: bytes_per_row as u64 * height as u64,
611 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
612 mapped_at_creation: false,
613 });
614 let mut encoder = self
615 .device
616 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
617 label: Some("readback"),
618 });
619 encoder.copy_texture_to_buffer(
620 wgpu::TexelCopyTextureInfo {
621 texture: &resolve,
622 mip_level: 0,
623 origin: wgpu::Origin3d::ZERO,
624 aspect: wgpu::TextureAspect::All,
625 },
626 wgpu::TexelCopyBufferInfo {
627 buffer: &readback,
628 layout: wgpu::TexelCopyBufferLayout {
629 offset: 0,
630 bytes_per_row: Some(bytes_per_row),
631 rows_per_image: None,
632 },
633 },
634 wgpu::Extent3d {
635 width,
636 height,
637 depth_or_array_layers: 1,
638 },
639 );
640 self.queue.submit([encoder.finish()]);
641
642 let slice = readback.slice(..);
643 let (sender, receiver) = std::sync::mpsc::channel();
644 slice.map_async(wgpu::MapMode::Read, move |result| {
645 let _ = sender.send(result);
646 });
647 self.device
648 .poll(wgpu::PollType::wait_indefinitely())
649 .map_err(|error| format!("wgpu poll: {error:?}"))?;
650 receiver
651 .recv()
652 .map_err(|_| "readback callback dropped".to_string())?
653 .map_err(|error| format!("readback map failed: {error:?}"))?;
654
655 let data = slice.get_mapped_range();
656 let mut rgb = Vec::with_capacity((width * height * 3) as usize);
657 for row in 0..height {
658 let start = (row * bytes_per_row) as usize;
659 for col in 0..width as usize {
660 let px = start + col * 4;
661 rgb.extend_from_slice(&data[px..px + 3]);
662 }
663 }
664 drop(data);
665 readback.unmap();
666
667 encode_png(&rgb, width, height)
668 }
669}
670
671fn draw_visible_edge_ranges(
676 pass: &mut wgpu::RenderPass<'_>,
677 solid: &SolidDisplay,
678 gpu_solid: &GpuSolid,
679) {
680 let mut run: Option<(u32, u32)> = None; for (index, _edge) in solid.edges.iter().enumerate() {
682 let range = &gpu_solid.edges[index];
683 if range.instance_count == 0 {
684 continue;
685 }
686 if !solid.visibility.is_edge_visible(index) {
687 if let Some((first, count)) = run.take() {
688 pass.draw(0..6, first..first + count);
689 }
690 continue;
691 }
692 match &mut run {
693 Some((first, count)) if *first + *count == range.first_instance => {
694 *count += range.instance_count;
695 }
696 _ => {
697 if let Some((first, count)) = run.take() {
698 pass.draw(0..6, first..first + count);
699 }
700 run = Some((range.first_instance, range.instance_count));
701 }
702 }
703 }
704 if let Some((first, count)) = run {
705 pass.draw(0..6, first..first + count);
706 }
707}
708
709fn overlay_tri_verts(ov: &brep_gizmos::Overlay) -> Vec<OverlayTriVertex> {
711 ov.tris
712 .iter()
713 .map(|v| OverlayTriVertex {
714 position: v.pos,
715 normal: v.normal,
716 color: v.color,
717 })
718 .collect()
719}
720
721fn overlay_line_insts(ov: &brep_gizmos::Overlay) -> Vec<OverlayLineInstance> {
724 ov.lines
725 .chunks_exact(2)
726 .map(|pair| OverlayLineInstance {
727 p0: pair[0].pos,
728 p1: pair[1].pos,
729 color: pair[0].color,
730 })
731 .collect()
732}