1use fast_mesh_store::{Transaction, UV_CHANNEL, VertexId};
14
15use crate::{OpsError, Result, reserve};
16
17#[derive(Debug, Clone, Default, PartialEq)]
19pub struct AppendSource {
20 pub positions: Vec<[f32; 3]>,
22 pub triangles: Vec<[u32; 3]>,
24}
25
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
28pub struct AppendReport {
29 pub vertex_map: Vec<VertexId>,
31 pub vertices_added: usize,
33 pub triangles_added: usize,
35 pub uvs_dropped: bool,
37}
38
39pub fn append_mesh(tx: &mut Transaction, source: &AppendSource) -> Result<AppendReport> {
44 append_mesh_with_uvs(tx, source, None)
45}
46
47pub fn append_mesh_with_uvs(
55 tx: &mut Transaction,
56 source: &AppendSource,
57 uvs: Option<&[f32]>,
58) -> Result<AppendReport> {
59 if uvs.is_some_and(|u| u.len() != 2 * source.positions.len()) {
60 return Err(OpsError::InvalidInput(
61 "append uvs need 2 values per position",
62 ));
63 }
64 for t in &source.triangles {
65 if t.iter().any(|&v| (v as usize) >= source.positions.len()) {
66 return Err(OpsError::InvalidInput(
67 "append triangle indexes no position",
68 ));
69 }
70 }
71 let mut mapped: Vec<[VertexId; 3]> = Vec::new();
75 reserve(&mut mapped, source.triangles.len())?;
76 let ids = tx.add_vertices(&source.positions)?;
77 for t in &source.triangles {
78 mapped.push([ids[t[0] as usize], ids[t[1] as usize], ids[t[2] as usize]]);
79 }
80 tx.add_triangles(&mapped)?;
81 let target_has_uvs = tx.mesh().uvs().is_some();
82 match uvs {
83 Some(u) if target_has_uvs => tx.write_channel(UV_CHANNEL, &ids, u)?,
84 None if target_has_uvs => tx.drop_channel(UV_CHANNEL)?,
85 _ => {}
86 }
87 Ok(AppendReport {
88 vertices_added: ids.len(),
89 triangles_added: mapped.len(),
90 vertex_map: ids,
91 uvs_dropped: target_has_uvs != uvs.is_some(),
92 })
93}
94
95#[cfg(test)]
96mod tests {
97 use fast_mesh_store::Transaction;
98
99 use super::*;
100 use crate::test_util::{edit, mesh_of, parts, sweep};
101
102 fn quad_source() -> AppendSource {
103 AppendSource {
104 positions: vec![
105 [0.0, 0.0, 1.0],
106 [1.0, 0.0, 1.0],
107 [1.0, 1.0, 1.0],
108 [0.0, 1.0, 1.0],
109 ],
110 triangles: vec![[0, 1, 2], [0, 2, 3]],
111 }
112 }
113
114 #[test]
115 fn append_copies_geometry_with_id_map() {
116 let mut mesh = mesh_of(&[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0], &[[0, 1, 2]]);
117 let (before_pos, before_tris) = parts(&mesh);
118 let (report, _) = edit(&mut mesh, |tx| append_mesh(tx, &quad_source()));
119 assert_eq!(report.vertices_added, 4);
120 assert_eq!(report.triangles_added, 2);
121 assert_eq!(report.vertex_map.len(), 4);
122 let (pos_after, tris_after) = parts(&mesh);
123 assert_eq!(tris_after.len(), before_tris.len() + 2);
124 assert_eq!(&pos_after[..before_pos.len()], &before_pos[..]);
126 for (k, &id) in report.vertex_map.iter().enumerate() {
127 let p = &pos_after[3 * id as usize..3 * id as usize + 3];
128 assert_eq!(p, &quad_source().positions[k][..]);
129 }
130 for t in &tris_after[before_tris.len()..] {
131 assert!(t.iter().all(|v| report.vertex_map.contains(v)));
132 }
133 }
134
135 #[test]
136 fn append_empty_source_is_noop() {
137 let mut mesh = mesh_of(&[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0], &[[0, 1, 2]]);
138 let before = parts(&mesh);
139 let empty = AppendSource {
140 positions: Vec::new(),
141 triangles: Vec::new(),
142 };
143 let (report, patch_empty) = edit(&mut mesh, |tx| append_mesh(tx, &empty));
144 assert_eq!((report.vertices_added, report.triangles_added), (0, 0));
145 assert!(report.vertex_map.is_empty());
146 assert!(patch_empty);
147 assert_eq!(parts(&mesh), before);
148 }
149
150 #[test]
151 fn append_sweep_keeps_atomicity() {
152 let mesh = mesh_of(&[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0], &[[0, 1, 2]]);
153 let source = quad_source();
154 let n = sweep(mesh, |tx: &mut Transaction| {
155 append_mesh(tx, &source)?;
156 Ok(())
157 });
158 assert!(n > 0);
159 }
160}