diff --git a/inox2d/src/render.rs b/inox2d/src/render.rs index c77fe74..4f86422 100644 --- a/inox2d/src/render.rs +++ b/inox2d/src/render.rs @@ -1,7 +1,7 @@ mod deform_stack; mod vertex_buffers; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::mem::swap; use crate::node::{ @@ -60,11 +60,42 @@ impl RenderCtx { let mut vertex_buffers = VertexBuffers::default(); let mut root_drawables_count: usize = 0; - for node in nodes.iter() { + + // Composite uuid => (descendant list, drawable count) + let mut composite_descendent_lists: HashMap, usize)> = HashMap::new(); + // Vec<(Node uuid, maybe highest Composite ancestor)> + // This stack skips on nodes that don't have a Composite ancestor + let mut ancestor_stack: Vec<(InoxNodeUuid, Option)> = Vec::new(); + + for node in nodes.pre_order_iter() { + // Pop until top of stack is parent of current node + while let Some(top) = ancestor_stack.last() { + if nodes.get_parent(node.uuid).uuid == top.0 { + break; + } + ancestor_stack.pop(); + } + + let top_composite = ancestor_stack.last().and_then(|curr_top| curr_top.1); + + if let Some(top_composite) = top_composite { + // Current node is a descendant of current highest Composite (could be anything, a Node, Part, Meshgroup, Composite etc.) + composite_descendent_lists + .entry(top_composite) + .or_default() + .0 + .push(node.uuid); + ancestor_stack.push((node.uuid, Some(top_composite))); + } + let drawable_kind = DrawableKind::new(node.uuid, comps, true); if let Some(drawable_kind) = drawable_kind { root_drawables_count += 1; + if let Some(target_id) = top_composite { + composite_descendent_lists.entry(target_id).or_default().1 += 1; + } + match drawable_kind { DrawableKind::TexturedMesh(components) => { let (index_offset, vert_offset) = vertex_buffers.push(components.mesh); @@ -86,33 +117,28 @@ impl RenderCtx { } } DrawableKind::Composite { .. } => { - // exclude non-drawable children - let children_list: Vec = nodes - .get_children(node.uuid) - .filter_map(|n| { - if DrawableKind::new(n.uuid, comps, false).is_some() { - Some(n.uuid) - } else { - None - } - }) - .collect(); - - // composite children are excluded from root_drawables_zsorted - root_drawables_count -= children_list.len(); - - comps.add( - node.uuid, - CompositeRenderCtx { - // sort later, before render - zsorted_children_list: children_list, - }, - ); + if top_composite.is_none() { + // Empty stack -> current node is the highest Composite on its branch + // (otherwise this Composite would be pushed twice) + ancestor_stack.push((node.uuid, Some(node.uuid))); + } } }; } } + composite_descendent_lists + .iter() + .for_each(|(composite_id, (descendents, drawable_count))| { + root_drawables_count -= drawable_count; + comps.add( + *composite_id, + CompositeRenderCtx { + zsorted_children_list: descendents.clone(), + }, + ); + }); + let mut root_drawables_zsorted = Vec::new(); // similarly, populate later, before render root_drawables_zsorted.resize(root_drawables_count, InoxNodeUuid(0)); @@ -136,48 +162,62 @@ impl RenderCtx { pub(crate) fn update(&mut self, nodes: &InoxNodeTree, comps: &mut World) { let mut root_drawable_uuid_zsort_vec = Vec::<(InoxNodeUuid, f32)>::new(); + let mut ancestor_stack: Vec<(InoxNodeUuid, Option)> = Vec::new(); + // root is definitely not a drawable. - for node in nodes.iter().skip(1) { + for node in nodes.pre_order_iter().skip(1) { + while let Some(top) = ancestor_stack.last() { + if nodes.get_parent(node.uuid).uuid == top.0 { + break; + } + ancestor_stack.pop(); + } + + let top_composite = ancestor_stack.last().and_then(|top| top.1); + if let Some(top_composite) = top_composite { + ancestor_stack.push((node.uuid, Some(top_composite))); + } if let Some(drawable_kind) = DrawableKind::new(node.uuid, comps, false) { - let parent = nodes.get_parent(node.uuid); let node_zsort = comps.get::(node.uuid).unwrap().0; - if !matches!( - DrawableKind::new(parent.uuid, comps, false), - Some(DrawableKind::Composite(_)) - ) { - // exclude composite children + if top_composite.is_none() { root_drawable_uuid_zsort_vec.push((node.uuid, node_zsort)); } match drawable_kind { // for Composite, update zsorted children list DrawableKind::Composite { .. } => { - // `swap()` usage is a trick that both: - // - returns mut borrowed comps early - // - does not involve any heap allocations - let mut zsorted_children_list = Vec::new(); - swap( - &mut zsorted_children_list, - &mut comps - .get_mut::(node.uuid) - .unwrap() - .zsorted_children_list, - ); + // Nested composites behave like a normal part zsort-wise + // i.e. not owning or managing a zsorted children list + if top_composite.is_none() { + ancestor_stack.push((node.uuid, Some(node.uuid))); + + // `swap()` usage is a trick that both: + // - returns mut borrowed comps early + // - does not involve any heap allocations + let mut zsorted_children_list = Vec::new(); + swap( + &mut zsorted_children_list, + &mut comps + .get_mut::(node.uuid) + .unwrap() + .zsorted_children_list, + ); - zsorted_children_list.sort_by(|a, b| { - let zsort_a = comps.get::(*a).unwrap(); - let zsort_b = comps.get::(*b).unwrap(); - zsort_a.total_cmp(zsort_b).reverse() - }); - - swap( - &mut zsorted_children_list, - &mut comps - .get_mut::(node.uuid) - .unwrap() - .zsorted_children_list, - ); + zsorted_children_list.sort_by(|a, b| { + let zsort_a = comps.get::(*a).unwrap(); + let zsort_b = comps.get::(*b).unwrap(); + zsort_a.total_cmp(zsort_b).reverse() + }); + + swap( + &mut zsorted_children_list, + &mut comps + .get_mut::(node.uuid) + .unwrap() + .zsorted_children_list, + ); + } } // for TexturedMesh, obtain and write deforms into vertex_buffer DrawableKind::TexturedMesh(..) => { @@ -324,7 +364,7 @@ impl InoxRendererExt for T { DrawableKind::TexturedMesh(components) => { self.draw_textured_mesh_content(as_mask, &components, comps.get(*uuid).unwrap(), *uuid) } - DrawableKind::Composite { .. } => panic!("Composite inside Composite not allowed."), + DrawableKind::Composite { .. } => continue, // Allow composite inside composite } } @@ -337,7 +377,7 @@ impl InoxRendererExt for T { /// /// This does not guarantee the display of a puppet on screen due to these possible reasons: /// - Only provided `InoxRenderer` method implementations are called. - /// + /// /// For example, maybe the caller still need to transfer content from a texture buffer to the screen surface buffer. /// - The provided `InoxRender` implementation is wrong. /// - `puppet` here does not belong to the `model` this `renderer` is initialized with. This will likely result in panics for non-existent node uuids.