1use crate::types::{Color, Vec3};
7use crate::wmo_group_types::*;
8use crate::wmo_types::*;
9
10#[derive(Debug, Clone)]
12pub struct WmoMesh {
13 pub positions: Vec<[f32; 3]>,
15
16 pub normals: Vec<[f32; 3]>,
18
19 pub tex_coords: Vec<[f32; 2]>,
21
22 pub colors: Vec<[u8; 4]>,
24
25 pub indices: Vec<u32>,
27
28 pub submeshes: Vec<WmoSubmesh>,
30}
31
32#[derive(Debug, Clone)]
34pub struct WmoSubmesh {
35 pub material_index: u16,
37
38 pub start_index: u32,
40
41 pub index_count: u32,
43
44 pub texture_filename: Option<String>,
46}
47
48pub struct WmoVisualizer;
50
51impl Default for WmoVisualizer {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl WmoVisualizer {
58 pub fn new() -> Self {
60 Self
61 }
62
63 pub fn create_mesh(&self, root: &WmoRoot, groups: &[WmoGroup]) -> WmoMesh {
65 let mut mesh = WmoMesh {
66 positions: Vec::new(),
67 normals: Vec::new(),
68 tex_coords: Vec::new(),
69 colors: Vec::new(),
70 indices: Vec::new(),
71 submeshes: Vec::new(),
72 };
73
74 let mut global_index_offset = 0;
75
76 for group in groups {
78 let vertex_offset = mesh.positions.len() as u32;
80
81 for vertex in &group.vertices {
83 mesh.positions.push([vertex.x, vertex.y, vertex.z]);
84 }
85
86 if !group.normals.is_empty() {
88 for normal in &group.normals {
89 mesh.normals.push([normal.x, normal.y, normal.z]);
90 }
91 } else {
92 for _ in 0..group.vertices.len() {
94 mesh.normals.push([0.0, 0.0, 1.0]);
95 }
96 }
97
98 if !group.tex_coords.is_empty() {
100 for tex_coord in &group.tex_coords {
101 mesh.tex_coords.push([tex_coord.u, tex_coord.v]);
102 }
103 } else {
104 for _ in 0..group.vertices.len() {
106 mesh.tex_coords.push([0.0, 0.0]);
107 }
108 }
109
110 if let Some(colors) = &group.vertex_colors {
112 for color in colors {
113 mesh.colors.push([color.r, color.g, color.b, color.a]);
114 }
115 } else {
116 for _ in 0..group.vertices.len() {
118 mesh.colors.push([255, 255, 255, 255]);
119 }
120 }
121
122 for batch in &group.batches {
124 let material_index = batch.material_id;
125 let start_index = batch.start_index;
126 let index_count = batch.count as u32;
127
128 let texture_filename = if material_index < root.materials.len() as u16 {
130 let material = &root.materials[material_index as usize];
131 if material.texture1 < root.textures.len() as u32 {
132 Some(root.textures[material.texture1 as usize].clone())
133 } else {
134 None
135 }
136 } else {
137 None
138 };
139
140 let submesh_start = mesh.indices.len() as u32;
142
143 for i in 0..index_count {
144 let idx = global_index_offset + start_index + i;
145 if idx < group.indices.len() as u32 {
146 let vertex_index = group.indices[idx as usize] as u32;
147 mesh.indices.push(vertex_offset + vertex_index);
148 }
149 }
150
151 let submesh_count = mesh.indices.len() as u32 - submesh_start;
152
153 mesh.submeshes.push(WmoSubmesh {
155 material_index,
156 start_index: submesh_start,
157 index_count: submesh_count,
158 texture_filename,
159 });
160 }
161
162 global_index_offset += group.indices.len() as u32;
163 }
164
165 mesh
166 }
167
168 pub fn extract_doodads(&self, root: &WmoRoot) -> Vec<WmoDoodadPlacement> {
170 let mut placements = Vec::new();
171
172 for (i, doodad) in root.doodad_defs.iter().enumerate() {
173 let mut set_names = Vec::new();
175
176 for set in &root.doodad_sets {
177 let start = set.start_doodad as usize;
178 let end = start + set.n_doodads as usize;
179
180 if i >= start && i < end {
181 set_names.push(set.name.clone());
182 }
183 }
184
185 let name = format!("Doodad_{}", doodad.name_offset);
188
189 placements.push(WmoDoodadPlacement {
190 index: i,
191 name,
192 position: doodad.position,
193 orientation: doodad.orientation,
194 scale: doodad.scale,
195 color: doodad.color,
196 set_indices: set_names,
197 });
198 }
199
200 placements
201 }
202
203 pub fn generate_triangles(&self, groups: &[WmoGroup]) -> Vec<Vec<WmoTriangle>> {
205 let mut result = Vec::with_capacity(groups.len());
206
207 for group in groups {
208 let mut triangles = Vec::new();
209
210 for batch in &group.batches {
212 let material_id = batch.material_id;
213
214 for i in 0..(batch.count / 3) {
216 let idx_base = (batch.start_index + i as u32 * 3) as usize;
217
218 if idx_base + 2 < group.indices.len() {
219 let idx1 = group.indices[idx_base] as usize;
220 let idx2 = group.indices[idx_base + 1] as usize;
221 let idx3 = group.indices[idx_base + 2] as usize;
222
223 if idx1 < group.vertices.len()
224 && idx2 < group.vertices.len()
225 && idx3 < group.vertices.len()
226 {
227 triangles.push(WmoTriangle {
228 vertices: [
229 group.vertices[idx1],
230 group.vertices[idx2],
231 group.vertices[idx3],
232 ],
233 normals: if !group.normals.is_empty() {
234 Some([
235 group.normals.get(idx1).cloned().unwrap_or_default(),
236 group.normals.get(idx2).cloned().unwrap_or_default(),
237 group.normals.get(idx3).cloned().unwrap_or_default(),
238 ])
239 } else {
240 None
241 },
242 tex_coords: if !group.tex_coords.is_empty() {
243 Some([
244 group.tex_coords.get(idx1).cloned().unwrap_or_default(),
245 group.tex_coords.get(idx2).cloned().unwrap_or_default(),
246 group.tex_coords.get(idx3).cloned().unwrap_or_default(),
247 ])
248 } else {
249 None
250 },
251 colors: group.vertex_colors.as_ref().map(|colors| {
252 [
253 colors.get(idx1).cloned().unwrap_or_default(),
254 colors.get(idx2).cloned().unwrap_or_default(),
255 colors.get(idx3).cloned().unwrap_or_default(),
256 ]
257 }),
258 material_id,
259 });
260 }
261 }
262 }
263 }
264
265 result.push(triangles);
266 }
267
268 result
269 }
270
271 pub fn export_to_obj(&self, root: &WmoRoot, groups: &[WmoGroup]) -> String {
273 let mut obj = String::new();
274
275 obj.push_str("# WMO Model exported from wow_wmo\n");
277 obj.push_str(&format!("# Version: {}\n", root.version.to_raw()));
278 obj.push_str(&format!("# Groups: {}\n\n", groups.len()));
279
280 let mut global_vertex_offset = 1; let mut global_normal_offset = 1;
282 let mut global_texcoord_offset = 1;
283
284 for (group_idx, group) in groups.iter().enumerate() {
286 obj.push_str(&format!("g Group_{group_idx}\n"));
287
288 for v in &group.vertices {
290 obj.push_str(&format!("v {} {} {}\n", v.x, v.y, v.z));
291 }
292
293 for t in &group.tex_coords {
295 obj.push_str(&format!("vt {} {}\n", t.u, t.v));
296 }
297
298 for n in &group.normals {
300 obj.push_str(&format!("vn {} {} {}\n", n.x, n.y, n.z));
301 }
302
303 for batch in &group.batches {
305 let material_id = batch.material_id;
306 obj.push_str(&format!("usemtl Material_{material_id}\n"));
307
308 for i in 0..(batch.count / 3) {
310 let idx_base = (batch.start_index + i as u32 * 3) as usize;
311
312 if idx_base + 2 < group.indices.len() {
313 let idx1 = group.indices[idx_base] as usize + global_vertex_offset;
314 let idx2 = group.indices[idx_base + 1] as usize + global_vertex_offset;
315 let idx3 = group.indices[idx_base + 2] as usize + global_vertex_offset;
316
317 let tex1 = idx1 - global_vertex_offset + global_texcoord_offset;
318 let tex2 = idx2 - global_vertex_offset + global_texcoord_offset;
319 let tex3 = idx3 - global_vertex_offset + global_texcoord_offset;
320
321 let norm1 = idx1 - global_vertex_offset + global_normal_offset;
322 let norm2 = idx2 - global_vertex_offset + global_normal_offset;
323 let norm3 = idx3 - global_vertex_offset + global_normal_offset;
324
325 if !group.tex_coords.is_empty() && !group.normals.is_empty() {
326 obj.push_str(&format!(
327 "f {idx1}/{tex1}/{norm1} {idx2}/{tex2}/{norm2} {idx3}/{tex3}/{norm3}\n"
328 ));
329 } else if !group.tex_coords.is_empty() {
330 obj.push_str(&format!("f {idx1}/{tex1} {idx2}/{tex2} {idx3}/{tex3}\n"));
331 } else if !group.normals.is_empty() {
332 obj.push_str(&format!(
333 "f {idx1}//{norm1} {idx2}//{norm2} {idx3}//{norm3}\n"
334 ));
335 } else {
336 obj.push_str(&format!("f {idx1} {idx2} {idx3}\n"));
337 }
338 }
339 }
340 }
341
342 global_vertex_offset += group.vertices.len();
344 global_texcoord_offset += group.tex_coords.len();
345 global_normal_offset += group.normals.len();
346
347 obj.push('\n');
348 }
349
350 obj.push_str("mtllib materials.mtl\n");
352
353 obj
354 }
355
356 pub fn export_to_mtl(&self, root: &WmoRoot) -> String {
358 let mut mtl = String::new();
359
360 mtl.push_str("# WMO Materials exported from wow_wmo\n");
362
363 for (i, material) in root.materials.iter().enumerate() {
365 mtl.push_str(&format!("newmtl Material_{i}\n"));
366
367 let diffuse = &material.diffuse_color;
369 let ambient = &material.sidn_color;
370 let emissive = &material.emissive_color;
371
372 mtl.push_str(&format!(
373 "Ka {} {} {}\n",
374 ambient.r as f32 / 255.0,
375 ambient.g as f32 / 255.0,
376 ambient.b as f32 / 255.0
377 ));
378
379 mtl.push_str(&format!(
380 "Kd {} {} {}\n",
381 diffuse.r as f32 / 255.0,
382 diffuse.g as f32 / 255.0,
383 diffuse.b as f32 / 255.0
384 ));
385
386 mtl.push_str(&format!(
387 "Ke {} {} {}\n",
388 emissive.r as f32 / 255.0,
389 emissive.g as f32 / 255.0,
390 emissive.b as f32 / 255.0
391 ));
392
393 if material.texture1 < root.textures.len() as u32 {
395 let texture = &root.textures[material.texture1 as usize];
396 mtl.push_str(&format!("map_Kd {texture}\n"));
397 }
398
399 if material.flags.contains(WmoMaterialFlags::TWO_SIDED) {
401 mtl.push_str("d 0.5\n");
402 } else {
403 mtl.push_str("d 1.0\n");
404 }
405
406 mtl.push('\n');
407 }
408
409 mtl
410 }
411}
412
413#[derive(Debug, Clone)]
415pub struct WmoDoodadPlacement {
416 pub index: usize,
418
419 pub name: String,
421
422 pub position: Vec3,
424
425 pub orientation: [f32; 4],
427
428 pub scale: f32,
430
431 pub color: Color,
433
434 pub set_indices: Vec<String>,
436}
437
438#[derive(Debug, Clone)]
440pub struct WmoTriangle {
441 pub vertices: [Vec3; 3],
443
444 pub normals: Option<[Vec3; 3]>,
446
447 pub tex_coords: Option<[TexCoord; 3]>,
449
450 pub colors: Option<[Color; 3]>,
452
453 pub material_id: u16,
455}