1use optic_core::consts::{OPTIC_CACHE_VERSION, OPTIC_MAGIC};
32use optic_core::{DrawMode, OpticError, OpticErrorKind, OpticResult};
33use cgmath::Vector2;
34use std::collections::HashMap;
35
36use crate::asset::attr::{ATTRInfo, ColATTR, CustomATTR, IndATTR, NrmATTR, Pos2DATTR, Pos3DATTR, UVMATTR};
37use crate::asset::attr::DataType;
38use crate::handles::mesh::{
39 create_index_buffer, create_mesh_buffer, fill_buffer, fill_index_buffer, set_attr_layout,
40 MeshHandle,
41};
42
43enum OBJ {
48 Parsed {
49 pos_attr: Pos3DATTR,
50 col_attr: ColATTR,
51 uvm_attr: UVMATTR,
52 nrm_attr: NrmATTR,
53 ind_attr: IndATTR,
54 },
55 NonTriangle(String),
56}
57
58impl OBJ {
59 fn parse(src: &str) -> Self {
64 let mut pos_attr = Pos3DATTR::empty();
65 let mut col_attr = ColATTR::empty();
66 let mut uvm_attr = UVMATTR::empty();
67 let mut nrm_attr = NrmATTR::empty();
68 let mut ind_attr = IndATTR::empty();
69
70 let mut pos_data = Vec::new();
71 let mut uvm_data = Vec::new();
72 let mut nrm_data = Vec::new();
73 type Vert = Vec<usize>;
74 let mut verts: Vec<Vert> = Vec::new();
75 let mut unique_verts = HashMap::new();
76
77 for line in src.lines() {
78 let line = line.trim();
79 let words: Vec<&str> = line.split(' ').collect();
80 if words.is_empty() { continue; }
81 match words[0] {
82 "v" => pos_data.push(Self::parse_3(&words)),
83 "vt" => uvm_data.push(Self::parse_2(&words)),
84 "vn" => nrm_data.push(Self::parse_3(&words)),
85 "f" => {
86 if words.len() != 4 {
87 return OBJ::NonTriangle(line.to_string());
88 }
89 for word in &words[1..] {
90 let tokens: Vec<&str> = word.split('/').collect();
91 let vert = tokens.iter()
92 .map(|s| s.parse::<usize>().unwrap_or(1).saturating_sub(1))
93 .collect();
94 verts.push(vert);
95 }
96 }
97 _ => {}
98 }
99 }
100
101 let attr_count = verts.first().map_or(0, |v| v.len());
102 let pos_exists = attr_count > 0;
103 let uvm_exists = attr_count > 1;
104 let nrm_exists = attr_count > 2;
105
106 let def_uvm = [[0.0, 0.0], [0.0, 1.0], [1.0, 0.0]];
107 let def_col = [1.0, 1.0, 1.0, 1.0];
108 let def_nrm = [1.0, 1.0, 1.0];
109
110 for (i, vert) in verts.iter().enumerate() {
111 let key = (
112 pos_exists.then(|| vert[0]),
113 uvm_exists.then(|| vert[1]),
114 nrm_exists.then(|| vert[2]),
115 );
116
117 if let Some(&idx) = unique_verts.get(&key) {
118 ind_attr.push(idx as u32);
119 } else {
120 let v_local = i % 3;
121 let new = pos_attr.data.len();
122 unique_verts.insert(key, new);
123 pos_attr.push(if pos_exists { pos_data[vert[0]] } else { [0.0; 3] });
124 uvm_attr.push(if uvm_exists { uvm_data[vert[1]] } else { def_uvm[v_local] });
125 nrm_attr.push(if nrm_exists { nrm_data[vert[2]] } else { def_nrm });
126 col_attr.push(def_col);
127 ind_attr.push(new as u32);
128 }
129 }
130
131 OBJ::Parsed { pos_attr, col_attr, uvm_attr, nrm_attr, ind_attr }
132 }
133
134 fn parse_2(words: &[&str]) -> [f32; 2] {
137 let x = words.get(1).and_then(|w| w.parse().ok()).unwrap_or(0.0);
138 let y = words.get(2).and_then(|w| w.parse().ok()).unwrap_or(0.0);
139 [x, 1.0 - y]
140 }
141
142 fn parse_3(words: &[&str]) -> [f32; 3] {
144 let x = words.get(1).and_then(|w| w.parse().ok()).unwrap_or(0.0);
145 let y = words.get(2).and_then(|w| w.parse().ok()).unwrap_or(0.0);
146 let z = words.get(3).and_then(|w| w.parse().ok()).unwrap_or(0.0);
147 [x, y, z]
148 }
149}
150
151pub struct Mesh3DFile {
222 pub pos_attr: Pos3DATTR,
223 pub col_attr: ColATTR,
224 pub uvm_attr: UVMATTR,
225 pub nrm_attr: NrmATTR,
226 pub ind_attr: IndATTR,
227 pub cus_attrs: Vec<CustomATTR>,
228}
229
230impl Mesh3DFile {
231 pub fn empty() -> Self {
233 Self {
234 pos_attr: Pos3DATTR::empty(),
235 col_attr: ColATTR::empty(),
236 uvm_attr: UVMATTR::empty(),
237 nrm_attr: NrmATTR::empty(),
238 ind_attr: IndATTR::empty(),
239 cus_attrs: Vec::new(),
240 }
241 }
242
243 pub fn from_obj_src(src: &str) -> OpticResult<Self> {
253 match OBJ::parse(src) {
254 OBJ::NonTriangle(line) => Err(OpticError::new(
255 OpticErrorKind::Asset,
256 &format!("mesh not triangulated at: {line}"),
257 )),
258 OBJ::Parsed { pos_attr, col_attr, uvm_attr, nrm_attr, ind_attr } => {
259 Ok(Self { pos_attr, col_attr, uvm_attr, nrm_attr, ind_attr, cus_attrs: Vec::new() })
260 }
261 }
262 }
263
264 pub fn from_stl_src(data: &[u8]) -> OpticResult<Self> {
273 let mut pos_attr = Pos3DATTR::empty();
274 let mut col_attr = ColATTR::empty();
275 let mut uvm_attr = UVMATTR::empty();
276 let mut nrm_attr = NrmATTR::empty();
277 let mut ind_attr = IndATTR::empty();
278
279 let def_col = [1.0, 1.0, 1.0, 1.0];
280 let def_uvm = [0.0, 0.0];
281 let mut unique_verts: HashMap<(u32, u32, u32, u32, u32, u32), u32> = HashMap::new();
282
283 let push_vert = |pos: [f32; 3], nrm: [f32; 3], unique: &mut HashMap<(u32, u32, u32, u32, u32, u32), u32>,
284 pos_attr: &mut Pos3DATTR, nrm_attr: &mut NrmATTR,
285 col_attr: &mut ColATTR, uvm_attr: &mut UVMATTR| -> u32 {
286 let key = (pos[0].to_bits(), pos[1].to_bits(), pos[2].to_bits(),
287 nrm[0].to_bits(), nrm[1].to_bits(), nrm[2].to_bits());
288 if let Some(&idx) = unique.get(&key) {
289 idx
290 } else {
291 let idx = pos_attr.data.len() as u32;
292 unique.insert(key, idx);
293 pos_attr.push(pos);
294 nrm_attr.push(nrm);
295 col_attr.push(def_col);
296 uvm_attr.push(def_uvm);
297 idx
298 }
299 };
300
301 let is_ascii = data.len() >= 6 && &data[0..6] == b"solid ";
302
303 if is_ascii {
304 let text = std::str::from_utf8(data)
305 .map_err(|_| OpticError::new(OpticErrorKind::Asset, "STL file is not valid UTF-8"))?;
306 let mut nrm = [0.0f32; 3];
307 let mut tri_verts = Vec::new();
308
309 for line in text.lines() {
310 let line = line.trim();
311 if line.starts_with("facet normal") {
312 let parts: Vec<&str> = line.split_whitespace().collect();
313 if parts.len() >= 5 {
314 nrm = [
315 parts[2].parse().unwrap_or(0.0),
316 parts[3].parse().unwrap_or(0.0),
317 parts[4].parse().unwrap_or(0.0),
318 ];
319 }
320 tri_verts.clear();
321 } else if line.starts_with("vertex") {
322 let parts: Vec<&str> = line.split_whitespace().collect();
323 if parts.len() >= 4 {
324 tri_verts.push([
325 parts[1].parse().unwrap_or(0.0),
326 parts[2].parse().unwrap_or(0.0),
327 parts[3].parse().unwrap_or(0.0),
328 ]);
329 }
330 } else if line.starts_with("endfacet") && tri_verts.len() == 3 {
331 for v in &tri_verts {
332 let idx = push_vert(*v, nrm, &mut unique_verts, &mut pos_attr, &mut nrm_attr, &mut col_attr, &mut uvm_attr);
333 ind_attr.push(idx);
334 }
335 }
336 }
337 } else {
338 if data.len() < 84 {
339 return Err(OpticError::new(OpticErrorKind::Asset, &format!("binary STL too short: {} bytes", data.len())));
340 }
341 let tri_count = u32::from_le_bytes([data[80], data[81], data[82], data[83]]) as usize;
342 if data.len() < 84 + tri_count * 50 {
343 return Err(OpticError::new(OpticErrorKind::Asset, &format!("binary STL truncated: expected {} triangles, got {} bytes", tri_count, data.len())));
344 }
345 for i in 0..tri_count {
346 let base = 84 + i * 50;
347 let nrm = [
348 f32::from_le_bytes([data[base], data[base + 1], data[base + 2], data[base + 3]]),
349 f32::from_le_bytes([data[base + 4], data[base + 5], data[base + 6], data[base + 7]]),
350 f32::from_le_bytes([data[base + 8], data[base + 9], data[base + 10], data[base + 11]]),
351 ];
352 let verts = [
353 [f32::from_le_bytes([data[base + 12], data[base + 13], data[base + 14], data[base + 15]]),
354 f32::from_le_bytes([data[base + 16], data[base + 17], data[base + 18], data[base + 19]]),
355 f32::from_le_bytes([data[base + 20], data[base + 21], data[base + 22], data[base + 23]])],
356 [f32::from_le_bytes([data[base + 24], data[base + 25], data[base + 26], data[base + 27]]),
357 f32::from_le_bytes([data[base + 28], data[base + 29], data[base + 30], data[base + 31]]),
358 f32::from_le_bytes([data[base + 32], data[base + 33], data[base + 34], data[base + 35]])],
359 [f32::from_le_bytes([data[base + 36], data[base + 37], data[base + 38], data[base + 39]]),
360 f32::from_le_bytes([data[base + 40], data[base + 41], data[base + 42], data[base + 43]]),
361 f32::from_le_bytes([data[base + 44], data[base + 45], data[base + 46], data[base + 47]])],
362 ];
363 for v in &verts {
364 let idx = push_vert(*v, nrm, &mut unique_verts, &mut pos_attr, &mut nrm_attr, &mut col_attr, &mut uvm_attr);
365 ind_attr.push(idx);
366 }
367 }
368 }
369
370 Ok(Self { pos_attr, col_attr, uvm_attr, nrm_attr, ind_attr, cus_attrs: Vec::new() })
371 }
372
373 #[cfg(debug_assertions)]
381 pub fn from_disk(path: &str) -> OpticResult<Self> {
382 let ext = optic_file::extension(path).unwrap_or_default();
383 let mesh = match ext.as_str() {
384 "obj" => {
385 let src = optic_file::read_string(path)?;
386 Self::from_obj_src(&src)?
387 }
388 "stl" => {
389 let data = optic_file::read_bytes(path)?;
390 Self::from_stl_src(&data)?
391 }
392 _ => return Err(OpticError::new(OpticErrorKind::Asset, &format!("unsupported mesh format: .{ext}"))),
393 };
394 let cache = optic_file::cached_path(path, "omesh");
395 mesh.save_cached(&cache)?;
396 Ok(mesh)
397 }
398
399 #[cfg(not(debug_assertions))]
400 pub fn from_disk(path: &str) -> OpticResult<Self> {
401 let cache = optic_file::cached_path(path, "omesh");
402 Self::from_cached(&cache)
403 }
404
405 pub fn save_cached(&self, path: &str) -> OpticResult<()> {
407 let has_normals = !self.nrm_attr.data.is_empty();
408 let has_uvs = !self.uvm_attr.data.is_empty();
409 let flags = (has_normals as u8) | ((has_uvs as u8) << 1);
410
411 let pos_bytes = self.pos_attr.data.len() * 12;
412 let nrm_bytes = self.nrm_attr.data.len() * 12;
413 let uvm_bytes = self.uvm_attr.data.len() * 8;
414 let col_bytes = self.col_attr.data.len() * 16;
415 let ind_bytes = self.ind_attr.data.len() * 4;
416
417 let size = 10 + 20 + pos_bytes + nrm_bytes + uvm_bytes + col_bytes + ind_bytes;
418 let mut data = Vec::with_capacity(size);
419 data.extend_from_slice(&OPTIC_MAGIC);
420 data.extend_from_slice(&OPTIC_CACHE_VERSION.to_le_bytes());
421 data.push(flags);
422
423 data.extend_from_slice(&(pos_bytes as u32).to_le_bytes());
424 for v in &self.pos_attr.data {
425 data.extend_from_slice(&v[0].to_le_bytes());
426 data.extend_from_slice(&v[1].to_le_bytes());
427 data.extend_from_slice(&v[2].to_le_bytes());
428 }
429
430 data.extend_from_slice(&(nrm_bytes as u32).to_le_bytes());
431 for v in &self.nrm_attr.data {
432 data.extend_from_slice(&v[0].to_le_bytes());
433 data.extend_from_slice(&v[1].to_le_bytes());
434 data.extend_from_slice(&v[2].to_le_bytes());
435 }
436
437 data.extend_from_slice(&(uvm_bytes as u32).to_le_bytes());
438 for v in &self.uvm_attr.data {
439 data.extend_from_slice(&v[0].to_le_bytes());
440 data.extend_from_slice(&v[1].to_le_bytes());
441 }
442
443 data.extend_from_slice(&(col_bytes as u32).to_le_bytes());
444 for v in &self.col_attr.data {
445 data.extend_from_slice(&v[0].to_le_bytes());
446 data.extend_from_slice(&v[1].to_le_bytes());
447 data.extend_from_slice(&v[2].to_le_bytes());
448 data.extend_from_slice(&v[3].to_le_bytes());
449 }
450
451 data.extend_from_slice(&(ind_bytes as u32).to_le_bytes());
452 for v in &self.ind_attr.data {
453 data.extend_from_slice(&v.to_le_bytes());
454 }
455
456 optic_file::write_bytes(path, &data)
457 }
458
459 #[cfg_attr(debug_assertions, allow(dead_code))]
460 fn from_cached(path: &str) -> OpticResult<Self> {
461 let data = optic_file::read_bytes(path)?;
462 if data.len() < 15 {
463 return Err(OpticError::new(OpticErrorKind::Asset, &format!("cached mesh too short: {path}")));
464 }
465 if data[0..8] != OPTIC_MAGIC {
466 return Err(OpticError::new(OpticErrorKind::Asset, &format!("not a valid Optic cache file (bad magic): {path}")));
467 }
468 let version = u16::from_le_bytes([data[8], data[9]]);
469 if version != OPTIC_CACHE_VERSION {
470 return Err(OpticError::new(OpticErrorKind::Asset, &format!(
471 "cache file version {version} is not supported (expected {OPTIC_CACHE_VERSION}): {path}"
472 )));
473 }
474
475 let mut off = 11usize;
476
477 let read_f32x3 = |off: &mut usize, data: &[u8]| -> [f32; 3] {
478 let x = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
479 let y = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
480 let z = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
481 [x, y, z]
482 };
483
484 let read_f32x2 = |off: &mut usize, data: &[u8]| -> [f32; 2] {
485 let x = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
486 let y = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
487 [x, y]
488 };
489
490 let read_f32x4 = |off: &mut usize, data: &[u8]| -> [f32; 4] {
491 let x = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
492 let y = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
493 let z = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
494 let w = f32::from_le_bytes([data[*off], data[*off + 1], data[*off + 2], data[*off + 3]]); *off += 4;
495 [x, y, z, w]
496 };
497
498 let pos_size = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
499 off += 4;
500 if off + pos_size > data.len() {
501 return Err(OpticError::new(OpticErrorKind::Asset, &format!("truncated cached mesh (position): {path}")));
502 }
503 let vert_count = pos_size / 12;
504 let mut pos_attr = Pos3DATTR::empty();
505 for _ in 0..vert_count {
506 pos_attr.push(read_f32x3(&mut off, &data));
507 }
508
509 let nrm_size = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
510 off += 4;
511 let mut nrm_attr = NrmATTR::empty();
512 if nrm_size > 0 {
513 if off + nrm_size > data.len() {
514 return Err(OpticError::new(OpticErrorKind::Asset, &format!("truncated cached mesh (normals): {path}")));
515 }
516 let nrm_count = nrm_size / 12;
517 for _ in 0..nrm_count {
518 nrm_attr.push(read_f32x3(&mut off, &data));
519 }
520 }
521
522 let uvm_size = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
523 off += 4;
524 let mut uvm_attr = UVMATTR::empty();
525 if uvm_size > 0 {
526 if off + uvm_size > data.len() {
527 return Err(OpticError::new(OpticErrorKind::Asset, &format!("truncated cached mesh (UVs): {path}")));
528 }
529 let uvm_count = uvm_size / 8;
530 for _ in 0..uvm_count {
531 uvm_attr.push(read_f32x2(&mut off, &data));
532 }
533 }
534
535 let col_size = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
536 off += 4;
537 if off + col_size > data.len() {
538 return Err(OpticError::new(OpticErrorKind::Asset, &format!("truncated cached mesh (colors): {path}")));
539 }
540 let mut col_attr = ColATTR::empty();
541 let col_count = col_size / 16;
542 for _ in 0..col_count {
543 col_attr.push(read_f32x4(&mut off, &data));
544 }
545
546 let ind_size = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize;
547 off += 4;
548 if off + ind_size > data.len() {
549 return Err(OpticError::new(OpticErrorKind::Asset, &format!("truncated cached mesh (indices): {path}")));
550 }
551 let mut ind_attr = IndATTR::empty();
552 let ind_count = ind_size / 4;
553 for _ in 0..ind_count {
554 let idx = u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
555 off += 4;
556 ind_attr.push(idx);
557 }
558
559 Ok(Self { pos_attr, col_attr, uvm_attr, nrm_attr, ind_attr, cus_attrs: Vec::new() })
560 }
561
562 pub fn cube(side: f32) -> Self {
583 Self::cuboid(side, side, side)
584 }
585
586 pub fn cuboid(w: f32, h: f32, d: f32) -> Self {
591 let mut mesh = Self::empty();
592 let hw = w * 0.5;
593 let hh = h * 0.5;
594 let hd = d * 0.5;
595 let faces: Vec<([f32; 3], [[f32; 3]; 4])> = vec![
596 ([0.0, 0.0, 1.0], [[-hw, -hh, hd], [hw, -hh, hd], [hw, hh, hd], [-hw, hh, hd]]),
597 ([0.0, 0.0, -1.0], [[hw, -hh, -hd], [-hw, -hh, -hd], [-hw, hh, -hd], [hw, hh, -hd]]),
598 ([0.0, 1.0, 0.0], [[-hw, hh, hd], [hw, hh, hd], [hw, hh, -hd], [-hw, hh, -hd]]),
599 ([0.0, -1.0, 0.0], [[-hw, -hh, -hd], [hw, -hh, -hd], [hw, -hh, hd], [-hw, -hh, hd]]),
600 ([1.0, 0.0, 0.0], [[hw, -hh, hd], [hw, -hh, -hd], [hw, hh, -hd], [hw, hh, hd]]),
601 ([-1.0, 0.0, 0.0], [[-hw, -hh, -hd], [-hw, -hh, hd], [-hw, hh, hd], [-hw, hh, -hd]]),
602 ];
603 for (nrm, verts) in &faces {
604 let base = mesh.pos_attr.data.len() as u32;
605 for v in verts {
606 mesh.pos_attr.push(*v);
607 mesh.nrm_attr.push(*nrm);
608 }
609 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
610 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
611 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
612 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
613 mesh.uvm_attr.push([0.0, 0.0]);
614 mesh.uvm_attr.push([1.0, 0.0]);
615 mesh.uvm_attr.push([1.0, 1.0]);
616 mesh.uvm_attr.push([0.0, 1.0]);
617 mesh.ind_attr.push(base);
618 mesh.ind_attr.push(base + 1);
619 mesh.ind_attr.push(base + 2);
620 mesh.ind_attr.push(base);
621 mesh.ind_attr.push(base + 2);
622 mesh.ind_attr.push(base + 3);
623 }
624 mesh
625 }
626
627 pub fn sphere(radius: f32, stacks: u32, sectors: u32) -> Self {
646 let mut mesh = Self::empty();
647 let pi = std::f32::consts::PI;
648 for i in 0..=stacks {
649 let phi = pi * i as f32 / stacks as f32;
650 for j in 0..=sectors {
651 let theta = std::f32::consts::TAU * j as f32 / sectors as f32;
652 let x = phi.sin() * theta.cos();
653 let y = phi.cos();
654 let z = phi.sin() * theta.sin();
655 mesh.pos_attr.push([radius * x, radius * y, radius * z]);
656 mesh.nrm_attr.push([x, y, z]);
657 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
658 mesh.uvm_attr.push([j as f32 / sectors as f32, i as f32 / stacks as f32]);
659 }
660 }
661 for i in 0..stacks {
662 for j in 0..sectors {
663 let first = i * (sectors + 1) + j;
664 let second = first + sectors + 1;
665 mesh.ind_attr.push(first);
666 mesh.ind_attr.push(second);
667 mesh.ind_attr.push(first + 1);
668 mesh.ind_attr.push(second);
669 mesh.ind_attr.push(second + 1);
670 mesh.ind_attr.push(first + 1);
671 }
672 }
673 mesh
674 }
675
676 pub fn cylinder(radius: f32, height: f32, segments: u32, cap: bool) -> Self {
694 let mut mesh = Self::empty();
695 let hh = height * 0.5;
696 for i in 0..=segments {
697 let a = std::f32::consts::TAU * i as f32 / segments as f32;
698 let (s, c) = a.sin_cos();
699 mesh.pos_attr.push([radius * c, hh, radius * s]);
700 mesh.nrm_attr.push([c, 0.0, s]);
701 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
702 mesh.uvm_attr.push([i as f32 / segments as f32, 1.0]);
703 }
704 for i in 0..=segments {
705 let a = std::f32::consts::TAU * i as f32 / segments as f32;
706 let (s, c) = a.sin_cos();
707 mesh.pos_attr.push([radius * c, -hh, radius * s]);
708 mesh.nrm_attr.push([c, 0.0, s]);
709 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
710 mesh.uvm_attr.push([i as f32 / segments as f32, 0.0]);
711 }
712 for i in 0..segments {
713 let t = i;
714 let b = segments + 1 + i;
715 mesh.ind_attr.push(t);
716 mesh.ind_attr.push(b);
717 mesh.ind_attr.push(t + 1);
718 mesh.ind_attr.push(b);
719 mesh.ind_attr.push(b + 1);
720 mesh.ind_attr.push(t + 1);
721 }
722 if cap {
723 let top_center = mesh.pos_attr.data.len() as u32;
724 mesh.pos_attr.push([0.0, hh, 0.0]);
725 mesh.nrm_attr.push([0.0, 1.0, 0.0]);
726 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
727 mesh.uvm_attr.push([0.5, 0.5]);
728 for i in 0..segments {
729 mesh.ind_attr.push(top_center);
730 mesh.ind_attr.push(i);
731 mesh.ind_attr.push(i + 1);
732 }
733 let bot_center = mesh.pos_attr.data.len() as u32;
734 mesh.pos_attr.push([0.0, -hh, 0.0]);
735 mesh.nrm_attr.push([0.0, -1.0, 0.0]);
736 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
737 mesh.uvm_attr.push([0.5, 0.5]);
738 for i in 0..segments {
739 let b = segments + 1 + i;
740 mesh.ind_attr.push(bot_center);
741 mesh.ind_attr.push(b + 1);
742 mesh.ind_attr.push(b);
743 }
744 }
745 mesh
746 }
747
748 pub fn cone(radius: f32, height: f32, segments: u32, cap: bool) -> Self {
766 let mut mesh = Self::empty();
767 let hh = height * 0.5;
768 mesh.pos_attr.push([0.0, hh, 0.0]);
769 mesh.nrm_attr.push([0.0, 1.0, 0.0]);
770 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
771 mesh.uvm_attr.push([0.5, 1.0]);
772 for i in 0..=segments {
773 let a = std::f32::consts::TAU * i as f32 / segments as f32;
774 let (s, c) = a.sin_cos();
775 let nx = c;
776 let nz = s;
777 let ny = radius / height;
778 let len = (nx * nx + ny * ny + nz * nz).sqrt();
779 mesh.pos_attr.push([radius * c, -hh, radius * s]);
780 mesh.nrm_attr.push([nx / len, ny / len, nz / len]);
781 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
782 mesh.uvm_attr.push([i as f32 / segments as f32, 0.0]);
783 }
784 for i in 0..segments {
785 mesh.ind_attr.push(0);
786 mesh.ind_attr.push(i + 1);
787 mesh.ind_attr.push(i + 2);
788 }
789 if cap {
790 let center = mesh.pos_attr.data.len() as u32;
791 mesh.pos_attr.push([0.0, -hh, 0.0]);
792 mesh.nrm_attr.push([0.0, -1.0, 0.0]);
793 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
794 mesh.uvm_attr.push([0.5, 0.5]);
795 for i in 0..segments {
796 mesh.ind_attr.push(center);
797 mesh.ind_attr.push(center + 1 + i + 1);
798 mesh.ind_attr.push(center + 1 + i);
799 }
800 }
801 mesh
802 }
803
804 pub fn torus(major_radius: f32, minor_radius: f32, major_segments: u32, minor_segments: u32) -> Self {
822 let mut mesh = Self::empty();
823 for i in 0..=major_segments {
824 let u = std::f32::consts::TAU * i as f32 / major_segments as f32;
825 for j in 0..=minor_segments {
826 let v = std::f32::consts::TAU * j as f32 / minor_segments as f32;
827 let x = (major_radius + minor_radius * v.cos()) * u.cos();
828 let y = minor_radius * v.sin();
829 let z = (major_radius + minor_radius * v.cos()) * u.sin();
830 mesh.pos_attr.push([x, y, z]);
831 let nx = v.cos() * u.cos();
832 let ny = v.sin();
833 let nz = v.cos() * u.sin();
834 mesh.nrm_attr.push([nx, ny, nz]);
835 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
836 mesh.uvm_attr.push([
837 i as f32 / major_segments as f32,
838 j as f32 / minor_segments as f32,
839 ]);
840 }
841 }
842 let stride = minor_segments + 1;
843 for i in 0..major_segments {
844 for j in 0..minor_segments {
845 let first = i * stride + j;
846 let second = first + stride;
847 mesh.ind_attr.push(first);
848 mesh.ind_attr.push(second);
849 mesh.ind_attr.push(first + 1);
850 mesh.ind_attr.push(second);
851 mesh.ind_attr.push(second + 1);
852 mesh.ind_attr.push(first + 1);
853 }
854 }
855 mesh
856 }
857
858 pub fn plane(width: f32, depth: f32) -> Self {
873 let mut mesh = Self::empty();
874 let hw = width * 0.5;
875 let hd = depth * 0.5;
876 mesh.pos_attr.push([-hw, 0.0, hd]);
877 mesh.pos_attr.push([hw, 0.0, hd]);
878 mesh.pos_attr.push([hw, 0.0, -hd]);
879 mesh.pos_attr.push([-hw, 0.0, -hd]);
880 for _ in 0..4 {
881 mesh.nrm_attr.push([0.0, 1.0, 0.0]);
882 mesh.col_attr.push([1.0, 1.0, 1.0, 1.0]);
883 }
884 mesh.uvm_attr.push([0.0, 0.0]);
885 mesh.uvm_attr.push([1.0, 0.0]);
886 mesh.uvm_attr.push([1.0, 1.0]);
887 mesh.uvm_attr.push([0.0, 1.0]);
888 mesh.ind_attr.push(0);
889 mesh.ind_attr.push(1);
890 mesh.ind_attr.push(2);
891 mesh.ind_attr.push(0);
892 mesh.ind_attr.push(2);
893 mesh.ind_attr.push(3);
894 mesh
895 }
896
897 pub fn attach_custom_attr(&mut self, attr: CustomATTR) {
902 self.cus_attrs.push(attr);
903 }
904
905 pub fn has_no_attr(&self) -> bool {
908 self.starts_with_custom() && self.cus_attrs.is_empty()
909 }
910
911 pub fn starts_with_custom(&self) -> bool {
916 self.pos_attr.is_empty() && self.col_attr.is_empty()
917 && self.uvm_attr.is_empty() && self.nrm_attr.is_empty()
918 }
919
920 pub fn ship(&self) -> MeshHandle {
925 create_mesh3d_handle(self)
926 }
927}
928
929pub struct Mesh2DFile {
947 pub pos_attr: Pos2DATTR,
948 pub layer: u8,
949 pub aspect: f32,
950 pub col_attr: ColATTR,
951 pub uvm_attr: UVMATTR,
952 pub ind_attr: IndATTR,
953 pub cus_attrs: Vec<CustomATTR>,
954}
955
956pub enum Center {
961 TopLeft, TopRight, BottomLeft, BottomRight, Middle, Custom(f32, f32),
962}
963
964impl Center {
965 fn offset(&self) -> Vector2<f32> {
966 match self {
967 Center::TopLeft => Vector2::new(1.0, -1.0),
968 Center::TopRight => Vector2::new(-1.0, -1.0),
969 Center::BottomRight => Vector2::new(-1.0, 1.0),
970 Center::BottomLeft => Vector2::new(1.0, 1.0),
971 Center::Middle => Vector2::new(0.0, 0.0),
972 Center::Custom(x, y) => Vector2::new(-x, -y),
973 }
974 }
975}
976
977impl Mesh2DFile {
978 pub fn empty() -> Self {
980 Self {
981 pos_attr: Pos2DATTR::empty(),
982 layer: 0,
983 aspect: 1.0,
984 col_attr: ColATTR::empty(),
985 uvm_attr: UVMATTR::empty(),
986 ind_attr: IndATTR::empty(),
987 cus_attrs: Vec::new(),
988 }
989 }
990
991 fn offset_pos_by_center(&mut self, center: &Center) {
992 let offset = center.offset();
993 for pos in &mut self.pos_attr.data {
994 pos[0] += offset.x * self.aspect;
995 pos[1] += offset.y;
996 }
997 }
998
999 pub fn set_pos_attr(&mut self, attr: Pos2DATTR) { self.pos_attr = attr; }
1001
1002 pub fn set_layer(&mut self, layer: u8) { self.layer = layer; }
1004
1005 pub fn set_center(&mut self, center: Center) { self.offset_pos_by_center(¢er); }
1007
1008 pub fn set_col_attr(&mut self, attr: ColATTR) { self.col_attr = attr; }
1010
1011 pub fn set_uvm_attr(&mut self, attr: UVMATTR) { self.uvm_attr = attr; }
1013
1014 pub fn set_ind_attr(&mut self, attr: IndATTR) { self.ind_attr = attr; }
1016
1017 pub fn quad(size: &optic_core::Size2D) -> Self {
1029 let mut mesh = Self::empty();
1030 mesh.aspect = size.aspect_ratio();
1031 let x = mesh.aspect;
1032 let y = 1.0;
1033 mesh.set_pos_attr(Pos2DATTR::from_array(&[[-x, y], [x, y], [x, -y], [-x, -y]]));
1034 mesh.offset_pos_by_center(&Center::Middle);
1035 mesh.set_col_attr(ColATTR::from_array(&[[1.0, 1.0, 1.0, 1.0]; 4]));
1036 mesh.set_uvm_attr(UVMATTR::from_array(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]));
1037 mesh.set_ind_attr(IndATTR::from_array(&[0, 2, 1, 2, 0, 3]));
1038 mesh
1039 }
1040
1041 pub fn fullscreen_quad() -> Self {
1045 let mut mesh = Self::empty();
1046 mesh.aspect = 1.0;
1047 mesh.set_pos_attr(Pos2DATTR::from_array(&[
1048 [-1.0, -1.0], [1.0, -1.0], [1.0, 1.0], [-1.0, 1.0],
1049 ]));
1050 mesh.set_col_attr(ColATTR::from_array(&[[1.0, 1.0, 1.0, 1.0]; 4]));
1051 mesh.set_uvm_attr(UVMATTR::from_array(&[
1052 [0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0],
1053 ]));
1054 mesh.set_ind_attr(IndATTR::from_array(&[0, 1, 2, 0, 2, 3]));
1055 mesh
1056 }
1057
1058 pub fn attach_custom_attr(&mut self, attr: CustomATTR) {
1060 self.cus_attrs.push(attr);
1061 }
1062
1063 pub fn circle(radius: f32, segments: u32) -> Self {
1067 let mut mesh = Self::empty();
1068 let mut pos = vec![[0.0f32, 0.0f32]];
1069 let mut uvm = vec![[0.5f32, 0.5f32]];
1070 for i in 0..segments {
1071 let a = std::f32::consts::TAU * i as f32 / segments as f32;
1072 pos.push([radius * a.cos(), radius * a.sin()]);
1073 uvm.push([0.5 + 0.5 * a.cos(), 0.5 + 0.5 * a.sin()]);
1074 }
1075 let mut ind = Vec::new();
1076 for i in 0..segments {
1077 ind.push(0);
1078 ind.push(i + 1);
1079 ind.push(if i + 1 < segments { i + 2 } else { 1 });
1080 }
1081 let vert_count = (segments + 1) as usize;
1082 mesh.set_pos_attr(Pos2DATTR::from(pos));
1083 mesh.set_col_attr(ColATTR::from(vec![[1.0f32; 4]; vert_count]));
1084 mesh.set_uvm_attr(UVMATTR::from(uvm));
1085 mesh.set_ind_attr(IndATTR::from(ind));
1086 mesh
1087 }
1088
1089 pub fn polygon(radius: f32, sides: u32) -> Self {
1091 Self::circle(radius, sides)
1092 }
1093
1094 pub fn ring(inner_radius: f32, outer_radius: f32, segments: u32) -> Self {
1096 let mut mesh = Self::empty();
1097 let mut pos = Vec::new();
1098 let mut uvm = Vec::new();
1099 for i in 0..segments {
1100 let a = std::f32::consts::TAU * i as f32 / segments as f32;
1101 let (s, c) = a.sin_cos();
1102 pos.push([outer_radius * c, outer_radius * s]);
1103 uvm.push([0.5 + 0.5 * c, 0.5 + 0.5 * s]);
1104 }
1105 for i in 0..segments {
1106 let a = std::f32::consts::TAU * i as f32 / segments as f32;
1107 let (s, c) = a.sin_cos();
1108 pos.push([inner_radius * c, inner_radius * s]);
1109 uvm.push([0.5 + 0.5 * c * inner_radius / outer_radius, 0.5 + 0.5 * s * inner_radius / outer_radius]);
1110 }
1111 let mut ind = Vec::new();
1112 for i in 0..segments {
1113 let next = (i + 1) % segments;
1114 let o1 = i;
1115 let o2 = next;
1116 let i1 = segments + i;
1117 let i2 = segments + next;
1118 ind.push(o1);
1119 ind.push(o2);
1120 ind.push(i1);
1121 ind.push(i1);
1122 ind.push(o2);
1123 ind.push(i2);
1124 }
1125 let vert_count = (segments * 2) as usize;
1126 mesh.set_pos_attr(Pos2DATTR::from(pos));
1127 mesh.set_col_attr(ColATTR::from(vec![[1.0f32; 4]; vert_count]));
1128 mesh.set_uvm_attr(UVMATTR::from(uvm));
1129 mesh.set_ind_attr(IndATTR::from(ind));
1130 mesh
1131 }
1132
1133 pub fn rect(width: f32, height: f32) -> Self {
1144 let mut mesh = Self::empty();
1145 let x = width * 0.5;
1146 let y = height * 0.5;
1147 mesh.set_pos_attr(Pos2DATTR::from_array(&[[-x, y], [x, y], [x, -y], [-x, -y]]));
1148 mesh.set_col_attr(ColATTR::from_array(&[[1.0, 1.0, 1.0, 1.0]; 4]));
1149 mesh.set_uvm_attr(UVMATTR::from_array(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]));
1150 mesh.set_ind_attr(IndATTR::from_array(&[0, 2, 1, 2, 0, 3]));
1151 mesh
1152 }
1153
1154 pub fn starts_with_custom(&self) -> bool {
1156 self.pos_attr.is_empty() && self.col_attr.is_empty()
1157 && self.uvm_attr.is_empty()
1158 }
1159
1160 pub fn ship(&self) -> MeshHandle {
1162 create_mesh2d_handle(self)
1163 }
1164}
1165
1166#[allow(dead_code)]
1167trait BufferExt {
1168 fn push_attr<T: DataType>(&mut self, attr: &[T]);
1169}
1170
1171impl BufferExt for Vec<u8> {
1172 fn push_attr<T: DataType>(&mut self, attr: &[T]) {
1173 for elem in attr {
1174 self.extend_from_slice(&elem.u8ify());
1175 }
1176 }
1177}
1178
1179#[allow(unused_mut)]
1180fn create_mesh3d_handle(mesh: &Mesh3DFile) -> MeshHandle {
1181 let (vao_id, buf_id) = create_mesh_buffer();
1182 let ind_id = create_index_buffer();
1183
1184 let mut stride = 0usize;
1185 let mut attrs: Vec<(&ATTRInfo, &dyn AsDataRef)> = Vec::new();
1186 let mut has_indices = false;
1187 let mut ind_count = 0;
1188 let mut ind_data: &[u32];
1189
1190 if !mesh.pos_attr.is_empty() {
1191 let info = &mesh.pos_attr.info;
1192 stride += info.elem_count * info.byte_count;
1193 attrs.push((info, &mesh.pos_attr.data as &dyn AsDataRef));
1194 }
1195 if !mesh.col_attr.is_empty() {
1196 let info = &mesh.col_attr.info;
1197 stride += info.elem_count * info.byte_count;
1198 attrs.push((info, &mesh.col_attr.data as &dyn AsDataRef));
1199 }
1200 if !mesh.uvm_attr.is_empty() {
1201 let info = &mesh.uvm_attr.info;
1202 stride += info.elem_count * info.byte_count;
1203 attrs.push((info, &mesh.uvm_attr.data as &dyn AsDataRef));
1204 }
1205 if !mesh.nrm_attr.is_empty() {
1206 let info = &mesh.nrm_attr.info;
1207 stride += info.elem_count * info.byte_count;
1208 attrs.push((info, &mesh.nrm_attr.data as &dyn AsDataRef));
1209 }
1210 for cus in &mesh.cus_attrs {
1211 stride += cus.info.elem_count * cus.info.byte_count;
1212 attrs.push((&cus.info, &cus.data as &dyn AsDataRef));
1213 }
1214
1215 let vert_count = if mesh.starts_with_custom() {
1216 let first = &mesh.cus_attrs[0];
1217 first.data.len() / (first.info.byte_count * first.info.elem_count)
1218 } else {
1219 mesh.pos_attr.data.len()
1220 } as u32;
1221
1222 let mut buffer: Vec<u8> = Vec::new();
1223 for i in 0..vert_count as usize {
1224 for &(info, data) in &attrs {
1225 let elem_size = info.elem_count * info.byte_count;
1226 let start = i * elem_size;
1227 let end = start + elem_size;
1228 buffer.extend_from_slice(&data.as_bytes()[start..end]);
1229 }
1230 }
1231
1232 crate::GL::bind_vao(vao_id);
1233 crate::GL::bind_buffer(buf_id);
1234 let mut attr_id = 0u32;
1235 let mut offset = 0usize;
1236 let mut layouts = Vec::new();
1237
1238 for &(info, _) in &attrs {
1239 set_attr_layout(info, attr_id, stride, offset);
1240 offset += info.elem_count * info.byte_count;
1241 layouts.push((info.clone(), attr_id));
1242 attr_id += 1;
1243 }
1244
1245 if !buffer.is_empty() {
1246 fill_buffer(buf_id, &buffer);
1247 }
1248
1249 if !mesh.ind_attr.is_empty() {
1250 has_indices = true;
1251 ind_data = &mesh.ind_attr.data;
1252 ind_count = ind_data.len() as u32;
1253 fill_index_buffer(ind_id, ind_data);
1254
1255 }
1256
1257 MeshHandle {
1258 layouts,
1259 draw_mode: DrawMode::Triangles,
1260 has_indices,
1261 vert_count,
1262 ind_count,
1263 vao_id,
1264 buf_id,
1265 ind_id,
1266 vert_stride: stride as u32,
1267 instance_buf_id: 0,
1268 instance_count: 0,
1269 }
1270}
1271
1272#[allow(unused_mut)]
1273fn create_mesh2d_handle(mesh: &Mesh2DFile) -> MeshHandle {
1274 let (vao_id, buf_id) = create_mesh_buffer();
1275 let ind_id = create_index_buffer();
1276
1277 let mut stride = 0usize;
1278 let mut attrs: Vec<(&ATTRInfo, &dyn AsDataRef)> = Vec::new();
1279 let mut has_indices = false;
1280 let mut ind_data: &[u32];
1281 let mut ind_count = 0;
1282
1283 if !mesh.pos_attr.is_empty() {
1284 let info = &mesh.pos_attr.info;
1285 stride += info.elem_count * info.byte_count;
1286 attrs.push((info, &mesh.pos_attr.data as &dyn AsDataRef));
1287 }
1288 if !mesh.col_attr.is_empty() {
1289 let info = &mesh.col_attr.info;
1290 stride += info.elem_count * info.byte_count;
1291 attrs.push((info, &mesh.col_attr.data as &dyn AsDataRef));
1292 }
1293 if !mesh.uvm_attr.is_empty() {
1294 let info = &mesh.uvm_attr.info;
1295 stride += info.elem_count * info.byte_count;
1296 attrs.push((info, &mesh.uvm_attr.data as &dyn AsDataRef));
1297 }
1298 for cus in &mesh.cus_attrs {
1299 stride += cus.info.elem_count * cus.info.byte_count;
1300 attrs.push((&cus.info, &cus.data as &dyn AsDataRef));
1301 }
1302
1303 let vert_count = if mesh.starts_with_custom() {
1304 let first = &mesh.cus_attrs[0];
1305 first.data.len() / (first.info.byte_count * first.info.elem_count)
1306 } else {
1307 mesh.pos_attr.data.len()
1308 } as u32;
1309
1310 let mut buffer: Vec<u8> = Vec::new();
1311 for i in 0..vert_count as usize {
1312 for &(info, data) in &attrs {
1313 let elem_size = info.elem_count * info.byte_count;
1314 let start = i * elem_size;
1315 let end = start + elem_size;
1316 buffer.extend_from_slice(&data.as_bytes()[start..end]);
1317 }
1318 }
1319
1320 crate::GL::bind_vao(vao_id);
1321 crate::GL::bind_buffer(buf_id);
1322 let mut attr_id = 0u32;
1323 let mut offset = 0usize;
1324 let mut layouts = Vec::new();
1325
1326 for &(info, _) in &attrs {
1327 set_attr_layout(info, attr_id, stride, offset);
1328 offset += info.elem_count * info.byte_count;
1329 layouts.push((info.clone(), attr_id));
1330 attr_id += 1;
1331 }
1332
1333 if !buffer.is_empty() {
1334 fill_buffer(buf_id, &buffer);
1335 }
1336
1337 if !mesh.ind_attr.is_empty() {
1338 has_indices = true;
1339 ind_data = &mesh.ind_attr.data;
1340 ind_count = ind_data.len() as u32;
1341 fill_index_buffer(ind_id, ind_data);
1342
1343 }
1344
1345 MeshHandle {
1346 layouts,
1347 draw_mode: DrawMode::Triangles,
1348 has_indices,
1349 vert_count,
1350 ind_count,
1351 vao_id,
1352 buf_id,
1353 ind_id,
1354 vert_stride: stride as u32,
1355 instance_buf_id: 0,
1356 instance_count: 0,
1357 }
1358}
1359
1360trait AsDataRef {
1361 fn as_bytes(&self) -> &[u8];
1362}
1363
1364impl AsDataRef for Vec<[f32; 3]> {
1365 fn as_bytes(&self) -> &[u8] {
1366 unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 12) }
1367 }
1368}
1369
1370impl AsDataRef for Vec<[f32; 2]> {
1371 fn as_bytes(&self) -> &[u8] {
1372 unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 8) }
1373 }
1374}
1375
1376impl AsDataRef for Vec<[f32; 4]> {
1377 fn as_bytes(&self) -> &[u8] {
1378 unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 16) }
1379 }
1380}
1381
1382impl AsDataRef for Vec<u32> {
1383 fn as_bytes(&self) -> &[u8] {
1384 unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 4) }
1385 }
1386}
1387
1388impl AsDataRef for Vec<u8> {
1389 fn as_bytes(&self) -> &[u8] {
1390 self
1391 }
1392}
1393
1394#[cfg(test)]
1395mod tests {
1396 use super::*;
1397 use optic_core::Size2D;
1398
1399 #[test]
1400 fn mesh3d_file_empty() {
1401 let m = Mesh3DFile::empty();
1402 assert!(m.pos_attr.is_empty());
1403 assert!(m.col_attr.is_empty());
1404 assert!(m.uvm_attr.is_empty());
1405 assert!(m.nrm_attr.is_empty());
1406 assert!(m.ind_attr.is_empty());
1407 assert!(m.cus_attrs.is_empty());
1408 }
1409
1410 #[test]
1411 fn mesh3d_file_starts_with_custom() {
1412 let m = Mesh3DFile::empty();
1413 assert!(m.starts_with_custom());
1414 }
1415
1416 #[test]
1417 fn mesh3d_file_has_no_attr_true() {
1418 let m = Mesh3DFile::empty();
1419 assert!(m.has_no_attr());
1420 }
1421
1422 #[test]
1423 fn mesh3d_file_has_no_attr_false_with_pos() {
1424 let mut m = Mesh3DFile::empty();
1425 m.pos_attr.push([1.0, 2.0, 3.0]);
1426 assert!(!m.has_no_attr());
1427 }
1428
1429 #[test]
1430 fn mesh3d_file_attach_custom() {
1431 let mut m = Mesh3DFile::empty();
1432 let attr = CustomATTR::empty::<u32>("bone_ids");
1433 m.attach_custom_attr(attr);
1434 assert_eq!(m.cus_attrs.len(), 1);
1435 }
1436
1437 #[test]
1438 fn mesh2d_file_empty() {
1439 let m = Mesh2DFile::empty();
1440 assert!(m.pos_attr.is_empty());
1441 assert!(m.col_attr.is_empty());
1442 assert!(m.uvm_attr.is_empty());
1443 assert!(m.ind_attr.is_empty());
1444 assert!(m.cus_attrs.is_empty());
1445 assert_eq!(m.layer, 0);
1446 assert!((m.aspect - 1.0).abs() < f32::EPSILON);
1447 }
1448
1449 #[test]
1450 fn mesh2d_file_starts_with_custom() {
1451 let m = Mesh2DFile::empty();
1452 assert!(m.starts_with_custom());
1453 }
1454
1455 #[test]
1456 fn mesh2d_quad() {
1457 let size = Size2D::from(100, 100);
1458 let m = Mesh2DFile::quad(&size);
1459 assert_eq!(m.pos_attr.data.len(), 4);
1460 assert_eq!(m.col_attr.data.len(), 4);
1461 assert_eq!(m.uvm_attr.data.len(), 4);
1462 assert_eq!(m.ind_attr.data.len(), 6);
1463 }
1464
1465 #[test]
1466 fn mesh2d_setters() {
1467 let mut m = Mesh2DFile::empty();
1468 m.set_pos_attr(Pos2DATTR::from_array(&[[0.0, 0.0], [1.0, 0.0]]));
1469 m.set_col_attr(ColATTR::from_array(&[[1.0; 4]; 2]));
1470 m.set_uvm_attr(UVMATTR::from_array(&[[0.0, 0.0], [1.0, 0.0]]));
1471 m.set_ind_attr(IndATTR::from_array(&[0, 1]));
1472 m.set_layer(5);
1473 assert_eq!(m.pos_attr.data.len(), 2);
1474 assert_eq!(m.layer, 5);
1475 }
1476
1477 #[test]
1478 fn mesh2d_set_center_topleft() {
1479 let mut m = Mesh2DFile::empty();
1480 m.set_pos_attr(Pos2DATTR::from_array(&[[0.0, 0.0]]));
1481 m.set_center(Center::TopLeft);
1482 assert!((m.pos_attr.data[0][0] - 1.0).abs() < f32::EPSILON);
1483 assert!((m.pos_attr.data[0][1] - (-1.0)).abs() < f32::EPSILON);
1484 }
1485
1486 #[test]
1487 fn mesh2d_set_center_custom() {
1488 let mut m = Mesh2DFile::empty();
1489 m.set_pos_attr(Pos2DATTR::from_array(&[[0.0, 0.0]]));
1490 m.set_center(Center::Custom(0.5, 0.5));
1491 assert!((m.pos_attr.data[0][0] - (-0.5)).abs() < f32::EPSILON);
1492 assert!((m.pos_attr.data[0][1] - (-0.5)).abs() < f32::EPSILON);
1493 }
1494
1495 #[test]
1496 fn mesh3d_from_obj_src() {
1497 let obj = "v 0.0 0.0 0.0\nv 1.0 0.0 0.0\nv 0.0 1.0 0.0\nf 1 2 3";
1498 let mesh = Mesh3DFile::from_obj_src(obj).unwrap();
1499 assert_eq!(mesh.pos_attr.data.len(), 3);
1500 assert_eq!(mesh.ind_attr.data.len(), 3);
1501 }
1502
1503 #[test]
1504 fn mesh3d_from_obj_src_non_triangle() {
1505 let obj = "v 0.0 0.0 0.0\nv 1.0 0.0 0.0\nv 0.0 1.0 0.0\nf 1 2 3 4";
1506 let result = Mesh3DFile::from_obj_src(obj);
1507 assert!(result.is_err());
1508 }
1509
1510 #[test]
1511 fn mesh3d_cached_roundtrip() {
1512 let obj = "v 0.0 0.0 0.0\nv 1.0 0.0 0.0\nv 0.0 1.0 0.0\nf 1 2 3";
1513 let mesh = Mesh3DFile::from_obj_src(obj).unwrap();
1514 let path = "/tmp/optic_test_mesh3d_cache.omesh";
1515 mesh.save_cached(path).unwrap();
1516 let loaded = Mesh3DFile::from_cached(path).unwrap();
1517 assert_eq!(loaded.pos_attr.data.len(), mesh.pos_attr.data.len());
1518 assert_eq!(loaded.ind_attr.data.len(), mesh.ind_attr.data.len());
1519 assert_eq!(loaded.pos_attr.data, mesh.pos_attr.data);
1520 assert_eq!(loaded.ind_attr.data, mesh.ind_attr.data);
1521 let _ = std::fs::remove_file(path);
1522 }
1523
1524 #[test]
1525 fn mesh3d_from_stl_ascii() {
1526 let stl = "solid cube\n\
1527 facet normal 0.0 0.0 1.0\n\
1528 outer loop\n\
1529 vertex -1.0 -1.0 1.0\n\
1530 vertex 1.0 -1.0 1.0\n\
1531 vertex 1.0 1.0 1.0\n\
1532 endloop\n\
1533 endfacet\n\
1534 endsolid cube\n";
1535 let mesh = Mesh3DFile::from_stl_src(stl.as_bytes()).unwrap();
1536 assert_eq!(mesh.pos_attr.data.len(), 3);
1537 assert_eq!(mesh.ind_attr.data.len(), 3);
1538 assert_eq!(mesh.nrm_attr.data.len(), 3);
1539 assert!((mesh.nrm_attr.data[0][2] - 1.0).abs() < f32::EPSILON);
1540 }
1541
1542 #[test]
1543 fn obj_parse_simple_triangle() {
1544 let obj = "v 0.0 0.0 0.0\nv 1.0 0.0 0.0\nv 0.0 1.0 0.0\nf 1 2 3";
1545 match OBJ::parse(obj) {
1546 OBJ::Parsed { pos_attr, ind_attr, .. } => {
1547 assert_eq!(pos_attr.data.len(), 3);
1548 assert_eq!(ind_attr.data.len(), 3);
1549 assert_eq!(pos_attr.data[0], [0.0, 0.0, 0.0]);
1550 assert_eq!(pos_attr.data[1], [1.0, 0.0, 0.0]);
1551 assert_eq!(pos_attr.data[2], [0.0, 1.0, 0.0]);
1552 }
1553 OBJ::NonTriangle(_) => panic!("expected parsed triangle"),
1554 }
1555 }
1556
1557 #[test]
1558 fn obj_parse_with_uv_and_normals() {
1559 let obj = "v 0.0 0.0 0.0\nv 1.0 0.0 0.0\nv 0.0 1.0 0.0\nvt 0.0 0.0\nvt 1.0 0.0\nvt 0.0 1.0\nvn 0.0 0.0 1.0\nf 1/1/1 2/2/1 3/3/1";
1560 match OBJ::parse(obj) {
1561 OBJ::Parsed { pos_attr, uvm_attr, nrm_attr, ind_attr, .. } => {
1562 assert_eq!(pos_attr.data.len(), 3);
1563 assert_eq!(uvm_attr.data.len(), 3);
1564 assert!(!nrm_attr.data.is_empty());
1565 assert_eq!(ind_attr.data.len(), 3);
1566 }
1567 OBJ::NonTriangle(_) => panic!("expected parsed triangle"),
1568 }
1569 }
1570
1571 #[test]
1572 fn obj_parse_non_triangle() {
1573 let obj = "v 0.0 0.0 0.0\nv 1.0 0.0 0.0\nv 0.0 1.0 0.0\nv 1.0 1.0 0.0\nf 1 2 3 4";
1574 match OBJ::parse(obj) {
1575 OBJ::Parsed { .. } => panic!("expected non-triangle error"),
1576 OBJ::NonTriangle(line) => assert!(line.contains("4")),
1577 }
1578 }
1579
1580 #[test]
1581 fn obj_parse_empty() {
1582 match OBJ::parse("") {
1583 OBJ::Parsed { pos_attr, ind_attr, .. } => {
1584 assert!(pos_attr.is_empty());
1585 assert!(ind_attr.is_empty());
1586 }
1587 OBJ::NonTriangle(_) => panic!("expected empty parsed"),
1588 }
1589 }
1590
1591 #[test]
1592 fn obj_parse_parse_2() {
1593 let words = vec!["vt", "0.5", "0.5"];
1594 let result = OBJ::parse_2(&words);
1595 assert!((result[0] - 0.5).abs() < f32::EPSILON);
1596 assert!((result[1] - 0.5).abs() < f32::EPSILON);
1597 }
1598
1599 #[test]
1600 fn obj_parse_parse_3() {
1601 let words = vec!["v", "1.0", "2.0", "3.0"];
1602 let result = OBJ::parse_3(&words);
1603 assert_eq!(result, [1.0, 2.0, 3.0]);
1604 }
1605
1606 #[test]
1607 fn center_variants() {
1608 assert_eq!(Center::TopLeft.offset(), Vector2::new(1.0, -1.0));
1609 assert_eq!(Center::TopRight.offset(), Vector2::new(-1.0, -1.0));
1610 assert_eq!(Center::BottomRight.offset(), Vector2::new(-1.0, 1.0));
1611 assert_eq!(Center::BottomLeft.offset(), Vector2::new(1.0, 1.0));
1612 assert_eq!(Center::Middle.offset(), Vector2::new(0.0, 0.0));
1613 }
1614
1615 #[test]
1616 fn mesh2d_circle() {
1617 let m = Mesh2DFile::circle(1.0, 8);
1618 assert_eq!(m.pos_attr.data.len(), 9);
1619 assert_eq!(m.col_attr.data.len(), 9);
1620 assert_eq!(m.uvm_attr.data.len(), 9);
1621 assert_eq!(m.ind_attr.data.len(), 24);
1622 }
1623
1624 #[test]
1625 fn mesh2d_polygon() {
1626 let m = Mesh2DFile::polygon(1.0, 6);
1627 assert_eq!(m.pos_attr.data.len(), 7);
1628 assert_eq!(m.ind_attr.data.len(), 18);
1629 }
1630
1631 #[test]
1632 fn mesh2d_ring() {
1633 let m = Mesh2DFile::ring(0.5, 1.0, 8);
1634 assert_eq!(m.pos_attr.data.len(), 16);
1635 assert_eq!(m.ind_attr.data.len(), 48);
1636 }
1637
1638 #[test]
1639 fn mesh2d_rect() {
1640 let m = Mesh2DFile::rect(2.0, 3.0);
1641 assert_eq!(m.pos_attr.data.len(), 4);
1642 assert_eq!(m.ind_attr.data.len(), 6);
1643 assert!((m.pos_attr.data[0][0] - (-1.0)).abs() < f32::EPSILON);
1644 assert!((m.pos_attr.data[0][1] - 1.5).abs() < f32::EPSILON);
1645 }
1646
1647 #[test]
1648 fn mesh3d_cube() {
1649 let m = Mesh3DFile::cube(2.0);
1650 assert_eq!(m.pos_attr.data.len(), 24);
1651 assert_eq!(m.nrm_attr.data.len(), 24);
1652 assert_eq!(m.ind_attr.data.len(), 36);
1653 }
1654
1655 #[test]
1656 fn mesh3d_cuboid() {
1657 let m = Mesh3DFile::cuboid(1.0, 2.0, 3.0);
1658 assert_eq!(m.pos_attr.data.len(), 24);
1659 assert_eq!(m.ind_attr.data.len(), 36);
1660 }
1661
1662 #[test]
1663 fn mesh3d_sphere() {
1664 let m = Mesh3DFile::sphere(1.0, 8, 16);
1665 let verts = (8 + 1) * (16 + 1);
1666 assert_eq!(m.pos_attr.data.len(), verts);
1667 assert_eq!(m.nrm_attr.data.len(), verts);
1668 assert_eq!(m.ind_attr.data.len(), 8 * 16 * 6);
1669 }
1670
1671 #[test]
1672 fn mesh3d_cylinder_with_caps() {
1673 let m = Mesh3DFile::cylinder(0.5, 2.0, 16, true);
1674 let body_verts = (16 + 1) * 2;
1675 let cap_verts = 2;
1676 assert_eq!(m.pos_attr.data.len(), body_verts + cap_verts);
1677 }
1678
1679 #[test]
1680 fn mesh3d_cylinder_no_caps() {
1681 let m = Mesh3DFile::cylinder(0.5, 2.0, 16, false);
1682 assert_eq!(m.pos_attr.data.len(), (16 + 1) * 2);
1683 }
1684
1685 #[test]
1686 fn mesh3d_cone_with_cap() {
1687 let m = Mesh3DFile::cone(0.5, 2.0, 16, true);
1688 let body_verts = 1 + (16 + 1);
1689 let cap_verts = 1;
1690 assert_eq!(m.pos_attr.data.len(), body_verts + cap_verts);
1691 }
1692
1693 #[test]
1694 fn mesh3d_cone_no_cap() {
1695 let m = Mesh3DFile::cone(0.5, 2.0, 16, false);
1696 assert_eq!(m.pos_attr.data.len(), 1 + (16 + 1));
1697 }
1698
1699 #[test]
1700 fn mesh3d_torus() {
1701 let m = Mesh3DFile::torus(1.0, 0.3, 12, 8);
1702 let verts = (12 + 1) * (8 + 1);
1703 assert_eq!(m.pos_attr.data.len(), verts);
1704 assert_eq!(m.nrm_attr.data.len(), verts);
1705 assert_eq!(m.ind_attr.data.len(), 12 * 8 * 6);
1706 }
1707
1708 #[test]
1709 fn mesh3d_plane() {
1710 let m = Mesh3DFile::plane(2.0, 3.0);
1711 assert_eq!(m.pos_attr.data.len(), 4);
1712 assert_eq!(m.nrm_attr.data.len(), 4);
1713 assert_eq!(m.ind_attr.data.len(), 6);
1714 for nrm in &m.nrm_attr.data {
1715 assert!((nrm[1] - 1.0).abs() < f32::EPSILON);
1716 }
1717 }
1718}