1use crate::circuit::{Circuit, OpKind};
6use crate::emit::{emit_relu, emit_select};
7use crate::wire::{Lin, Term, Wire};
8
9fn has_finite_range(circuit: &Circuit, wire: Wire) -> bool {
10 let range = circuit.range(wire);
11 (range.min as f32).is_finite() && (range.max as f32).is_finite()
12}
13
14fn emit_absolute(circuit: &mut Circuit, value: Wire) -> Wire {
15 let positive = emit_relu(circuit, value);
16 let negated = circuit.affine(-value);
17 let negative = emit_relu(circuit, negated);
18 circuit.affine(positive + negative)
19}
20
21fn emit_clamp_zero_one(circuit: &mut Circuit, value: Wire, unit: Wire) -> Wire {
22 let positive = emit_relu(circuit, value);
23 let over = circuit.affine(value - unit);
24 let above_one = emit_relu(circuit, over);
25 let result = circuit.affine(positive - above_one);
26 circuit.assume_range(result, 0.0, 1.0);
27 result
28}
29
30#[derive(Copy, Clone, Debug)]
32pub struct SpringState {
33 pub position: Wire,
34 pub velocity: Wire,
35 pub active: Wire,
38}
39
40#[allow(clippy::too_many_arguments)]
46pub fn emit_spring(
47 circuit: &mut Circuit,
48 position_source: Wire,
49 destination: Wire,
50 velocity_source: Wire,
51 stiffness: Wire,
52 damping: Wire,
53 timestep: Wire,
54 settle_distance: Wire,
55 settle_speed: Wire,
56 settle_distance_scale: Wire,
57 settle_speed_scale: Wire,
58 unit: Wire,
59) -> SpringState {
60 let (mut position, mut velocity) = (position_source, velocity_source);
61 for _ in 0..4 {
62 let displacement = circuit.affine(position - destination);
63 let spring_force = circuit.multiply(stiffness, displacement);
64 let damping_force = circuit.multiply(damping, velocity);
65 let acceleration = circuit.affine(-Lin::from(spring_force) - damping_force);
66 let velocity_change = circuit.multiply(acceleration, timestep);
67 velocity = circuit.affine(velocity + velocity_change);
68 let position_change = circuit.multiply(velocity, timestep);
69 position = circuit.affine(position + position_change);
70 }
71 let offset = circuit.affine(position - destination);
72 let distance = emit_absolute(circuit, offset);
73 let distance_over = circuit.affine(distance - settle_distance);
74 let distance_above = emit_relu(circuit, distance_over);
75 let speed = emit_absolute(circuit, velocity);
76 let speed_over = circuit.affine(speed - settle_speed);
77 let speed_above = emit_relu(circuit, speed_over);
78 let scaled_distance = circuit.multiply(distance_above, settle_distance_scale);
79 let scaled_speed = circuit.multiply(speed_above, settle_speed_scale);
80 let combined = circuit.affine(scaled_distance + scaled_speed);
81 let active = emit_clamp_zero_one(circuit, combined, unit);
82 SpringState {
83 position: emit_select(circuit, active, destination, position),
84 velocity: circuit.multiply(active, velocity),
85 active,
86 }
87}
88
89pub type SpringMatrixWires = [Wire; 4];
93
94pub fn emit_apply_spring_matrix(
100 circuit: &mut Circuit,
101 matrix: SpringMatrixWires,
102 displacement: Wire,
103 velocity: Wire,
104) -> (Wire, Wire) {
105 for entry in matrix {
106 assert!(
107 has_finite_range(circuit, entry),
108 "spring matrix wires need finite declared ranges, e.g. ±spring_matrix_bound(constants)"
109 );
110 }
111 let products = [
112 circuit.multiply(matrix[0], displacement),
113 circuit.multiply(matrix[1], velocity),
114 circuit.multiply(matrix[2], displacement),
115 circuit.multiply(matrix[3], velocity),
116 ];
117 let next_displacement = circuit.round_affine(products[0] + products[1]);
118 let next_velocity = circuit.round_affine(products[2] + products[3]);
119 (next_displacement, next_velocity)
120}
121
122#[derive(Copy, Clone, Debug)]
124pub struct AnimatedSpring {
125 pub matrix: SpringMatrixWires,
126 pub displacement: Wire,
128 pub velocity: Wire,
129 pub destination: Wire,
131 pub destination_during_steps: Option<Wire>,
135}
136
137#[derive(Copy, Clone, Debug)]
138pub struct AnimatedObjectOptions {
139 pub settle_check: Wire,
143 pub snap: Option<Wire>,
146 pub settle_distance: f64,
148 pub settle_speed: f64,
149}
150
151impl AnimatedObjectOptions {
152 pub fn new(settle_check: Wire) -> AnimatedObjectOptions {
153 AnimatedObjectOptions { settle_check, snap: None, settle_distance: 0.01, settle_speed: 0.01 }
154 }
155}
156
157#[derive(Clone, Debug)]
158pub struct AnimatedObject {
159 pub displacements: Vec<Wire>,
162 pub velocities: Vec<Wire>,
163 pub positions: Vec<Wire>,
165 pub settled: Wire,
168 pub active: Wire,
171}
172
173pub fn emit_animated_object(
184 circuit: &mut Circuit,
185 springs: &[AnimatedSpring],
186 options: AnimatedObjectOptions,
187) -> AnimatedObject {
188 assert!(!springs.is_empty(), "an animated object needs at least one spring");
189 let settle_distance = options.settle_distance as f32 as f64;
190 let settle_speed = options.settle_speed as f32 as f64;
191 for threshold in [settle_distance, settle_speed] {
192 assert!(
193 threshold.is_finite() && threshold >= 2f64.powi(-22),
194 "settle thresholds must be finite and at least 2^-22, got {threshold}"
195 );
196 }
197 for bit in std::iter::once(options.settle_check).chain(options.snap) {
198 let range = circuit.range(bit);
199 assert!(range.min >= 0.0 && range.max <= 1.0, "settle_check and snap must be declared within 0..1");
200 }
201 let mut exceedance_terms: Vec<Term> = Vec::new();
202 let mut rest_terms: Vec<Term> = Vec::new();
203 let mut stepped = Vec::with_capacity(springs.len());
204 for spring in springs {
205 let (next_displacement, next_velocity) =
206 emit_apply_spring_matrix(circuit, spring.matrix, spring.displacement, spring.velocity);
207 let displacement = match spring.destination_during_steps {
209 Some(during) if during != spring.destination => circuit
210 .round_affine(Lin::new(vec![(during, 1.0), (spring.destination, -1.0), (next_displacement, 1.0)], 0.0)),
211 _ => next_displacement,
212 };
213 for (value, threshold) in [(displacement, settle_distance), (next_velocity, settle_speed)] {
214 let positive_part = circuit.reglu(1.0, value);
215 let negative_part = circuit.reglu(1.0, -value);
216 exceedance_terms.push((circuit.reglu(1.0, positive_part + negative_part - threshold), 1.0));
218 rest_terms.push((positive_part, 2f64.powi(127)));
219 rest_terms.push((negative_part, 2f64.powi(127)));
220 }
221 stepped.push((displacement, next_velocity));
222 }
223 let exceedance = circuit.round_affine(Lin::new(exceedance_terms, 0.0));
224 let lifted_rest = circuit.append(OpKind::Reglu, Lin::constant(1.0), Lin::new(rest_terms, 0.0), true, None);
227 let at_rest = circuit.reglu(1.0, Lin::new(vec![(lifted_rest, -(2f64.powi(22)))], 1.0));
228 let score_terms = vec![(options.settle_check, 0.5), (at_rest, 0.5), (exceedance, -(2f64.powi(20)))];
230 let settled_score = circuit.append(OpKind::Reglu, Lin::constant(1.0), Lin::new(score_terms, 0.0), true, None);
231 circuit.mark_rounding_point(settled_score);
232 let settled = circuit.boolean(settled_score);
233 let not_settled = circuit.round_affine(1.0 - settled);
234 let active = match options.snap {
235 None => not_settled,
236 Some(snap) => {
237 let not_snapped = circuit.round_affine(1.0 - snap);
238 circuit.multiply(not_settled, not_snapped)
239 }
240 };
241 let displacements: Vec<Wire> =
242 stepped.iter().map(|&(displacement, _)| circuit.multiply(displacement, active)).collect();
243 let velocities = stepped.iter().map(|&(_, velocity)| circuit.multiply(velocity, active)).collect();
244 let positions = springs
245 .iter()
246 .zip(&displacements)
247 .map(|(spring, &displacement)| circuit.round_affine(spring.destination + displacement))
248 .collect();
249 AnimatedObject { displacements, velocities, positions, settled, active }
250}