@@ -26,11 +26,12 @@ use crate::sketch::Sketch;
2626use crate :: csg:: CSG ;
2727#[ cfg( feature = "sketch" ) ]
2828use geo:: { CoordsIter , Geometry , Polygon as GeoPolygon } ;
29+ use hashbrown:: HashMap ;
2930use nalgebra:: {
3031 Isometry3 , Matrix4 , Point3 , Quaternion , Unit , Vector3 , partial_max, partial_min,
3132} ;
3233use std:: {
33- cmp:: PartialEq ,
34+ cmp:: { Ordering , PartialEq } ,
3435 fmt:: Debug ,
3536 num:: NonZeroU32 ,
3637 sync:: OnceLock ,
@@ -64,6 +65,16 @@ pub mod smoothing;
6465pub mod tpms;
6566pub mod triangulated;
6667
68+ /// Stored as `(position: [f32; 3], normal: [f32; 3])`.
69+ pub type GraphicsMeshVertex = ( [ f32 ; 3 ] , [ f32 ; 3 ] ) ;
70+
71+ /// Mesh data laid out for renderers that want packed f32 vertices plus u32 indices.
72+ #[ derive( Debug , Clone ) ]
73+ pub struct GraphicsMesh {
74+ pub vertices : Vec < GraphicsMeshVertex > ,
75+ pub indices : Vec < u32 > ,
76+ }
77+
6778#[ derive( Clone , Debug ) ]
6879pub struct Mesh < S : Clone + Send + Sync + Debug > {
6980 /// 3D polygons for volumetric shapes
@@ -144,9 +155,114 @@ impl<S: Clone + Send + Sync + Debug> Mesh<S> {
144155 . collect ( )
145156 }
146157
158+ /// Pre-pass to remove T-junctions between polygons by inserting missing
159+ /// vertices on shared or colinear overlapping edges before triangulation.
160+ fn fix_t_junctions_on_shared_edges ( polygons : & mut [ Polygon < S > ] ) {
161+ let eps = tolerance ( ) ;
162+ let eps2 = eps * eps;
163+
164+ if polygons. len ( ) < 2 {
165+ return ;
166+ }
167+
168+ let poly_count = polygons. len ( ) ;
169+ let mut edge_splits: Vec < HashMap < usize , Vec < ( Real , Vertex ) > > > =
170+ vec ! [ HashMap :: new( ) ; poly_count] ;
171+
172+ for ( i, poly_i) in polygons. iter ( ) . enumerate ( ) {
173+ if poly_i. vertices . len ( ) < 2 {
174+ continue ;
175+ }
176+
177+ for vert in & poly_i. vertices {
178+ for ( j, poly_j) in polygons. iter ( ) . enumerate ( ) {
179+ if i == j || poly_j. vertices . len ( ) < 2 {
180+ continue ;
181+ }
182+
183+ let verts_j = & poly_j. vertices ;
184+ let n_j = verts_j. len ( ) ;
185+ for edge_start in 0 ..n_j {
186+ let a = & verts_j[ edge_start] ;
187+ let b = & verts_j[ ( edge_start + 1 ) % n_j] ;
188+ let ab = b. position - a. position ;
189+ let ab_len_sq = ab. norm_squared ( ) ;
190+ if ab_len_sq < eps2 {
191+ continue ;
192+ }
193+
194+ let av = vert. position - a. position ;
195+ let bv = vert. position - b. position ;
196+ if av. norm_squared ( ) < eps2 || bv. norm_squared ( ) < eps2 {
197+ continue ;
198+ }
199+
200+ let t = ab. dot ( & av) / ab_len_sq;
201+ if t <= eps || t >= 1.0 - eps {
202+ continue ;
203+ }
204+
205+ let projected = a. position + ab * t;
206+ if ( vert. position - projected) . norm_squared ( ) > eps2 {
207+ continue ;
208+ }
209+
210+ let new_vertex = Vertex :: new ( vert. position , poly_j. plane . normal ( ) ) ;
211+ let entry = edge_splits[ j] . entry ( edge_start) . or_default ( ) ;
212+ let already_present = entry. iter ( ) . any ( |( existing_t, existing_v) | {
213+ ( existing_t - t) . abs ( ) < eps
214+ || ( existing_v. position - new_vertex. position ) . norm_squared ( )
215+ < eps2
216+ } ) ;
217+
218+ if !already_present {
219+ entry. push ( ( t, new_vertex) ) ;
220+ }
221+ }
222+ }
223+ }
224+ }
225+
226+ for ( poly_index, poly) in polygons. iter_mut ( ) . enumerate ( ) {
227+ let splits_map = & edge_splits[ poly_index] ;
228+ if splits_map. is_empty ( ) {
229+ continue ;
230+ }
231+
232+ let original = poly. vertices . clone ( ) ;
233+ let n = original. len ( ) ;
234+ if n < 2 {
235+ continue ;
236+ }
237+
238+ let extra_vertices: usize = splits_map. values ( ) . map ( Vec :: len) . sum ( ) ;
239+ let mut new_vertices = Vec :: with_capacity ( n + extra_vertices) ;
240+
241+ for edge_start in 0 ..n {
242+ new_vertices. push ( original[ edge_start] ) ;
243+
244+ if let Some ( splits) = splits_map. get ( & edge_start) {
245+ let mut splits_sorted = splits. clone ( ) ;
246+ splits_sorted. sort_by ( |( t_a, _) , ( t_b, _) | {
247+ t_a. partial_cmp ( t_b) . unwrap_or ( Ordering :: Equal )
248+ } ) ;
249+
250+ for ( _, vertex) in splits_sorted {
251+ new_vertices. push ( vertex) ;
252+ }
253+ }
254+ }
255+
256+ poly. vertices = new_vertices;
257+ }
258+ }
259+
147260 /// Triangulate each polygon in the Mesh returning a Mesh containing triangles
148261 pub fn triangulate ( & self ) -> Mesh < S > {
149- let triangles = self . polygons
262+ let mut polygons = self . polygons . clone ( ) ;
263+ Self :: fix_t_junctions_on_shared_edges ( & mut polygons) ;
264+
265+ let triangles = polygons
150266 . iter ( )
151267 . flat_map ( |poly| {
152268 poly. triangulate ( ) . into_iter ( ) . map ( move |triangle| {
@@ -258,7 +374,48 @@ impl<S: Clone + Send + Sync + Debug> Mesh<S> {
258374 dot. acos ( )
259375 }
260376
377+ /// Converts this mesh into f32 vertex/index buffers suitable for rendering.
378+ ///
379+ /// Vertices are deduplicated by exact f32 position and normal bit pattern.
380+ pub fn build_graphics_mesh ( & self ) -> GraphicsMesh {
381+ let triangles = self . triangulate ( ) . polygons ;
382+ let triangle_count = triangles. len ( ) ;
383+
384+ let mut indices = Vec :: with_capacity ( triangle_count * 3 ) ;
385+ let mut vertices = Vec :: with_capacity ( triangle_count * 3 ) ;
386+ let mut vertices_hash: HashMap < ( [ u32 ; 3 ] , [ u32 ; 3 ] ) , u32 > =
387+ HashMap :: with_capacity ( triangle_count * 3 ) ;
388+
389+ for triangle in triangles {
390+ for vertex in triangle. vertices {
391+ let position = [
392+ vertex. position . x as f32 ,
393+ vertex. position . y as f32 ,
394+ vertex. position . z as f32 ,
395+ ] ;
396+ let normal = [
397+ vertex. normal . x as f32 ,
398+ vertex. normal . y as f32 ,
399+ vertex. normal . z as f32 ,
400+ ] ;
401+ let key = ( position. map ( f32:: to_bits) , normal. map ( f32:: to_bits) ) ;
402+
403+ let index = * vertices_hash. entry ( key) . or_insert_with ( || {
404+ let new_index = vertices. len ( ) as u32 ;
405+ vertices. push ( ( position, normal) ) ;
406+ new_index
407+ } ) ;
408+ indices. push ( index) ;
409+ }
410+ }
411+
412+ vertices. shrink_to_fit ( ) ;
413+ GraphicsMesh { vertices, indices }
414+ }
415+
261416 /// Extracts vertices and indices from the Mesh's tessellated polygons.
417+ ///
418+ /// This intentionally does not remove duplicate vertices.
262419 pub fn get_vertices_and_indices ( & self ) -> ( Vec < Point3 < Real > > , Vec < [ u32 ; 3 ] > ) {
263420 let tri_csg = self . triangulate ( ) ;
264421 let vertices = tri_csg
0 commit comments