Skip to content

Commit 5b9f906

Browse files
committed
initial abstracting of IO over backends for gltf and stl
1 parent 0a42505 commit 5b9f906

26 files changed

+271
-576
lines changed

src/bmesh/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::float_types::{
77
parry3d::bounding_volume::Aabb,
88
Real,
99
};
10-
use crate::traits::CSG;
10+
use crate::csg::CSG;
1111

1212
use boolmesh::{
1313
compute_boolean,
@@ -21,6 +21,8 @@ use std::{fmt::Debug, sync::OnceLock};
2121
#[cfg(feature = "mesh")]
2222
use crate::mesh::Mesh;
2323

24+
pub mod triangulated;
25+
2426
/// A solid represented by boolmesh’s `Manifold`, wired into csgrs’ `CSG` trait.
2527
///
2628
/// `metadata` is whole-shape metadata, mirroring `Mesh<S>`.

src/bmesh/triangulated.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use crate::bmesh::BMesh;
2+
use crate::float_types::Real;
3+
use nalgebra::{Point3, Vector3};
4+
use crate::triangulated::{TriVertex, Triangulated3D};
5+
6+
impl<S: Clone + Send + Sync + std::fmt::Debug> Triangulated3D for BMesh<S> {
7+
fn visit_triangles<F>(&self, mut f: F)
8+
where
9+
F: FnMut([TriVertex; 3]),
10+
{
11+
let Some(m) = &self.manifold else { return; };
12+
13+
// Manifold has `ps` (points) and `hs` (half-edges). Triangles are 3 half-edges per face.
14+
for face_idx in 0..m.nf {
15+
let base = face_idx * 3;
16+
let v_idx = [
17+
m.hs[base].tail,
18+
m.hs[base + 1].tail,
19+
m.hs[base + 2].tail,
20+
];
21+
22+
let p: [Point3<Real>; 3] = v_idx.map(|i| {
23+
let v = &m.ps[i];
24+
Point3::new(v.x as Real, v.y as Real, v.z as Real)
25+
});
26+
27+
let n = {
28+
let e1 = p[1] - p[0];
29+
let e2 = p[2] - p[0];
30+
Vector3::new(e1.x, e1.y, e1.z)
31+
.cross(&Vector3::new(e2.x, e2.y, e2.z))
32+
.normalize()
33+
};
34+
35+
f([
36+
TriVertex { position: p[0], normal: n },
37+
TriVertex { position: p[1], normal: n },
38+
TriVertex { position: p[2], normal: n },
39+
]);
40+
}
41+
}
42+
}
File renamed without changes.

src/io/gltf.rs

Lines changed: 39 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
2-
#![doc = " glTF 2.0 file format support for Mesh and Sketch objects"]
1+
#![doc = " glTF 2.0 file format support"]
32
#![doc = ""]
43
#![doc = " This module provides export functionality for glTF 2.0 files,"]
54
#![doc = " a modern, efficient, and widely supported 3D asset format."]
65

76
use crate::float_types::{tolerance, Real};
8-
use crate::mesh::Mesh;
9-
use crate::sketch::Sketch;
10-
use geo::CoordsIter;
7+
use crate::triangulated::Triangulated3D;
118
use nalgebra::{Point3, Vector3};
129
use std::fmt::Debug;
1310
use std::io::Write;
14-
1511
use base64::engine::general_purpose::STANDARD as BASE64_ENGINE;
1612
use base64::Engine;
1713

@@ -39,6 +35,26 @@ fn add_unique_vertex_gltf(
3935
(vertices.len() - 1) as u32
4036
}
4137

38+
fn build_gltf_buffers<T: Triangulated3D>(
39+
shape: &T,
40+
) -> (Vec<GltfVertex>, Vec<u32>) {
41+
let mut vertices = Vec::<GltfVertex>::new();
42+
let mut indices = Vec::<u32>::new();
43+
44+
shape.visit_triangles(|tri| {
45+
for v in tri {
46+
let idx = add_unique_vertex_gltf(
47+
&mut vertices,
48+
v.position,
49+
v.normal,
50+
);
51+
indices.push(idx);
52+
}
53+
});
54+
55+
(vertices, indices)
56+
}
57+
4258
/// Build a glTF 2.0 JSON document with a single mesh & single scene,
4359
/// using POSITION and NORMAL attributes and UNSIGNED_INT indices.
4460
///
@@ -159,63 +175,12 @@ fn gltf_from_vertices(
159175
json
160176
}
161177

162-
impl<S: Clone + Debug + Send + Sync> Mesh<S> {
163-
#[doc = " Export this Mesh to glTF 2.0 format as a string"]
164-
#[doc = ""]
165-
#[doc = " Creates a glTF 2.0 (.gltf) JSON file containing:"]
166-
#[doc = " 1. All 3D polygons from `self.polygons` (tessellated to triangles)"]
167-
#[doc = " 2. POSITION and NORMAL attributes and triangle indices"]
168-
#[doc = " 3. A single mesh / single node / single scene"]
169-
#[doc = ""]
170-
#[doc = " The binary data is embedded as a base64 buffer in the JSON file."]
171-
#[doc = ""]
172-
#[doc = " # Arguments"]
173-
#[doc = " * `object_name` - Name for the mesh object in the glTF file"]
174-
#[doc = ""]
175-
#[doc = " # Example"]
176-
#[doc = " ```"]
177-
#[doc = " use csgrs::mesh::Mesh;"]
178-
#[doc = " let csg: Mesh<()> = Mesh::cube(10.0, None);"]
179-
#[doc = " let gltf_content = csg.to_gltf(\"my_cube\");"]
180-
#[doc = " println!(\"{}\", gltf_content);"]
181-
#[doc = " ```"]
178+
impl<S: Clone + Debug + Send + Sync> crate::mesh::Mesh<S> {
182179
pub fn to_gltf(&self, object_name: &str) -> String {
183-
let mut vertices = Vec::<GltfVertex>::new();
184-
let mut indices = Vec::<u32>::new();
185-
186-
for poly in &self.polygons {
187-
let triangles = poly.triangulate();
188-
let normal = poly.plane.normal().normalize();
189-
190-
for triangle in triangles {
191-
for vertex in triangle {
192-
let idx =
193-
add_unique_vertex_gltf(&mut vertices, vertex.pos, normal);
194-
indices.push(idx);
195-
}
196-
}
197-
}
198-
180+
let (vertices, indices) = build_gltf_buffers(self);
199181
gltf_from_vertices(&vertices, &indices, object_name)
200182
}
201183

202-
#[doc = " Export this Mesh to a glTF 2.0 file"]
203-
#[doc = ""]
204-
#[doc = " # Arguments"]
205-
#[doc = " * `writer` - Where to write the glTF JSON data"]
206-
#[doc = " * `object_name` - Name for the object in the glTF file"]
207-
#[doc = ""]
208-
#[doc = " # Example"]
209-
#[doc = " ```"]
210-
#[doc = " use csgrs::mesh::Mesh;"]
211-
#[doc = " use std::fs::File;"]
212-
#[doc = " # fn main() -> Result<(), Box<dyn std::error::Error>> {"]
213-
#[doc = " let csg: Mesh<()> = Mesh::cube(10.0, None);"]
214-
#[doc = " let mut file = File::create(\"stl/output.gltf\")?;"]
215-
#[doc = " csg.write_gltf(&mut file, \"my_mesh\")?;"]
216-
#[doc = " # Ok(())"]
217-
#[doc = " # }"]
218-
#[doc = " ```"]
219184
pub fn write_gltf<W: Write>(
220185
&self,
221186
writer: &mut W,
@@ -226,68 +191,12 @@ impl<S: Clone + Debug + Send + Sync> Mesh<S> {
226191
}
227192
}
228193

229-
impl<S: Clone + Debug + Send + Sync> Sketch<S> {
230-
#[doc = " Export this Sketch to glTF 2.0 format as a string"]
231-
#[doc = ""]
232-
#[doc = " Creates a glTF 2.0 (.gltf) JSON file containing:"]
233-
#[doc = " 1. All 2D polygons from `self.geometry` triangulated and placed at Z=0"]
234-
#[doc = " 2. POSITION and NORMAL attributes and triangle indices"]
235-
#[doc = " 3. A single mesh / single node / single scene"]
236-
#[doc = ""]
237-
#[doc = " The binary data is embedded as a base64 buffer in the JSON file."]
238-
#[doc = ""]
239-
#[doc = " # Arguments"]
240-
#[doc = " * `object_name` - Name for the object in the glTF file"]
241-
#[doc = ""]
242-
#[doc = " # Example"]
243-
#[doc = " ```"]
244-
#[doc = " use csgrs::sketch::Sketch;"]
245-
#[doc = " let sketch: Sketch<()> = Sketch::square(2.0, None);"]
246-
#[doc = " let gltf_content = sketch.to_gltf(\"my_sketch\");"]
247-
#[doc = " println!(\"{}\", gltf_content);"]
248-
#[doc = " ```"]
194+
impl<S: Clone + Debug + Send + Sync> crate::sketch::Sketch<S> {
249195
pub fn to_gltf(&self, object_name: &str) -> String {
250-
let mut vertices = Vec::<GltfVertex>::new();
251-
let mut indices = Vec::<u32>::new();
252-
253-
for geom in &self.geometry.0 {
254-
match geom {
255-
geo::Geometry::Polygon(poly2d) => {
256-
self.add_2d_polygon_to_gltf(poly2d, &mut vertices, &mut indices);
257-
}
258-
geo::Geometry::MultiPolygon(mp) => {
259-
for poly2d in &mp.0 {
260-
self.add_2d_polygon_to_gltf(
261-
poly2d,
262-
&mut vertices,
263-
&mut indices,
264-
);
265-
}
266-
}
267-
_ => {}
268-
}
269-
}
270-
196+
let (vertices, indices) = build_gltf_buffers(self);
271197
gltf_from_vertices(&vertices, &indices, object_name)
272198
}
273199

274-
#[doc = " Export this Sketch to a glTF 2.0 file"]
275-
#[doc = ""]
276-
#[doc = " # Arguments"]
277-
#[doc = " * `writer` - Where to write the glTF JSON data"]
278-
#[doc = " * `object_name` - Name for the object in the glTF file"]
279-
#[doc = ""]
280-
#[doc = " # Example"]
281-
#[doc = " ```"]
282-
#[doc = " use csgrs::sketch::Sketch;"]
283-
#[doc = " use std::fs::File;"]
284-
#[doc = " # fn main() -> Result<(), Box<dyn std::error::Error>> {"]
285-
#[doc = " let sketch: Sketch<()> = Sketch::square(2.0, None);"]
286-
#[doc = " let mut file = File::create(\"stl/output.gltf\")?;"]
287-
#[doc = " sketch.write_gltf(&mut file, \"my_sketch\")?;"]
288-
#[doc = " # Ok(())"]
289-
#[doc = " # }"]
290-
#[doc = " ```"]
291200
pub fn write_gltf<W: Write>(
292201
&self,
293202
writer: &mut W,
@@ -296,38 +205,20 @@ impl<S: Clone + Debug + Send + Sync> Sketch<S> {
296205
let gltf_content = self.to_gltf(object_name);
297206
writer.write_all(gltf_content.as_bytes())
298207
}
208+
}
299209

300-
fn add_2d_polygon_to_gltf(
301-
&self,
302-
poly2d: &geo::Polygon<Real>,
303-
vertices: &mut Vec<GltfVertex>,
304-
indices: &mut Vec<u32>,
305-
) {
306-
let exterior: Vec<[Real; 2]> = poly2d
307-
.exterior()
308-
.coords_iter()
309-
.map(|c| [c.x, c.y])
310-
.collect();
311-
312-
let holes_vec: Vec<Vec<[Real; 2]>> = poly2d
313-
.interiors()
314-
.iter()
315-
.map(|ring| ring.coords_iter().map(|c| [c.x, c.y]).collect())
316-
.collect();
317-
318-
let hole_refs: Vec<&[[Real; 2]]> =
319-
holes_vec.iter().map(|h| &h[..]).collect();
320-
321-
let triangles_2d = Self::triangulate_with_holes(&exterior, &hole_refs);
322-
let normal = Vector3::new(0.0, 0.0, 1.0);
210+
impl<S: Clone + Debug + Send + Sync> crate::bmesh::BMesh<S> {
211+
pub fn to_gltf(&self, object_name: &str) -> String {
212+
let (vertices, indices) = build_gltf_buffers(self);
213+
gltf_from_vertices(&vertices, &indices, object_name)
214+
}
323215

324-
for triangle in triangles_2d {
325-
for point in triangle {
326-
let vertex_3d = Point3::new(point.x, point.y, point.z);
327-
let idx =
328-
add_unique_vertex_gltf(vertices, vertex_3d, normal);
329-
indices.push(idx);
330-
}
331-
}
216+
pub fn write_gltf<W: Write>(
217+
&self,
218+
writer: &mut W,
219+
object_name: &str,
220+
) -> std::io::Result<()> {
221+
let gltf_content = self.to_gltf(object_name);
222+
writer.write_all(gltf_content.as_bytes())
332223
}
333224
}

0 commit comments

Comments
 (0)