1use crate::MeshError;
4use glam::Vec3;
5
6#[cfg(feature = "std")]
7use std::fs::File;
8#[cfg(feature = "std")]
9use std::io::{BufRead, BufReader};
10#[cfg(feature = "std")]
11use std::path::Path;
12
13#[derive(Debug, Clone, Default)]
15pub struct TriMesh {
16 pub vertices: Vec<f32>,
18 pub indices: Vec<i32>,
20 pub vert_count: usize,
21 pub tri_count: usize,
22}
23
24impl TriMesh {
25 pub fn new() -> Self {
27 Self::default()
28 }
29
30 #[cfg(feature = "std")]
34 pub fn from_obj<P: AsRef<Path>>(path: P) -> Result<Self, MeshError> {
35 let file = File::open(path)?;
36 let reader = BufReader::new(file);
37
38 let mut mesh = Self::new();
39
40 for line in reader.lines() {
41 let line = line?;
42 Self::parse_obj_line(&line, &mut mesh)?;
43 }
44
45 Ok(mesh)
46 }
47
48 pub fn from_obj_str(content: &str) -> Result<Self, MeshError> {
70 let mut mesh = Self::new();
71
72 for line in content.lines() {
73 Self::parse_obj_line(line, &mut mesh)?;
74 }
75
76 Ok(mesh)
77 }
78
79 fn parse_obj_line(line: &str, mesh: &mut Self) -> Result<(), MeshError> {
81 let mut tokens = line.split_whitespace();
82
83 match tokens.next() {
84 Some("v") => {
85 let x = tokens
87 .next()
88 .ok_or_else(|| {
89 MeshError::ObjParse("Invalid vertex: missing x coordinate".to_string())
90 })?
91 .parse::<f32>()
92 .map_err(|_| {
93 MeshError::ObjParse(
94 "Invalid vertex: x coordinate is not a number".to_string(),
95 )
96 })?;
97
98 let y = tokens
99 .next()
100 .ok_or_else(|| {
101 MeshError::ObjParse("Invalid vertex: missing y coordinate".to_string())
102 })?
103 .parse::<f32>()
104 .map_err(|_| {
105 MeshError::ObjParse(
106 "Invalid vertex: y coordinate is not a number".to_string(),
107 )
108 })?;
109
110 let z = tokens
111 .next()
112 .ok_or_else(|| {
113 MeshError::ObjParse("Invalid vertex: missing z coordinate".to_string())
114 })?
115 .parse::<f32>()
116 .map_err(|_| {
117 MeshError::ObjParse(
118 "Invalid vertex: z coordinate is not a number".to_string(),
119 )
120 })?;
121
122 mesh.vertices.push(x);
123 mesh.vertices.push(y);
124 mesh.vertices.push(z);
125 mesh.vert_count += 1;
126 }
127 Some("f") => {
128 let mut face_indices = Vec::new();
130
131 for token in tokens {
132 let index_str = token.split('/').next().ok_or_else(|| {
133 MeshError::ObjParse("Invalid face: missing vertex index".to_string())
134 })?;
135
136 let index = index_str.parse::<i32>().map_err(|_| {
137 MeshError::ObjParse(
138 "Invalid face: vertex index is not a number".to_string(),
139 )
140 })? - 1; face_indices.push(index);
143 }
144
145 if face_indices.len() < 3 {
146 return Err(MeshError::ObjParse(
147 "Invalid face: less than 3 vertices".to_string(),
148 ));
149 }
150
151 for i in 1..(face_indices.len() - 1) {
153 mesh.indices.push(face_indices[0]);
154 mesh.indices.push(face_indices[i]);
155 mesh.indices.push(face_indices[i + 1]);
156 mesh.tri_count += 1;
157 }
158 }
159 _ => {
160 }
162 }
163
164 Ok(())
165 }
166
167 pub fn calculate_bounds(&self) -> (Vec3, Vec3) {
169 if self.vert_count == 0 {
170 return (Vec3::ZERO, Vec3::ZERO);
171 }
172
173 let mut bmin = Vec3::new(f32::MAX, f32::MAX, f32::MAX);
174 let mut bmax = Vec3::new(f32::MIN, f32::MIN, f32::MIN);
175
176 for i in 0..self.vert_count {
177 let x = self.vertices[i * 3];
178 let y = self.vertices[i * 3 + 1];
179 let z = self.vertices[i * 3 + 2];
180
181 bmin.x = bmin.x.min(x);
182 bmin.y = bmin.y.min(y);
183 bmin.z = bmin.z.min(z);
184
185 bmax.x = bmax.x.max(x);
186 bmax.y = bmax.y.max(y);
187 bmax.z = bmax.z.max(z);
188 }
189
190 (bmin, bmax)
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_from_obj_str_simple_triangle() {
200 let obj = r#"
201v 0.0 0.0 0.0
202v 1.0 0.0 0.0
203v 0.5 1.0 0.0
204f 1 2 3
205"#;
206 let mesh = TriMesh::from_obj_str(obj).unwrap();
207 assert_eq!(mesh.vert_count, 3);
208 assert_eq!(mesh.tri_count, 1);
209 assert_eq!(mesh.vertices.len(), 9);
210 assert_eq!(mesh.indices.len(), 3);
211 }
212
213 #[test]
214 fn test_from_obj_str_quad_triangulation() {
215 let obj = r#"
216v 0.0 0.0 0.0
217v 1.0 0.0 0.0
218v 1.0 1.0 0.0
219v 0.0 1.0 0.0
220f 1 2 3 4
221"#;
222 let mesh = TriMesh::from_obj_str(obj).unwrap();
223 assert_eq!(mesh.vert_count, 4);
224 assert_eq!(mesh.tri_count, 2); assert_eq!(mesh.indices.len(), 6);
226 }
227
228 #[test]
229 fn test_from_obj_str_with_texture_coords() {
230 let obj = r#"
232v 0.0 0.0 0.0
233v 1.0 0.0 0.0
234v 0.5 1.0 0.0
235vt 0.0 0.0
236vt 1.0 0.0
237vt 0.5 1.0
238f 1/1 2/2 3/3
239"#;
240 let mesh = TriMesh::from_obj_str(obj).unwrap();
241 assert_eq!(mesh.vert_count, 3);
242 assert_eq!(mesh.tri_count, 1);
243 }
244
245 #[test]
246 fn test_from_obj_str_with_normals() {
247 let obj = r#"
249v 0.0 0.0 0.0
250v 1.0 0.0 0.0
251v 0.5 1.0 0.0
252vn 0.0 0.0 1.0
253f 1//1 2//1 3//1
254"#;
255 let mesh = TriMesh::from_obj_str(obj).unwrap();
256 assert_eq!(mesh.vert_count, 3);
257 assert_eq!(mesh.tri_count, 1);
258 }
259
260 #[test]
261 fn test_from_obj_str_skips_comments() {
262 let obj = r#"
263# This is a comment
264v 0.0 0.0 0.0
265# Another comment
266v 1.0 0.0 0.0
267v 0.5 1.0 0.0
268f 1 2 3
269"#;
270 let mesh = TriMesh::from_obj_str(obj).unwrap();
271 assert_eq!(mesh.vert_count, 3);
272 assert_eq!(mesh.tri_count, 1);
273 }
274
275 #[test]
276 fn test_from_obj_str_invalid_vertex() {
277 let obj = "v 0.0 0.0"; let result = TriMesh::from_obj_str(obj);
279 assert!(result.is_err());
280 }
281
282 #[test]
283 fn test_from_obj_str_invalid_face() {
284 let obj = r#"
285v 0.0 0.0 0.0
286v 1.0 0.0 0.0
287f 1 2
288"#;
289 let result = TriMesh::from_obj_str(obj);
290 assert!(result.is_err());
291 }
292}