makepad/libs/mesh_edit/tests/topology_tools.rs
Admin d2130e6550 libs: model, mesh_edit, csg, scene, sim, render, xr
Squash of 25 work commits (Sep 1–12):
  237da90  render: the sprite lane hands the screen draw back the way it found it
  895b9a7  particles: an emitter can ride a body's own frame
  914471f  ai-hub: a feed session whose last socket left ends on its idle timeout; skin: parent, skinned centroid and a nodes-only rig for retargets
  3fcccf3  sim: the whole world is implicitly editable, and one seam says where the ground is
  5692fab  sim: the landform world proves itself — walker through the tunnel included
  f4af6df  sim: terrain knows who changed it — a plan layer over player history
  adbb078  render: a water volume can be physics without a picture
  c17480f  render: a non-rigid body may carry its own orientation
  acad401  sim: an agent with no route holds and retries instead of walking into the wall
  22b2bf9  sim + chat: the composed world surface takes a map floor; the chat gets plan tools
  faf8112  web path: the tessellator's lap timer, the trace span and the fusion cycle timer have no clock on the web
  d3472eb  tsdf: the clock-taking XR helpers are native-only — the browser has no depth camera and no Instant
  4946c9e  sim + render: a repaint is not a world edit — colour and glow restyles never rebake the lightmap
  4317f58  sim + render + chat: walk decks as a surface, the filmed body is no obstruction, the brief never asks
  9791279  render: support rigged models and custom materials across viewers
  3ce2792  sim: use deck geometry for collision and sensing
  c8b79ff  Add portable PBR, rig and soft-body authoring support
  108d423  Add transactional polygon modeling and editable asset documents
  38f4d3b  Refine editable modeling and firm yarn character behavior
  08a2637  sim: add entity-owned lights and vehicle headlights
  00d96a7  render: add clustered lights, local shadows and incremental GI
  cf916af  render: import glTF asset extensions and wire clustered GI
  c6d2cca  model: cut transaction memory and raise capacity limits
  41af47a  raytrace: add a CPU probe-ray BVH budget example
  c963d0a  libs: the game sim splits into makepad-scene and makepad-soft-body; render, model, fab and the asset importer retarget

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:40:31 +02:00

48 lines
4.2 KiB
Rust

