1use fast_mesh_store::{Mesh, Transaction, VertexId};
8
9use crate::{OpsError, Result, reserve};
10
11pub fn with_protection<R>(
26 tx: &mut Transaction,
27 vertices: &[VertexId],
28 weights: &[f32],
29 op: impl FnOnce(&mut Transaction) -> Result<R>,
30) -> Result<R> {
31 check_protection(tx.mesh(), vertices, weights)?;
32 let positions = tx.mesh().positions();
33 let mut before: Vec<[f32; 3]> = Vec::new();
34 reserve(&mut before, vertices.len())?;
35 before.extend(vertices.iter().map(|&v| positions[v as usize]));
36 let mut ids: Vec<VertexId> = Vec::new();
39 reserve(&mut ids, vertices.len())?;
40 let mut back: Vec<[f32; 3]> = Vec::new();
41 reserve(&mut back, vertices.len())?;
42 let report = op(tx)?;
43 let positions = tx.mesh().positions();
44 for ((&v, &w), from) in vertices.iter().zip(weights).zip(&before) {
45 let to = positions[v as usize];
46 if w == 1.0 || same_bits(&to, from) {
47 continue;
48 }
49 let at = if w == 0.0 {
50 *from
51 } else {
52 [
53 from[0] + w * (to[0] - from[0]),
54 from[1] + w * (to[1] - from[1]),
55 from[2] + w * (to[2] - from[2]),
56 ]
57 };
58 ids.push(v);
59 back.push(at);
60 }
61 if !ids.is_empty() {
62 tx.set_positions(&ids, &back)?;
63 }
64 Ok(report)
65}
66
67pub fn check_protection(mesh: &Mesh, vertices: &[VertexId], weights: &[f32]) -> Result<()> {
73 if vertices.len() != weights.len() {
74 return Err(OpsError::InvalidInput(
75 "protection needs one weight per vertex",
76 ));
77 }
78 if weights.iter().any(|w| !(0.0..=1.0).contains(w)) {
79 return Err(OpsError::InvalidInput(
80 "protection weights must be between 0 and 1",
81 ));
82 }
83 if vertices.iter().any(|&v| !mesh.is_vertex_alive(v)) {
84 return Err(OpsError::InvalidInput("a protected vertex is not alive"));
85 }
86 Ok(())
87}
88
89fn same_bits(a: &[f32; 3], b: &[f32; 3]) -> bool {
90 a.iter().zip(b).all(|(a, b)| a.to_bits() == b.to_bits())
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::deform::{self, Brush, Falloff, GrabOptions, InflateOptions, SmoothOptions};
97 use crate::test_util::{mesh_of, sweep};
98
99 fn grid(n: u32) -> Mesh {
101 let mut positions = Vec::new();
102 for y in 0..=n {
103 for x in 0..=n {
104 positions.extend([x as f32 / n as f32, y as f32 / n as f32, 0.0]);
105 }
106 }
107 let at = |x: u32, y: u32| y * (n + 1) + x;
108 let mut triangles = Vec::new();
109 for y in 0..n {
110 for x in 0..n {
111 triangles.push([at(x, y), at(x + 1, y), at(x + 1, y + 1)]);
112 triangles.push([at(x, y), at(x + 1, y + 1), at(x, y + 1)]);
113 }
114 }
115 mesh_of(&positions, &triangles)
116 }
117
118 fn brush() -> Brush {
119 Brush {
120 center: [0.5, 0.5, 0.0],
121 radius: 0.4,
122 falloff: Falloff::Smooth,
123 strength: 0.1,
124 }
125 }
126
127 fn left_half(mesh: &Mesh) -> Vec<VertexId> {
129 mesh.positions()
130 .iter()
131 .enumerate()
132 .filter(|(_, p)| p[0] < 0.5)
133 .map(|(v, _)| v as VertexId)
134 .collect()
135 }
136
137 fn holds(op: impl FnOnce(&mut Transaction) -> Result<()>) {
140 let mut mesh = grid(20);
141 let held = left_half(&mesh);
142 let weights = vec![0.0; held.len()];
143 let before = mesh.positions().to_vec();
144 let mut tx = mesh.begin_edit();
145 with_protection(&mut tx, &held, &weights, op).unwrap();
146 tx.commit().unwrap();
147 for &v in &held {
148 let (a, b) = (before[v as usize], mesh.positions()[v as usize]);
149 assert!(same_bits(&a, &b), "held vertex {v} moved");
150 }
151 let moved = before
152 .iter()
153 .zip(mesh.positions())
154 .filter(|(a, b)| !same_bits(a, b))
155 .count();
156 assert!(moved > 0, "nothing moved on the free side");
157 }
158
159 #[test]
160 fn inflate_moves_only_free_vertices() {
161 holds(|tx| {
162 deform::inflate(tx, &InflateOptions { brush: brush() })?;
163 Ok(())
164 });
165 }
166
167 #[test]
168 fn grab_moves_only_free_vertices() {
169 holds(|tx| {
170 let options = GrabOptions {
171 brush: brush(),
172 direction: [0.0, 0.0, 0.2],
173 };
174 deform::grab(tx, &options)?;
175 Ok(())
176 });
177 }
178
179 #[test]
180 fn smooth_moves_only_free_vertices() {
181 let mut mesh = grid(20);
182 let mut tx = mesh.begin_edit();
184 deform::grab(
185 &mut tx,
186 &GrabOptions {
187 brush: brush(),
188 direction: [0.0, 0.0, 0.3],
189 },
190 )
191 .unwrap();
192 tx.commit().unwrap();
193 let held = left_half(&mesh);
194 let weights = vec![0.0; held.len()];
195 let before = mesh.positions().to_vec();
196 let mut tx = mesh.begin_edit();
197 with_protection(&mut tx, &held, &weights, |tx| {
198 let options = SmoothOptions {
199 brush: Brush {
200 strength: 0.5,
201 ..brush()
202 },
203 cycles: 2,
204 ..SmoothOptions::default()
205 };
206 deform::smooth(tx, &options)
207 })
208 .unwrap();
209 tx.commit().unwrap();
210 for &v in &held {
211 assert!(same_bits(
212 &before[v as usize],
213 &mesh.positions()[v as usize]
214 ));
215 }
216 assert!(
217 before
218 .iter()
219 .zip(mesh.positions())
220 .any(|(a, b)| !same_bits(a, b))
221 );
222 }
223
224 #[test]
225 fn a_weight_scales_the_move() {
226 let run = |weight: Option<f32>| {
227 let mut mesh = grid(10);
228 let v = 5 * 11 + 5; let (ids, weights) = match weight {
230 Some(w) => (vec![v as VertexId], vec![w]),
231 None => (vec![], vec![]),
232 };
233 let mut tx = mesh.begin_edit();
234 with_protection(&mut tx, &ids, &weights, |tx| {
235 deform::inflate(tx, &InflateOptions { brush: brush() })
236 })
237 .unwrap();
238 tx.commit().unwrap();
239 mesh.positions()[v][2]
240 };
241 let whole = run(None);
242 assert!(whole != 0.0);
243 assert_eq!(run(Some(1.0)).to_bits(), whole.to_bits());
244 assert!((run(Some(0.25)) - 0.25 * whole).abs() < 1e-6);
245 assert_eq!(run(Some(0.0)).to_bits(), 0f32.to_bits());
246 }
247
248 #[test]
249 fn undo_restores_every_vertex() {
250 let mut mesh = grid(10);
251 let held = left_half(&mesh);
252 let weights = vec![0.0; held.len()];
253 let before = mesh.positions().to_vec();
254 let mut tx = mesh.begin_edit();
255 with_protection(&mut tx, &held, &weights, |tx| {
256 deform::inflate(tx, &InflateOptions { brush: brush() })
257 })
258 .unwrap();
259 let patch = tx.commit().unwrap();
260 assert!(mesh.positions() != before.as_slice());
261 patch.revert(&mut mesh).unwrap();
262 assert_eq!(mesh.positions(), before.as_slice());
263 }
264
265 #[test]
266 fn running_out_of_memory_writes_nothing() {
267 let mesh = grid(4);
268 let held = left_half(&mesh);
269 let weights = vec![0.0; held.len()];
270 let failures = sweep(mesh, |tx| {
271 with_protection(tx, &held, &weights, |tx| {
272 deform::inflate(tx, &InflateOptions { brush: brush() })?;
273 Ok(())
274 })
275 });
276 assert!(failures >= 3);
277 }
278
279 #[test]
280 fn rejects_bad_input_before_running() {
281 let mut mesh = grid(2);
282 let mut tx = mesh.begin_edit();
283 let mut ran = false;
284 let mut try_with = |ids: &[VertexId], weights: &[f32]| {
285 with_protection(&mut tx, ids, weights, |_| {
286 ran = true;
287 Ok(())
288 })
289 .unwrap_err()
290 };
291 assert!(matches!(try_with(&[0], &[]), OpsError::InvalidInput(_)));
292 assert!(matches!(try_with(&[0], &[1.5]), OpsError::InvalidInput(_)));
293 assert!(matches!(
294 try_with(&[0], &[f32::NAN]),
295 OpsError::InvalidInput(_)
296 ));
297 assert!(matches!(
298 try_with(&[9999], &[0.0]),
299 OpsError::InvalidInput(_)
300 ));
301 assert!(!ran);
302 }
303}