1use crate::{Diagnostic, Geometry, MAX_COORDINATE_M, Vec3};
2use alloc::{format, vec, vec::Vec};
3use serde::{Deserialize, Serialize};
4#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
5#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
6#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct Mesh {
14 pub positions: Vec<Vec3>,
16 pub normals: Vec<Vec3>,
18 pub indices: Vec<u32>,
20}
21pub fn tessellate(geometry: &Geometry) -> Result<Mesh, Diagnostic> {
38 match geometry {
39 Geometry::Group => Err(Diagnostic::plain("a group has no tessellated mesh")),
40 Geometry::Sphere { radius_m } => sphere(*radius_m),
41 Geometry::Torus {
42 major_radius_m,
43 minor_radius_m,
44 } => torus(*major_radius_m, *minor_radius_m),
45 Geometry::Box { size_m } => box_mesh(*size_m),
46 Geometry::Cylinder { radius_m, height_m } => cylinder(*radius_m, *height_m),
47 Geometry::Plane { width_m, depth_m } => plane(*width_m, *depth_m),
48 Geometry::Line { from_m, to_m } => {
49 valid_point(*from_m)?;
50 valid_point(*to_m)?;
51 Ok(Mesh {
52 positions: vec![*from_m, *to_m],
53 normals: vec![Vec3::ZERO; 2],
54 indices: vec![0, 1],
55 })
56 }
57 Geometry::Mesh { asset } => Err(Diagnostic::plain(format!(
58 "mesh asset `{asset}` requires an adapter-provided mesh"
59 ))),
60 }
61}
62fn valid_point(point: Vec3) -> Result<(), Diagnostic> {
63 if [point.x, point.y, point.z]
64 .into_iter()
65 .all(|value| value.is_finite() && value.abs() <= MAX_COORDINATE_M)
66 {
67 Ok(())
68 } else {
69 Err(Diagnostic::plain(
70 "line endpoints must be finite and within coordinate bounds",
71 ))
72 }
73}
74pub(crate) fn finite_positive(v: f64, what: &str) -> Result<(), Diagnostic> {
75 if v.is_finite() && (1e-6..=MAX_COORDINATE_M).contains(&v) {
76 Ok(())
77 } else {
78 Err(Diagnostic::plain(format!(
79 "{what} must be finite and between 0.000001 and {MAX_COORDINATE_M}"
80 )))
81 }
82}
83fn plane(w: f64, d: f64) -> Result<Mesh, Diagnostic> {
84 finite_positive(w, "plane width")?;
85 finite_positive(d, "plane depth")?;
86 let x = w / 2.;
87 let z = d / 2.;
88 Ok(Mesh {
89 positions: vec![
90 Vec3 {
91 x: -x,
92 y: 0.,
93 z: -z,
94 },
95 Vec3 { x, y: 0., z: -z },
96 Vec3 { x, y: 0., z },
97 Vec3 { x: -x, y: 0., z },
98 ],
99 normals: vec![
100 Vec3 {
101 x: 0.,
102 y: 1.,
103 z: 0.
104 };
105 4
106 ],
107 indices: vec![0, 2, 1, 0, 3, 2],
108 })
109}
110fn box_mesh(s: Vec3) -> Result<Mesh, Diagnostic> {
111 finite_positive(s.x, "box width")?;
112 finite_positive(s.y, "box height")?;
113 finite_positive(s.z, "box depth")?;
114 let x = s.x / 2.;
115 let y = s.y / 2.;
116 let z = s.z / 2.;
117 let faces = [
118 (
119 [
120 Vec3 {
121 x: -x,
122 y: -y,
123 z: -z,
124 },
125 Vec3 { x: -x, y, z: -z },
126 Vec3 { x, y, z: -z },
127 Vec3 { x, y: -y, z: -z },
128 ],
129 Vec3 {
130 x: 0.,
131 y: 0.,
132 z: -1.,
133 },
134 ),
135 (
136 [
137 Vec3 { x: -x, y: -y, z },
138 Vec3 { x, y: -y, z },
139 Vec3 { x, y, z },
140 Vec3 { x: -x, y, z },
141 ],
142 Vec3 {
143 x: 0.,
144 y: 0.,
145 z: 1.,
146 },
147 ),
148 (
149 [
150 Vec3 {
151 x: -x,
152 y: -y,
153 z: -z,
154 },
155 Vec3 { x: -x, y: -y, z },
156 Vec3 { x: -x, y, z },
157 Vec3 { x: -x, y, z: -z },
158 ],
159 Vec3 {
160 x: -1.,
161 y: 0.,
162 z: 0.,
163 },
164 ),
165 (
166 [
167 Vec3 { x, y: -y, z: -z },
168 Vec3 { x, y, z: -z },
169 Vec3 { x, y, z },
170 Vec3 { x, y: -y, z },
171 ],
172 Vec3 {
173 x: 1.,
174 y: 0.,
175 z: 0.,
176 },
177 ),
178 (
179 [
180 Vec3 { x: -x, y, z: -z },
181 Vec3 { x: -x, y, z },
182 Vec3 { x, y, z },
183 Vec3 { x, y, z: -z },
184 ],
185 Vec3 {
186 x: 0.,
187 y: 1.,
188 z: 0.,
189 },
190 ),
191 (
192 [
193 Vec3 {
194 x: -x,
195 y: -y,
196 z: -z,
197 },
198 Vec3 { x, y: -y, z: -z },
199 Vec3 { x, y: -y, z },
200 Vec3 { x: -x, y: -y, z },
201 ],
202 Vec3 {
203 x: 0.,
204 y: -1.,
205 z: 0.,
206 },
207 ),
208 ];
209 let mut positions = Vec::with_capacity(24);
210 let mut normals = Vec::with_capacity(24);
211 let mut indices = Vec::with_capacity(36);
212 for (face, normal) in faces {
213 let start = positions.len() as u32;
214 positions.extend_from_slice(&face);
215 normals.extend_from_slice(&[normal; 4]);
216 indices.extend_from_slice(&[start, start + 1, start + 2, start, start + 2, start + 3]);
217 }
218 Ok(Mesh {
219 positions,
220 normals,
221 indices,
222 })
223}
224fn sphere(r: f64) -> Result<Mesh, Diagnostic> {
225 finite_positive(r, "sphere radius")?;
226 let (rings, segments) = (16usize, 32usize);
227 let mut p = Vec::with_capacity((rings + 1) * (segments + 1));
228 let mut n = Vec::with_capacity(p.capacity());
229 for y in 0..=rings {
230 let v = y as f64 / rings as f64;
231 let phi = v * core::f64::consts::PI;
232 for x in 0..=segments {
233 let u = x as f64 / segments as f64;
234 let t = u * 2. * core::f64::consts::PI;
235 let q = Vec3 {
236 x: libm::sin(phi) * libm::cos(t),
237 y: libm::cos(phi),
238 z: libm::sin(phi) * libm::sin(t),
239 };
240 p.push(Vec3 {
241 x: q.x * r,
242 y: q.y * r,
243 z: q.z * r,
244 });
245 n.push(q)
246 }
247 }
248 let mut i = Vec::new();
249 for y in 0..rings {
250 for x in 0..segments {
251 let a = (y * (segments + 1) + x) as u32;
252 let b = a + segments as u32 + 1;
253 i.extend_from_slice(&[a, a + 1, b, a + 1, b + 1, b])
254 }
255 }
256 Ok(Mesh {
257 positions: p,
258 normals: n,
259 indices: i,
260 })
261}
262fn torus(major: f64, minor: f64) -> Result<Mesh, Diagnostic> {
263 finite_positive(major, "torus major radius")?;
264 finite_positive(minor, "torus minor radius")?;
265 if minor >= major {
266 return Err(Diagnostic::plain(
267 "torus minor radius must be less than major radius",
268 ));
269 }
270 let (rings, segments) = (48usize, 12usize);
271 let mut positions = Vec::with_capacity((rings + 1) * (segments + 1));
272 let mut normals = Vec::with_capacity(positions.capacity());
273 for ring in 0..=rings {
274 let u = ring as f64 / rings as f64 * 2. * core::f64::consts::PI;
275 let (cu, su) = (libm::cos(u), libm::sin(u));
276 for segment in 0..=segments {
277 let v = segment as f64 / segments as f64 * 2. * core::f64::consts::PI;
278 let (cv, sv) = (libm::cos(v), libm::sin(v));
279 let normal = Vec3 {
280 x: cu * cv,
281 y: sv,
282 z: su * cv,
283 };
284 positions.push(Vec3 {
285 x: (major + minor * cv) * cu,
286 y: minor * sv,
287 z: (major + minor * cv) * su,
288 });
289 normals.push(normal);
290 }
291 }
292 let mut indices = Vec::with_capacity(rings * segments * 6);
293 for ring in 0..rings {
294 for segment in 0..segments {
295 let a = (ring * (segments + 1) + segment) as u32;
296 let b = a + segments as u32 + 1;
297 indices.extend_from_slice(&[a, a + 1, b, a + 1, b + 1, b]);
298 }
299 }
300 Ok(Mesh {
301 positions,
302 normals,
303 indices,
304 })
305}
306fn cylinder(r: f64, h: f64) -> Result<Mesh, Diagnostic> {
307 finite_positive(r, "cylinder radius")?;
308 finite_positive(h, "cylinder height")?;
309 let segments = 16usize;
310 let mut p = Vec::with_capacity(2 * segments + 2);
311 let mut n = Vec::with_capacity(p.capacity());
312 for y in [-h / 2., h / 2.] {
313 for x in 0..segments {
314 let t = x as f64 / segments as f64 * 2. * core::f64::consts::PI;
315 let q = Vec3 {
316 x: libm::cos(t),
317 y: 0.,
318 z: libm::sin(t),
319 };
320 p.push(Vec3 {
321 x: q.x * r,
322 y,
323 z: q.z * r,
324 });
325 n.push(q)
326 }
327 }
328 let bottom = p.len() as u32;
329 p.push(Vec3 {
330 x: 0.,
331 y: -h / 2.,
332 z: 0.,
333 });
334 n.push(Vec3 {
335 x: 0.,
336 y: -1.,
337 z: 0.,
338 });
339 let top = p.len() as u32;
340 p.push(Vec3 {
341 x: 0.,
342 y: h / 2.,
343 z: 0.,
344 });
345 n.push(Vec3 {
346 x: 0.,
347 y: 1.,
348 z: 0.,
349 });
350 let mut i = Vec::new();
351 for x in 0..segments {
352 let next = (x + 1) % segments;
353 i.extend_from_slice(&[
354 x as u32,
355 (x + segments) as u32,
356 next as u32,
357 next as u32,
358 (x + segments) as u32,
359 (next + segments) as u32,
360 bottom,
361 x as u32,
362 next as u32,
363 top,
364 (next + segments) as u32,
365 (x + segments) as u32,
366 ]);
367 }
368 Ok(Mesh {
369 positions: p,
370 normals: n,
371 indices: i,
372 })
373}