Skip to content

Commit

Permalink
Implement reset methods for puppet to run across frames.
Browse files Browse the repository at this point in the history
General pattern for a "system": Takes the form of an optional `-Ctx` struct attached to a puppet, which can be initialized if dependencies met.
Interactions with the puppet data are done in methods of the struct, so a `None` system can do nothing.
Current systems: Transform, Param, Render.
  • Loading branch information
Richardn2002 committed Jul 28, 2024
1 parent 49111a7 commit e08b1ca
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 56 deletions.
42 changes: 12 additions & 30 deletions inox2d/src/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,41 +217,28 @@ impl Param {
}
}

pub(crate) struct ParamCtx {
/// Additional struct attached to a puppet for animating through params.
pub struct ParamCtx {
values: HashMap<String, Vec2>,
}

impl ParamCtx {
pub fn new(puppet: &Puppet) -> Self {
pub(crate) fn new(puppet: &Puppet) -> Self {
Self {
values: puppet.params.iter().map(|p| (p.0.to_owned(), p.1.defaults)).collect(),
}
}
}

impl Puppet {
/// Reset all params to default value.
pub fn reset_params(&mut self) {
for (name, value) in self
.param_ctx
.as_mut()
.expect("Params can be written after .init_params().")
.values
.iter_mut()
{
*value = self.params.get(name).unwrap().defaults;
pub(crate) fn reset(&mut self, params: &HashMap<String, Param>) {
for (name, value) in self.values.iter_mut() {
*value = params.get(name).unwrap().defaults;
}
}

/// Set param with name to value `val`.
pub fn set_param(&mut self, param_name: &str, val: Vec2) -> Result<(), SetParamError> {
if let Some(value) = self
.param_ctx
.as_mut()
.expect("Params can be written after .init_params().")
.values
.get_mut(param_name)
{
pub fn set(&mut self, param_name: &str, val: Vec2) -> Result<(), SetParamError> {
if let Some(value) = self.values.get_mut(param_name) {
*value = val;
Ok(())
} else {
Expand All @@ -260,20 +247,15 @@ impl Puppet {
}

/// Modify components as specified by all params. Must be called ONCE per frame.
pub(crate) fn apply_params(&mut self) {
pub(crate) fn apply(&self, params: &HashMap<String, Param>, comps: &mut World) {
// a correct implementation should not care about the order of `.apply()`
for (param_name, val) in self
.param_ctx
.as_mut()
.expect("Params can be applied after .init_params().")
.values
.iter()
{
self.params.get(param_name).unwrap().apply(*val, &mut self.node_comps);
for (param_name, val) in self.values.iter() {
params.get(param_name).unwrap().apply(*val, comps);
}
}
}

/// Possible errors setting a param.
#[derive(Debug, thiserror::Error)]
pub enum SetParamError {
#[error("No parameter named {0}")]
Expand Down
69 changes: 62 additions & 7 deletions inox2d/src/puppet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@ use crate::params::{Param, ParamCtx};
use crate::render::RenderCtx;

use meta::PuppetMeta;
use transforms::TransformCtx;
pub use tree::InoxNodeTree;
pub use world::World;

/// Inochi2D puppet.
pub struct Puppet {
pub meta: PuppetMeta,
physics: PuppetPhysics,
// TODO: define the actual ctx
// TODO: define the actual ctx for physics
pub(crate) nodes: InoxNodeTree,
pub(crate) node_comps: World,
/// Context for rendering this puppet. See `.init_render_ctx()`.
/// Currently only a marker for if transform/zsort components are initialized.
pub(crate) transform_ctx: Option<TransformCtx>,
/// Context for rendering this puppet. See `.init_rendering()`.
pub render_ctx: Option<RenderCtx>,
pub(crate) params: HashMap<String, Param>,
pub(crate) param_ctx: Option<ParamCtx>,
/// Context for animating puppet with parameters. See `.init_params()`
pub param_ctx: Option<ParamCtx>,
}

impl Puppet {
Expand All @@ -38,33 +42,84 @@ impl Puppet {
physics,
nodes: InoxNodeTree::new_with_root(root),
node_comps: World::new(),
transform_ctx: None,
render_ctx: None,
params,
param_ctx: None,
}
}

/// Call this on a freshly loaded puppet if rendering is needed. Panicks on second call.
/// Create a copy of node transform/zsort for modification. Panicks on second call.
pub fn init_transforms(&mut self) {
if self.transform_ctx.is_some() {
panic!("Puppet transforms already initialized.")
}

let transform_ctx = TransformCtx::new(self);
self.transform_ctx = Some(transform_ctx);
}

/// Call this on a freshly loaded puppet if rendering is needed. Panicks:
/// - if transforms are not initialized.
/// - on second call.
pub fn init_rendering(&mut self) {
if self.transform_ctx.is_none() {
panic!("Puppet rendering depends on initialized puppet transforms.")
}
if self.render_ctx.is_some() {
panic!("Puppet already initialized for rendering.");
}

self.init_node_transforms();

let render_ctx = RenderCtx::new(self);
self.render_ctx = Some(render_ctx);
}

/// Call this on a puppet if params are going to be used. Panicks on second call.
/// Call this on a puppet if params are going to be used. Panicks:
/// - if rendering is not initialized.
/// - on second call.
pub fn init_params(&mut self) {
if self.render_ctx.is_none() {
panic!("Only a puppet initialized for rendering can be animated by params.");
}
if self.param_ctx.is_some() {
panic!("Puppet already initialized for params.");
}

let param_ctx = ParamCtx::new(self);
self.param_ctx = Some(param_ctx);
}

/// Prepare the puppet for a new frame. User may set params afterwards.
pub fn begin_frame(&mut self) {
if let Some(render_ctx) = self.render_ctx.as_mut() {
render_ctx.reset(&self.nodes, &mut self.node_comps);
}

if let Some(transform_ctx) = self.transform_ctx.as_mut() {
transform_ctx.reset(&self.nodes, &mut self.node_comps);
}

if let Some(param_ctx) = self.param_ctx.as_mut() {
param_ctx.reset(&self.params);
}
}

/// Freeze puppet for one frame. Rendering, if initialized, may follow.
///
/// 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);
}

if let Some(transform_ctx) = self.transform_ctx.as_mut() {
transform_ctx.update(&self.nodes, &mut self.node_comps);
}

if let Some(render_ctx) = self.render_ctx.as_mut() {
render_ctx.update(&self.nodes, &mut self.node_comps);
}
}
}

/// Global physics parameters for the puppet.
Expand Down
40 changes: 24 additions & 16 deletions inox2d/src/puppet/transforms.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,45 @@
use crate::node::components::TransformStore;
use crate::node::components::{TransformStore, ZSort};

use super::Puppet;
use super::{InoxNodeTree, Puppet, World};

impl Puppet {
/// Give every node a `TransformStore` component, if the puppet is going to be rendered/animated
pub(super) fn init_node_transforms(&mut self) {
for node in self.nodes.iter() {
self.node_comps.add(node.uuid, TransformStore::default());
pub(crate) struct TransformCtx {}

impl TransformCtx {
/// Give every node a `TransformStore` and a `ZSort` component, if the puppet is going to be rendered/animated
pub fn new(puppet: &mut Puppet) -> Self {
for node in puppet.nodes.iter() {
puppet.node_comps.add(node.uuid, TransformStore::default());
puppet.node_comps.add(node.uuid, ZSort::default());
}
TransformCtx {}
}

/// Reset all transform/zsort values to default.
pub fn reset(&mut self, nodes: &InoxNodeTree, comps: &mut World) {
for node in nodes.iter() {
comps.get_mut::<TransformStore>(node.uuid).unwrap().relative = node.trans_offset;
}
}

/// Update the puppet's nodes' absolute transforms, by combining transforms
/// from each node's ancestors in a pre-order traversal manner.
pub(crate) fn update_trans(&mut self) {
let root_trans_store = self
.node_comps
.get_mut::<TransformStore>(self.nodes.root_node_id)
.unwrap();
pub(crate) fn update(&mut self, nodes: &InoxNodeTree, comps: &mut World) {
let root_trans_store = comps.get_mut::<TransformStore>(nodes.root_node_id).unwrap();
// The root's absolute transform is its relative transform.
let root_trans = root_trans_store.relative.to_matrix();
root_trans_store.absolute = root_trans;

// Pre-order traversal, just the order to ensure that parents are accessed earlier than children
// Skip the root
for node in self.nodes.pre_order_iter().skip(1) {
for node in nodes.pre_order_iter().skip(1) {
let base_trans = if node.lock_to_root {
root_trans
} else {
let parent = self.nodes.get_parent(node.uuid);
self.node_comps.get_mut::<TransformStore>(parent.uuid).unwrap().absolute
let parent = nodes.get_parent(node.uuid);
comps.get_mut::<TransformStore>(parent.uuid).unwrap().absolute
};

let node_trans_store = self.node_comps.get_mut::<TransformStore>(node.uuid).unwrap();
let node_trans_store = comps.get_mut::<TransformStore>(node.uuid).unwrap();
let node_trans = node_trans_store.relative.to_matrix();
node_trans_store.absolute = base_trans * node_trans;
}
Expand Down
15 changes: 12 additions & 3 deletions inox2d/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,6 @@ impl RenderCtx {
);
}
};

// although it is tempting to put actual zsort in, a correct implementation would later set zsort values before rendering anyways.
comps.add(node.uuid, ZSort::default());
}
}

Expand All @@ -113,6 +110,18 @@ impl RenderCtx {
}
}

/// Reset all `DeformStack`.
pub(crate) fn reset(&mut self, nodes: &InoxNodeTree, comps: &mut World) {
for node in nodes.iter() {
if let Some(DrawableKind::TexturedMesh(..)) = DrawableKind::new(node.uuid, comps) {
let deform_stack = comps
.get_mut::<DeformStack>(node.uuid)
.expect("A TexturedMesh must have an associated DeformStack.");
deform_stack.reset();
}
}
}

/// Update
/// - zsort-ordered info
/// - deform buffer content
Expand Down

0 comments on commit e08b1ca

Please sign in to comment.