Skip to main content

blast_stress_solver/rapier/
split_migrator.rs

1use std::collections::{HashMap, HashSet};
2
3use rapier3d::prelude::RigidBodyHandle;
4
5use crate::types::SplitChild;
6
7/// Body state before a split.
8pub struct ExistingBodyState {
9    pub handle: RigidBodyHandle,
10    pub node_indices: HashSet<u32>,
11    pub is_fixed: bool,
12}
13
14/// Plan for how to handle split children: reuse existing bodies or create new ones.
15pub struct SplitMigrationPlan {
16    /// Children that can reuse an existing body (same node set).
17    pub reuse: Vec<ReuseEntry>,
18    /// Children that need a new body created.
19    pub create: Vec<CreateEntry>,
20}
21
22pub struct ReuseEntry {
23    pub child_index: usize,
24    pub body_handle: RigidBodyHandle,
25}
26
27pub struct CreateEntry {
28    pub child_index: usize,
29}
30
31#[derive(Clone, Copy, Debug, Default)]
32pub struct PlannerChildSupport {
33    pub is_support: bool,
34}
35
36/// Plan body reuse vs. creation for split children.
37///
38/// The planner optimizes for minimal edit distance on the Rapier side:
39/// preserve exact matches first, then maximize node overlap with existing bodies.
40/// Support status is handled during reconciliation by changing the body type
41/// in-place if needed, so fixed bodies may be reused for dynamic children.
42pub fn plan_split_migration(
43    bodies: &[ExistingBodyState],
44    children: &[SplitChild],
45) -> SplitMigrationPlan {
46    plan_split_migration_with_support(
47        bodies,
48        children,
49        &vec![PlannerChildSupport::default(); children.len()],
50    )
51}
52
53pub fn plan_split_migration_with_support(
54    bodies: &[ExistingBodyState],
55    children: &[SplitChild],
56    _child_support: &[PlannerChildSupport],
57) -> SplitMigrationPlan {
58    if bodies.is_empty() || children.is_empty() {
59        return SplitMigrationPlan {
60            reuse: Vec::new(),
61            create: (0..children.len())
62                .map(|i| CreateEntry { child_index: i })
63                .collect(),
64        };
65    }
66
67    // Build hash index of existing bodies
68    let mut body_hashes: HashMap<u64, Vec<usize>> = HashMap::new();
69    for (i, body) in bodies.iter().enumerate() {
70        let hash = hash_node_set(&body.node_indices);
71        body_hashes.entry(hash).or_default().push(i);
72    }
73
74    let mut reuse = Vec::new();
75    let mut assigned_bodies = HashSet::new();
76    let mut unmatched_children = Vec::new();
77
78    for (ci, child) in children.iter().enumerate() {
79        let child_set: HashSet<u32> = child.nodes.iter().copied().collect();
80        let hash = hash_node_set(&child_set);
81
82        let mut matched = false;
83        if let Some(candidates) = body_hashes.get(&hash) {
84            for &bi in candidates {
85                if assigned_bodies.contains(&bi) {
86                    continue;
87                }
88                if bodies[bi].node_indices == child_set {
89                    reuse.push(ReuseEntry {
90                        child_index: ci,
91                        body_handle: bodies[bi].handle,
92                    });
93                    assigned_bodies.insert(bi);
94                    matched = true;
95                    break;
96                }
97            }
98        }
99        if !matched {
100            unmatched_children.push(ci);
101        }
102    }
103
104    let unmatched_bodies: Vec<usize> = bodies
105        .iter()
106        .enumerate()
107        .filter_map(|(idx, _)| (!assigned_bodies.contains(&idx)).then_some(idx))
108        .collect();
109
110    if !unmatched_bodies.is_empty() && !unmatched_children.is_empty() {
111        let overlap =
112            build_overlap_matrix(bodies, &unmatched_bodies, children, &unmatched_children);
113        let assignments = hungarian_max(&overlap);
114        let mut reused_children = HashSet::new();
115
116        for (row_idx, assignment) in assignments.into_iter().enumerate() {
117            let Some(col_idx) = assignment else {
118                continue;
119            };
120            let overlap_score = overlap
121                .get(row_idx)
122                .and_then(|row| row.get(col_idx))
123                .copied()
124                .unwrap_or(0);
125            if overlap_score == 0 {
126                continue;
127            }
128
129            let body_idx = unmatched_bodies[row_idx];
130            let child_index = unmatched_children[col_idx];
131            reuse.push(ReuseEntry {
132                child_index,
133                body_handle: bodies[body_idx].handle,
134            });
135            reused_children.insert(child_index);
136        }
137
138        let create = unmatched_children
139            .into_iter()
140            .filter(|child_index| !reused_children.contains(child_index))
141            .map(|child_index| CreateEntry { child_index })
142            .collect();
143        return SplitMigrationPlan { reuse, create };
144    }
145
146    let create = unmatched_children
147        .into_iter()
148        .map(|child_index| CreateEntry { child_index })
149        .collect();
150    SplitMigrationPlan { reuse, create }
151}
152
153fn hash_node_set(nodes: &HashSet<u32>) -> u64 {
154    let mut sorted: Vec<u32> = nodes.iter().copied().collect();
155    sorted.sort_unstable();
156    let mut hash = 0u64;
157    for n in sorted {
158        // Simple FNV-1a-like hash
159        hash ^= n as u64;
160        hash = hash.wrapping_mul(0x100000001b3);
161    }
162    hash
163}
164
165fn build_overlap_matrix(
166    bodies: &[ExistingBodyState],
167    unmatched_bodies: &[usize],
168    children: &[SplitChild],
169    unmatched_children: &[usize],
170) -> Vec<Vec<usize>> {
171    unmatched_bodies
172        .iter()
173        .map(|&body_idx| {
174            let body = &bodies[body_idx];
175            unmatched_children
176                .iter()
177                .map(|&child_idx| {
178                    children[child_idx]
179                        .nodes
180                        .iter()
181                        .filter(|node| body.node_indices.contains(node))
182                        .count()
183                })
184                .collect()
185        })
186        .collect()
187}
188
189fn hungarian_max(matrix: &[Vec<usize>]) -> Vec<Option<usize>> {
190    let rows = matrix.len();
191    let cols = matrix.first().map(Vec::len).unwrap_or(0);
192    if rows == 0 || cols == 0 {
193        return vec![None; rows];
194    }
195
196    let size = rows.max(cols);
197    let max_val = matrix
198        .iter()
199        .flat_map(|row| row.iter())
200        .copied()
201        .max()
202        .unwrap_or(0) as i64;
203    let mut cost = vec![vec![max_val; size]; size];
204    for (i, row) in matrix.iter().enumerate() {
205        for (j, value) in row.iter().enumerate() {
206            cost[i][j] = max_val - (*value as i64);
207        }
208    }
209
210    let assignments = hungarian(&cost);
211    assignments
212        .into_iter()
213        .take(rows)
214        .map(|col| (col < cols).then_some(col))
215        .collect()
216}
217
218fn hungarian(cost: &[Vec<i64>]) -> Vec<usize> {
219    let size = cost.len();
220    let mut u = vec![0i64; size + 1];
221    let mut v = vec![0i64; size + 1];
222    let mut p = vec![0usize; size + 1];
223    let mut way = vec![0usize; size + 1];
224
225    for i in 1..=size {
226        p[0] = i;
227        let mut minv = vec![i64::MAX; size + 1];
228        let mut used = vec![false; size + 1];
229        let mut j0 = 0usize;
230        loop {
231            used[j0] = true;
232            let i0 = p[j0];
233            let mut delta = i64::MAX;
234            let mut j1 = 0usize;
235            for j in 1..=size {
236                if used[j] {
237                    continue;
238                }
239                let cur = cost[i0 - 1][j - 1] - u[i0] - v[j];
240                if cur < minv[j] {
241                    minv[j] = cur;
242                    way[j] = j0;
243                }
244                if minv[j] < delta {
245                    delta = minv[j];
246                    j1 = j;
247                }
248            }
249            for j in 0..=size {
250                if used[j] {
251                    u[p[j]] += delta;
252                    v[j] -= delta;
253                } else if minv[j] != i64::MAX {
254                    minv[j] -= delta;
255                }
256            }
257            j0 = j1;
258            if p[j0] == 0 {
259                break;
260            }
261        }
262        loop {
263            let j1 = way[j0];
264            p[j0] = p[j1];
265            j0 = j1;
266            if j0 == 0 {
267                break;
268            }
269        }
270    }
271
272    let mut result = vec![usize::MAX; size];
273    for j in 1..=size {
274        if p[j] > 0 {
275            result[p[j] - 1] = j - 1;
276        }
277    }
278    result
279}