khora_io/asset/decoders/mesh/
obj.rs1use ahash::AHashMap;
18use anyhow::{Context, Result};
19use khora_core::{
20 math::{geometry::Aabb, Vec2, Vec3},
21 renderer::api::{
22 pipeline::enums::{PrimitiveTopology, VertexFormat},
23 pipeline::VertexAttributeDescriptor,
24 scene::Mesh,
25 },
26};
27use std::error::Error;
28
29use crate::asset::AssetDecoder;
30
31#[derive(Clone, Default)]
33pub struct ObjDecoder;
34
35impl AssetDecoder<Mesh> for ObjDecoder {
36 fn load(&self, bytes: &[u8]) -> Result<Mesh, Box<dyn Error + Send + Sync>> {
37 let obj_text = std::str::from_utf8(bytes).context("OBJ file is not valid UTF-8")?;
38
39 let obj = tobj::load_obj_buf(
40 &mut std::io::Cursor::new(obj_text),
41 &tobj::LoadOptions {
42 triangulate: true,
43 single_index: true,
44 ..Default::default()
45 },
46 |_| Ok((Vec::new(), AHashMap::new())),
47 )
48 .context("Failed to parse OBJ file")?;
49
50 let (models, _materials) = obj;
51
52 if models.is_empty() {
53 return Err("No models found in OBJ file".into());
54 }
55
56 let model = &models[0];
57 let mesh = &model.mesh;
58
59 let positions = mesh
60 .positions
61 .chunks(3)
62 .map(|v| Vec3::new(v[0], v[1], v[2]))
63 .collect();
64
65 let normals = if !mesh.normals.is_empty() {
66 Some(
67 mesh.normals
68 .chunks(3)
69 .map(|n| Vec3::new(n[0], n[1], n[2]))
70 .collect(),
71 )
72 } else {
73 None
74 };
75
76 let tex_coords = if !mesh.texcoords.is_empty() {
77 Some(
78 mesh.texcoords
79 .chunks(2)
80 .map(|t| Vec2::new(t[0], t[1]))
81 .collect(),
82 )
83 } else {
84 None
85 };
86
87 let bounding_box = Aabb::from_points(
88 &mesh
89 .positions
90 .chunks(3)
91 .map(|v| Vec3::new(v[0], v[1], v[2]))
92 .collect::<Vec<_>>(),
93 )
94 .unwrap_or(Aabb::INVALID);
95
96 let mut vertex_layout = vec![VertexAttributeDescriptor {
97 shader_location: 0,
98 format: VertexFormat::Float32x3,
99 offset: 0,
100 }];
101
102 let mut next_location = 1;
103 if normals.is_some() {
104 vertex_layout.push(VertexAttributeDescriptor {
105 shader_location: next_location,
106 format: VertexFormat::Float32x3,
107 offset: std::mem::size_of::<Vec3>() as u64,
108 });
109 next_location += 1;
110 }
111
112 if tex_coords.is_some() {
113 vertex_layout.push(VertexAttributeDescriptor {
114 shader_location: next_location,
115 format: VertexFormat::Float32x2,
116 offset: (std::mem::size_of::<Vec3>() * (1 + normals.as_ref().map_or(0, |_| 1)))
117 as u64,
118 });
119 }
120
121 Ok(Mesh {
122 positions,
123 normals,
124 tex_coords,
125 tangents: None,
126 colors: None,
127 indices: Some(mesh.indices.clone()),
128 primitive_type: PrimitiveTopology::TriangleList,
129 bounding_box,
130 vertex_layout,
131 })
132 }
133}