Skip to main content

proof_engine/geometry/
topology.rs

1//! Topology operations — genus changes, handle attachment, non-orientable surfaces.
2
3use glam::Vec3;
4use super::{GeoMesh, parametric::ParametricSurface};
5
6/// Topology operation to apply to a surface.
7#[derive(Debug, Clone)]
8pub enum TopologyOp {
9    /// Attach a handle (increases genus by 1).
10    AttachHandle { position: Vec3, radius: f32 },
11    /// Punch a hole through the surface.
12    Puncture { position: Vec3, radius: f32 },
13    /// Create a crosscap (non-orientable).
14    CrossCap { position: Vec3, radius: f32 },
15    /// Connect two surfaces via a tube.
16    Connect { pos_a: Vec3, pos_b: Vec3, tube_radius: f32 },
17    /// Twist a surface strip (half-twist = Möbius).
18    Twist { axis: Vec3, center: Vec3, half_twists: u32 },
19}
20
21/// Topological properties of a surface.
22#[derive(Debug, Clone)]
23pub struct SurfaceTopology {
24    /// Euler characteristic: V - E + F.
25    pub euler_characteristic: i32,
26    /// Genus (number of handles).
27    pub genus: u32,
28    /// Whether the surface is orientable.
29    pub orientable: bool,
30    /// Number of boundary loops.
31    pub boundary_loops: u32,
32    /// Number of connected components.
33    pub components: u32,
34}
35
36impl SurfaceTopology {
37    /// Compute topological properties from a mesh.
38    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        // Count unique edges
43        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        // For closed orientable surface: χ = 2 - 2g
57        let genus = ((2 - euler) / 2).max(0) as u32;
58
59        // Count boundary edges (edges shared by only 1 face)
60        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 }; // simplified
72
73        Self {
74            euler_characteristic: euler,
75            genus,
76            orientable: true, // simplified: assume orientable
77            boundary_loops,
78            components: 1, // simplified
79        }
80    }
81
82    /// Expected Euler characteristic for given genus and boundary.
83    pub fn expected_euler(genus: u32, boundaries: u32) -> i32 {
84        2 - 2 * genus as i32 - boundaries as i32
85    }
86}
87
88/// A surface with tracked topology.
89pub 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    /// Apply a topology operation (modifies the mesh).
102    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                // genus may increase
115            }
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); // sphere topology: V-E+F = 4-6+4 = 2
156    }
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}