1use fast_mesh_ops::append::{AppendSource, append_mesh_with_uvs};
5use fast_mesh_ops::bridge::{BridgeOptions, bridge_loops};
6use fast_mesh_ops::{LoopId, OpsError, boundary_loops};
7use fast_mesh_store::{Mesh, StoreError, VertexId};
8
9use crate::placement::relative_affine;
10use crate::{MeshId, Result, Scene, SceneError};
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct JoinOptions {
15 pub loop_a: LoopId,
17 pub loop_b: LoopId,
19 pub max_edges: Option<usize>,
21}
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
25pub struct JoinReport {
26 pub vertices_added: usize,
28 pub triangles_added: usize,
30 pub bridge_triangles_added: usize,
32 pub uvs_dropped: bool,
35}
36
37fn oom() -> SceneError {
38 SceneError::Store(StoreError::OutOfMemory)
39}
40
41struct Source {
44 source: AppendSource,
45 uvs: Option<Vec<f32>>,
47 index_of: Vec<u32>,
50 loop_vertices: Vec<VertexId>,
52}
53
54fn prepare_source(
58 target: &Mesh,
59 target_placement: &crate::Placement,
60 source: &Mesh,
61 source_placement: &crate::Placement,
62 loop_b: LoopId,
63) -> Result<Source> {
64 let rigid = relative_affine(
65 target_placement,
66 target.origin(),
67 source_placement,
68 source.origin(),
69 );
70 let loops = boundary_loops(source)?;
71 let loop_vertices = loops
72 .iter()
73 .find(|l| l.id == loop_b)
74 .map(|l| l.vertices.clone())
75 .ok_or(OpsError::StaleLoops)?;
76 let mut index_of: Vec<u32> = Vec::new();
77 index_of
78 .try_reserve(source.vertex_slots())
79 .map_err(|_| oom())?;
80 index_of.resize(source.vertex_slots(), u32::MAX);
81 let mut positions: Vec<[f32; 3]> = Vec::new();
82 positions
83 .try_reserve(source.vertex_count())
84 .map_err(|_| oom())?;
85 for (v, slot) in index_of.iter_mut().enumerate() {
86 let Some(p) = source.position(v as VertexId) else {
87 continue;
88 };
89 let w = rigid.apply(p.map(f64::from));
90 let q = [w[0] as f32, w[1] as f32, w[2] as f32];
91 if !q.iter().all(|c| c.is_finite()) {
92 return Err(SceneError::InvalidInput("joined positions must be finite"));
93 }
94 let idx = u32::try_from(positions.len()).map_err(|_| oom())?;
95 *slot = idx;
96 positions.push(q);
97 }
98 let mut uvs = None;
99 if let Some(from) = source.uvs() {
100 let mut to: Vec<f32> = Vec::new();
101 to.try_reserve_exact(2 * positions.len())
102 .map_err(|_| oom())?;
103 for (v, &slot) in index_of.iter().enumerate() {
104 if slot != u32::MAX {
105 to.extend_from_slice(&from[2 * v..2 * v + 2]);
106 }
107 }
108 uvs = Some(to);
109 }
110 let mut triangles: Vec<[u32; 3]> = Vec::new();
111 triangles
112 .try_reserve(source.triangle_count())
113 .map_err(|_| oom())?;
114 for t in 0..source.triangle_slots() {
115 let Some(c) = source.triangle(t as u32) else {
116 continue;
117 };
118 let mapped = c.map(|v| index_of[v as usize]);
119 debug_assert!(
120 mapped.iter().all(|&m| m != u32::MAX),
121 "live triangle on a dead vertex"
122 );
123 triangles.push(mapped);
124 }
125 Ok(Source {
126 source: AppendSource {
127 positions,
128 triangles,
129 },
130 uvs,
131 index_of,
132 loop_vertices,
133 })
134}
135
136impl Scene {
137 pub fn join(&mut self, a: MeshId, b: MeshId, options: &JoinOptions) -> Result<JoinReport> {
160 if a == b {
161 return Err(SceneError::InvalidInput("cannot join a mesh to itself"));
162 }
163 let prepared = {
164 let target = self.entry(a)?;
165 let source = self.entry(b)?;
166 prepare_source(
167 &target.mesh,
168 &target.placement,
169 &source.mesh,
170 &source.placement,
171 options.loop_b,
172 )?
173 };
174 let Source {
175 source,
176 uvs,
177 index_of,
178 loop_vertices,
179 } = prepared;
180 let report = self.edit(a, |tx| -> std::result::Result<JoinReport, OpsError> {
181 let appended = append_mesh_with_uvs(tx, &source, uvs.as_deref())?;
182 let mapped = |v: VertexId| appended.vertex_map[index_of[v as usize] as usize];
186 let loops = boundary_loops(tx.mesh())?;
187 let copied = loops
188 .iter()
189 .find(|l| {
190 l.vertices.len() == loop_vertices.len()
191 && loop_vertices
192 .iter()
193 .all(|&v| l.vertices.contains(&mapped(v)))
194 })
195 .map(|l| l.id)
196 .ok_or(OpsError::StaleLoops)?;
197 let bridged = bridge_loops(
198 tx,
199 &BridgeOptions {
200 loops: (options.loop_a, copied),
201 max_edges: options.max_edges,
202 },
203 )?;
204 Ok(JoinReport {
205 vertices_added: appended.vertices_added,
206 triangles_added: appended.triangles_added,
207 bridge_triangles_added: bridged.triangles_added,
208 uvs_dropped: appended.uvs_dropped,
209 })
210 })?;
211 self.remove_mesh(b)?;
212 Ok(report)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use fast_mesh_ops::{LoopId, OpsError};
219 use fast_mesh_store::Mesh;
220
221 use crate::{MeshOptions, Placement, Scene, SceneError, SceneOptions};
222
223 fn quad_a() -> Mesh {
224 Mesh::from_buffers(
225 &[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0],
226 &[0, 2, 1, 0, 3, 2],
227 )
228 .unwrap()
229 }
230
231 fn quad_b() -> Mesh {
232 Mesh::from_buffers(
233 &[0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0],
234 &[0, 1, 2, 0, 2, 3],
235 )
236 .unwrap()
237 }
238
239 fn loop_of(mesh: &Mesh, min_vertex: u32) -> LoopId {
240 fast_mesh_ops::boundary_loops(mesh)
241 .unwrap()
242 .iter()
243 .find(|l| l.id.min_vertex == min_vertex)
244 .unwrap()
245 .id
246 }
247
248 fn scene_of(a: Mesh, b: Mesh) -> Scene {
249 let mut scene = Scene::new(SceneOptions::default());
250 scene.add_mesh(a, MeshOptions::default()).unwrap();
251 scene.add_mesh(b, MeshOptions::default()).unwrap();
252 scene
253 }
254
255 #[test]
256 fn join_merges_source_into_target_and_removes_it() {
257 let a_mesh = quad_a();
258 let b_mesh = quad_b();
259 let loop_a = loop_of(&a_mesh, 0);
260 let loop_b = loop_of(&b_mesh, 0);
261 let mut scene = scene_of(a_mesh, b_mesh);
262 let ids: Vec<_> = scene.ids().collect();
263 let (a, b) = (ids[0], ids[1]);
264
265 let report = scene
266 .join(
267 a,
268 b,
269 &crate::JoinOptions {
270 loop_a,
271 loop_b,
272 max_edges: None,
273 },
274 )
275 .unwrap();
276 assert_eq!(report.vertices_added, 4);
277 assert_eq!(report.triangles_added, 2);
278 assert_eq!(report.bridge_triangles_added, 8);
279 assert_eq!(scene.mesh(a).unwrap().triangle_count(), 2 + 2 + 8);
280 assert_eq!(scene.mesh(b).unwrap_err(), SceneError::UnknownMesh);
281
282 assert!(scene.undo().unwrap());
284 assert_eq!(scene.mesh(a).unwrap().triangle_count(), 2);
285 assert_eq!(scene.mesh(b).unwrap_err(), SceneError::UnknownMesh);
286 assert!(scene.redo().unwrap());
287 assert_eq!(scene.mesh(a).unwrap().triangle_count(), 2 + 2 + 8);
288 }
289
290 fn join_options(mesh_a: &Mesh, mesh_b: &Mesh) -> crate::JoinOptions {
291 crate::JoinOptions {
292 loop_a: loop_of(mesh_a, 0),
293 loop_b: loop_of(mesh_b, 0),
294 max_edges: None,
295 }
296 }
297
298 #[test]
299 fn join_rejects_same_mesh() {
300 let a_mesh = quad_a();
301 let b_mesh = quad_b();
302 let options = join_options(&a_mesh, &b_mesh);
303 let mut scene = scene_of(a_mesh, b_mesh);
304 let ids: Vec<_> = scene.ids().collect();
305 let (a, b) = (ids[0], ids[1]);
306
307 assert_eq!(
308 scene.join(a, a, &options),
309 Err(SceneError::InvalidInput("cannot join a mesh to itself"))
310 );
311 assert_eq!(scene.mesh(a).unwrap().triangle_count(), 2);
312 assert_eq!(scene.mesh(b).unwrap().triangle_count(), 2);
313 assert!(!scene.can_undo());
314 }
315
316 #[test]
317 fn join_rejects_stale_loops_and_changes_nothing() {
318 let a_mesh = quad_a();
319 let b_mesh = quad_b();
320 let fake = LoopId {
321 min_vertex: 99,
322 len: 4,
323 hash: 7,
324 };
325 let mut scene = scene_of(a_mesh, b_mesh);
326 let ids: Vec<_> = scene.ids().collect();
327 let (a, b) = (ids[0], ids[1]);
328 let options = join_options(scene.mesh(a).unwrap(), scene.mesh(b).unwrap());
329
330 let bad_b = crate::JoinOptions {
332 loop_b: fake,
333 ..options
334 };
335 assert_eq!(
336 scene.join(a, b, &bad_b),
337 Err(SceneError::Ops(OpsError::StaleLoops))
338 );
339 let bad_a = crate::JoinOptions {
341 loop_a: fake,
342 ..options
343 };
344 assert_eq!(
345 scene.join(a, b, &bad_a),
346 Err(SceneError::Ops(OpsError::StaleLoops))
347 );
348 assert_eq!(scene.mesh(a).unwrap().triangle_count(), 2);
349 assert_eq!(scene.mesh(b).unwrap().triangle_count(), 2);
350 assert!(!scene.can_undo());
351 }
352
353 #[test]
354 fn join_rejects_same_winding_and_changes_nothing() {
355 let a_mesh = quad_a();
356 let b_mesh = Mesh::from_buffers(
359 &[0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0],
360 &[0, 2, 1, 0, 3, 2],
361 )
362 .unwrap();
363 let options = join_options(&a_mesh, &b_mesh);
364 let mut scene = scene_of(a_mesh, b_mesh);
365 let ids: Vec<_> = scene.ids().collect();
366 let (a, b) = (ids[0], ids[1]);
367
368 assert!(matches!(
369 scene.join(a, b, &options),
370 Err(SceneError::Ops(OpsError::InvalidInput(_)))
371 ));
372 assert_eq!(scene.mesh(a).unwrap().triangle_count(), 2);
373 assert_eq!(scene.mesh(b).unwrap().triangle_count(), 2);
374 assert!(!scene.can_undo());
375 }
376
377 #[test]
378 fn join_maps_source_into_target_frame() {
379 let a_mesh = quad_a();
380 let b_mesh = Mesh::from_buffers(
382 &[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0],
383 &[0, 1, 2, 0, 2, 3],
384 )
385 .unwrap();
386 let options = join_options(&a_mesh, &b_mesh);
387 let mut scene = Scene::new(SceneOptions::default());
388 let a = scene.add_mesh(a_mesh, MeshOptions::default()).unwrap();
389 let b = scene
390 .add_mesh(
391 b_mesh,
392 MeshOptions {
393 placement: Placement {
394 translation: [0.0, 0.0, 1.0],
395 ..Placement::IDENTITY
396 },
397 ..MeshOptions::default()
398 },
399 )
400 .unwrap();
401
402 let report = scene.join(a, b, &options).unwrap();
403 assert_eq!((report.vertices_added, report.triangles_added), (4, 2));
404 assert_eq!(report.bridge_triangles_added, 8);
405 let mesh = scene.mesh(a).unwrap();
408 let mut low = 0;
409 let mut high = 0;
410 for v in 0..mesh.vertex_slots() {
411 let Some(p) = mesh.position(v as u32) else {
412 continue;
413 };
414 if p[2] < 0.5 {
415 low += 1;
416 } else {
417 assert!((p[2] - 1.0).abs() < 1e-6);
418 high += 1;
419 }
420 }
421 assert_eq!((low, high), (4, 4));
422 }
423}