diff --git a/inox2d/src/formats/payload.rs b/inox2d/src/formats/payload.rs index c8c122f9..8acf7e07 100644 --- a/inox2d/src/formats/payload.rs +++ b/inox2d/src/formats/payload.rs @@ -91,7 +91,7 @@ fn deserialize_node(obj: JsonObject) -> InoxParseResult { Ok(ParsedNode { node: InoxNode { uuid: InoxNodeUuid(obj.get_u32("uuid")?), - name: obj.get_str("name")?.to_owned(), + name: obj.get_str("name")?.trim_end_matches('\0').to_owned(), enabled: obj.get_bool("enabled")?, zsort: obj.get_f32("zsort")?, trans_offset: vals("transform", deserialize_transform(obj.get_object("transform")?))?, @@ -226,7 +226,7 @@ fn deserialize_mesh(obj: JsonObject) -> InoxParseResult { let uvs = match uvs { Ok(uvs) => uvs, - Err(e) => vec![], + Err(_e) => vec![], }; Ok(Mesh { @@ -415,7 +415,7 @@ fn deserialize_params(vals: &[json::JsonValue]) -> InoxParseResult InoxParseResult<(String, Param)> { - let name = obj.get_str("name")?.to_owned(); + let name = obj.get_str("name")?.trim_end_matches('\0').to_owned(); Ok(( name.clone(), Param { diff --git a/inox2d/src/math/deform.rs b/inox2d/src/math/deform.rs index 5d08fd2e..71513313 100644 --- a/inox2d/src/math/deform.rs +++ b/inox2d/src/math/deform.rs @@ -1,10 +1,13 @@ use glam::{Mat2, Vec2}; +use crate::node::InoxNodeUuid; + /// Different kinds of deform. -// TODO: Meshgroup. pub(crate) enum Deform { /// Specifying a displacement for every vertex. Direct(Vec), + /// MESHGROUP_ATTEMPT #1 + FromMeshGroup(Vec, InoxNodeUuid), } /// Element-wise add direct deforms up and write result. diff --git a/inox2d/src/math/triangle.rs b/inox2d/src/math/triangle.rs index b875ef0b..38050fdc 100644 --- a/inox2d/src/math/triangle.rs +++ b/inox2d/src/math/triangle.rs @@ -189,7 +189,7 @@ impl<'mesh> MeshBitMask<'mesh> { } /// Return the index of the triangle point `p` is in, if any. - pub fn test(&self, p: Vec2) -> Option { + pub fn test(&self, p: &Vec2) -> Option { // handle empty mesh case if self.mask.is_empty() { return None; @@ -208,7 +208,7 @@ impl<'mesh> MeshBitMask<'mesh> { candidates .into_iter() - .find(|&t| is_point_in_triangle(p, &self.mesh.get_triangle(t))) + .find(|&t| is_point_in_triangle(*p, &self.mesh.get_triangle(t))) } } @@ -325,7 +325,7 @@ mod tests { test_with_mesh(*transform, |mesh, ps| { let bit_mask = MeshBitMask::new(mesh); - ps.into_iter().map(|p| bit_mask.test(p)).collect() + ps.into_iter().map(|p| bit_mask.test(&p)).collect() }) }) } @@ -342,7 +342,7 @@ mod tests { assert_eq!(bit_mask.width, 0); assert_eq!(bit_mask.height, 0); - assert_eq!(bit_mask.test(vec2(-1.0, 0.0)), None); - assert_eq!(bit_mask.test(vec2(1.0, 2.0)), None); + assert_eq!(bit_mask.test(&vec2(-1.0, 0.0)), None); + assert_eq!(bit_mask.test(&vec2(1.0, 2.0)), None); } } diff --git a/inox2d/src/node/components.rs b/inox2d/src/node/components.rs index f60925b4..044d14bd 100644 --- a/inox2d/src/node/components.rs +++ b/inox2d/src/node/components.rs @@ -211,7 +211,7 @@ pub struct MeshGroup { #[derive(Hash, PartialEq, Eq, Copy, Clone)] pub(crate) enum DeformSource { Param(ParamUuid), - MeshGroup(InoxNodeUuid), + MeshGroup(ParamUuid, InoxNodeUuid), } /// Internal component solving for deforms of a node. diff --git a/inox2d/src/params.rs b/inox2d/src/params.rs index 2b17a91d..241362a9 100644 --- a/inox2d/src/params.rs +++ b/inox2d/src/params.rs @@ -8,10 +8,10 @@ use crate::math::{ matrix::Matrix2d, }; use crate::node::{ - components::{DeformSource, DeformStack, Mesh, TransformStore, ZSort}, + components::{DeformSource, DeformStack, Mesh, MeshGroup, TransformStore, ZSort}, InoxNodeUuid, }; -use crate::puppet::{Puppet, World}; +use crate::puppet::{InoxNodeTree, Puppet, World}; /// Parameter binding to a node. This allows to animate a node based on the value of the parameter that owns it. pub struct Binding { @@ -75,7 +75,7 @@ impl Param { /// /// End users may repeatedly apply a same parameter for multiple times in between frames, /// but other facilities should be present to make sure this `apply()` is only called once per parameter. - pub(crate) fn apply(&self, val: Vec2, comps: &mut World) { + pub(crate) fn apply(&self, val: Vec2, nodes: &InoxNodeTree, comps: &mut World) { let val = val.clamp(self.min, self.max); let val_normed = (val - self.min) / (self.max - self.min); @@ -113,8 +113,6 @@ impl Param { vec2(self.axis_points.x[x_maxdex], self.axis_points.y[y_maxdex]), ); - let val_normed = val_normed.clamp(range_in.beg, range_in.end); - match binding.values { BindingValues::ZSort(ref matrix) => { let (out_top, out_bottom) = ranges_out(matrix, x_mindex, x_maxdex, y_mindex, y_maxdex); @@ -194,32 +192,70 @@ impl Param { matrix[(x_maxdex, y_maxdex)].as_slice(), ); - // deform specified by a parameter must be direct, i.e., in the form of displacements of all vertices - let direct_deform = { - let mesh = comps - .get::(binding.node) - .expect("Deform param target must have an associated Mesh."); - - let vert_len = mesh.vertices.len(); - let mut direct_deform: Vec = Vec::with_capacity(vert_len); - direct_deform.resize(vert_len, Vec2::ZERO); - - bi_interpolate_vec2s_additive( - val_normed, - range_in, - out_top, - out_bottom, - binding.interpolate_mode, - &mut direct_deform, - ); - - direct_deform - }; - - comps - .get_mut::(binding.node) - .expect("Nodes being deformed must have a DeformStack component.") - .push(DeformSource::Param(self.uuid), Deform::Direct(direct_deform)); + // case Meshgroup + if comps.get::(binding.node).is_some() { + let direct_deform = { + let mesh = comps.get::(binding.node).unwrap_or_else(|| { + panic!( + "Deform param target must have an associated Mesh. (Binding Node ID: {:?})", + binding.node.0 + ) + }); + + let vert_len = mesh.vertices.len(); + let mut direct_deform: Vec = Vec::with_capacity(vert_len); + direct_deform.resize(vert_len, Vec2::ZERO); + + bi_interpolate_vec2s_additive( + val_normed, + range_in, + out_top, + out_bottom, + binding.interpolate_mode, + &mut direct_deform, + ); + direct_deform + }; + comps + .get_mut::(binding.node) + .expect("Nodes being deformed must have a DeformStack component.") + .push(DeformSource::Param(self.uuid), Deform::Direct(direct_deform.clone())); + + // Push deform data of descendants from meshgroup onto deform stack + if comps.get::(binding.node).unwrap().dynamic { + push_children(nodes, comps, self.uuid, &direct_deform, binding.node, binding.node); + } + } else { + // deform specified by a parameter must be direct, i.e., in the form of displacements of all vertices + let direct_deform = { + let mesh = comps.get::(binding.node).unwrap_or_else(|| { + panic!( + "Deform param target must have an associated Mesh. (Binding Node ID: {:?})", + binding.node.0 + ) + }); + + let vert_len = mesh.vertices.len(); + let mut direct_deform: Vec = Vec::with_capacity(vert_len); + direct_deform.resize(vert_len, Vec2::ZERO); + + bi_interpolate_vec2s_additive( + val_normed, + range_in, + out_top, + out_bottom, + binding.interpolate_mode, + &mut direct_deform, + ); + + direct_deform + }; + + comps + .get_mut::(binding.node) + .expect("Nodes being deformed must have a DeformStack component.") + .push(DeformSource::Param(self.uuid), Deform::Direct(direct_deform)); + } } // TODO BindingValues::Opacity => {} @@ -228,6 +264,28 @@ impl Param { } } +fn push_children( + nodes: &InoxNodeTree, + comps: &mut World, + param_uuid: ParamUuid, + meshgroup_deform: &Vec, + meshgroup_uuid: InoxNodeUuid, + parent_uuid: InoxNodeUuid, +) { + for child in nodes.get_children(parent_uuid) { + if comps.get::(child.uuid).is_some() { + continue; + } + if comps.get::(child.uuid).is_some() { + comps.get_mut::(child.uuid).unwrap().push( + DeformSource::MeshGroup(param_uuid, meshgroup_uuid), + Deform::FromMeshGroup(meshgroup_deform.to_vec(), child.uuid), + ); + } + push_children(nodes, comps, param_uuid, meshgroup_deform, meshgroup_uuid, child.uuid); + } +} + /// Additional struct attached to a puppet for animating through params. pub struct ParamCtx { values: HashMap, @@ -258,12 +316,12 @@ impl ParamCtx { } /// Modify components as specified by all params. Must be called ONCE per frame. - pub(crate) fn apply(&self, params: &HashMap, comps: &mut World) { + pub(crate) fn apply(&self, params: &HashMap, nodes: &InoxNodeTree, comps: &mut World) { // a correct implementation should not care about the order of `.apply()` for (param_name, val) in self.values.iter() { // TODO: a correct implementation should not fail on param value (0, 0) if *val != Vec2::ZERO { - params.get(param_name).unwrap().apply(*val, comps); + params.get(param_name).unwrap().apply(*val, nodes, comps); } } } diff --git a/inox2d/src/puppet.rs b/inox2d/src/puppet.rs index ce67ff8d..7cdcbfa1 100644 --- a/inox2d/src/puppet.rs +++ b/inox2d/src/puppet.rs @@ -126,7 +126,7 @@ impl Puppet { /// Provide elapsed time for physics, if initialized, to run. Provide `0` for the first call. pub fn end_frame(&mut self, dt: f32) { if let Some(param_ctx) = self.param_ctx.as_mut() { - param_ctx.apply(&self.params, &mut self.node_comps); + param_ctx.apply(&self.params, &self.nodes, &mut self.node_comps); } if let Some(transform_ctx) = self.transform_ctx.as_mut() { @@ -158,7 +158,7 @@ impl Puppet { .set(param_name, *value) .expect("Param name returned by .step() must exist."); } - param_ctx.apply(&self.params, &mut self.node_comps); + param_ctx.apply(&self.params, &self.nodes, &mut self.node_comps); transform_ctx.update(&self.nodes, &mut self.node_comps); } diff --git a/inox2d/src/render.rs b/inox2d/src/render.rs index d78a236f..a9d66bed 100644 --- a/inox2d/src/render.rs +++ b/inox2d/src/render.rs @@ -48,14 +48,36 @@ impl RenderCtx { let comps = &mut puppet.node_comps; let mut nodes_to_deform = HashSet::new(); + + fn insert_children( + nodes: &InoxNodeTree, + comps: &World, + parent: InoxNodeUuid, + node_set: &mut HashSet, + ) { + for child in nodes.get_children(parent) { + if comps.get::(child.uuid).is_some() { + node_set.insert(child.uuid); + insert_children(nodes, comps, child.uuid, node_set); + } + } + } + + // TODO: Refactoring and nested meshgroup. Maybe putting this into the below loop would be better? for param in &puppet.params { param.1.bindings.iter().for_each(|b| { if matches!(b.values, BindingValues::Deform(_)) { nodes_to_deform.insert(b.node); + // TODO: what is translate children?? + + if let Some(meshgroup) = comps.get::(b.node) { + if meshgroup.dynamic { + insert_children(nodes, comps, b.node, &mut nodes_to_deform); + } + } } }); } - // TODO: Further fill the set when Meshgroup is implemented. let mut vertex_buffers = VertexBuffers::default(); @@ -334,7 +356,7 @@ impl InoxRendererExt for T { .expect("All children in zsorted_children_list should be a Drawable."); match drawable_kind { DrawableKind::TexturedMesh(components) => { - self.draw_textured_mesh_content(as_mask, &components, comps.get(*uuid).unwrap(), *uuid) + self.draw_textured_mesh_content(as_mask, &components, comps.get(*uuid).unwrap(), *uuid) } DrawableKind::Composite { .. } => panic!("Composite inside Composite not allowed."), } diff --git a/inox2d/src/render/deform_stack.rs b/inox2d/src/render/deform_stack.rs index 037a23db..9f75d2ab 100644 --- a/inox2d/src/render/deform_stack.rs +++ b/inox2d/src/render/deform_stack.rs @@ -3,8 +3,9 @@ use std::mem::swap; use glam::Vec2; -use crate::math::deform::{linear_combine, Deform}; -use crate::node::components::{DeformSource, DeformStack}; +use crate::math::deform::{deform_by_parent_triangle, linear_combine, vector_decompose_matrix, Deform}; +use crate::math::triangle::MeshBitMask; +use crate::node::components::{DeformSource, DeformStack, Mesh, TransformStore}; use crate::puppet::{InoxNodeTree, World}; impl DeformStack { @@ -23,39 +24,142 @@ impl DeformStack { } /// Combine the deformations received so far according to some rules, and write to the result - pub(crate) fn combine(&self, _nodes: &InoxNodeTree, _node_comps: &World, result: &mut [Vec2]) { + pub(crate) fn combine(&self, nodes: &InoxNodeTree, node_comps: &World, result: &mut [Vec2]) { if result.len() != self.deform_len { panic!("Required output deform dimensions different from what DeformStack is initialized with.") } - let direct_deforms = self.stack.values().filter_map(|enabled_deform| { + // Single pass might require more structure changes. I can't figure it out how without. + // I will try to make it work and look as straightforward as possible. + + let mut maybe_meshgroup_uuid: Option<&crate::node::InoxNodeUuid> = None; + let mut maybe_meshgroup_deform: Option<&Vec> = None; + let mut maybe_node_id: Option<&crate::node::InoxNodeUuid> = None; + + // Apply direct deform first + let direct_deforms = self.stack.iter().filter_map(|(deform_source, enabled_deform)| { if enabled_deform.0 { - let Deform::Direct(ref direct_deform) = enabled_deform.1; - Some(direct_deform) + match (&deform_source, &enabled_deform.1) { + (DeformSource::Param(_), Deform::Direct(ref direct_deform)) => Some(direct_deform), + (DeformSource::MeshGroup(_, mg_uuid), Deform::FromMeshGroup(ref mg_deform, ref node_uuid)) => { + maybe_meshgroup_uuid = Some(mg_uuid); + maybe_meshgroup_deform = Some(mg_deform); + maybe_node_id = Some(node_uuid); + None + } + _ => panic!("Unexpected DeformSource and Deform tuple"), + } } else { None } }); linear_combine(direct_deforms, result); + + if let (Some(node_id), Some(meshgroup_uuid), Some(meshgroup_deform)) = + (maybe_node_id, maybe_meshgroup_uuid, maybe_meshgroup_deform) + { + // Apply transform to meshgroup's space before testing (fast) + let _child_node = nodes.get_node(*node_id).unwrap(); + let child_mesh = node_comps.get::(*node_id).unwrap(); + let child_init_verts = &child_mesh.vertices; // must have a mesh if already on deform stack + let child_direct_verts = child_init_verts + .iter() + .zip(result.iter()) + .map(|(point, deform)| point + deform); + + let child_transform = node_comps.get::(*node_id).unwrap().absolute; + // Need this because triangle test is in meshgroup's space + let meshgroup_transform = node_comps.get::(*meshgroup_uuid).unwrap().absolute; + let to_meshgroup_space = meshgroup_transform.inverse() * child_transform; + + // take account of child deform results + // the "result" so far should be applied on the initial mesh, not the transformed mesh + let child_meshgroup_verts = + child_direct_verts.map(|point| to_meshgroup_space.transform_point3(point.extend(0.0)).truncate()); + + let mut deform_results = vec![Vec2::default(); child_meshgroup_verts.len()]; + let mut grouped_points: HashMap, Vec)> = HashMap::new(); + + // Triangle test (takes most time) + // let triangle_by_point = meshgroup_mesh.test(child_meshgroup_verts.iter()); + let meshgroup_mesh = node_comps.get::(*meshgroup_uuid).unwrap(); + let mesh_mask = MeshBitMask::new(meshgroup_mesh); + // TODO: parallel iteration? + child_meshgroup_verts.enumerate().for_each(|(idx, point)| { + if let Some(id) = mesh_mask.test(&point) { + let (indices, pts) = grouped_points.entry(id).or_default(); + indices.push(idx); + pts.push(point); + } + }); + + // Calculate deform by meshgroup (fast) + for (tri_idx, (indices, points_in_tri)) in grouped_points { + let tri = meshgroup_mesh.get_triangle(tri_idx); + let decompose_matrix = vector_decompose_matrix(tri[1] - tri[0], tri[2] - tri[0]); + let parent_deforms = [ + meshgroup_deform[meshgroup_mesh.indices[3 * tri_idx as usize] as usize], + meshgroup_deform[meshgroup_mesh.indices[(3 * tri_idx + 1) as usize] as usize], + meshgroup_deform[meshgroup_mesh.indices[(3 * tri_idx + 2) as usize] as usize], + ]; + + let deform_by_triangle = + deform_by_parent_triangle(&decompose_matrix, tri[0], &parent_deforms, points_in_tri.iter()); + + for (idx, deform_point) in indices.iter().zip(deform_by_triangle) { + deform_results[*idx] = deform_point; + } + } + + // Apply deform by meshgroup (fast) + // The calculated deform is in meshgroup's space, + // so convert the deform back to the target mesh's space + let to_node_space = child_transform.inverse() * meshgroup_transform; + let deform_node_space = deform_results + .iter() + .map(|deform| to_node_space.transform_vector3(deform.extend(0.0)).truncate()); + + result + .iter_mut() + .zip(deform_node_space) + .for_each(|(sum, addition)| *sum += addition); + } } /// Submit a deform from a source for a node. pub(crate) fn push(&mut self, src: DeformSource, mut deform: Deform) { - let Deform::Direct(ref direct_deform) = deform; - if direct_deform.len() != self.deform_len { - panic!("A direct deform with non-matching dimensions is submitted to a node."); - } - - self.stack - .entry(src) - .and_modify(|enabled_deform| { - if enabled_deform.0 { - panic!("A same source submitted deform twice for a same node within one frame.") + match deform { + Deform::Direct(ref direct_deform) => { + if direct_deform.len() != self.deform_len { + panic!("A direct deform with non-matching dimensions is submitted to a node."); } - enabled_deform.0 = true; - swap(&mut enabled_deform.1, &mut deform); - }) - .or_insert((true, deform)); + self.stack + .entry(src) + .and_modify(|enabled_deform| { + if enabled_deform.0 { + panic!("A same source submitted deform twice for a same node within one frame.") + } + enabled_deform.0 = true; + + swap(&mut enabled_deform.1, &mut deform); + }) + .or_insert((true, deform)); + } + // TODO: I don't know if we can add necessary information so we can use them during combine + Deform::FromMeshGroup(_, _) => { + self.stack + .entry(src) + .and_modify(|enabled_deform| { + if enabled_deform.0 { + panic!("A same source submitted deform twice for a same node within one frame.") + } + enabled_deform.0 = true; + + swap(&mut enabled_deform.1, &mut deform); + }) + .or_insert((true, deform)); + } + } } }