Skip to content

Commit

Permalink
Physics refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Speykious committed Feb 17, 2024
1 parent 29667c2 commit e10dd55
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 246 deletions.
22 changes: 12 additions & 10 deletions inox2d/src/formats/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ use crate::math::transform::TransformOffset;
use crate::mesh::{f32s_as_vec2s, Mesh};
use crate::nodes::node::{InoxNode, InoxNodeUuid};
use crate::nodes::node_data::{
BlendMode, Composite, Drawable, InoxData, Mask, MaskMode, Part, UnknownBlendModeError, UnknownMaskModeError,
BlendMode, Composite, Drawable, InoxData, Mask, MaskMode, ParamMapMode, Part, PhysicsModel, PhysicsProps,
SimplePhysics, UnknownBlendModeError, UnknownMaskModeError,
};
use crate::nodes::node_tree::InoxNodeTree;
use crate::params::{AxisPoints, Binding, BindingValues, Param, ParamUuid};
use crate::physics::{ParamMapMode, SimplePhysics, SimplePhysicsProps, SimplePhysicsSystem};
use crate::physics::runge_kutta::PhysicsState;
use crate::puppet::{
Puppet, PuppetAllowedModification, PuppetAllowedRedistribution, PuppetAllowedUsers, PuppetMeta, PuppetPhysics,
PuppetUsageRights, UnknownPuppetAllowedModificationError, UnknownPuppetAllowedRedistributionError,
Expand Down Expand Up @@ -157,33 +158,34 @@ fn deserialize_composite(obj: &JsonObject) -> InoxParseResult<Composite> {
fn deserialize_simple_physics(obj: &JsonObject) -> InoxParseResult<SimplePhysics> {
let param = ParamUuid(obj.get_u32("param")?);

let system = match obj.get_str("model_type")? {
"Pendulum" => SimplePhysicsSystem::new_rigid_pendulum(),
"SpringPendulum" => SimplePhysicsSystem::new_spring_pendulum(),
_ => todo!(),
let model_type = match obj.get_str("model_type")? {
"Pendulum" => PhysicsModel::RigidPendulum(PhysicsState::default()),
"SpringPendulum" => PhysicsModel::SpringPendulum(PhysicsState::default()),
a => todo!("{}", a),
};

let map_mode = match obj.get_str("map_mode")? {
"angle_length" => ParamMapMode::AngleLength,
"XY" => ParamMapMode::XY,
a => todo!("{}", a),
};

let local_only = obj.get_bool("local_only").unwrap_or_default();

let gravity = obj.get_f32("gravity")?;
let length = obj.get_f32("length")?;
let frequency = obj.get_f32("frequency")?;
let angle_damping = obj.get_f32("angle_damping")?;
let length_damping = obj.get_f32("length_damping")?;

let output_scale = obj.get_vec2("output_scale")?;

Ok(SimplePhysics {
param,

system,
model_type,
map_mode,

props: SimplePhysicsProps {
props: PhysicsProps {
gravity,
length,
frequency,
Expand All @@ -194,7 +196,7 @@ fn deserialize_simple_physics(obj: &JsonObject) -> InoxParseResult<SimplePhysics

local_only,

anchor: Vec2::ZERO,
bob: Vec2::ZERO,
output: Vec2::ZERO,
})
}
Expand Down
85 changes: 78 additions & 7 deletions inox2d/src/nodes/node_data.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use glam::Vec3;
use glam::{Vec2, Vec3};

use crate::mesh::Mesh;
use crate::params::ParamUuid;
use crate::physics::pendulum::rigid::RigidPendulum;
use crate::physics::pendulum::spring::SpringPendulum;
use crate::physics::runge_kutta::PhysicsState;
use crate::texture::TextureId;

use super::node::InoxNodeUuid;
use crate::physics::SimplePhysics;

#[derive(Debug, Clone)]
pub struct Composite {
pub draw_state: Drawable,
}

/// Blending mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -129,6 +127,79 @@ pub struct Part {
pub tex_bumpmap: TextureId,
}

#[derive(Debug, Clone)]
pub struct Composite {
pub draw_state: Drawable,
}

// TODO: PhysicsModel should just be a flat enum with no physics state.
// There's no reason to store a state if we're not simulating anything.
// This can be fixed in the component refactor.
// (I didn't want to create yet another separate PhysicsCtx just for this.)

/// Physics model to use for simple physics
#[derive(Debug, Clone)]
pub enum PhysicsModel {
/// Rigid pendulum
RigidPendulum(PhysicsState<RigidPendulum>),

/// Springy pendulum
SpringPendulum(PhysicsState<SpringPendulum>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ParamMapMode {
AngleLength,
XY,
}

#[derive(Debug, Clone)]
pub struct PhysicsProps {
/// Gravity scale (1.0 = puppet gravity)
pub gravity: f32,
/// Pendulum/spring rest length (pixels)
pub length: f32,
/// Resonant frequency (Hz)
pub frequency: f32,
/// Angular damping ratio
pub angle_damping: f32,
/// Length damping ratio
pub length_damping: f32,

pub output_scale: Vec2,
}

impl Default for PhysicsProps {
fn default() -> Self {
Self {
gravity: 1.,
length: 1.,
frequency: 1.,
angle_damping: 0.5,
length_damping: 0.5,
output_scale: Vec2::ONE,
}
}
}

#[derive(Debug, Clone)]
pub struct SimplePhysics {
pub param: ParamUuid,

pub model_type: PhysicsModel,
pub map_mode: ParamMapMode,

pub props: PhysicsProps,

/// Whether physics system listens to local transform only.
pub local_only: bool,

// TODO: same as above, this state shouldn't be here.
// It is only useful when simulating physics.
pub bob: Vec2,
pub output: Vec2,
}

#[derive(Debug, Clone)]
pub enum InoxData<T> {
Node,
Expand Down
179 changes: 84 additions & 95 deletions inox2d/src/physics.rs
Original file line number Diff line number Diff line change
@@ -1,102 +1,13 @@
pub mod pendulum;
mod runge_kutta;
mod simple_physics;
pub(crate) mod runge_kutta;

use crate::nodes::node_data::InoxData;
use crate::params::ParamUuid;
use crate::puppet::{Puppet, PuppetPhysics};

use glam::Vec2;

use self::pendulum::rigid::RigidPendulumSystem;
use self::pendulum::spring::SpringPendulumSystem;

/// Physics model to use for simple physics
#[derive(Debug, Clone)]
pub enum SimplePhysicsSystem {
/// Rigid pendulum
RigidPendulum(RigidPendulumSystem),

// Springy pendulum
SpringPendulum(SpringPendulumSystem),
}

impl SimplePhysicsSystem {
pub fn new_rigid_pendulum() -> Self {
Self::RigidPendulum(RigidPendulumSystem::default())
}

pub fn new_spring_pendulum() -> Self {
Self::SpringPendulum(SpringPendulumSystem::default())
}

fn tick(&mut self, anchor: Vec2, puppet_physics: PuppetPhysics, props: &SimplePhysicsProps, dt: f32) -> Vec2 {
// enum dispatch, fill the branches once other systems are implemented
// as for inox2d, users are not expected to bring their own physics system,
// no need to do dynamic dispatch with something like Box<dyn SimplePhysicsSystem>
match self {
SimplePhysicsSystem::RigidPendulum(system) => system.tick(anchor, puppet_physics, props, dt),
SimplePhysicsSystem::SpringPendulum(system) => system.tick(anchor, puppet_physics, props, dt),
}
}
}

#[derive(Debug, Clone)]
pub struct SimplePhysicsProps {
/// Gravity scale (1.0 = puppet gravity)
pub gravity: f32,
/// Pendulum/spring rest length (pixels)
pub length: f32,
/// Resonant frequency (Hz)
pub frequency: f32,
/// Angular damping ratio
pub angle_damping: f32,
/// Length damping ratio
pub length_damping: f32,

pub output_scale: Vec2,
}

impl Default for SimplePhysicsProps {
fn default() -> Self {
Self {
gravity: 1.,
length: 1.,
frequency: 1.,
angle_damping: 0.5,
length_damping: 0.5,
output_scale: Vec2::ONE,
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ParamMapMode {
AngleLength,
XY,
}

#[derive(Debug, Clone)]
pub struct SimplePhysics {
pub param: ParamUuid,

pub system: SimplePhysicsSystem,
pub map_mode: ParamMapMode,

pub props: SimplePhysicsProps,
use std::f32::consts::PI;

/// Whether physics system listens to local transform only.
pub local_only: bool,
use glam::{vec2, vec4, Vec2};

pub anchor: Vec2,
pub output: Vec2,
}

impl SimplePhysics {
pub fn tick(&mut self, dt: f32, puppet_physics: PuppetPhysics) {
self.output = self.system.tick(self.anchor, puppet_physics, &self.props, dt);
}
}
use crate::nodes::node_data::{InoxData, ParamMapMode, PhysicsModel, SimplePhysics};
use crate::puppet::{Puppet, PuppetPhysics};
use crate::render::NodeRenderCtx;

impl Puppet {
/// Update the puppet's nodes' absolute transforms, by applying further displacements yielded by the physics system
Expand All @@ -106,9 +17,11 @@ impl Puppet {
let Some(driver) = self.nodes.get_node_mut(driver_uuid) else {
continue;
};

let InoxData::SimplePhysics(ref mut system) = driver.data else {
continue;
};

let nrc = &self.render_ctx.node_render_ctxs[&driver.uuid];

let output = system.update(dt, puppet_physics, nrc);
Expand All @@ -117,3 +30,79 @@ impl Puppet {
}
}
}

impl SimplePhysics {
fn update(&mut self, dt: f32, puppet_physics: PuppetPhysics, node_render_ctx: &NodeRenderCtx) -> Vec2 {
// Timestep is limited to 10 seconds.
// If you're getting 0.1 FPS, you have bigger issues to deal with.
let mut dt = dt.min(10.);

let anchor = self.calc_anchor(node_render_ctx);

// Minimum physics timestep: 0.01s
while dt > 0.01 {
self.tick(0.01, anchor, puppet_physics);
dt -= 0.01;
}

self.tick(dt, anchor, puppet_physics);

self.output = self.calc_output(anchor, node_render_ctx);

self.output
}

fn tick(&mut self, dt: f32, anchor: Vec2, puppet_physics: PuppetPhysics) {
// enum dispatch, fill the branches once other systems are implemented
// as for inox2d, users are not expected to bring their own physics system,
// no need to do dynamic dispatch with something like Box<dyn SimplePhysicsSystem>
self.bob = match &mut self.model_type {
PhysicsModel::RigidPendulum(state) => state.tick(puppet_physics, &self.props, self.bob, anchor, dt),
PhysicsModel::SpringPendulum(state) => state.tick(puppet_physics, &self.props, self.bob, anchor, dt),
};
}

fn calc_anchor(&self, node_render_ctx: &NodeRenderCtx) -> Vec2 {
let anchor = match self.local_only {
true => node_render_ctx.trans_offset.translation.extend(1.0),
false => node_render_ctx.trans * vec4(0.0, 0.0, 0.0, 1.0),
};

vec2(anchor.x, anchor.y)
}

fn calc_output(&self, anchor: Vec2, node_render_ctx: &NodeRenderCtx) -> Vec2 {
let oscale = self.props.output_scale;
let output = self.output;

// "Okay, so this is confusing. We want to translate the angle back to local space, but not the coordinates."
// - Asahi Lina

// Transform the physics output back into local space.
// The origin here is the anchor. This gives us the local angle.
let local_pos4 = match self.local_only {
true => vec4(output.x, output.y, 0.0, 1.0),
false => node_render_ctx.trans.inverse() * vec4(output.x, output.y, 0.0, 1.0),
};

let local_angle = vec2(local_pos4.x, local_pos4.y).normalize();

// Figure out the relative length. We can work this out directly in global space.
let relative_length = output.distance(anchor) / self.props.length;

let param_value = match self.map_mode {
ParamMapMode::XY => {
let local_pos_norm = local_angle * relative_length;
let mut result = local_pos_norm - Vec2::Y;
result.y = -result.y; // Y goes up for params
result
}
ParamMapMode::AngleLength => {
let a = f32::atan2(-local_angle.x, local_angle.y) / PI;
vec2(a, relative_length)
}
};

param_value * oscale
}
}
Loading

0 comments on commit e10dd55

Please sign in to comment.