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();
195 let any_face_colored = !gpu_solid.face_styles.is_empty();
196 if !has_emphasis && !any_face_hidden && !any_face_colored {
197 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
198 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
199 continue;
200 }
201 let style_for = |state: EmphasisState, slot: Option<u32>| match state {
207 EmphasisState::Base => match slot.and_then(|s| gpu_solid.face_styles.get(s as usize)) {
208 Some(style) => &style.bind,
209 None => &gpu_solid.base_style.bind,
210 },
211 EmphasisState::Selected => &self.styles.face_selected.bind,
212 EmphasisState::Hovered => &self.styles.face_hovered.bind,
213 };
214 type FaceRun = (EmphasisState, Option<u32>, u32, u32); let mut run: Option<FaceRun> = None;
216 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<FaceRun>| {
217 if let Some((state, slot, first, count)) = run.take() {
218 if count > 0 {
219 pass.set_bind_group(1, style_for(state, slot), &[]);
220 pass.draw_indexed(first..first + count, 0, 0..1);
221 }
222 }
223 };
224 for (index, face) in solid.faces.iter().enumerate() {
225 let range = &gpu_solid.faces[index];
226 if range.index_count == 0 {
227 continue;
228 }
229 if !solid.visibility.is_face_visible(index) {
232 flush(&mut pass, &mut run);
233 continue;
234 }
235 let state = if has_emphasis {
236 params.emphasis.face_state(&solid.name, &face.name)
237 } else {
238 EmphasisState::Base
239 };
240 let slot = if state == EmphasisState::Base {
241 range.style
242 } else {
243 None
244 };
245 match &mut run {
246 Some((run_state, run_slot, first, count))
247 if *run_state == state
248 && *run_slot == slot
249 && *first + *count == range.first_index =>
250 {
251 *count += range.index_count;
252 }
253 _ => {
254 flush(&mut pass, &mut run);
255 run = Some((state, slot, range.first_index, range.index_count));
256 }
257 }
258 }
259 flush(&mut pass, &mut run);
260 }
261 }
262
263 if params.settings.hidden_edge_alpha > 0.0 {
266 pass.set_pipeline(&self.edge_hidden_pipeline);
267 pass.set_bind_group(1, &self.styles.edge_hidden.bind, &[]);
268 for (solid, gpu_solid) in &visible_solids {
269 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
270 if gpu_solid.edge_instances == 0 {
271 continue;
272 }
273 pass.set_vertex_buffer(0, edge_buf.slice(..));
274 if solid.visibility.any_edge_hidden() {
275 draw_visible_edge_ranges(&mut pass, solid, gpu_solid);
277 } else {
278 pass.draw(0..6, 0..gpu_solid.edge_instances);
279 }
280 }
281 }
282
283 pass.set_pipeline(&self.edge_visible_pipeline);
285 for (solid, gpu_solid) in &visible_solids {
286 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
287 if gpu_solid.edge_instances == 0 {
288 continue;
289 }
290 pass.set_vertex_buffer(0, edge_buf.slice(..));
291 let any_edge_hidden = solid.visibility.any_edge_hidden();
292 if !has_emphasis {
293 pass.set_bind_group(1, &self.styles.edge_base.bind, &[]);
294 if any_edge_hidden {
295 draw_visible_edge_ranges(&mut pass, solid, gpu_solid);
296 } else {
297 pass.draw(0..6, 0..gpu_solid.edge_instances);
298 }
299 continue;
300 }
301 let style_for = |state: EmphasisState| match state {
302 EmphasisState::Base => &self.styles.edge_base.bind,
303 EmphasisState::Selected => &self.styles.edge_selected.bind,
304 EmphasisState::Hovered => &self.styles.edge_hovered.bind,
305 };
306 let mut run: Option<(EmphasisState, u32, u32)> = None;
307 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<(EmphasisState, u32, u32)>| {
308 if let Some((state, first, count)) = run.take() {
309 if count > 0 {
310 pass.set_bind_group(1, style_for(state), &[]);
311 pass.draw(0..6, first..first + count);
312 }
313 }
314 };
315 for (index, edge) in solid.edges.iter().enumerate() {
316 let range = &gpu_solid.edges[index];
317 if range.instance_count == 0 {
318 continue;
319 }
320 if !solid.visibility.is_edge_visible(index) {
322 flush(&mut pass, &mut run);
323 continue;
324 }
325 let state = params.emphasis.edge_state(&solid.name, &edge.name);
326 match &mut run {
327 Some((run_state, first, count))
328 if *run_state == state
329 && *first + *count == range.first_instance =>
330 {
331 *count += range.instance_count;
332 }
333 _ => {
334 flush(&mut pass, &mut run);
335 run = Some((state, range.first_instance, range.instance_count));
336 }
337 }
338 }
339 flush(&mut pass, &mut run);
340 }
341
342 if has_emphasis {
344 pass.set_bind_group(1, &self.styles.boundary.bind, &[]);
345 for (_, gpu_solid) in &visible_solids {
346 let Some(boundary) = &gpu_solid.boundary else { continue };
347 let Some(buf) = &boundary.buf else { continue };
348 if boundary.count == 0 {
349 continue;
350 }
351 pass.set_vertex_buffer(0, buf.slice(..));
352 pass.draw(0..6, 0..boundary.count);
353 }
354 }
355
356 if params.settings.vertex_size_px > 0.0 {
358 pass.set_pipeline(&self.point_pipeline);
359 for (solid, gpu_solid) in &visible_solids {
360 let Some(point_buf) = &gpu_solid.point_buf else { continue };
361 if gpu_solid.point_count == 0 {
362 continue;
363 }
364 if solid.visibility.all_vertices_hidden(solid.vertices.len()) {
370 continue;
371 }
372 pass.set_vertex_buffer(0, point_buf.slice(..));
373 let any_vertex_hidden = solid.visibility.any_vertex_hidden();
374 if has_emphasis || any_vertex_hidden {
375 let tol = 1e-9_f64.max(params.world_per_pixel * 1e-3);
376 let mut base_run: Option<(u32, u32)> = None;
377 let mut emphasized: Vec<(EmphasisState, u32)> = Vec::new();
378 for (index, vertex) in solid.vertices.iter().enumerate() {
379 if !solid.visibility.is_vertex_visible(index) {
381 if let Some((first, count)) = base_run.take() {
382 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
383 pass.draw(0..6, first..first + count);
384 }
385 continue;
386 }
387 let state = if has_emphasis {
388 params.emphasis.vertex_state(&solid.name, vertex.position, tol)
389 } else {
390 EmphasisState::Base
391 };
392 if state == EmphasisState::Base {
393 match &mut base_run {
394 Some((first, count)) if *first + *count == index as u32 => {
395 *count += 1
396 }
397 _ => {
398 if let Some((first, count)) = base_run.take() {
399 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
400 pass.draw(0..6, first..first + count);
401 }
402 base_run = Some((index as u32, 1));
403 }
404 }
405 } else {
406 emphasized.push((state, index as u32));
407 }
408 }
409 if let Some((first, count)) = base_run {
410 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
411 pass.draw(0..6, first..first + count);
412 }
413 for (state, index) in emphasized {
414 let style = match state {
415 EmphasisState::Selected => &self.styles.point_selected.bind,
416 _ => &self.styles.point_hovered.bind,
417 };
418 pass.set_bind_group(1, style, &[]);
419 pass.draw(0..6, index..index + 1);
420 }
421 } else {
422 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
423 pass.draw(0..6, 0..gpu_solid.point_count);
424 }
425 }
426 }
427
428 if draw_axes {
430 if let Some(axis_buf) = &gpu_scene.axis_buf {
431 pass.set_pipeline(&self.edge_visible_pipeline);
432 pass.set_vertex_buffer(0, axis_buf.slice(..));
433 for (index, style) in [
434 &self.styles.axis_x,
435 &self.styles.axis_y,
436 &self.styles.axis_z,
437 ]
438 .iter()
439 .enumerate()
440 {
441 pass.set_bind_group(1, &style.bind, &[]);
442 let i = index as u32;
443 pass.draw(0..6, i..i + 1);
444 }
445 }
446 }
447 }
448
449 if let Some(overlay) = params.overlay {
454 let make_tris = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
455 let verts = overlay_tri_verts(ov);
456 (!verts.is_empty()).then(|| {
457 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
458 label: Some("overlay tris"),
459 contents: bytemuck::cast_slice(&verts),
460 usage: wgpu::BufferUsages::VERTEX,
461 })
462 })
463 };
464 let make_lines = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
465 let insts = overlay_line_insts(ov);
466 (!insts.is_empty()).then(|| {
467 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
468 label: Some("overlay lines"),
469 contents: bytemuck::cast_slice(&insts),
470 usage: wgpu::BufferUsages::VERTEX,
471 })
472 })
473 };
474
475 let main_tri_count = overlay.main.tris.len() as u32;
476 let main_line_count = (overlay.main.lines.len() / 2) as u32;
477 let main_tri_buf = make_tris(&overlay.main);
478 let main_line_buf = make_lines(&overlay.main);
479 if main_tri_buf.is_some() || main_line_buf.is_some() {
480 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
481 label: Some("overlay-main"),
482 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
483 view: &targets.msaa_view,
484 depth_slice: None,
485 resolve_target: Some(resolve_view),
486 ops: wgpu::Operations {
487 load: wgpu::LoadOp::Load,
488 store: wgpu::StoreOp::Store,
489 },
490 })],
491 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
492 view: &targets.depth_view,
493 depth_ops: Some(wgpu::Operations {
494 load: wgpu::LoadOp::Clear(1.0),
495 store: wgpu::StoreOp::Discard,
496 }),
497 stencil_ops: None,
498 }),
499 timestamp_writes: None,
500 occlusion_query_set: None,
501 multiview_mask: None,
502 });
503 pass.set_bind_group(0, &self.globals_bind, &[]);
504 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
505 if let Some(buf) = &main_tri_buf {
506 pass.set_vertex_buffer(0, buf.slice(..));
507 let plane_verts = (overlay.plane_tri_verts as u32).min(main_tri_count);
512 if plane_verts > 0 {
513 pass.set_pipeline(&self.overlay_tri_nodepth_pipeline);
514 pass.draw(0..plane_verts, 0..1);
515 }
516 if plane_verts < main_tri_count {
517 pass.set_pipeline(&self.overlay_tri_pipeline);
518 pass.draw(plane_verts..main_tri_count, 0..1);
519 }
520 }
521 if let Some(buf) = &main_line_buf {
522 pass.set_pipeline(&self.overlay_line_pipeline);
523 pass.set_vertex_buffer(0, buf.slice(..));
524 pass.draw(0..6, 0..main_line_count);
525 }
526 }
527
528 if let Some(vc) = &overlay.viewcube {
529 let dpr = params.dpr.max(1e-3);
530 let mut x = (vc.rect_css[0] * dpr).max(0.0);
531 let mut y = (vc.rect_css[1] * dpr).max(0.0);
532 let mut w = (vc.rect_css[2] * dpr).max(1.0);
533 let mut h = (vc.rect_css[3] * dpr).max(1.0);
534 w = w.min(width as f32 - x).max(1.0);
536 h = h.min(height as f32 - y).max(1.0);
537 x = x.min(width as f32 - w).max(0.0);
538 y = y.min(height as f32 - h).max(0.0);
539
540 let vc_globals = Globals {
541 view_proj: vc.view_proj,
542 viewport: [w, h, dpr, 0.0],
543 forward: [vc.forward[0], vc.forward[1], vc.forward[2], 0.0],
544 };
545 self.queue
546 .write_buffer(&self.vc_globals_buf, 0, bytemuck::bytes_of(&vc_globals));
547
548 let vc_tri_count = vc.overlay.tris.len() as u32;
549 let vc_line_count = (vc.overlay.lines.len() / 2) as u32;
550 let vc_tri_buf = make_tris(&vc.overlay);
551 let vc_line_buf = make_lines(&vc.overlay);
552 if vc_tri_buf.is_some() || vc_line_buf.is_some() {
553 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
554 label: Some("overlay-viewcube"),
555 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
556 view: &targets.msaa_view,
557 depth_slice: None,
558 resolve_target: Some(resolve_view),
559 ops: wgpu::Operations {
560 load: wgpu::LoadOp::Load,
561 store: wgpu::StoreOp::Store,
562 },
563 })],
564 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
565 view: &targets.depth_view,
566 depth_ops: Some(wgpu::Operations {
567 load: wgpu::LoadOp::Clear(1.0),
568 store: wgpu::StoreOp::Discard,
569 }),
570 stencil_ops: None,
571 }),
572 timestamp_writes: None,
573 occlusion_query_set: None,
574 multiview_mask: None,
575 });
576 pass.set_viewport(x, y, w, h, 0.0, 1.0);
577 pass.set_scissor_rect(x as u32, y as u32, w as u32, h as u32);
578 pass.set_bind_group(0, &self.vc_globals_bind, &[]);
579 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
580 if let Some(buf) = &vc_tri_buf {
581 pass.set_pipeline(&self.overlay_tri_pipeline);
582 pass.set_vertex_buffer(0, buf.slice(..));
583 pass.draw(0..vc_tri_count, 0..1);
584 }
585 if let Some(buf) = &vc_line_buf {
586 pass.set_pipeline(&self.overlay_line_pipeline);
587 pass.set_vertex_buffer(0, buf.slice(..));
588 pass.draw(0..6, 0..vc_line_count);
589 }
590 }
591 }
592 }
593
594 self.queue.submit([encoder.finish()]);
595 }
596
597 #[cfg(not(target_arch = "wasm32"))]
600 pub fn render_to_png(
601 &mut self,
602 scene: &RenderScene,
603 camera: &Camera,
604 width: u32,
605 height: u32,
606 ) -> Result<Vec<u8>, String> {
607 let settings = RenderSettings::artifact();
608 let emphasis = Emphasis::default();
609 let mut gpu_scene = self.upload_scene_with(scene, &settings);
610 let params = FrameParams {
611 camera,
612 width,
613 height,
614 dpr: 1.0,
615 settings: &settings,
616 emphasis: &emphasis,
617 world_per_pixel: 0.0,
618 overlay: None,
619 };
620 let resolve = self.device.create_texture(&wgpu::TextureDescriptor {
621 label: Some("resolve"),
622 size: wgpu::Extent3d {
623 width,
624 height,
625 depth_or_array_layers: 1,
626 },
627 mip_level_count: 1,
628 sample_count: 1,
629 dimension: wgpu::TextureDimension::D2,
630 format: self.format,
631 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
632 view_formats: &[],
633 });
634 let resolve_view = resolve.create_view(&Default::default());
635 self.render_to_view(&mut gpu_scene, scene, ¶ms, &resolve_view);
636
637 let bytes_per_row = (width * 4).div_ceil(256) * 256;
639 let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
640 label: Some("readback"),
641 size: bytes_per_row as u64 * height as u64,
642 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
643 mapped_at_creation: false,
644 });
645 let mut encoder = self
646 .device
647 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
648 label: Some("readback"),
649 });
650 encoder.copy_texture_to_buffer(
651 wgpu::TexelCopyTextureInfo {
652 texture: &resolve,
653 mip_level: 0,
654 origin: wgpu::Origin3d::ZERO,
655 aspect: wgpu::TextureAspect::All,
656 },
657 wgpu::TexelCopyBufferInfo {
658 buffer: &readback,
659 layout: wgpu::TexelCopyBufferLayout {
660 offset: 0,
661 bytes_per_row: Some(bytes_per_row),
662 rows_per_image: None,
663 },
664 },
665 wgpu::Extent3d {
666 width,
667 height,
668 depth_or_array_layers: 1,
669 },
670 );
671 self.queue.submit([encoder.finish()]);
672
673 let slice = readback.slice(..);
674 let (sender, receiver) = std::sync::mpsc::channel();
675 slice.map_async(wgpu::MapMode::Read, move |result| {
676 let _ = sender.send(result);
677 });
678 self.device
679 .poll(wgpu::PollType::wait_indefinitely())
680 .map_err(|error| format!("wgpu poll: {error:?}"))?;
681 receiver
682 .recv()
683 .map_err(|_| "readback callback dropped".to_string())?
684 .map_err(|error| format!("readback map failed: {error:?}"))?;
685
686 let data = slice.get_mapped_range();
687 let mut rgb = Vec::with_capacity((width * height * 3) as usize);
688 for row in 0..height {
689 let start = (row * bytes_per_row) as usize;
690 for col in 0..width as usize {
691 let px = start + col * 4;
692 rgb.extend_from_slice(&data[px..px + 3]);
693 }
694 }
695 drop(data);
696 readback.unmap();
697
698 encode_png(&rgb, width, height)
699 }
700}
701
702fn draw_visible_edge_ranges(
707 pass: &mut wgpu::RenderPass<'_>,
708 solid: &SolidDisplay,
709 gpu_solid: &GpuSolid,
710) {
711 let mut run: Option<(u32, u32)> = None; for (index, _edge) in solid.edges.iter().enumerate() {
713 let range = &gpu_solid.edges[index];
714 if range.instance_count == 0 {
715 continue;
716 }
717 if !solid.visibility.is_edge_visible(index) {
718 if let Some((first, count)) = run.take() {
719 pass.draw(0..6, first..first + count);
720 }
721 continue;
722 }
723 match &mut run {
724 Some((first, count)) if *first + *count == range.first_instance => {
725 *count += range.instance_count;
726 }
727 _ => {
728 if let Some((first, count)) = run.take() {
729 pass.draw(0..6, first..first + count);
730 }
731 run = Some((range.first_instance, range.instance_count));
732 }
733 }
734 }
735 if let Some((first, count)) = run {
736 pass.draw(0..6, first..first + count);
737 }
738}
739
740fn overlay_tri_verts(ov: &brep_gizmos::Overlay) -> Vec<OverlayTriVertex> {
742 ov.tris
743 .iter()
744 .map(|v| OverlayTriVertex {
745 position: v.pos,
746 normal: v.normal,
747 color: v.color,
748 })
749 .collect()
750}
751
752fn overlay_line_insts(ov: &brep_gizmos::Overlay) -> Vec<OverlayLineInstance> {
755 ov.lines
756 .chunks_exact(2)
757 .map(|pair| OverlayLineInstance {
758 p0: pair[0].pos,
759 p1: pair[1].pos,
760 color: pair[0].color,
761 })
762 .collect()
763}