diff --git a/crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs b/crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs index 54c46c2..ed2686e 100644 --- a/crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs +++ b/crates/polymodel-renderer-worker/src/bin/ldraw_preview.rs @@ -10,9 +10,9 @@ use polymodel_renderer_protocol::scene::Scene; #[cfg(not(target_arch = "wasm32"))] use polymodel_renderer_worker::{ NativeLights, NativeRenderOptions, NativeStage, NativeTerminal, NativeTrace, PickRay, - RenderStyle, ScenePicker, build_edge_model_for_view, build_scene_model_parts_for_style, - camera_space_clip_planes, logical_part_selection, prepare_preview_scene, - render_scene_window_configured, scene_camera_fit, yaw_camera_position, + RenderStyle, ScenePicker, SceneRenderPlan, build_conditional_edge_model_for_view, + build_scene_model_parts_for_style, camera_space_clip_planes, logical_part_selection, + prepare_preview_scene, render_scene_window_configured, scene_camera_fit, yaw_camera_position, }; #[cfg(not(target_arch = "wasm32"))] @@ -272,7 +272,9 @@ fn run_visible_preview( let lights = NativeLights::new(&context); let mut static_model: Option>> = None; + let mut explicit_model: Option>> = None; let mut conditional_model: Option>> = None; + let mut render_plan: Option = None; let mut last_view: Option<[f64; 3]> = None; let mut selection_bounds: Option> = None; let mut scene: Option = None; @@ -314,11 +316,11 @@ fn run_visible_preview( [0.0, 0.0, 1.0], if edges { RenderStyle::Edges } else { RenderStyle::Faces }, ) { - Ok((static_instanced, conditional_instanced, picker)) => { + Ok((static_instanced, (explicit_instanced, conditional_instanced), picker, plan)) => { eprintln!( "[ldraw-preview] edge_style={} gpu-objects static={} conditional={}", if edges { "on" } else { "off" }, - static_instanced.len(), + static_instanced.len() + explicit_instanced.as_ref().map_or(0, Vec::len), conditional_instanced.as_ref().map_or(0, Vec::len), ); loop_trace.record(NativeStage::NativeModelBuildComplete); @@ -352,7 +354,9 @@ fn run_visible_preview( )); scene_bounds = Some(bounds); static_model = Some(static_instanced); + explicit_model = explicit_instanced; conditional_model = conditional_instanced; + render_plan = Some(plan); last_view = edges.then_some([0.0, 0.0, 1.0]); loop_trace.record(NativeStage::PickIndexBuildStarted); scene_picker = Some(picker); @@ -392,7 +396,7 @@ fn run_visible_preview( camera.set_perspective_projection(three_d::degrees(45.0), near, far); } } - if let (Some(active_scene), Some(active_camera)) = (scene.as_ref(), camera.as_ref()) { + if let (Some(_active_scene), Some(active_camera)) = (scene.as_ref(), camera.as_ref()) { let direction = active_camera.position() - active_camera.target(); let length = f64::from(direction.magnitude()); if length > f64::EPSILON { @@ -410,13 +414,14 @@ fn run_visible_preview( if edges && changed && !orbit_dragging - && let Ok(model) = build_edge_model_for_view( + && let Some(plan) = render_plan.as_ref() + && let Ok(model) = build_conditional_edge_model_for_view( &context, - active_scene, + &plan.conditional_edges, view, ) { - conditional_model = Some(model); + conditional_model = (!model.is_empty()).then_some(model); last_view = Some(view); } } @@ -429,6 +434,9 @@ fn run_visible_preview( } let light_refs = lights.as_refs(); screen.render(&mut *camera, &mut *static_model, &light_refs); + if let Some(explicit_model) = explicit_model.as_mut() { + screen.render(&mut *camera, explicit_model, &light_refs); + } if let Some(conditional_model) = conditional_model.as_mut() { screen.render(&mut *camera, conditional_model, &light_refs); } diff --git a/crates/polymodel-renderer-worker/src/lib.rs b/crates/polymodel-renderer-worker/src/lib.rs index 57fd628..9ceb0c9 100644 --- a/crates/polymodel-renderer-worker/src/lib.rs +++ b/crates/polymodel-renderer-worker/src/lib.rs @@ -1540,7 +1540,8 @@ fn scene_mesh_topology_for_view_kind( ) -> Result { let include_faces = matches!(kind, SceneTopologyKind::Faces | SceneTopologyKind::Complete); let include_explicit = matches!(kind, SceneTopologyKind::Edges | SceneTopologyKind::Complete); - let include_conditional = matches!(kind, SceneTopologyKind::Edges | SceneTopologyKind::Complete); + let include_conditional = + matches!(kind, SceneTopologyKind::Edges | SceneTopologyKind::Complete); let source_positions = geometry .points .iter() @@ -2301,6 +2302,26 @@ pub struct SceneInstanceBatch { pub instances: Vec, } +#[derive(Debug, Clone, PartialEq)] +pub struct ExplicitEdgeInput { + pub geometry: GeometryId, + pub occurrence_index: usize, + pub material: MaterialId, + pub colour: u32, + pub transform: [[f64; 4]; 4], + pub lines: Vec<[[f32; 3]; 2]>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ConditionalEdgeInput { + pub geometry: GeometryId, + pub occurrence_index: usize, + pub material: MaterialId, + pub colour: u32, + pub transform: [[f64; 4]; 4], + pub lines: Vec<[[f32; 3]; 4]>, +} + #[derive(Debug, Clone, PartialEq)] pub struct SceneRenderPlan { /// Planned batches retain all semantic occurrences for picking and diagnostics. @@ -2311,6 +2332,10 @@ pub struct SceneRenderPlan { pub planned_to_upload: Vec>, pub occurrence_ranges: Vec>, pub topologies: Vec, + /// Immutable type-2 upload sources captured during the authoritative pass. + pub explicit_edges: Vec, + /// Immutable type-5 sources captured during the authoritative pass. + pub conditional_edges: Vec, pick_occurrences: Vec, logical_parts: Vec>, logical_bounds: Vec, @@ -2359,6 +2384,8 @@ pub fn scene_render_plan(scene: &Scene) -> Result { let mut logical_parts = Vec::>::new(); let mut logical_bounds = Vec::::new(); let mut logical_part_bounds = std::collections::HashMap::::new(); + let mut explicit_edges = Vec::new(); + let mut conditional_edges = Vec::new(); let batches = scene_instance_batches_for_mode( scene, &index, @@ -2368,6 +2395,8 @@ pub fn scene_render_plan(scene: &Scene) -> Result { &mut logical_parts, &mut logical_bounds, &mut logical_part_bounds, + &mut explicit_edges, + &mut conditional_edges, false, )?; let batches_complete = started.elapsed(); @@ -2396,6 +2425,8 @@ pub fn scene_render_plan(scene: &Scene) -> Result { logical_parts, logical_bounds, logical_part_bounds, + explicit_edges, + conditional_edges, index, }) } @@ -2413,6 +2444,8 @@ fn scene_instance_batches_for_mode( logical_parts: &mut Vec>, logical_bounds: &mut Vec, logical_part_bounds: &mut std::collections::HashMap, + explicit_edges: &mut Vec, + conditional_edges: &mut Vec, separate_conditional_occurrences: bool, ) -> Result, String> { #[derive(Clone, Copy, PartialEq, Eq, Hash)] @@ -2471,8 +2504,108 @@ fn scene_instance_batches_for_mode( max: bounds.max, }); let material = occurrence.material.unwrap_or(default_material); - index.material(scene, material)?; + let material_record = index.material(scene, material)?; let reverse_winding = occurrence.bfc_inverted ^ !occurrence.bfc_ccw; + if !geometry.lines.is_empty() { + let lines = geometry + .lines + .iter() + .copied() + .map(|line| { + line.into_iter() + .map(|point| { + geometry.points.get(point as usize).copied().ok_or_else(|| { + format!( + "geometry {} line index {point} is outside its local {}-position domain", + geometry.id.0, + geometry.points.len() + ) + }) + }) + .collect::, _>>() + .and_then(|points| { + points.try_into().map_err(|_| "line endpoint count mismatch".to_owned()) + }) + }) + .collect::, _>>()?; + let lines = lines + .into_iter() + .map(|line| { + line.into_iter() + .map(|point| { + Ok([ + finite_f32(point[0]).map_err(str::to_owned)?, + finite_f32(point[1]).map_err(str::to_owned)?, + finite_f32(point[2]).map_err(str::to_owned)?, + ]) + }) + .collect::, String>>() + .and_then(|points| { + points + .try_into() + .map_err(|_| "line endpoint count mismatch".to_owned()) + }) + }) + .collect::, _>>()?; + explicit_edges.push(ExplicitEdgeInput { + geometry: geometry.id, + occurrence_index, + material, + colour: material_record.colour, + transform: occurrence.transform, + lines, + }); + } + if !geometry.conditional_lines.is_empty() { + let lines = geometry + .conditional_lines + .iter() + .copied() + .map(|line| { + line.into_iter() + .map(|point| { + geometry.points.get(point as usize).copied().ok_or_else(|| { + format!( + "geometry {} conditional-line index {point} is outside its local {}-position domain", + geometry.id.0, + geometry.points.len() + ) + }) + }) + .collect::, _>>() + .and_then(|points| { + points.try_into().map_err(|_| "conditional endpoint count mismatch".to_owned()) + }) + }) + .collect::, _>>()?; + let lines = lines + .into_iter() + .map(|line| { + line.into_iter() + .map(|point| { + Ok([ + finite_f32(point[0]).map_err(str::to_owned)?, + finite_f32(point[1]).map_err(str::to_owned)?, + finite_f32(point[2]).map_err(str::to_owned)?, + ]) + }) + .collect::, String>>() + .and_then(|points| { + points + .try_into() + .map_err(|_| "conditional endpoint count mismatch".to_owned()) + }) + }) + .collect::, _>>()?; + conditional_edges.push(ConditionalEdgeInput { + geometry: geometry.id, + occurrence_index, + material, + colour: material_record.colour, + transform: occurrence.transform, + lines, + }); + } let key = BatchKey { node: occurrence.node, material, @@ -2583,12 +2716,20 @@ pub fn build_scene_model_for_view( ) } +pub type EdgeModelParts = (Option, Option); +pub type SceneModelParts = ( + NativeSceneModel, + EdgeModelParts, + ScenePicker, + SceneRenderPlan, +); + pub fn build_scene_model_parts_for_style( context: &three_d::Context, scene: &Scene, view: [f64; 3], style: RenderStyle, -) -> Result<(NativeSceneModel, Option, ScenePicker), String> { +) -> Result { let started = Instant::now(); let plan = scene_render_plan(scene)?; eprintln!( @@ -2606,19 +2747,35 @@ pub fn build_scene_model_parts_for_style( "[ldraw-preview] native-build-stage=static elapsed_ms={}", started.elapsed().as_millis() ); - let edge_model = (style == RenderStyle::Edges) - .then(|| build_edge_model_with_index(context, scene, view, &plan.index)) - .transpose()?; - eprintln!( - "[ldraw-preview] native-build-stage=edges elapsed_ms={}", - started.elapsed().as_millis() - ); - let picker = ScenePicker::new(scene, plan)?; + let (explicit_model, conditional_model) = if style == RenderStyle::Edges { + let explicit_started = Instant::now(); + let explicit = build_explicit_edge_model(context, &plan.explicit_edges)?; + eprintln!( + "[ldraw-preview] native-build-stage=explicit-edges elapsed_ms={}", + explicit_started.elapsed().as_millis() + ); + let conditional_started = Instant::now(); + let conditional = + build_conditional_edge_model_for_view(context, &plan.conditional_edges, view)?; + eprintln!( + "[ldraw-preview] native-build-stage=conditional-edges elapsed_ms={}", + conditional_started.elapsed().as_millis() + ); + (explicit, (!conditional.is_empty()).then_some(conditional)) + } else { + (None, None) + }; + let picker = ScenePicker::new(scene, plan.clone())?; eprintln!( "[ldraw-preview] native-build-stage=picker elapsed_ms={}", started.elapsed().as_millis() ); - Ok((static_model, edge_model, picker)) + Ok(( + static_model, + (explicit_model, conditional_model), + picker, + plan, + )) } pub fn build_scene_faces_and_picker( @@ -2650,76 +2807,175 @@ pub fn build_scene_faces_and_picker( Ok((static_model, picker)) } -pub fn build_edge_model_for_view( - context: &three_d::Context, - scene: &Scene, +pub fn build_conditional_edge_topology_for_inputs( + inputs: &[ConditionalEdgeInput], view: [f64; 3], -) -> Result { - let index = SceneIndex::new(scene)?; - build_edge_model_with_index(context, scene, view, &index) +) -> Result, String> { + inputs + .iter() + .map(|input| { + let local_view = + conditional_line_view_for_transform(input.transform, view).unwrap_or(view); + let points = input + .lines + .iter() + .flat_map(|line| { + line.iter().map(|point| { + [ + f64::from(point[0]), + f64::from(point[1]), + f64::from(point[2]), + ] + }) + }) + .collect(); + let geometry = SceneGeometry { + id: input.geometry, + provenance: ProvenanceId(0), + points, + triangles: Vec::new(), + quads: Vec::new(), + lines: Vec::new(), + conditional_lines: (0..input.lines.len()) + .map(|offset| { + let base = u32::try_from(offset * 4).unwrap_or(u32::MAX); + [base, base + 1, base + 2, base + 3] + }) + .collect(), + primitives: Vec::new(), + }; + let topology = + scene_mesh_topology_for_view_kind(&geometry, local_view, SceneTopologyKind::Edges)?; + transformed_edge_topology(topology, input.transform) + .map(|topology| (input.material, input.colour, topology)) + }) + .collect() } -fn build_edge_model_with_index( +pub fn build_conditional_edge_model_for_view( context: &three_d::Context, - scene: &Scene, + inputs: &[ConditionalEdgeInput], view: [f64; 3], - index: &SceneIndex, ) -> Result { - let default_material = scene - .materials - .first() - .map(|material| material.id) - .unwrap_or(MaterialId(1)); - let mut meshes = std::collections::HashMap::::new(); + build_edge_model_from_inputs( + context, + build_conditional_edge_topology_for_inputs(inputs, view)? + .into_iter() + .map(Ok), + ) +} - for occurrence in &scene.occurrences { - let geometry = index.geometry(scene, occurrence.node)?; - if geometry.lines.is_empty() && geometry.conditional_lines.is_empty() { - continue; - } - let local_view = - conditional_line_view_for_transform(occurrence.transform, view).unwrap_or(view); - let topology = - scene_mesh_topology_for_view_kind(geometry, local_view, SceneTopologyKind::Edges)?; +fn build_explicit_edge_model( + context: &three_d::Context, + inputs: &[ExplicitEdgeInput], +) -> Result, String> { + let models = build_edge_model_from_inputs( + context, + inputs.iter().map(|input| { + let geometry = SceneGeometry { + id: input.geometry, + provenance: ProvenanceId(0), + points: input + .lines + .iter() + .flat_map(|line| { + line.iter().map(|point| { + [ + f64::from(point[0]), + f64::from(point[1]), + f64::from(point[2]), + ] + }) + }) + .collect(), + triangles: Vec::new(), + quads: Vec::new(), + lines: (0..input.lines.len()) + .map(|offset| { + let base = u32::try_from(offset * 2).unwrap_or(u32::MAX); + [base, base + 1] + }) + .collect(), + conditional_lines: Vec::new(), + primitives: Vec::new(), + }; + let topology = scene_mesh_topology_for_view_kind( + &geometry, + [0.0, 0.0, 1.0], + SceneTopologyKind::Edges, + )?; + transformed_edge_topology(topology, input.transform) + .map(|topology| (input.material, input.colour, topology)) + }), + )?; + Ok((!models.is_empty()).then_some(models)) +} + +fn transformed_edge_topology( + topology: SceneMeshTopology, + transform: [[f64; 4]; 4], +) -> Result { + Ok(SceneMeshTopology { + positions: topology + .positions + .into_iter() + .map(|point| { + let world = apply_scene_transform( + transform, + [ + f64::from(point[0]), + f64::from(point[1]), + f64::from(point[2]), + ], + )?; + Ok([ + finite_f32(world[0]).map_err(str::to_owned)?, + finite_f32(world[1]).map_err(str::to_owned)?, + finite_f32(world[2]).map_err(str::to_owned)?, + ]) + }) + .collect::, String>>()?, + indices: topology.indices, + }) +} + +fn build_edge_model_from_inputs( + context: &three_d::Context, + inputs: impl Iterator>, +) -> Result { + let mut meshes = std::collections::BTreeMap::::new(); + for input in inputs { + let (material, colour, topology) = input?; if topology.indices.is_empty() { continue; } - let material = occurrence.material.unwrap_or(default_material); - index.material(scene, material)?; - let mesh = meshes.entry(material).or_insert_with(|| SceneMeshTopology { - positions: Vec::new(), - indices: Vec::new(), + let mesh = meshes.entry(material).or_insert_with(|| { + ( + colour, + SceneMeshTopology { + positions: Vec::new(), + indices: Vec::new(), + }, + ) }); - let base = u32::try_from(mesh.positions.len()) - .map_err(|_| "conditional vertex range overflow".to_owned())?; - for position in topology.positions { - let world = apply_scene_transform( - occurrence.transform, - [ - f64::from(position[0]), - f64::from(position[1]), - f64::from(position[2]), - ], - )?; - mesh.positions.push([ - finite_f32(world[0]).map_err(str::to_owned)?, - finite_f32(world[1]).map_err(str::to_owned)?, - finite_f32(world[2]).map_err(str::to_owned)?, - ]); - } - mesh.indices.extend( + let base = u32::try_from(mesh.1.positions.len()) + .map_err(|_| "edge vertex range overflow".to_owned())?; + mesh.1.positions.extend(topology.positions); + mesh.1.indices.extend( topology .indices .into_iter() - .map(|index| index.checked_add(base).ok_or("conditional index overflow")) + .map(|index| { + index + .checked_add(base) + .ok_or("edge index overflow".to_owned()) + }) .collect::, _>>()?, ); } - meshes - .into_iter() - .map(|(material_id, topology)| { - let colour = index.material(scene, material_id)?.colour; + .into_values() + .map(|(colour, topology)| { let mut material = PhysicalMaterial::new_opaque( context, &CpuMaterial { @@ -3518,22 +3774,15 @@ mod tests { #[test] fn edge_topology_excludes_source_faces() { let mut geometry = topology_geometry( - vec![ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, 1.0, 0.0], - ], + vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], vec![[0, 1, 2]], Vec::new(), ); geometry.lines = vec![[0, 1]]; - let edges = scene_mesh_topology_for_view_kind( - &geometry, - [0.0, 0.0, 1.0], - SceneTopologyKind::Edges, - ) - .unwrap(); + let edges = + scene_mesh_topology_for_view_kind(&geometry, [0.0, 0.0, 1.0], SceneTopologyKind::Edges) + .unwrap(); assert_eq!(edges.triangle_count(), 2, "one line ribbon only"); assert_eq!(edges.positions.len(), 4); @@ -3567,6 +3816,57 @@ mod tests { assert!(error.contains("conditional-line index 2")); } + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn render_plan_captures_explicit_and_conditional_sources_in_one_pass() { + let mut scene = picker_scene(); + scene.geometries[0].lines = vec![[0, 1]]; + scene.geometries[0].conditional_lines = vec![[0, 1, 2, 1]]; + let plan = scene_render_plan(&scene).expect("render plan"); + assert_eq!(plan.explicit_edges.len(), 2); + assert_eq!(plan.conditional_edges.len(), 2); + assert!( + plan.explicit_edges + .iter() + .all(|input| input.lines == vec![[[-1.0, -1.0, 0.0], [1.0, -1.0, 0.0]]]) + ); + assert!( + plan.conditional_edges + .iter() + .all(|input| input.lines.len() == 1) + ); + } + + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn conditional_cache_filtering_is_view_only_and_does_not_change_plan() { + let mut scene = picker_scene(); + scene.geometries[0].conditional_lines = vec![[0, 1, 2, 1]]; + let plan = scene_render_plan(&scene).expect("render plan"); + let before = plan.clone(); + let first = + build_conditional_edge_topology_for_inputs(&plan.conditional_edges, [0.0, 0.0, 1.0]) + .expect("first conditional topology"); + let second = + build_conditional_edge_topology_for_inputs(&plan.conditional_edges, [0.0, 0.0, -1.0]) + .expect("second conditional topology"); + assert_eq!(plan, before); + assert_eq!(first.len(), second.len()); + } + + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn faces_style_has_no_edge_gpu_models() { + assert_eq!(RenderStyle::default(), RenderStyle::Faces); + let mut scene = picker_scene(); + scene.geometries[0].lines = vec![[0, 1]]; + scene.geometries[0].conditional_lines = vec![[0, 1, 2, 1]]; + let plan = scene_render_plan(&scene).expect("render plan"); + assert!(!plan.explicit_edges.is_empty()); + assert!(!plan.conditional_edges.is_empty()); + // GPU construction is gated by RenderStyle::Edges in the native and worker paths. + } + #[test] fn ldraw_basis_preserves_winding_and_has_positive_determinant() { assert_eq!(ldraw_to_world([1.0, 2.0, 3.0]), [1.0, 3.0, -2.0]); diff --git a/crates/polymodel-renderer-worker/src/worker.rs b/crates/polymodel-renderer-worker/src/worker.rs index 7bd716a..68408e5 100644 --- a/crates/polymodel-renderer-worker/src/worker.rs +++ b/crates/polymodel-renderer-worker/src/worker.rs @@ -65,9 +65,13 @@ struct RendererState { context: three_d::Context, model: Option>, ldraw_static_model: Option>>, + ldraw_explicit_model: + Option>>, ldraw_conditional_model: Option>>, ldraw_scene: Option>, + ldraw_plan: Option, + ldraw_style: polymodel_renderer_worker::RenderStyle, ldraw_last_view: Option<[f64; 3]>, ambient_light: three_d::AmbientLight, key_light: three_d::DirectionalLight, @@ -409,8 +413,11 @@ impl WorkerState { context, model: None, ldraw_static_model: None, + ldraw_explicit_model: None, ldraw_conditional_model: None, ldraw_scene: None, + ldraw_plan: None, + ldraw_style: polymodel_renderer_worker::RenderStyle::Faces, ldraw_last_view: None, ambient_light, key_light, @@ -1111,7 +1118,10 @@ fn consume_input_and_render(st: &mut WorkerState) { .handle_events(&mut renderer.camera, &mut events); } - if !button_down && let Some(scene) = renderer.ldraw_scene.as_ref() { + if !button_down + && renderer.ldraw_style == polymodel_renderer_worker::RenderStyle::Edges + && let Some(plan) = renderer.ldraw_plan.as_ref() + { let direction = renderer.camera.position() - renderer.camera.target(); let length = f64::from(direction.magnitude()); if length > f64::EPSILON { @@ -1125,13 +1135,13 @@ fn consume_input_and_render(st: &mut WorkerState) { dot < 0.9995 }); if changed { - match polymodel_renderer_worker::build_edge_model_for_view( + match polymodel_renderer_worker::build_conditional_edge_model_for_view( &renderer.context, - scene, + &plan.conditional_edges, view, ) { Ok(model) => { - renderer.ldraw_conditional_model = Some(model); + renderer.ldraw_conditional_model = (!model.is_empty()).then_some(model); renderer.ldraw_last_view = Some(view); } Err(error) => { @@ -1171,6 +1181,9 @@ fn consume_input_and_render(st: &mut WorkerState) { if let Some(model) = &renderer.ldraw_static_model { let _ = screen.render(&renderer.camera, model, &lights); } + if let Some(model) = &renderer.ldraw_explicit_model { + let _ = screen.render(&renderer.camera, model, &lights); + } if let Some(model) = &renderer.ldraw_conditional_model { let _ = screen.render(&renderer.camera, model, &lights); } @@ -1423,8 +1436,11 @@ fn handle_load_mesh( if let Some(renderer) = st.renderer.as_mut() { renderer.model = Some(render_model); renderer.ldraw_static_model = None; + renderer.ldraw_explicit_model = None; renderer.ldraw_conditional_model = None; renderer.ldraw_scene = None; + renderer.ldraw_plan = None; + renderer.ldraw_style = polymodel_renderer_worker::RenderStyle::Faces; renderer.ldraw_last_view = None; renderer.scene_bounds = None; renderer.camera = camera; @@ -1820,13 +1836,16 @@ fn finish_compound_source( } }; let scene_ref = std::rc::Rc::new(scene.clone()); + let style = polymodel_renderer_worker::RenderStyle::Faces; let models = match polymodel_renderer_worker::build_scene_model_parts_for_style( &context, &scene_ref, [0.0, 0.0, 1.0], - polymodel_renderer_worker::RenderStyle::Faces, + style, ) { - Ok((static_model, edge_model, _picker)) => (static_model, edge_model), + Ok((static_model, edge_model, _picker, plan)) => { + (static_model, edge_model, plan, style) + } Err(detail) => { fail_compound_install(&mut st, namespace, identity, detail); return; @@ -1841,11 +1860,15 @@ fn finish_compound_source( } else { (None, None) }; - if let Some((static_model, conditional_model, scene)) = - candidate_models.map(|((static_model, conditional_model), scene)| { - (static_model, conditional_model, scene) - }) - { + if let Some(( + static_model, + (explicit_model, conditional_model), + plan, + style, + scene, + )) = candidate_models.map(|((static_model, edge_models, plan, style), scene)| { + (static_model, edge_models, plan, style, scene) + }) { if st.load_reducer.current_identity() != Some(identity) || st.disposed { return; } @@ -1877,7 +1900,10 @@ fn finish_compound_source( renderer.scene_bounds = Some(bounds); renderer.model = None; renderer.ldraw_static_model = Some(static_model); + renderer.ldraw_explicit_model = explicit_model; renderer.ldraw_conditional_model = conditional_model; + renderer.ldraw_plan = Some(plan); + renderer.ldraw_style = style; renderer.ldraw_last_view = Some([0.0, 0.0, 1.0]); renderer.ldraw_scene = Some(scene); st.needs_render = true;