oxihuman_export/
dotobj_export.rs1#![allow(dead_code)]
4
5#[derive(Clone, Debug)]
9pub struct DotObjMaterial {
10 pub name: String,
11 pub diffuse: [f32; 3],
12 pub specular: [f32; 3],
13 pub shininess: f32,
14 pub opacity: f32,
15 pub diffuse_map: Option<String>,
16}
17
18impl Default for DotObjMaterial {
19 fn default() -> Self {
20 Self {
21 name: "default".to_string(),
22 diffuse: [0.8, 0.8, 0.8],
23 specular: [0.5, 0.5, 0.5],
24 shininess: 32.0,
25 opacity: 1.0,
26 diffuse_map: None,
27 }
28 }
29}
30
31#[derive(Clone, Debug, Default)]
33pub struct DotObjExport {
34 pub positions: Vec<[f32; 3]>,
35 pub normals: Vec<[f32; 3]>,
36 pub uvs: Vec<[f32; 2]>,
37 pub indices: Vec<u32>,
38 pub materials: Vec<DotObjMaterial>,
39 pub object_name: String,
40 pub mtl_lib_name: String,
41}
42
43pub fn new_dotobj_export(name: &str) -> DotObjExport {
45 DotObjExport {
46 object_name: name.to_string(),
47 mtl_lib_name: format!("{}.mtl", name),
48 ..Default::default()
49 }
50}
51
52pub fn dotobj_set_mesh(
54 doc: &mut DotObjExport,
55 positions: Vec<[f32; 3]>,
56 normals: Vec<[f32; 3]>,
57 uvs: Vec<[f32; 2]>,
58 indices: Vec<u32>,
59) {
60 doc.positions = positions;
61 doc.normals = normals;
62 doc.uvs = uvs;
63 doc.indices = indices;
64}
65
66pub fn dotobj_add_material(doc: &mut DotObjExport, mat: DotObjMaterial) {
68 doc.materials.push(mat);
69}
70
71pub fn dotobj_material_count(doc: &DotObjExport) -> usize {
73 doc.materials.len()
74}
75
76pub fn dotobj_face_count(doc: &DotObjExport) -> usize {
78 doc.indices.len() / 3
79}
80
81pub fn render_dotobj(doc: &DotObjExport) -> String {
83 let mut out = String::from("# oxihuman dotobj export\n");
84 if !doc.mtl_lib_name.is_empty() {
85 out.push_str(&format!("mtllib {}\n", doc.mtl_lib_name));
86 }
87 if !doc.object_name.is_empty() {
88 out.push_str(&format!("o {}\n", doc.object_name));
89 }
90 for p in &doc.positions {
91 out.push_str(&format!("v {:.6} {:.6} {:.6}\n", p[0], p[1], p[2]));
92 }
93 for uv in &doc.uvs {
94 out.push_str(&format!("vt {:.6} {:.6}\n", uv[0], uv[1]));
95 }
96 for n in &doc.normals {
97 out.push_str(&format!("vn {:.6} {:.6} {:.6}\n", n[0], n[1], n[2]));
98 }
99 if !doc.materials.is_empty() {
100 out.push_str(&format!("usemtl {}\n", doc.materials[0].name));
101 }
102 let tri_count = doc.indices.len() / 3;
103 let has_uv = !doc.uvs.is_empty();
104 let has_n = !doc.normals.is_empty();
105 for t in 0..tri_count {
106 let mut face = String::from("f");
107 for k in 0..3 {
108 let vi = doc.indices[t * 3 + k] + 1; face.push(' ');
110 if has_uv && has_n {
111 face.push_str(&format!("{}/{}/{}", vi, vi, vi));
112 } else if has_uv {
113 face.push_str(&format!("{}/{}", vi, vi));
114 } else if has_n {
115 face.push_str(&format!("{}//{}", vi, vi));
116 } else {
117 face.push_str(&vi.to_string());
118 }
119 }
120 out.push_str(&face);
121 out.push('\n');
122 }
123 out
124}
125
126pub fn render_dotmtl(doc: &DotObjExport) -> String {
128 let mut out = String::from("# oxihuman mtl export\n");
129 for mat in &doc.materials {
130 out.push_str(&format!("newmtl {}\n", mat.name));
131 out.push_str(&format!(
132 "Kd {:.4} {:.4} {:.4}\n",
133 mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]
134 ));
135 out.push_str(&format!(
136 "Ks {:.4} {:.4} {:.4}\n",
137 mat.specular[0], mat.specular[1], mat.specular[2]
138 ));
139 out.push_str(&format!("Ns {:.4}\n", mat.shininess));
140 out.push_str(&format!("d {:.4}\n", mat.opacity));
141 if let Some(ref map) = mat.diffuse_map {
142 out.push_str(&format!("map_Kd {}\n", map));
143 }
144 }
145 out
146}
147
148pub fn validate_dotobj(doc: &DotObjExport) -> bool {
150 if doc.positions.is_empty() {
151 return false;
152 }
153 let n = doc.positions.len() as u32;
154 doc.indices.iter().all(|&i| i < n)
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 fn simple_doc() -> DotObjExport {
162 let mut d = new_dotobj_export("test");
163 dotobj_set_mesh(
164 &mut d,
165 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
166 vec![],
167 vec![],
168 vec![0, 1, 2],
169 );
170 d
171 }
172
173 #[test]
174 fn new_export_empty() {
175 let d = new_dotobj_export("test");
176 assert_eq!(dotobj_face_count(&d), 0);
177 }
178
179 #[test]
180 fn face_count_after_set() {
181 let d = simple_doc();
182 assert_eq!(dotobj_face_count(&d), 1);
183 }
184
185 #[test]
186 fn add_material_increments() {
187 let mut d = simple_doc();
188 dotobj_add_material(&mut d, DotObjMaterial::default());
189 assert_eq!(dotobj_material_count(&d), 1);
190 }
191
192 #[test]
193 fn render_obj_contains_v() {
194 let d = simple_doc();
195 let s = render_dotobj(&d);
196 assert!(s.contains("\nv "));
197 }
198
199 #[test]
200 fn render_obj_contains_face() {
201 let d = simple_doc();
202 let s = render_dotobj(&d);
203 assert!(s.contains("\nf "));
204 }
205
206 #[test]
207 fn render_mtl_contains_newmtl() {
208 let mut d = simple_doc();
209 dotobj_add_material(
210 &mut d,
211 DotObjMaterial {
212 name: "mat1".to_string(),
213 ..Default::default()
214 },
215 );
216 let s = render_dotmtl(&d);
217 assert!(s.contains("newmtl mat1"));
218 }
219
220 #[test]
221 fn validate_valid_doc() {
222 let d = simple_doc();
223 assert!(validate_dotobj(&d));
224 }
225
226 #[test]
227 fn validate_empty_fails() {
228 let d = new_dotobj_export("test");
229 assert!(!validate_dotobj(&d));
230 }
231}