1use fast_mesh_ops::deform::{
4 Brush, GrabOptions, InflateOptions, SmoothOptions, grab, grab_region, inflate, inflate_region,
5 smooth, smooth_region,
6};
7use fast_mesh_ops::{check_protection, with_protection};
8use fast_mesh_query::{Region, vertices_in};
9use fast_mesh_store::VertexId;
10
11use crate::scene::Entry;
12use crate::{MeshId, Result, Scene, SceneError, UpdateMode};
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum Dab {
17 Inflate(InflateOptions),
19 Smooth(SmoothOptions),
21 Grab(GrabOptions),
23}
24
25impl Dab {
26 fn brush(&self) -> &Brush {
27 match self {
28 Dab::Inflate(o) => &o.brush,
29 Dab::Smooth(o) => &o.brush,
30 Dab::Grab(o) => &o.brush,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Copy, Default, PartialEq)]
37pub struct DabReport {
38 pub moved: usize,
40 pub max_displacement: f32,
42}
43
44fn region_of(entry: &mut Entry, brush: &Brush) -> Option<Vec<VertexId>> {
49 let valid = brush.radius.is_finite()
50 && brush.radius > 0.0
51 && brush.center.iter().all(|c| c.is_finite())
52 && brush.strength.is_finite();
53 if !valid || (entry.mode == UpdateMode::Manual && !entry.is_current()) {
54 return None;
55 }
56 entry.update_bvh().ok()?;
57 entry.mesh.ensure_incidence().ok()?;
58 let mut region = Vec::new();
59 let sphere = Region::Sphere {
60 center: brush.center,
61 radius: brush.radius,
62 };
63 vertices_in(&entry.mesh, &entry.bvh, &sphere, &mut region).ok()?;
64 Some(region)
65}
66
67fn fully_held(region: &[VertexId], vertices: &[VertexId], weights: &[f32]) -> bool {
69 let mut left = region.len();
70 let mut seen = vec![false; region.len()];
71 for (v, w) in vertices.iter().zip(weights) {
72 if *w == 0.0
73 && let Ok(k) = region.binary_search(v)
74 && !seen[k]
75 {
76 seen[k] = true;
77 left -= 1;
78 }
79 }
80 left == 0
81}
82
83impl Scene {
84 pub fn dab(&mut self, id: MeshId, dab: &Dab) -> Result<DabReport> {
100 self.dab_protected(id, dab, &[], &[])
101 }
102
103 pub fn dab_protected(
114 &mut self,
115 id: MeshId,
116 dab: &Dab,
117 vertices: &[VertexId],
118 weights: &[f32],
119 ) -> Result<DabReport> {
120 self.drop_field_unit();
121 let entry = self.entries.get_mut(id).ok_or(SceneError::UnknownMesh)?;
122 let region = region_of(entry, dab.brush());
123 check_protection(&entry.mesh, vertices, weights)?;
124 if let Some(r) = ®ion
125 && !vertices.is_empty()
126 && fully_held(r, vertices, weights)
127 {
128 return Ok(DabReport::default());
129 }
130 self.edit(id, |tx| -> std::result::Result<DabReport, SceneError> {
131 let (moved, max_displacement) = with_protection(tx, vertices, weights, |tx| {
132 Ok(match (dab, ®ion) {
133 (Dab::Inflate(o), Some(r)) => {
134 let r = inflate_region(tx, o, r)?;
135 (r.moved, r.max_displacement)
136 }
137 (Dab::Inflate(o), None) => {
138 let r = inflate(tx, o)?;
139 (r.moved, r.max_displacement)
140 }
141 (Dab::Smooth(o), Some(r)) => {
142 let r = smooth_region(tx, o, r)?;
143 (r.moved, r.max_displacement)
144 }
145 (Dab::Smooth(o), None) => {
146 let r = smooth(tx, o)?;
147 (r.moved, r.max_displacement)
148 }
149 (Dab::Grab(o), Some(r)) => {
150 let r = grab_region(tx, o, r)?;
151 (r.moved, r.max_displacement)
152 }
153 (Dab::Grab(o), None) => {
154 let r = grab(tx, o)?;
155 (r.moved, r.max_displacement)
156 }
157 })
158 })?;
159 Ok(DabReport {
160 moved,
161 max_displacement,
162 })
163 })
164 }
165}
166
167#[cfg(test)]
168mod protected_tests {
169 use fast_mesh_ops::deform::Falloff;
170 use fast_mesh_store::Mesh;
171
172 use super::*;
173 use crate::{MeshOptions, SceneOptions};
174
175 fn setup(n: u32) -> (Scene, MeshId, Vec<VertexId>, Dab) {
177 let mut positions = Vec::new();
178 for y in 0..=n {
179 for x in 0..=n {
180 positions.extend([x as f32, y as f32, 0.0]);
181 }
182 }
183 let mut indices = Vec::new();
184 for y in 0..n {
185 for x in 0..n {
186 let a = y * (n + 1) + x;
187 indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
188 }
189 }
190 let mut scene = Scene::new(SceneOptions::default());
191 let id = scene
192 .add_mesh(
193 Mesh::from_buffers(&positions, &indices).unwrap(),
194 MeshOptions::default(),
195 )
196 .unwrap();
197 let local = scene.mesh(id).unwrap().positions().to_vec();
198 let middle = local[((n / 2) * (n + 1) + n / 2) as usize];
199 let held = local
200 .iter()
201 .enumerate()
202 .filter(|(_, p)| p[0] < middle[0])
203 .map(|(v, _)| v as VertexId)
204 .collect();
205 let dab = Dab::Inflate(InflateOptions {
206 brush: Brush {
207 center: middle,
208 radius: n as f32 / 4.0,
209 falloff: Falloff::Smooth,
210 strength: 0.5,
211 },
212 });
213 (scene, id, held, dab)
214 }
215
216 #[test]
217 fn holds_listed_vertices_and_matches_the_transaction_form() {
218 let (mut a, id_a, held, dab) = setup(20);
219 let weights = vec![0.0; held.len()];
220 let before = a.mesh(id_a).unwrap().positions().to_vec();
221 let report = a.dab_protected(id_a, &dab, &held, &weights).unwrap();
222 assert!(report.moved > 0);
223 let after = a.mesh(id_a).unwrap().positions().to_vec();
224 for &v in &held {
225 let (p, q) = (before[v as usize], after[v as usize]);
226 assert_eq!(p.map(f32::to_bits), q.map(f32::to_bits));
227 }
228 assert_ne!(before, after);
229 let (mut b, id_b, _, _) = setup(20);
231 let Dab::Inflate(o) = dab else { unreachable!() };
232 b.edit(id_b, |tx| {
233 with_protection(tx, &held, &weights, |tx| inflate(tx, &o))
234 })
235 .unwrap();
236 let other = b.mesh(id_b).unwrap().positions().to_vec();
237 assert_eq!(after, other);
238 a.undo().unwrap();
240 assert_eq!(a.mesh(id_a).unwrap().positions(), before.as_slice());
241 }
242
243 #[test]
244 fn a_fully_held_brush_edits_nothing() {
245 let (mut scene, id, _, dab) = setup(20);
246 let all: Vec<VertexId> = (0..scene.mesh(id).unwrap().vertex_slots() as u32).collect();
247 let weights = vec![0.0; all.len()];
248 let report = scene.dab_protected(id, &dab, &all, &weights).unwrap();
249 assert_eq!(report, DabReport::default());
250 assert!(!scene.can_undo());
251 }
252
253 #[test]
254 fn a_soft_weight_is_not_fully_held() {
255 let (mut scene, id, _, dab) = setup(20);
256 let all: Vec<VertexId> = (0..scene.mesh(id).unwrap().vertex_slots() as u32).collect();
257 let weights = vec![0.5; all.len()];
258 assert!(scene.dab_protected(id, &dab, &all, &weights).unwrap().moved > 0);
259 assert!(scene.can_undo());
260 }
261
262 #[test]
263 fn rejects_a_bad_list_and_changes_nothing() {
264 let (mut scene, id, _, dab) = setup(4);
265 assert!(scene.dab_protected(id, &dab, &[9999], &[0.0]).is_err());
266 assert!(scene.dab_protected(id, &dab, &[0], &[2.0]).is_err());
267 assert!(scene.dab_protected(id, &dab, &[0], &[]).is_err());
268 assert!(!scene.can_undo());
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use fast_mesh_ops::deform::{Falloff, SmoothMethod};
275 use fast_mesh_store::Mesh;
276
277 use super::*;
278 use crate::{MeshOptions, SceneOptions};
279
280 fn terrain(n: u32) -> Mesh {
281 let mut positions = Vec::new();
282 for y in 0..=n {
283 for x in 0..=n {
284 let (fx, fy) = (x as f32, y as f32);
285 positions.extend([fx, fy, (fx * 0.3).sin() * (fy * 0.2).cos()]);
286 }
287 }
288 let mut indices = Vec::new();
289 for y in 0..n {
290 for x in 0..n {
291 let a = y * (n + 1) + x;
292 indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
293 }
294 }
295 Mesh::from_buffers(&positions, &indices).unwrap()
296 }
297
298 fn scene_with(mode: UpdateMode) -> (Scene, MeshId) {
299 let mut scene = Scene::new(SceneOptions::default());
300 let options = MeshOptions {
301 mode,
302 ..MeshOptions::default()
303 };
304 let id = scene.add_mesh(terrain(24), options).unwrap();
305 (scene, id)
306 }
307
308 fn brush(center: [f32; 3]) -> Brush {
309 Brush {
310 center,
311 radius: 3.5,
312 falloff: Falloff::Smooth,
313 strength: 0.4,
314 }
315 }
316
317 fn dabs() -> Vec<Dab> {
318 let mut out = Vec::new();
319 for k in 0..12 {
320 let b = brush([6.0 + k as f32, 9.0, 0.5]);
321 out.push(match k % 3 {
322 0 => Dab::Inflate(InflateOptions { brush: b }),
323 1 => Dab::Smooth(SmoothOptions {
324 brush: b,
325 cycles: 2,
326 method: SmoothMethod::Laplacian,
327 }),
328 _ => Dab::Grab(GrabOptions {
329 brush: b,
330 direction: [0.2, 0.1, 0.5],
331 }),
332 });
333 }
334 out
335 }
336
337 fn whole(scene: &mut Scene, id: MeshId, dab: &Dab) -> DabReport {
338 scene
339 .edit(id, |tx| -> std::result::Result<_, SceneError> {
340 Ok(match dab {
341 Dab::Inflate(o) => {
342 let r = inflate(tx, o)?;
343 DabReport {
344 moved: r.moved,
345 max_displacement: r.max_displacement,
346 }
347 }
348 Dab::Smooth(o) => {
349 let r = smooth(tx, o)?;
350 DabReport {
351 moved: r.moved,
352 max_displacement: r.max_displacement,
353 }
354 }
355 Dab::Grab(o) => {
356 let r = grab(tx, o)?;
357 DabReport {
358 moved: r.moved,
359 max_displacement: r.max_displacement,
360 }
361 }
362 })
363 })
364 .unwrap()
365 }
366
367 fn bits(scene: &Scene, id: MeshId) -> Vec<[u32; 3]> {
368 scene
369 .mesh(id)
370 .unwrap()
371 .positions()
372 .iter()
373 .map(|p| p.map(f32::to_bits))
374 .collect()
375 }
376
377 #[test]
378 fn dabs_match_the_whole_mesh_deforms_bit_for_bit_in_every_mode() {
379 for mode in [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual] {
380 let (mut a, ia) = scene_with(mode);
381 let (mut b, ib) = scene_with(mode);
382 for dab in dabs() {
383 let ra = a.dab(ia, &dab).unwrap();
384 let rb = whole(&mut b, ib, &dab);
385 assert_eq!(ra, rb, "{mode:?}");
386 assert_eq!(bits(&a, ia), bits(&b, ib), "{mode:?}");
387 }
388 }
389 }
390
391 #[test]
392 fn a_dab_is_one_undo_step_and_reports_its_change() {
393 let (mut scene, id) = scene_with(UpdateMode::Eager);
394 let before = bits(&scene, id);
395 scene.take_changes();
396 let report = scene
397 .dab(
398 id,
399 &Dab::Inflate(InflateOptions {
400 brush: brush([12.0, 12.0, 0.0]),
401 }),
402 )
403 .unwrap();
404 assert!(report.moved > 0);
405 assert_eq!(scene.take_changes().len(), 1);
406 assert_ne!(bits(&scene, id), before);
407 scene.undo().unwrap();
408 assert_eq!(bits(&scene, id), before);
409 assert!(!scene.can_undo());
410 }
411
412 #[test]
413 fn dabs_in_an_open_step_undo_together() {
414 let (mut scene, id) = scene_with(UpdateMode::Eager);
415 let before = bits(&scene, id);
416 scene.begin_step();
417 for dab in dabs() {
418 scene.dab(id, &dab).unwrap();
419 }
420 scene.end_step().unwrap();
421 assert_ne!(bits(&scene, id), before);
422 scene.undo().unwrap();
423 assert_eq!(bits(&scene, id), before);
424 }
425
426 #[test]
427 fn an_empty_region_records_nothing_and_a_bad_brush_fails() {
428 let (mut scene, id) = scene_with(UpdateMode::Eager);
429 let far = Dab::Grab(GrabOptions {
430 brush: brush([1000.0, 0.0, 0.0]),
431 direction: [0.0, 0.0, 1.0],
432 });
433 assert_eq!(scene.dab(id, &far).unwrap(), DabReport::default());
434 assert!(!scene.can_undo());
435 let mut bad = brush([0.0; 3]);
436 bad.radius = -1.0;
437 assert!(matches!(
438 scene.dab(id, &Dab::Inflate(InflateOptions { brush: bad })),
439 Err(SceneError::Ops(_))
440 ));
441 }
442
443 #[test]
444 fn a_stale_manual_mesh_still_dabs_by_scanning() {
445 let (mut scene, id) = scene_with(UpdateMode::Manual);
446 let dab = Dab::Inflate(InflateOptions {
447 brush: brush([12.0, 12.0, 0.0]),
448 });
449 scene.dab(id, &dab).unwrap();
450 assert!(scene.dab(id, &dab).unwrap().moved > 0);
452 }
453
454 #[test]
455 fn a_dab_reaches_vertices_grown_into_the_region_by_earlier_dabs() {
456 let (mut scene, id) = scene_with(UpdateMode::Eager);
459 let mut at = [12.0, 12.0, 0.0];
460 for _ in 0..30 {
461 let dab = Dab::Grab(GrabOptions {
462 brush: Brush {
463 center: at,
464 radius: 2.0,
465 falloff: Falloff::Smooth,
466 strength: 1.0,
467 },
468 direction: [0.0, 0.0, 1.0],
469 });
470 assert!(scene.dab(id, &dab).unwrap().moved > 0);
471 at[2] += 1.0;
472 }
473 let top = scene
474 .mesh(id)
475 .unwrap()
476 .positions()
477 .iter()
478 .map(|p| p[2])
479 .fold(f32::MIN, f32::max);
480 assert!(top > 25.0, "the lump followed: {top}");
481 }
482}