use makepad_mesh_edit::*;
fn ctx()->Context<'static>{Context::default()}
fn closed(m:&Mesh){assert!(m.validate(&mut ctx()).unwrap().is_closed_manifold);m.triangulate(&mut ctx()).unwrap();}
fn volume(m:&Mesh)->f64{let t=m.triangulate(&mut ctx()).unwrap();t.triangles.iter().map(|t0|{let [a,b,c]=t0.indices.map(|i|t.vertices[i as usize].position);(a[0]*(b[1]*c[2]-b[2]*c[1])+a[1]*(b[2]*c[0]-b[0]*c[2])+a[2]*(b[0]*c[1]-b[1]*c[0]))/6.}).sum()}
#[test]
fn connected_region_has_only_boundary_walls_and_retains_caps(){
let mut m=Mesh::cube([2.;3],&mut ctx()).unwrap();let fs=[m.faces()[1].id,m.faces()[3].id];
let out=m.extrude_region(&fs,[0.,1.,1.],&mut ctx()).unwrap();
assert_eq!(out.side_faces.len(),6);assert_eq!(out.caps,fs);assert_eq!(m.vertices().len(),14);closed(&m);assert!(volume(&m)>8.);
let saved=m.clone();assert!(m.extrude_region(&fs,[1.,0.,0.],&mut ctx()).is_err());assert_eq!(m,saved);
}
#[test]
fn cube_loop_cut_splits_four_faces_and_preserves_volume(){
let mut m=Mesh::cube([2.;3],&mut ctx()).unwrap();let before=volume(&m);let edge=*m.adjacency(&mut ctx()).unwrap().edges.keys().next().unwrap();
let out=m.loop_cut(edge,0.3,&mut ctx()).unwrap();assert_eq!(m.vertices().len(),12);assert_eq!(m.faces().len(),10);closed(&m);assert!((volume(&m)-before).abs()<1e-10);
assert_eq!(out.remapped.iter().filter(|(s,_)|matches!(s,ElementId::Face(_))).count(),4);
let bytes=m.to_bytes(&mut ctx()).unwrap();assert_eq!(Mesh::from_bytes(&bytes,&mut ctx()).unwrap(),m);
}
#[test]
fn split_and_collapse_preserve_closed_topology_and_interpolate_weights(){
let mut m=Mesh::cube([2.;3],&mut ctx()).unwrap();let e=*m.adjacency(&mut ctx()).unwrap().edges.keys().next().unwrap();
m.set_vertex_weights(e.0,&[JointWeight{joint:0,weight:1.}],&mut ctx()).unwrap();m.set_vertex_weights(e.1,&[JointWeight{joint:1,weight:1.}],&mut ctx()).unwrap();
let change=m.split_edges(&[e],0.25,&mut ctx()).unwrap();closed(&m);let v=change.created.iter().find_map(|i|if let ElementId::Vertex(v)=i{Some(*v)}else{None}).unwrap();
assert_eq!(m.vertex(v).unwrap().weights,vec![JointWeight{joint:0,weight:0.75},JointWeight{joint:1,weight:0.25}]);
m.collapse_edge(EdgeKey::new(e.0,v),0.5,&mut ctx()).unwrap();closed(&m);assert_eq!(m.vertices().len(),8);
}
#[test]
fn fill_and_bridge_close_real_boundaries_and_solidify_has_positive_volume(){
let mut m=Mesh::cube([2.;3],&mut ctx()).unwrap();let top=m.faces()[3].id;let cycle=m.face_corners(top).unwrap().iter().map(|c|c.vertex).collect::<Vec<_>>();m.delete_faces(&[top],&mut ctx()).unwrap();
m.fill_boundary(&cycle,0,&mut ctx()).unwrap();closed(&m);assert!((volume(&m)-8.).abs()<1e-10);
let mut p=Mesh::plane([2.,3.],&mut ctx()).unwrap();p.solidify(0.5,&mut ctx()).unwrap();closed(&p);assert!((volume(&p)-3.).abs()<1e-10);
let mut two=Mesh::from_polygons(&[[-1.,0.,-1.],[-1.,0.,1.],[1.,0.,1.],[1.,0.,-1.],[-1.,1.,-1.],[-1.,1.,1.],[1.,1.,1.],[1.,1.,-1.]],
&[Polygon::new(vec![3,2,1,0]),Polygon::new(vec![4,5,6,7])],&mut ctx()).unwrap();
let a=two.vertices()[..4].iter().map(|v|v.id).collect::<Vec<_>>();let b=two.vertices()[4..].iter().map(|v|v.id).collect::<Vec<_>>();
two.bridge_boundaries(&a,&b,0,&mut ctx()).unwrap();closed(&two);assert!((volume(&two)-4.).abs()<1e-10);
}
#[test]
fn coplanar_dissolve_preserves_area_and_refuses_material_or_uv_loss(){
let mut m=Mesh::from_polygons(&[[0.,0.,0.],[1.,0.,0.],[1.,1.,0.],[0.,1.,0.]],&[Polygon::new(vec![0,1,2]),Polygon::new(vec![0,2,3])],&mut ctx()).unwrap();
let e=EdgeKey::new(m.vertices()[0].id,m.vertices()[2].id);m.dissolve_edge(e,&mut ctx()).unwrap();assert_eq!(m.faces().len(),1);assert_eq!(m.corners().len(),4);assert_eq!(m.triangulate(&mut ctx()).unwrap().triangles.len(),2);
}
#[test]
fn isolated_cube_edge_bevel_has_uniform_width_and_refuses_overrun(){
let mut m=Mesh::cube([2.;3],&mut ctx()).unwrap();let e=*m.adjacency(&mut ctx()).unwrap().edges.keys().next().unwrap();
let before=m.clone();assert!(m.bevel_edges(&[e],2.,&mut ctx()).is_err());assert_eq!(m,before);
let changes=m.bevel_edges(&[e],0.2,&mut ctx()).unwrap();closed(&m);assert_eq!(m.vertices().len(),10);assert_eq!(m.faces().len(),7);
assert!((volume(&m)-(8.-0.2*0.2)).abs()<1e-10);assert!(changes.deleted.contains(&ElementId::Vertex(e.0)));assert!(changes.deleted.contains(&ElementId::Vertex(e.1)));
}