viewport_lib/plugins/constraint/
plugin.rs1use std::collections::HashMap;
4
5use crate::interaction::select::selection::NodeId;
6use crate::runtime::context::RuntimeStepContext;
7use crate::runtime::plugin::{RuntimePlugin, phase};
8
9#[derive(Debug, Clone)]
11pub enum Constraint {
12 SpringTarget {
17 node_id: NodeId,
19 target: glam::Vec3,
21 stiffness: f32,
23 damping: f32,
25 },
26 Dampen {
31 node_id: NodeId,
33 damping: f32,
35 },
36 ClampBounds {
40 node_id: NodeId,
42 min: glam::Vec3,
44 max: glam::Vec3,
46 },
47}
48
49impl Constraint {
50 fn node_id(&self) -> NodeId {
51 match self {
52 Constraint::SpringTarget { node_id, .. }
53 | Constraint::Dampen { node_id, .. }
54 | Constraint::ClampBounds { node_id, .. } => *node_id,
55 }
56 }
57}
58
59pub struct ConstraintPlugin {
64 constraints: Vec<Constraint>,
65 velocities: HashMap<NodeId, glam::Vec3>,
66}
67
68impl Default for ConstraintPlugin {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl ConstraintPlugin {
75 pub fn new() -> Self {
77 Self {
78 constraints: Vec::new(),
79 velocities: HashMap::new(),
80 }
81 }
82
83 pub fn add(mut self, constraint: Constraint) -> Self {
85 self.constraints.push(constraint);
86 self
87 }
88
89 pub fn push(&mut self, constraint: Constraint) {
91 self.constraints.push(constraint);
92 }
93}
94
95impl RuntimePlugin for ConstraintPlugin {
96 fn priority(&self) -> i32 {
97 phase::ANIMATE + 50
98 }
99
100 fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
101 for constraint in &self.constraints {
102 let id = constraint.node_id();
103 let Some(node) = ctx.scene.node(id) else {
104 continue;
105 };
106 let world = node.world_transform();
107 let (scale, rotation, pos) = world.to_scale_rotation_translation();
108 let vel = self.velocities.entry(id).or_insert(glam::Vec3::ZERO);
109
110 let new_pos = match constraint {
111 Constraint::SpringTarget {
112 target,
113 stiffness,
114 damping,
115 ..
116 } => {
117 let spring = (*target - pos) * *stiffness;
119 let damp = *vel * *damping;
120 *vel += (spring - damp) * ctx.dt;
121 pos + *vel * ctx.dt
122 }
123 Constraint::Dampen { damping, .. } => {
124 *vel *= (1.0 - damping * ctx.dt).max(0.0);
125 pos + *vel * ctx.dt
126 }
127 Constraint::ClampBounds { min, max, .. } => {
128 let clamped = pos.clamp(*min, *max);
129 if clamped != pos {
130 *vel = glam::Vec3::ZERO;
131 }
132 clamped
133 }
134 };
135
136 let new_t = glam::Affine3A::from_scale_rotation_translation(scale, rotation, new_pos);
137 ctx.writeback.set(id, new_t);
138 }
139 }
140}