Skip to content

Commit

Permalink
SpringPendulum and a more functional API
Browse files Browse the repository at this point in the history
  • Loading branch information
Speykious committed Jan 7, 2024
1 parent b804a6b commit 8bd5718
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 115 deletions.
12 changes: 4 additions & 8 deletions inox2d/src/formats/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ use crate::nodes::node_data::{
};
use crate::nodes::node_tree::InoxNodeTree;
use crate::params::{AxisPoints, Binding, BindingValues, Param, ParamUuid};
use crate::physics::{
pendulum::Pendulum, ParamMapMode, SimplePhysics, SimplePhysicsProps, SimplePhysicsSystem,
};
use crate::physics::{ParamMapMode, SimplePhysics, SimplePhysicsProps, SimplePhysicsSystem};
use crate::puppet::{
Puppet, PuppetAllowedModification, PuppetAllowedRedistribution, PuppetAllowedUsers, PuppetMeta,
PuppetPhysics, PuppetUsageRights, UnknownPuppetAllowedModificationError,
Expand Down Expand Up @@ -177,11 +175,9 @@ 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::Pendulum(Pendulum::default()),
// "spring_pendulum" => todo!(),
// _ => todo!(),
// TODO
_ => SimplePhysicsSystem::Pendulum(Pendulum::default()),
"Pendulum" => SimplePhysicsSystem::new_rigid_pendulum(),
"SpringPendulum" => SimplePhysicsSystem::new_spring_pendulum(),
_ => todo!(),
};
let map_mode = match obj.get_str("map_mode")? {
"angle_length" => ParamMapMode::AngleLength,
Expand Down
78 changes: 68 additions & 10 deletions inox2d/src/physics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,78 @@ mod simple_physics;
use crate::nodes::node_data::InoxData;
use crate::params::ParamUuid;
use crate::puppet::Puppet;
use pendulum::Pendulum;

use glam::Vec2;
use glam::{vec2, Vec2};

use self::pendulum::rigid_pendulum::{self, RigidPendulum};
use self::pendulum::spring_pendulum::{self, SpringPendulum};
use self::runge_kutta::PhysicsState;

/// Physics model to use for simple physics
#[derive(Debug, Clone)]
pub enum SimplePhysicsSystem {
/// Rigid pendulum
Pendulum(Pendulum),
// TODO: Springy pendulum
// SpringPendulum(),
RigidPendulum {
bob: Vec2,
state: PhysicsState<2, RigidPendulum>,
},

// Springy pendulum
SpringPendulum {
bob: Vec2,
state: PhysicsState<4, SpringPendulum>,
},
}

impl SimplePhysicsSystem {
pub fn tick(&mut self, anchor: &Vec2, props: &SimplePhysicsProps, dt: f32) -> Vec2 {
pub fn new_rigid_pendulum() -> Self {
Self::RigidPendulum {
bob: Vec2::ZERO,
state: PhysicsState::default(),
}
}

pub fn new_spring_pendulum() -> Self {
Self::SpringPendulum {
bob: Vec2::ZERO,
state: PhysicsState::default(),
}
}

fn tick(&mut self, anchor: Vec2, 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::Pendulum(system) => system.tick(anchor, props, dt),
SimplePhysicsSystem::RigidPendulum {
ref mut bob,
ref mut state,
} => {
// Compute the angle against the updated anchor position
let d_bob = *bob - anchor;
state.setv_angle(f32::atan2(-d_bob.x, d_bob.y));

// Run the pendulum simulation in terms of angle
runge_kutta::tick(&rigid_pendulum::eval, state, props, anchor, dt);

// Update the bob position at the new angle
let angle = state.getv_angle();
let d_bob = vec2(-angle.sin(), angle.cos());
*bob = anchor + d_bob * props.length;

*bob
}
SimplePhysicsSystem::SpringPendulum {
ref mut bob,
ref mut state,
} => {
// Run the spring pendulum simulation
runge_kutta::tick(&spring_pendulum::eval, state, props, anchor, dt);

*bob = state.getv_bob();

*bob
}
}
}
}
Expand All @@ -49,10 +101,10 @@ impl Default for SimplePhysicsProps {
fn default() -> Self {
Self {
gravity: 1.,
length: 1.,
length: 0.,
frequency: 1.,
angle_damping: 1.,
length_damping: 1.,
angle_damping: 0.5,
length_damping: 0.5,
output_scale: Vec2::ONE,
}
}
Expand Down Expand Up @@ -81,6 +133,12 @@ pub struct SimplePhysics {
pub output: Vec2,
}

impl SimplePhysics {
pub fn tick(&mut self, dt: f32) {
self.output = self.system.tick(self.anchor, &self.props, dt);
}
}

impl Puppet {
/// Update the puppet's nodes' absolute transforms, by applying further displacements yielded by the physics system
/// in response to displacements caused by parameter changes
Expand Down
198 changes: 137 additions & 61 deletions inox2d/src/physics/pendulum.rs
Original file line number Diff line number Diff line change
@@ -1,86 +1,162 @@
use super::{
runge_kutta::{self, PhysicsState, PhysicsSystem},
SimplePhysicsProps,
};

use glam::Vec2;

#[derive(Default, Debug, Clone)]
pub struct Pendulum {
/// bob is happy
pub bob: Vec2,
/// contains the angle and delta-angle
physics_state: PhysicsState<2>,
}
pub mod rigid_pendulum {
use glam::Vec2;

impl Pendulum {
pub fn angle(&self) -> f32 {
self.physics_state.vars[0]
}
use crate::physics::runge_kutta::PhysicsState;
use crate::physics::SimplePhysicsProps;

pub fn set_angle(&mut self, angle: f32) {
self.physics_state.vars[0] = angle;
}
pub struct RigidPendulum;

pub fn set_derivative_angle(&mut self, angle: f32) {
self.physics_state.derivatives[0] = angle;
}
impl PhysicsState<2, RigidPendulum> {
// angle

pub fn delta_angle(&self) -> f32 {
self.physics_state.vars[1]
}
pub fn getv_angle(&self) -> f32 {
self.vars[0]
}

pub fn set_delta_angle(&mut self, delta_angle: f32) {
self.physics_state.vars[1] = delta_angle;
}
pub fn setv_angle(&mut self, angle: f32) {
self.vars[0] = angle;
}

pub fn set_derivative_delta_angle(&mut self, delta_angle: f32) {
self.physics_state.derivatives[1] = delta_angle;
}
}
pub fn getd_angle(&self) -> f32 {
self.derivatives[0]
}

impl PhysicsSystem<2> for Pendulum {
fn state(&self) -> &PhysicsState<2> {
&self.physics_state
}
pub fn setd_angle(&mut self, angle: f32) {
self.derivatives[0] = angle;
}

fn state_mut(&mut self) -> &mut PhysicsState<2> {
&mut self.physics_state
}
// dangle

pub fn getv_dangle(&self) -> f32 {
self.vars[1]
}

pub fn setv_dangle(&mut self, dangle: f32) {
self.vars[1] = dangle;
}

pub fn getd_dangle(&self) -> f32 {
self.derivatives[1]
}

fn set_state(&mut self, state: PhysicsState<2>) {
self.physics_state = state;
pub fn setd_dangle(&mut self, dangle: f32) {
self.derivatives[1] = dangle;
}
}

fn eval(&mut self, physics_props: &SimplePhysicsProps, _t: f32) -> &mut PhysicsState<2> {
self.set_derivative_angle(self.delta_angle());
pub fn eval(
state: &mut PhysicsState<2, RigidPendulum>,
physics_props: &SimplePhysicsProps,
_anchor: Vec2,
_t: f32,
) {
state.setd_angle(state.getv_dangle());

let dd = {
let length_ratio = physics_props.gravity / physics_props.length;
let crit_damp = 2. * length_ratio.sqrt();
let dd = -length_ratio * self.angle().sin();
dd - self.delta_angle() * physics_props.angle_damping * crit_damp
let dd = -length_ratio * state.getv_angle().sin();
dd - state.getv_dangle() * physics_props.angle_damping * crit_damp
};
self.set_derivative_delta_angle(dd);

&mut self.physics_state
state.setd_dangle(dd);
}
}

impl Pendulum {
pub fn tick(&mut self, anchor: &Vec2, props: &SimplePhysicsProps, dt: f32) -> Vec2 {
// Compute the angle against the updated anchor position
let delta_bob = self.bob - *anchor;
self.set_angle(f32::atan2(-delta_bob.x, delta_bob.y));
pub mod spring_pendulum {
use crate::physics::runge_kutta::PhysicsState;
use crate::physics::SimplePhysicsProps;
use glam::{vec2, Vec2};
use std::f32::consts::PI;

#[derive(Debug, Clone)]
pub struct SpringPendulum;

impl PhysicsState<4, SpringPendulum> {
// bob

pub fn getv_bob(&self) -> Vec2 {
vec2(self.vars[0], self.vars[1])
}

pub fn setv_bob(&mut self, bob: Vec2) {
self.vars[0] = bob.x;
self.vars[1] = bob.y;
}

pub fn getd_bob(&self) -> Vec2 {
vec2(self.derivatives[0], self.derivatives[1])
}

pub fn setd_bob(&mut self, bob: Vec2) {
self.derivatives[0] = bob.x;
self.derivatives[1] = bob.y;
}

// dbob

pub fn getv_dbob(&self) -> Vec2 {
vec2(self.vars[2], self.vars[3])
}

pub fn setv_dbob(&mut self, dbob: Vec2) {
self.vars[2] = dbob.x;
self.vars[3] = dbob.y;
}

pub fn getd_dbob(&self) -> Vec2 {
vec2(self.derivatives[2], self.derivatives[3])
}

pub fn setd_dbob(&mut self, dbob: Vec2) {
self.derivatives[2] = dbob.x;
self.derivatives[3] = dbob.y;
}
}

pub fn eval(
state: &mut PhysicsState<4, SpringPendulum>,
props: &SimplePhysicsProps,
anchor: Vec2,
_t: f32,
) {
state.setd_bob(state.getv_dbob());

// These are normalized vs. mass
let spring_ksqrt = props.frequency * 2. * PI;
let spring_k = spring_ksqrt.powi(2);

let g = props.gravity;
let rest_length = props.length - g / spring_k;

let off_pos = state.getv_bob() - anchor;
let off_pos_norm = off_pos.normalize();

let length_ratio = props.gravity / props.length;
let crit_damp_angle = 2. * length_ratio.sqrt();
let crit_damp_length = 2. * spring_ksqrt;

let dist = anchor.distance(state.getv_bob()).abs();
let force = vec2(0., g) - (off_pos_norm * (dist - rest_length) * spring_k);

let d_bob = state.getv_dbob();
let d_bob_rot = vec2(
d_bob.x * off_pos_norm.y + d_bob.y * off_pos_norm.x,
d_bob.y * off_pos_norm.y - d_bob.x * off_pos_norm.x,
);

let dd_bob_rot = -vec2(
d_bob_rot.x * props.angle_damping * crit_damp_angle,
d_bob_rot.y * props.length_damping * crit_damp_length,
);

// Run the pendulum simulation in terms of angle
runge_kutta::tick(self, props, dt);
let dd_bob_damping = vec2(
dd_bob_rot.x * off_pos_norm.y - d_bob_rot.y * off_pos_norm.x,
dd_bob_rot.y * off_pos_norm.y + d_bob_rot.x * off_pos_norm.x,
);

// Update bob's position at the new angle
let angle = self.angle();
let delta_bob = Vec2::new(-angle.sin(), angle.cos());
self.bob = *anchor + delta_bob * props.length;
let dd_bob = force + dd_bob_damping;

self.bob
state.setd_dbob(dd_bob);
}
}
Loading

0 comments on commit 8bd5718

Please sign in to comment.