proof_engine/geometry/
topology.rs1use glam::Vec3;
4use super::{GeoMesh, parametric::ParametricSurface};
5
6#[derive(Debug, Clone)]
8pub enum TopologyOp {
9 AttachHandle { position: Vec3, radius: f32 },
11 Puncture { position: Vec3, radius: f32 },
13 CrossCap { position: Vec3, radius: f32 },
15 Connect { pos_a: Vec3, pos_b: Vec3, tube_radius: f32 },
17 Twist { axis: Vec3, center: Vec3, half_twists: u32 },
19}
20
21#[derive(Debug, Clone)]
23pub struct SurfaceTopology {
24 pub euler_characteristic: i32,
26 pub genus: u32,
28 pub orientable: bool,
30 pub boundary_loops: u32,
32 pub components: u32,
34}
35
36impl SurfaceTopology {
37 pub fn from_mesh(mesh: &GeoMesh) -> Self {
39 let v = mesh.vertices.len() as i32;
40 let f = mesh.triangles.len() as i32;
41
42 let mut edges = std::collections::HashSet::new();
44 for tri in &mesh.triangles {
45 let mut add = |a: u32, b: u32| {
46 let key = if a < b { (a, b) } else { (b, a) };
47 edges.insert(key);
48 };
49 add(tri.a, tri.b);
50 add(tri.b, tri.c);
51 add(tri.c, tri.a);
52 }
53 let e = edges.len() as i32;
54
55 let euler = v - e + f;
56 let genus = ((2 - euler) / 2).max(0) as u32;
58
59 let mut edge_count: std::collections::HashMap<(u32, u32), u32> = std::collections::HashMap::new();
61 for tri in &mesh.triangles {
62 let mut inc = |a: u32, b: u32| {
63 let key = if a < b { (a, b) } else { (b, a) };
64 *edge_count.entry(key).or_insert(0) += 1;
65 };
66 inc(tri.a, tri.b);
67 inc(tri.b, tri.c);
68 inc(tri.c, tri.a);
69 }
70 let boundary_edges: Vec<_> = edge_count.iter().filter(|(_, &c)| c == 1).collect();
71 let boundary_loops = if boundary_edges.is_empty() { 0 } else { 1 }; Self {
74 euler_characteristic: euler,
75 genus,
76 orientable: true, boundary_loops,
78 components: 1, }
80 }
81
82 pub fn expected_euler(genus: u32, boundaries: u32) -> i32 {
84 2 - 2 * genus as i32 - boundaries as i32
85 }
86}
87
88pub struct TopologicalSurface {
90 pub mesh: GeoMesh,
91 pub topology: SurfaceTopology,
92 pub operations: Vec<TopologyOp>,
93}
94
95impl TopologicalSurface {
96 pub fn new(mesh: GeoMesh) -> Self {
97 let topology = SurfaceTopology::from_mesh(&mesh);
98 Self { mesh, topology, operations: Vec::new() }
99 }
100
101 pub fn apply(&mut self, op: TopologyOp) {
103 match &op {
104 TopologyOp::AttachHandle { position, radius } => {
105 self.topology.genus += 1;
106 }
107 TopologyOp::Puncture { .. } => {
108 self.topology.boundary_loops += 1;
109 }
110 TopologyOp::CrossCap { .. } => {
111 self.topology.orientable = false;
112 }
113 TopologyOp::Connect { .. } => {
114 }
116 TopologyOp::Twist { half_twists, .. } => {
117 if *half_twists % 2 != 0 {
118 self.topology.orientable = false;
119 }
120 }
121 }
122 self.topology.euler_characteristic = SurfaceTopology::expected_euler(
123 self.topology.genus, self.topology.boundary_loops
124 );
125 self.operations.push(op);
126 }
127
128 pub fn recompute_topology(&mut self) {
129 self.topology = SurfaceTopology::from_mesh(&self.mesh);
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use glam::Vec2;
137
138 fn make_tetrahedron() -> GeoMesh {
139 let mut mesh = GeoMesh::new();
140 mesh.add_vertex(Vec3::new(0.0, 1.0, 0.0), Vec3::Y, Vec2::ZERO);
141 mesh.add_vertex(Vec3::new(-1.0, -1.0, 1.0), Vec3::Y, Vec2::ZERO);
142 mesh.add_vertex(Vec3::new(1.0, -1.0, 1.0), Vec3::Y, Vec2::ZERO);
143 mesh.add_vertex(Vec3::new(0.0, -1.0, -1.0), Vec3::Y, Vec2::ZERO);
144 mesh.add_triangle(0, 1, 2);
145 mesh.add_triangle(0, 2, 3);
146 mesh.add_triangle(0, 3, 1);
147 mesh.add_triangle(1, 3, 2);
148 mesh
149 }
150
151 #[test]
152 fn tetrahedron_euler() {
153 let mesh = make_tetrahedron();
154 let topo = SurfaceTopology::from_mesh(&mesh);
155 assert_eq!(topo.euler_characteristic, 2); }
157
158 #[test]
159 fn genus_zero_for_sphere() {
160 let mesh = make_tetrahedron();
161 let topo = SurfaceTopology::from_mesh(&mesh);
162 assert_eq!(topo.genus, 0);
163 }
164
165 #[test]
166 fn attach_handle_increases_genus() {
167 let mesh = make_tetrahedron();
168 let mut surface = TopologicalSurface::new(mesh);
169 assert_eq!(surface.topology.genus, 0);
170 surface.apply(TopologyOp::AttachHandle { position: Vec3::ZERO, radius: 0.5 });
171 assert_eq!(surface.topology.genus, 1);
172 }
173}