1use crate::error::SampleError;
7use crate::physics::Solver;
8
9use crate::physics::bound::Bound::*;
10use crate::physics::bound::all;
11use crate::physics::hammer::Hammer;
12
13#[derive(Clone, Debug, PartialEq)]
14pub struct RhaoutiChaigneJolyParams {
15 pub f0: f64,
16 pub aspect_ratio: f64,
17 pub strike_x: f64,
18 pub strike_y: f64,
19 pub vel: f64,
20 pub hammer_mass: f64,
21 pub hammer_k: f64,
22 pub hammer_p: f64,
23 pub damp_dc: f64,
24 pub damp_freq: f64,
25}
26
27impl RhaoutiChaigneJolyParams {
28 pub fn at(f0: f64) -> RhaoutiChaigneJolyParams {
30 RhaoutiChaigneJolyParams {
31 f0,
32 aspect_ratio: 1.0,
33 strike_x: 0.3,
34 strike_y: 0.4,
35 vel: 3.2,
36 hammer_mass: 2.9e-3,
37 hammer_k: 2.6646e8,
38 hammer_p: 2.5,
39 damp_dc: 0.6,
40 damp_freq: 1.6e-4,
41 }
42 }
43}
44
45impl RhaoutiChaigneJolyParams {
46 pub fn valid(&self) -> bool {
47 all(&[
48 (self.f0, Positive),
49 (self.aspect_ratio, Positive),
50 (self.strike_x, OpenUnit),
51 (self.strike_y, OpenUnit),
52 (self.vel, Positive),
53 (self.hammer_mass, Positive),
54 (self.hammer_k, Positive),
55 (self.hammer_p, Positive),
56 (self.damp_dc, NonNegative),
57 (self.damp_freq, NonNegative),
58 ])
59 }
60}
61
62const MEMBRANE_TENSION_N_M: f64 = 3000.0;
64const MEMBRANE_AREAL_DENSITY_KG_M2: f64 = 0.3;
66#[derive(Clone)]
68struct MembraneGrid {
69 u_now: Vec<f64>,
70 u_prev: Vec<f64>,
71 u_next: Vec<f64>,
72 nx: usize,
73 ny: usize,
74 h: f64,
75 sigma: f64,
76 courant_sq: f64,
78 damp_a: f64,
79 damp_b: f64,
80}
81
82#[inline]
83fn node_index(nx: usize, ix: usize, iy: usize) -> usize {
84 iy * (nx + 1) + ix
85}
86
87const MALLET_WINDOW_QUARTIC_COEFF_M4: f64 = 1.0e7;
89const MALLET_WINDOW_WEIGHT_FLOOR: f64 = 1e-8;
91
92#[derive(Clone)]
94struct ContactPatch {
95 nodes: Vec<usize>,
96 weights: Vec<f64>,
97}
98
99fn build_contact_patch(
100 nx: usize,
101 ny: usize,
102 contact_ix: usize,
103 contact_iy: usize,
104 h: f64,
105) -> ContactPatch {
106 let cutoff_m = libm::pow(
107 libm::log(MALLET_WINDOW_WEIGHT_FLOOR).abs() / MALLET_WINDOW_QUARTIC_COEFF_M4,
108 0.25,
109 );
110 let radius_cells = (cutoff_m / h).ceil() as isize;
111
112 let mut nodes = Vec::new();
113 let mut weights = Vec::new();
114 let mut total = 0.0;
115 for dj in -radius_cells..=radius_cells {
116 for di in -radius_cells..=radius_cells {
117 let (ix, iy) = (contact_ix as isize + di, contact_iy as isize + dj);
118 if ix < 1 || ix > nx as isize - 1 || iy < 1 || iy > ny as isize - 1 {
119 continue;
120 }
121 let (dx, dy) = (di as f64 * h, dj as f64 * h);
122 let w = sva_formula::exp(-MALLET_WINDOW_QUARTIC_COEFF_M4 * (dx.powi(4) + dy.powi(4)));
123 if w < MALLET_WINDOW_WEIGHT_FLOOR {
124 continue;
125 }
126 nodes.push(node_index(nx, ix as usize, iy as usize));
127 weights.push(w);
128 total += w;
129 }
130 }
131 for w in &mut weights {
132 *w /= total;
133 }
134 ContactPatch { nodes, weights }
135}
136
137const LAMBDA_TARGET: f64 = 0.9 * std::f64::consts::FRAC_1_SQRT_2;
139
140pub const NODE_COUNT_CEILING: usize = 4_000_000;
142
143pub fn node_count(params: &RhaoutiChaigneJolyParams, sr: f64) -> f64 {
145 let (lx, ly) = sides(params);
146 let h = step(MEMBRANE_TENSION_N_M, MEMBRANE_AREAL_DENSITY_KG_M2, sr);
147 (axis_nodes(lx, h) + 1.0) * (axis_nodes(ly, h) + 1.0)
148}
149
150fn step(tension: f64, sigma: f64, sr: f64) -> f64 {
151 (tension / sigma).sqrt() * (1.0 / sr) / LAMBDA_TARGET
152}
153
154fn axis_nodes(span: f64, h: f64) -> f64 {
155 (span / h).round().max(4.0)
156}
157
158fn membrane_grid(
159 sigma: f64,
160 tension: f64,
161 lx: f64,
162 ly: f64,
163 damp_dc: f64,
164 damp_freq: f64,
165 sr: f64,
166) -> MembraneGrid {
167 let dt = 1.0 / sr;
168 let h = step(tension, sigma, sr);
169 let nx = axis_nodes(lx, h) as usize;
170 let ny = axis_nodes(ly, h) as usize;
171 let courant_sq = LAMBDA_TARGET * LAMBDA_TARGET;
172
173 let count = (nx + 1) * (ny + 1);
174 MembraneGrid {
175 u_now: vec![0.0; count],
176 u_prev: vec![0.0; count],
177 u_next: vec![0.0; count],
178 nx,
179 ny,
180 h,
181 sigma,
182 courant_sq,
183 damp_a: 2.0 * damp_dc * dt,
184 damp_b: 2.0 * damp_freq * dt / (h * h),
185 }
186}
187
188fn sides(params: &RhaoutiChaigneJolyParams) -> (f64, f64) {
191 let c = (MEMBRANE_TENSION_N_M / MEMBRANE_AREAL_DENSITY_KG_M2).sqrt();
192 let l0 = c / (params.f0 * std::f64::consts::SQRT_2);
193 (
194 l0 * params.aspect_ratio.sqrt(),
195 l0 / params.aspect_ratio.sqrt(),
196 )
197}
198
199fn build_grid(params: &RhaoutiChaigneJolyParams, sr: f64) -> MembraneGrid {
200 let (lx, ly) = sides(params);
201 membrane_grid(
202 MEMBRANE_AREAL_DENSITY_KG_M2,
203 MEMBRANE_TENSION_N_M,
204 lx,
205 ly,
206 params.damp_dc,
207 params.damp_freq,
208 sr,
209 )
210}
211
212#[derive(Clone)]
213pub struct RhaoutiChaigneJolySite {
214 grid: MembraneGrid,
215 hammer: Hammer,
216 detached: bool,
217 contact_patch: ContactPatch,
218 pickup_ix: usize,
219 pickup_iy: usize,
220 dt: f64,
221}
222
223impl RhaoutiChaigneJolySite {
224 pub fn new(params: &RhaoutiChaigneJolyParams, sr: f64) -> RhaoutiChaigneJolySite {
225 let grid = build_grid(params, sr);
226 let contact_ix = (params.strike_x * grid.nx as f64)
227 .round()
228 .clamp(1.0, (grid.nx - 1) as f64) as usize;
229 let contact_iy = (params.strike_y * grid.ny as f64)
230 .round()
231 .clamp(1.0, (grid.ny - 1) as f64) as usize;
232 let pickup_ix = (0.8 * grid.nx as f64)
234 .round()
235 .clamp(1.0, (grid.nx - 1) as f64) as usize;
236 let pickup_iy = (0.2 * grid.ny as f64)
237 .round()
238 .clamp(1.0, (grid.ny - 1) as f64) as usize;
239 let contact_patch = build_contact_patch(grid.nx, grid.ny, contact_ix, contact_iy, grid.h);
240 let dt = 1.0 / sr;
241 RhaoutiChaigneJolySite {
242 grid,
243 hammer: Hammer::new(
244 params.hammer_mass,
245 params.hammer_k,
246 params.hammer_p,
247 params.vel,
248 dt,
249 ),
250 detached: false,
251 contact_patch,
252 pickup_ix,
253 pickup_iy,
254 dt,
255 }
256 }
257}
258
259impl RhaoutiChaigneJolySite {
260 fn advance(&mut self) -> f64 {
261 let grid = &mut self.grid;
262 let (nx, ny) = (grid.nx, grid.ny);
263 let u_h = if self.detached {
264 0.0
265 } else {
266 self.contact_patch
267 .nodes
268 .iter()
269 .zip(&self.contact_patch.weights)
270 .map(|(&n, &w)| w * grid.u_now[n])
271 .sum::<f64>()
272 };
273
274 let mut forces = [0.0f64; 1];
275 self.hammer.substeps(
276 self.dt,
277 &[u_h],
278 std::slice::from_mut(&mut self.detached),
279 &mut forces,
280 );
281 let force = forces[0];
282
283 for iy in 1..ny {
284 for ix in 1..nx {
285 let c = node_index(nx, ix, iy);
286 let lap_now = grid.u_now[node_index(nx, ix + 1, iy)]
287 + grid.u_now[node_index(nx, ix - 1, iy)]
288 + grid.u_now[node_index(nx, ix, iy + 1)]
289 + grid.u_now[node_index(nx, ix, iy - 1)]
290 - 4.0 * grid.u_now[c];
291 let lap_prev = grid.u_prev[node_index(nx, ix + 1, iy)]
292 + grid.u_prev[node_index(nx, ix - 1, iy)]
293 + grid.u_prev[node_index(nx, ix, iy + 1)]
294 + grid.u_prev[node_index(nx, ix, iy - 1)]
295 - 4.0 * grid.u_prev[c];
296 grid.u_next[c] = 2.0 * grid.u_now[c] - grid.u_prev[c] + grid.courant_sq * lap_now
297 - grid.damp_a * (grid.u_now[c] - grid.u_prev[c])
298 + grid.damp_b * (lap_now - lap_prev);
299 }
300 }
301 if force != 0.0 {
302 let injection = (self.dt * self.dt / (grid.sigma * grid.h * grid.h)) * force;
303 for (&node, &w) in self
304 .contact_patch
305 .nodes
306 .iter()
307 .zip(&self.contact_patch.weights)
308 {
309 grid.u_next[node] += injection * w;
310 }
311 }
312 for ix in 0..=nx {
313 grid.u_next[node_index(nx, ix, 0)] = 0.0;
314 grid.u_next[node_index(nx, ix, ny)] = 0.0;
315 }
316 for iy in 0..=ny {
317 grid.u_next[node_index(nx, 0, iy)] = 0.0;
318 grid.u_next[node_index(nx, nx, iy)] = 0.0;
319 }
320
321 let sample = grid.u_now[node_index(nx, self.pickup_ix, self.pickup_iy)];
322
323 std::mem::swap(&mut grid.u_prev, &mut grid.u_now);
324 std::mem::swap(&mut grid.u_now, &mut grid.u_next);
325
326 sample
327 }
328}
329
330impl Solver for RhaoutiChaigneJolySite {
331 fn bytes(&self) -> usize {
332 let (grid, patch) = (&self.grid, &self.contact_patch);
333 size_of::<Self>()
334 + super::floats(&grid.u_now)
335 + super::floats(&grid.u_prev)
336 + super::floats(&grid.u_next)
337 + std::mem::size_of_val(patch.nodes.as_slice())
338 + super::floats(&patch.weights)
339 }
340
341 fn step(&mut self, _args: &[f64]) -> Result<f64, SampleError> {
342 Ok(self.advance())
343 }
344}