1use super::World;
2use super::arena::{Arena, Run};
3use super::ids::IdSpace;
4use dynamis_abi::{
5 ELEMENT_PARTICLES, ELEMENT_ROLE_BITS, NO_SLOT, SoftAttachmentInit, SoftAttachmentRecord,
6 SoftBodyRecord, SoftElementInit, SoftElementRecord, SoftParticleInit, SoftParticleRecord,
7};
8use dynamis_model::math::{add, quat_rotate};
9use dynamis_model::{BodyHandle, SoftBodyDesc, SoftBodyHandle, SoftElement, SoftElementState};
10use dynamis_soft::SoftStreams;
11
12#[derive(Clone, Copy)]
13pub(crate) struct SoftRuns {
14 particles: Run,
15 elements: Run,
16 attachments: Run,
17 adjacency: Run,
18 strength: bool,
19}
20
21impl SoftRuns {
22 const EMPTY: Self = Self {
23 particles: Run::EMPTY,
24 elements: Run::EMPTY,
25 attachments: Run::EMPTY,
26 adjacency: Run::EMPTY,
27 strength: false,
28 };
29}
30
31#[derive(Clone)]
32pub(crate) struct SoftBodies {
33 alive: Vec<SoftBodyHandle>,
34 ids: IdSpace,
35 index_of: Vec<u32>,
36 runs: Vec<SoftRuns>,
37 states: Vec<SoftBodyRecord>,
38 dirty_states: Vec<u32>,
39 particles: Vec<SoftParticleRecord>,
40 elements: Vec<SoftElementRecord>,
41 attachments: Vec<SoftAttachmentRecord>,
42 attachment_refs: Vec<u32>,
43 adjacency: Vec<u32>,
44 particle_arena: Arena,
45 element_arena: Arena,
46 attachment_arena: Arena,
47 adjacency_arena: Arena,
48 pending_particles: Vec<Run>,
49 pending_elements: Vec<Run>,
50 pending_attachments: Vec<Run>,
51 pending_adjacency: Vec<Run>,
52 pub(crate) uploaded: bool,
53}
54
55impl SoftBodies {
56 pub(crate) const fn new() -> Self {
57 Self {
58 alive: Vec::new(),
59 ids: IdSpace::new(),
60 index_of: Vec::new(),
61 runs: Vec::new(),
62 states: Vec::new(),
63 dirty_states: Vec::new(),
64 particles: Vec::new(),
65 elements: Vec::new(),
66 attachments: Vec::new(),
67 attachment_refs: Vec::new(),
68 adjacency: Vec::new(),
69 particle_arena: Arena::new(),
70 element_arena: Arena::new(),
71 attachment_arena: Arena::new(),
72 adjacency_arena: Arena::new(),
73 pending_particles: Vec::new(),
74 pending_elements: Vec::new(),
75 pending_attachments: Vec::new(),
76 pending_adjacency: Vec::new(),
77 uploaded: false,
78 }
79 }
80
81 pub(crate) fn count(&self) -> usize {
82 self.alive.len()
83 }
84
85 pub(crate) fn ids_len(&self) -> usize {
86 self.ids.len()
87 }
88
89 pub(crate) fn carries_strength(&self) -> bool {
90 self.alive
91 .iter()
92 .any(|handle| self.runs[handle.id as usize].strength)
93 }
94
95 pub(crate) fn bodies(&self) -> &[SoftBodyHandle] {
96 &self.alive
97 }
98
99 pub(crate) fn used(&self) -> (u32, u32, u32, u32) {
100 (
101 self.particles.len() as u32,
102 self.elements.len() as u32,
103 self.attachments.len() as u32,
104 self.adjacency.len() as u32,
105 )
106 }
107
108 pub(crate) fn attachment_refs(&self, id: u32) -> u32 {
109 self.attachment_refs.get(id as usize).copied().unwrap_or(0)
110 }
111
112 pub(crate) fn run_of(&self, handle: SoftBodyHandle) -> Run {
113 self.runs_of(handle).particles
114 }
115
116 pub(crate) fn wake_all(&mut self) {
117 let states = &mut self.states;
118 let dirty = &mut self.dirty_states;
119 for handle in &self.alive {
120 states[handle.id as usize] = SoftBodyRecord::awake();
121 dirty.push(handle.id);
122 }
123 }
124
125 pub(crate) fn runs_of(&self, handle: SoftBodyHandle) -> SoftRuns {
126 self.validate(handle);
127 self.runs[handle.id as usize]
128 }
129
130 fn validate(&self, handle: SoftBodyHandle) {
131 let id = handle.id as usize;
132 if id >= self.ids.len() {
133 panic!("soft body handle {handle:?} is out of range");
134 }
135 if self.ids.generation(handle.id) != handle.generation {
136 panic!("soft body handle {handle:?} is stale");
137 }
138 if self.index_of[id] == u32::MAX {
139 panic!("soft body handle {handle:?} is not alive");
140 }
141 }
142
143 fn grow_to(&mut self, id: u32) {
144 if self.runs.len() > id as usize {
145 return;
146 }
147 self.index_of.resize(id as usize + 1, u32::MAX);
148 self.runs.resize(id as usize + 1, SoftRuns::EMPTY);
149 }
150
151 fn take_element_run(&mut self, elements: usize) -> Run {
152 if elements == 0 {
153 return Run::EMPTY;
154 }
155 self.element_arena.take(elements as u32)
156 }
157
158 fn take_adjacency_run(&mut self, entries: usize) -> Run {
159 if entries == 0 {
160 return Run::EMPTY;
161 }
162 self.adjacency_arena.take(entries as u32)
163 }
164
165 fn take_attachment_run(&mut self, attachments: usize) -> Run {
166 if attachments == 0 {
167 return Run::EMPTY;
168 }
169 self.attachment_arena.take(attachments as u32)
170 }
171
172 fn hold_attachment(&mut self, body: BodyHandle) {
173 let id = body.id as usize;
174 if self.attachment_refs.len() <= id {
175 self.attachment_refs.resize(id + 1, 0);
176 }
177 self.attachment_refs[id] += 1;
178 }
179
180 fn release_attachment(&mut self, body_id: u32) {
181 let refs = self
182 .attachment_refs
183 .get_mut(body_id as usize)
184 .filter(|refs| **refs > 0)
185 .unwrap_or_else(|| panic!("soft attachments must hold a live body id {body_id}"));
186 *refs -= 1;
187 }
188
189 pub(crate) fn spawn(&mut self, desc: &SoftBodyDesc) -> SoftBodyHandle {
190 let (id, generation) = self.ids.acquire();
191 self.grow_to(id);
192 self.states
193 .resize(self.ids.len(), SoftBodyRecord::cleared());
194 self.states[id as usize] = SoftBodyRecord::awake();
195 self.dirty_states.push(id);
196 let particles = self.particle_arena.take(desc.particles.len() as u32);
197 let elements = self.take_element_run(desc.elements.len());
198 let attachments = self.take_attachment_run(desc.attachments.len());
199 let adjacency = self.take_adjacency_run(desc.elements.len() * ELEMENT_PARTICLES as usize);
200 self.particles.resize(
201 self.particle_arena.used() as usize,
202 SoftParticleRecord::cleared(),
203 );
204 self.elements.resize(
205 self.element_arena.used() as usize,
206 SoftElementRecord::cleared(),
207 );
208 self.attachments.resize(
209 self.attachment_arena.used() as usize,
210 SoftAttachmentRecord::cleared(),
211 );
212 self.adjacency
213 .resize(self.adjacency_arena.used() as usize, u32::MAX);
214 let neighbours = assemble_adjacency(
215 &mut self.adjacency,
216 desc.particles.len(),
217 &desc.elements,
218 adjacency.offset,
219 elements.offset,
220 );
221 for (slot, local) in desc.particles.iter().enumerate() {
222 let position = add(desc.position, quat_rotate(desc.orientation, *local));
223 let (offset, count) = neighbours[slot];
224 self.particles[particles.offset as usize + slot] =
225 SoftParticleRecord::build(SoftParticleInit {
226 position,
227 prev_position: position,
228 velocity: desc.velocity,
229 radius: desc.radius,
230 inverse_mass: desc.inverse_masses[slot],
231 friction: desc.friction,
232 support: desc.fluid.map_or(0.0, |fluid| fluid.support()),
233 rest_spacing: desc.fluid.map_or(0.0, |fluid| fluid.spacing()),
234 neighbour_offset: offset,
235 neighbour_count: count,
236 owner: id,
237 generation,
238 });
239 }
240 for (slot, element) in desc.elements.iter().enumerate() {
241 self.elements[elements.offset as usize + slot] =
242 SoftElementRecord::build(SoftElementInit {
243 kind: element.kind() as u32,
244 particles: global_particles(element, particles.offset),
245 rest: element.rest(),
246 compliance: element.compliance_of(),
247 yield_strain: element.yield_strain_of(),
248 break_strain: element.break_strain_of(),
249 plastic_flow: element.plastic_flow_of(),
250 });
251 }
252 for (slot, attachment) in desc.attachments.iter().enumerate() {
253 self.hold_attachment(attachment.body());
254 self.attachments[attachments.offset as usize + slot] =
255 SoftAttachmentRecord::build(SoftAttachmentInit {
256 particle: particles.offset + attachment.particle(),
257 body_id: attachment.body().id,
258 generation: attachment.body().generation,
259 local: attachment.local(),
260 });
261 }
262 self.runs[id as usize] = SoftRuns {
263 particles,
264 elements,
265 attachments,
266 adjacency,
267 strength: desc.carries_strength(),
268 };
269 let handle = SoftBodyHandle { id, generation };
270 self.index_of[id as usize] = self.alive.len() as u32;
271 self.alive.push(handle);
272 self.pending_particles.push(particles);
273 self.pending_elements.push(elements);
274 self.pending_attachments.push(attachments);
275 self.pending_adjacency.push(adjacency);
276 handle
277 }
278
279 pub(crate) fn remove(&mut self, handle: SoftBodyHandle) {
280 self.validate(handle);
281 let id = handle.id as usize;
282 let runs = self.runs[id];
283 self.states[id] = SoftBodyRecord::cleared();
284 self.dirty_states.push(handle.id);
285 for index in runs.particles.span() {
286 self.particles[index] = SoftParticleRecord::cleared();
287 }
288 for index in runs.elements.span() {
289 self.elements[index] = SoftElementRecord::cleared();
290 }
291 for index in runs.attachments.span() {
292 let released = self.attachments[index];
293 self.release_attachment(released.body_id);
294 self.attachments[index] = SoftAttachmentRecord::cleared();
295 }
296 self.particle_arena.release(runs.particles);
297 if runs.elements.len > 0 {
298 self.element_arena.release(runs.elements);
299 }
300 if runs.attachments.len > 0 {
301 self.attachment_arena.release(runs.attachments);
302 }
303 if runs.adjacency.len > 0 {
304 self.adjacency_arena.release(runs.adjacency);
305 }
306 self.runs[id] = SoftRuns::EMPTY;
307 let slot = self.index_of[id] as usize;
308 self.alive.swap_remove(slot);
309 if slot < self.alive.len() {
310 self.index_of[self.alive[slot].id as usize] = slot as u32;
311 }
312 self.index_of[id] = u32::MAX;
313 self.ids.release(handle.id);
314 self.pending_particles.push(runs.particles);
315 if runs.elements.len > 0 {
316 self.pending_elements.push(runs.elements);
317 }
318 if runs.attachments.len > 0 {
319 self.pending_attachments.push(runs.attachments);
320 }
321 if runs.adjacency.len > 0 {
322 self.pending_adjacency.push(runs.adjacency);
323 }
324 }
325
326 pub(crate) fn upload(&mut self, queue: &wgpu::Queue, streams: &SoftStreams) {
327 if !self.dirty_states.is_empty() {
328 self.uploaded = true;
329 let stride = streams.bodies.stride();
330 for id in self.dirty_states.drain(..) {
331 streams.bodies.write_at(
332 queue,
333 u64::from(id) * stride,
334 bytemuck::bytes_of(&self.states[id as usize]),
335 );
336 }
337 }
338 let particle_stride = streams.particles.stride();
339 for run in self.pending_particles.drain(..) {
340 if run.len == 0 {
341 continue;
342 }
343 streams.particles.write_at(
344 queue,
345 run.offset as u64 * particle_stride,
346 bytemuck::cast_slice(&self.particles[run.span()]),
347 );
348 }
349 let element_stride = streams.elements.stride();
350 for run in self.pending_elements.drain(..) {
351 if run.len == 0 {
352 continue;
353 }
354 streams.elements.write_at(
355 queue,
356 run.offset as u64 * element_stride,
357 bytemuck::cast_slice(&self.elements[run.span()]),
358 );
359 }
360 let attachment_stride = streams.attachments.stride();
361 for run in self.pending_attachments.drain(..) {
362 if run.len == 0 {
363 continue;
364 }
365 streams.attachments.write_at(
366 queue,
367 run.offset as u64 * attachment_stride,
368 bytemuck::cast_slice(&self.attachments[run.span()]),
369 );
370 }
371 for run in self.pending_adjacency.drain(..) {
372 if run.len == 0 {
373 continue;
374 }
375 streams.adjacency.write_at(
376 queue,
377 run.offset as u64 * 4,
378 bytemuck::cast_slice(&self.adjacency[run.span()]),
379 );
380 }
381 }
382
383 pub(crate) fn observe(&mut self, run: Run, records: &[SoftParticleRecord]) {
384 for (slot, record) in records.iter().enumerate() {
385 self.particles[run.offset as usize + slot] = *record;
386 }
387 }
388
389 pub(crate) fn observe_elements(&mut self, run: Run, records: &[SoftElementRecord]) {
390 for (slot, record) in records.iter().enumerate() {
391 self.elements[run.offset as usize + slot] = *record;
392 }
393 }
394}
395
396fn global_particles(element: &SoftElement, base: u32) -> [u32; ELEMENT_PARTICLES as usize] {
397 element.particles().map(|particle| {
398 if particle == SoftElement::UNUSED {
399 NO_SLOT
400 } else {
401 base + particle
402 }
403 })
404}
405
406fn assemble_adjacency(
407 adjacency: &mut [u32],
408 particles: usize,
409 elements: &[SoftElement],
410 adjacency_base: u32,
411 element_base: u32,
412) -> Vec<(u32, u32)> {
413 let mut counts = vec![0u32; particles];
414 for element in elements {
415 for particle in element.participants() {
416 counts[particle as usize] += 1;
417 }
418 }
419 let mut ranges = Vec::with_capacity(particles);
420 let mut cursor = Vec::with_capacity(particles);
421 let mut offset = adjacency_base;
422 for count in counts {
423 ranges.push((offset, count));
424 cursor.push(offset);
425 offset += count;
426 }
427 for (slot, element) in elements.iter().enumerate() {
428 for (role, particle) in element.participants().enumerate() {
429 let entry = ((element_base + slot as u32) << ELEMENT_ROLE_BITS) | role as u32;
430 adjacency[cursor[particle as usize] as usize] = entry;
431 cursor[particle as usize] += 1;
432 }
433 }
434 ranges
435}
436
437impl World {
438 pub fn add_soft_body(&mut self, desc: SoftBodyDesc) -> SoftBodyHandle {
439 desc.assert_attachments();
440 for attachment in &desc.attachments {
441 self.validate(attachment.body());
442 }
443 self.soft.spawn(&desc)
444 }
445
446 pub fn remove_soft_body(&mut self, handle: SoftBodyHandle) {
447 self.soft.remove(handle);
448 }
449
450 pub fn soft_bodies(&self) -> &[SoftBodyHandle] {
451 self.soft.bodies()
452 }
453
454 pub fn soft_body_count(&self) -> usize {
455 self.soft.count()
456 }
457
458 pub fn soft_body_positions(&mut self, handle: SoftBodyHandle) -> Vec<[f32; 3]> {
459 self.backend.gpu.assert_alive();
460 self.collect_readbacks();
461 let live = self.live();
462 self.apply_plan(&live);
463 self.flush_rows();
464 let run = self.soft.run_of(handle);
465 let stride = self.backend.streams.soft.particles.stride();
466 let bytes = run.len as u64 * stride;
467 let buffer = self.backend.streams.soft.particles.buffer().clone();
468 let raw = self.read_regions(
469 "soft body particles",
470 &[(&buffer, run.offset as u64 * stride, bytes)],
471 );
472 let records = dynamis_abi::decode::<SoftParticleRecord>(&raw);
473 assert!(
474 records
475 .iter()
476 .all(|record| record.owner == handle.id && record.generation == handle.generation),
477 "a soft body readback must return only its own particles"
478 );
479 let positions = records
480 .iter()
481 .map(|record| [record.position[0], record.position[1], record.position[2]])
482 .collect();
483 self.soft.observe(run, &records);
484 positions
485 }
486
487 pub fn soft_body_elements(&mut self, handle: SoftBodyHandle) -> Vec<SoftElementState> {
488 self.backend.gpu.assert_alive();
489 self.collect_readbacks();
490 let live = self.live();
491 self.apply_plan(&live);
492 self.flush_rows();
493 let runs = self.soft.runs_of(handle);
494 if runs.elements.len == 0 {
495 return Vec::new();
496 }
497 let stride = self.backend.streams.soft.elements.stride();
498 let bytes = runs.elements.len as u64 * stride;
499 let buffer = self.backend.streams.soft.elements.buffer().clone();
500 let raw = self.read_regions(
501 "soft body elements",
502 &[(&buffer, runs.elements.offset as u64 * stride, bytes)],
503 );
504 let records = dynamis_abi::decode::<SoftElementRecord>(&raw);
505 let states = records
506 .iter()
507 .map(SoftElementRecord::state)
508 .collect::<Vec<_>>();
509 assert!(
510 states
511 .iter()
512 .flat_map(|state| state.participants())
513 .all(|particle| runs.particles.span().contains(&(particle as usize))),
514 "a soft body readback must return only its own elements"
515 );
516 self.soft.observe_elements(runs.elements, &records);
517 states
518 }
519}