use std::collections::HashMap;
use crate::interaction::select::selection::NodeId;
use crate::runtime::context::RuntimeStepContext;
use crate::runtime::plugin::{RuntimePlugin, phase};
#[derive(Debug, Clone)]
pub enum Constraint {
SpringTarget {
node_id: NodeId,
target: glam::Vec3,
stiffness: f32,
damping: f32,
},
Dampen {
node_id: NodeId,
damping: f32,
},
ClampBounds {
node_id: NodeId,
min: glam::Vec3,
max: glam::Vec3,
},
}
impl Constraint {
fn node_id(&self) -> NodeId {
match self {
Constraint::SpringTarget { node_id, .. }
| Constraint::Dampen { node_id, .. }
| Constraint::ClampBounds { node_id, .. } => *node_id,
}
}
}
pub struct ConstraintPlugin {
constraints: Vec<Constraint>,
velocities: HashMap<NodeId, glam::Vec3>,
}
impl Default for ConstraintPlugin {
fn default() -> Self {
Self::new()
}
}
impl ConstraintPlugin {
pub fn new() -> Self {
Self {
constraints: Vec::new(),
velocities: HashMap::new(),
}
}
pub fn add(mut self, constraint: Constraint) -> Self {
self.constraints.push(constraint);
self
}
pub fn push(&mut self, constraint: Constraint) {
self.constraints.push(constraint);
}
}
impl RuntimePlugin for ConstraintPlugin {
fn priority(&self) -> i32 {
phase::ANIMATE + 50
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
for constraint in &self.constraints {
let id = constraint.node_id();
let Some(node) = ctx.scene.node(id) else {
continue;
};
let world = node.world_transform();
let (scale, rotation, pos) = world.to_scale_rotation_translation();
let vel = self.velocities.entry(id).or_insert(glam::Vec3::ZERO);
let new_pos = match constraint {
Constraint::SpringTarget {
target,
stiffness,
damping,
..
} => {
let spring = (*target - pos) * *stiffness;
let damp = *vel * *damping;
*vel += (spring - damp) * ctx.dt;
pos + *vel * ctx.dt
}
Constraint::Dampen { damping, .. } => {
*vel *= (1.0 - damping * ctx.dt).max(0.0);
pos + *vel * ctx.dt
}
Constraint::ClampBounds { min, max, .. } => {
let clamped = pos.clamp(*min, *max);
if clamped != pos {
*vel = glam::Vec3::ZERO;
}
clamped
}
};
let new_t = glam::Affine3A::from_scale_rotation_translation(scale, rotation, new_pos);
ctx.writeback.set(id, new_t);
}
}
}