Skip to main content

dynamis_world/
soft.rs

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}