1use std::collections::HashMap;
2use std::fs;
3use std::io::{Cursor as IoCursor, Read, Write};
4use std::path::{Path, PathBuf};
5
6use anyhow::{Context, Result, bail};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
9pub enum MeshLightingType {
10 #[default]
11 None = 0,
12 Lambert = 1,
13 BlinnPhong = 2,
14 PerPixelBlinnPhong = 3,
15 PerPixelHalfLambert = 4,
16 Toon = 5,
17 FixedFunction = 6,
18 PerPixelFixedFunction = 7,
19 Bump = 8,
20 Parallax = 9,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
24pub enum MeshShadingType {
25 #[default]
26 None = 0,
27 DepthBuffer = 1,
28}
29
30pub const MESH_SHADER_OPTION_NONE: u32 = 0;
31pub const MESH_SHADER_OPTION_RIM_LIGHT: u32 = 1 << 0;
32pub const MAX_GPU_BONE_PALETTE: usize = 64;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
38pub enum MeshEffectProfile {
39 #[default]
40 None = 0,
41 Mesh = 1,
42 SkinnedMesh = 2,
43 ShadowMap = 3,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
47pub struct MeshRuntimeMaterialKey {
48 pub use_mesh_tex: bool,
49 pub use_shadow_tex: bool,
50 pub use_toon_tex: bool,
51 pub use_normal_tex: bool,
52 pub use_mul_vertex_color: bool,
53 pub use_mrbd: bool,
54 pub use_rgb: bool,
55 pub lighting_type: MeshLightingType,
56 pub shading_type: MeshShadingType,
57 pub shader_option: u32,
58 pub skinned: bool,
59 pub alpha_test_enable: bool,
60 pub cull_disable: bool,
61 pub shadow_map_enable: bool,
62}
63
64#[derive(Debug, Clone, Default)]
65pub struct MeshPrimitiveRuntimeDesc {
66 pub effect_profile: MeshEffectProfile,
67 pub effect_key: String,
68 pub technique_name: String,
69 pub shadow_effect_key: String,
70 pub shadow_technique_name: String,
71 pub use_mesh_texture_slot: bool,
72 pub use_normal_texture_slot: bool,
73 pub use_toon_texture_slot: bool,
74 pub use_shadow_texture_slot: bool,
75 pub material_key: MeshRuntimeMaterialKey,
76 pub vertex_stride_bytes: u32,
77 pub vertex_count: u32,
78 pub bone_palette_len: u32,
79}
80
81#[derive(Debug, Clone, Default)]
82pub struct MeshTriVertex {
83 pub pos: [f32; 3],
84 pub uv: [f32; 2],
85 pub normal: [f32; 3],
86 pub tangent: [f32; 3],
87 pub binormal: [f32; 3],
88 pub color: [f32; 4],
89 pub bone_indices: [u16; 4],
90 pub bone_weights: [f32; 4],
91}
92
93#[derive(Debug, Clone, Default)]
94pub struct MeshMaterial {
95 pub diffuse: [f32; 4],
96 pub ambient: [f32; 4],
97 pub specular: [f32; 4],
98 pub emissive: [f32; 4],
99 pub power: f32,
100 pub lighting_type: MeshLightingType,
101 pub shading_type: MeshShadingType,
102 pub shader_option: u32,
103 pub rim_light_color: [f32; 4],
104 pub rim_light_power: f32,
105 pub parallax_max_height: f32,
106 pub alpha_test_enable: bool,
107 pub alpha_ref: f32,
108 pub cull_disable: bool,
109 pub shadow_map_enable: bool,
110 pub use_mesh_tex: bool,
111 pub use_mrbd: bool,
112 pub mrbd: [f32; 4],
113 pub use_rgb: bool,
114 pub rgb_rate: [f32; 4],
115 pub add_rgb: [f32; 4],
116 pub use_mul_vertex_color: bool,
117 pub mul_vertex_color_rate: f32,
118 pub depth_buffer_shadow_bias: f32,
119 pub directional_light_ids: Vec<i32>,
120 pub point_light_ids: Vec<i32>,
121 pub spot_light_ids: Vec<i32>,
122 pub normal_texture_path: Option<PathBuf>,
123 pub toon_texture_path: Option<PathBuf>,
124 pub effect_filename: Option<String>,
125}
126
127#[derive(Debug, Clone, Default)]
128pub struct MeshGpuPrimitiveBatch {
129 pub vertices: Vec<MeshTriVertex>,
130 pub frame_cols: [[f32; 4]; 4],
131 pub bone_cols: Vec<[[f32; 4]; 4]>,
132 pub skinned: bool,
133 pub texture_path: Option<PathBuf>,
134 pub material: MeshMaterial,
135 pub runtime_desc: MeshPrimitiveRuntimeDesc,
136}
137
138#[derive(Debug, Clone)]
139pub struct MeshAnimationState {
140 pub clip_name: Option<String>,
141 pub clip_index: Option<usize>,
142 pub blend_clip_name: Option<String>,
143 pub blend_clip_index: Option<usize>,
144 pub blend_weight: f32,
145 pub time_sec: f32,
147 pub rate: f32,
148 pub time_offset_sec: f32,
149 pub hold_time_sec: f32,
151 pub paused: bool,
152 pub looped: bool,
153 pub anim_track_no: u32,
155 pub anim_shift_time_sec: f32,
156 pub is_anim: bool,
157 pub prev_clip_name: Option<String>,
158 pub prev_clip_index: Option<usize>,
159 pub prev_time_sec: f32,
160 pub prev_time_offset_sec: f32,
161 pub prev_rate: f32,
162 pub transition_elapsed_sec: f32,
163}
164
165pub const MESH_ANIM_CONTROLLER_STEP_SEC: f32 = 60.0 / 4800.0;
166
167impl Default for MeshAnimationState {
168 fn default() -> Self {
169 Self {
170 clip_name: None,
171 clip_index: None,
172 blend_clip_name: None,
173 blend_clip_index: None,
174 blend_weight: 0.0,
175 time_sec: 0.0,
176 rate: 1.0,
177 time_offset_sec: 0.0,
178 hold_time_sec: 0.0,
179 paused: false,
180 looped: true,
181 anim_track_no: 0,
182 anim_shift_time_sec: 0.0,
183 is_anim: true,
184 prev_clip_name: None,
185 prev_clip_index: None,
186 prev_time_sec: 0.0,
187 prev_time_offset_sec: 0.0,
188 prev_rate: 1.0,
189 transition_elapsed_sec: 0.0,
190 }
191 }
192}
193
194impl MeshAnimationState {
195 pub fn sanitized(&self) -> Self {
196 let mut out = self.clone();
197 out.blend_weight = out.blend_weight.clamp(0.0, 1.0);
198 out.time_sec = out.time_sec.max(0.0);
199 out.rate = out.rate.max(0.0);
200 out.time_offset_sec = out.time_offset_sec.max(0.0);
201 out.hold_time_sec = out.hold_time_sec.max(0.0);
202 out.anim_shift_time_sec = out.anim_shift_time_sec.max(0.0);
203 out.prev_time_sec = out.prev_time_sec.max(0.0);
204 out.prev_time_offset_sec = out.prev_time_offset_sec.max(0.0);
205 out.prev_rate = out.prev_rate.max(0.0);
206 out.transition_elapsed_sec = out.transition_elapsed_sec.max(0.0);
207 if out.anim_shift_time_sec <= 0.0 {
208 out.prev_clip_name = None;
209 out.prev_clip_index = None;
210 out.prev_time_sec = 0.0;
211 out.prev_time_offset_sec = 0.0;
212 out.prev_rate = 1.0;
213 out.transition_elapsed_sec = 0.0;
214 }
215 out
216 }
217
218 pub fn current_sample_base_sec(&self) -> f32 {
219 if self.paused || !self.is_anim {
220 self.hold_time_sec.max(0.0)
221 } else {
222 self.time_sec.max(0.0) * self.rate.max(0.0)
223 }
224 }
225
226 pub fn current_sample_time_sec(&self) -> f32 {
227 self.current_sample_base_sec() + self.time_offset_sec.max(0.0)
228 }
229
230 pub fn previous_track_sample_time_sec(&self) -> f32 {
231 (self.prev_time_sec.max(0.0) * self.prev_rate.max(0.0)) + self.prev_time_offset_sec.max(0.0)
232 }
233
234 pub fn previous_track_weight(&self) -> f32 {
235 if (self.prev_clip_name.is_none() && self.prev_clip_index.is_none())
236 || self.anim_shift_time_sec <= 0.0
237 {
238 0.0
239 } else {
240 (1.0 - (self.transition_elapsed_sec / self.anim_shift_time_sec)).clamp(0.0, 1.0)
241 }
242 }
243
244 pub fn current_track_weight(&self) -> f32 {
245 (1.0 - self.previous_track_weight()).clamp(0.0, 1.0)
246 }
247
248 pub fn previous_track_speed(&self) -> f32 {
249 if (self.prev_clip_name.is_none() && self.prev_clip_index.is_none())
250 || self.anim_shift_time_sec <= 0.0
251 {
252 0.0
253 } else {
254 (1.0 - (self.transition_elapsed_sec / self.anim_shift_time_sec)).clamp(0.0, 1.0)
255 }
256 }
257
258 pub fn current_track_speed(&self) -> f32 {
259 if (self.prev_clip_name.is_none() && self.prev_clip_index.is_none())
260 || self.anim_shift_time_sec <= 0.0
261 {
262 1.0
263 } else {
264 (self.transition_elapsed_sec / self.anim_shift_time_sec).clamp(0.0, 1.0)
265 }
266 }
267
268 pub fn set_anim_shift_time_sec(&mut self, shift_sec: f32) {
269 self.anim_shift_time_sec = shift_sec.max(0.0);
270 *self = self.sanitized();
271 }
272
273 pub fn change_animation_clip(
274 &mut self,
275 next_clip_name: Option<String>,
276 next_clip_index: Option<usize>,
277 ) {
278 if self.clip_name == next_clip_name && self.clip_index == next_clip_index {
279 return;
280 }
281 self.prev_clip_name = self.clip_name.clone();
282 self.prev_clip_index = self.clip_index;
283 self.prev_time_sec = self.time_sec.max(0.0);
284 self.prev_time_offset_sec = self.time_offset_sec.max(0.0);
285 self.prev_rate = self.rate.max(0.0);
286 self.clip_name = next_clip_name;
287 self.clip_index = next_clip_index;
288 self.time_sec = 0.0;
289 self.hold_time_sec = 0.0;
290 self.transition_elapsed_sec = 0.0;
291 self.anim_track_no = if self.anim_track_no == 0 { 1 } else { 0 };
292 if self.anim_shift_time_sec <= 0.0 {
293 self.prev_clip_name = None;
294 self.prev_clip_index = None;
295 self.prev_time_sec = 0.0;
296 self.prev_time_offset_sec = 0.0;
297 self.prev_rate = 1.0;
298 }
299 *self = self.sanitized();
300 }
301
302 pub fn advance_controller_frames(&mut self, delta_frames: i32) {
303 let delta_sec = (delta_frames.max(0) as f32) * MESH_ANIM_CONTROLLER_STEP_SEC;
304 if !self.paused && self.is_anim {
305 let has_prev = self.prev_clip_name.is_some() || self.prev_clip_index.is_some();
306 if has_prev && self.anim_shift_time_sec > 0.0 {
307 let start = self.transition_elapsed_sec.max(0.0);
308 let end = (start + delta_sec).min(self.anim_shift_time_sec);
309 let active_delta = (end - start).max(0.0);
310 if active_delta > 0.0 {
311 let shift = self.anim_shift_time_sec.max(0.000_001);
312 let cur_advance = ((end * end) - (start * start)) / (2.0 * shift);
313 let prev_advance = active_delta - cur_advance;
314 self.time_sec = (self.time_sec.max(0.0) + cur_advance.max(0.0)).max(0.0);
315 self.prev_time_sec =
316 (self.prev_time_sec.max(0.0) + prev_advance.max(0.0)).max(0.0);
317 self.transition_elapsed_sec = end.max(0.0);
318 }
319 if delta_sec > active_delta {
320 self.time_sec = (self.time_sec.max(0.0) + (delta_sec - active_delta)).max(0.0);
321 }
322 if self.transition_elapsed_sec >= self.anim_shift_time_sec {
323 self.prev_clip_name = None;
324 self.prev_clip_index = None;
325 self.prev_time_sec = 0.0;
326 self.prev_time_offset_sec = 0.0;
327 self.prev_rate = 1.0;
328 self.transition_elapsed_sec = 0.0;
329 }
330 } else {
331 self.time_sec = (self.time_sec.max(0.0) + delta_sec).max(0.0);
332 if has_prev {
333 self.prev_clip_name = None;
334 self.prev_clip_index = None;
335 self.prev_time_sec = 0.0;
336 self.prev_time_offset_sec = 0.0;
337 self.prev_rate = 1.0;
338 self.transition_elapsed_sec = 0.0;
339 }
340 }
341 }
342 *self = self.sanitized();
343 }
344}
345
346#[derive(Debug, Clone, Default)]
347pub struct MeshAsset {
348 pub source_path: PathBuf,
349 pub texture_path: Option<PathBuf>,
350 pub vertices: Vec<MeshTriVertex>,
351 pub bone_count: usize,
352 pub ticks_per_second: f32,
353 primitives: Vec<MeshPrimitive>,
354 frames: Vec<FrameNode>,
355 animations: Vec<AnimationClip>,
356}
357
358impl MeshAsset {
359 pub fn bounds_size(&self) -> [f32; 3] {
360 let vertices = if self.vertices.is_empty() {
361 self.sample_vertices(0.0)
362 } else {
363 self.vertices.clone()
364 };
365 if vertices.is_empty() {
366 return [0.0, 0.0, 0.0];
367 }
368
369 let mut min = [f32::INFINITY; 3];
370 let mut max = [f32::NEG_INFINITY; 3];
371 for v in vertices {
372 for axis in 0..3 {
373 min[axis] = min[axis].min(v.pos[axis]);
374 max[axis] = max[axis].max(v.pos[axis]);
375 }
376 }
377 [
378 (max[0] - min[0]).max(0.0),
379 (max[1] - min[1]).max(0.0),
380 (max[2] - min[2]).max(0.0),
381 ]
382 }
383
384 pub fn is_skinned(&self) -> bool {
385 self.bone_count != 0
386 && self.primitives.iter().any(|p| {
387 !p.bones.is_empty()
388 && p.vertices
389 .iter()
390 .any(|v| v.bone_weights.iter().copied().sum::<f32>() > 0.0)
391 })
392 }
393
394 pub fn duration_seconds(&self) -> f32 {
395 self.animations
396 .iter()
397 .map(|a| self.seconds_from_ticks(a.max_time))
398 .fold(0.0, f32::max)
399 }
400
401 fn seconds_from_ticks(&self, ticks: i64) -> f32 {
402 ticks as f32 / self.ticks_per_second.max(1.0)
403 }
404
405 fn ticks_from_seconds(&self, sec: f32) -> i64 {
406 (sec.max(0.0) * self.ticks_per_second.max(1.0)) as i64
407 }
408
409 pub fn animation_clip_names(&self) -> Vec<String> {
410 self.animations.iter().map(|a| a.name.clone()).collect()
411 }
412
413 fn resolve_clip_index(
414 &self,
415 clip_name: Option<&str>,
416 clip_index: Option<usize>,
417 ) -> Option<usize> {
418 if let Some(name) = clip_name
419 && let Some((idx, _)) = self
420 .animations
421 .iter()
422 .enumerate()
423 .find(|(_, c)| c.name.eq_ignore_ascii_case(name))
424 {
425 return Some(idx);
426 }
427 if let Some(idx) = clip_index
428 && idx < self.animations.len()
429 {
430 return Some(idx);
431 }
432 if self.animations.is_empty() {
433 None
434 } else {
435 Some(0)
436 }
437 }
438
439 pub fn sample_vertices_with_clip(
440 &self,
441 time_sec: f32,
442 clip_name: Option<&str>,
443 clip_index: Option<usize>,
444 ) -> Vec<MeshTriVertex> {
445 if self.primitives.is_empty() {
446 return self.vertices.clone();
447 }
448 let pose = PoseState::new_with_clip(
449 self,
450 time_sec,
451 self.resolve_clip_index(clip_name, clip_index),
452 );
453 let mut out = Vec::new();
454 for prim in &self.primitives {
455 let frame_world = pose
456 .combined
457 .get(prim.frame_index)
458 .copied()
459 .unwrap_or(Mat4::identity());
460 for src in &prim.vertices {
461 let (pos, normal) = if prim.bones.is_empty() {
462 (
463 frame_world.transform_point(src.pos),
464 frame_world.transform_vector(src.normal),
465 )
466 } else {
467 skin_vertex(src, prim, &pose)
468 };
469 out.push(MeshTriVertex {
470 pos,
471 uv: src.uv,
472 normal: normalize3(normal),
473 tangent: src.tangent,
474 binormal: src.binormal,
475 color: src.color,
476 bone_indices: src.bone_indices,
477 bone_weights: src.bone_weights,
478 });
479 }
480 }
481 out
482 }
483
484 pub fn sample_vertices(&self, time_sec: f32) -> Vec<MeshTriVertex> {
485 self.sample_vertices_with_clip(time_sec, None, None)
486 }
487
488 pub fn sample_gpu_primitives_with_clip(
489 &self,
490 time_sec: f32,
491 clip_name: Option<&str>,
492 clip_index: Option<usize>,
493 ) -> Vec<MeshGpuPrimitiveBatch> {
494 if self.primitives.is_empty() {
495 return vec![MeshGpuPrimitiveBatch {
496 vertices: self.vertices.clone(),
497 frame_cols: Mat4::identity().m,
498 bone_cols: Vec::new(),
499 skinned: false,
500 texture_path: self.texture_path.clone(),
501 material: default_mesh_material(),
502 runtime_desc: build_mesh_primitive_runtime_desc_from_material(
503 &default_mesh_material(),
504 self.texture_path.is_some(),
505 false,
506 self.vertices.len() as u32,
507 0,
508 ),
509 }];
510 }
511 let pose = PoseState::new_with_clip(
512 self,
513 time_sec,
514 self.resolve_clip_index(clip_name, clip_index),
515 );
516 let mut out = Vec::with_capacity(self.primitives.len());
517 for prim in &self.primitives {
518 let frame_world = pose
519 .combined
520 .get(prim.frame_index)
521 .copied()
522 .unwrap_or(Mat4::identity());
523 let mut bone_cols = Vec::with_capacity(prim.bones.len());
524 for bone in &prim.bones {
525 bone_cols.push(skin_matrix_for_bone(bone, &pose).m);
526 }
527 append_gpu_batches_for_primitive(&mut out, prim, frame_world.m, &bone_cols);
528 }
529 out
530 }
531
532 pub fn sample_gpu_primitives(&self, time_sec: f32) -> Vec<MeshGpuPrimitiveBatch> {
533 self.sample_gpu_primitives_with_clip(time_sec, None, None)
534 }
535
536 pub fn sample_gpu_primitives_with_state(
537 &self,
538 state: &MeshAnimationState,
539 ) -> Vec<MeshGpuPrimitiveBatch> {
540 if self.primitives.is_empty() {
541 return vec![MeshGpuPrimitiveBatch {
542 vertices: self.vertices.clone(),
543 frame_cols: Mat4::identity().m,
544 bone_cols: Vec::new(),
545 skinned: false,
546 texture_path: self.texture_path.clone(),
547 material: default_mesh_material(),
548 runtime_desc: build_mesh_primitive_runtime_desc_from_material(
549 &default_mesh_material(),
550 self.texture_path.is_some(),
551 false,
552 self.vertices.len() as u32,
553 0,
554 ),
555 }];
556 }
557 let pose = PoseState::new_with_state(self, state);
558 let mut out = Vec::with_capacity(self.primitives.len());
559 for prim in &self.primitives {
560 let frame_world = pose
561 .combined
562 .get(prim.frame_index)
563 .copied()
564 .unwrap_or(Mat4::identity());
565 let mut bone_cols = Vec::with_capacity(prim.bones.len());
566 for bone in &prim.bones {
567 bone_cols.push(skin_matrix_for_bone(bone, &pose).m);
568 }
569 append_gpu_batches_for_primitive(&mut out, prim, frame_world.m, &bone_cols);
570 }
571 out
572 }
573}
574
575fn append_gpu_batches_for_primitive(
576 out: &mut Vec<MeshGpuPrimitiveBatch>,
577 prim: &MeshPrimitive,
578 frame_cols: [[f32; 4]; 4],
579 all_bone_cols: &[[[f32; 4]; 4]],
580) {
581 if prim.bones.is_empty() || all_bone_cols.len() <= MAX_GPU_BONE_PALETTE {
582 let mut runtime_desc = prim.runtime_desc.clone();
583 runtime_desc.bone_palette_len = all_bone_cols.len() as u32;
584 out.push(MeshGpuPrimitiveBatch {
585 vertices: prim.vertices.clone(),
586 frame_cols,
587 bone_cols: all_bone_cols.to_vec(),
588 skinned: !prim.bones.is_empty(),
589 texture_path: prim.texture_path.clone(),
590 material: prim.material.clone(),
591 runtime_desc,
592 });
593 return;
594 }
595
596 let mut batch_vertices = Vec::<MeshTriVertex>::new();
602 let mut palette = Vec::<usize>::new();
603 let mut palette_lookup = HashMap::<usize, u16>::new();
604
605 let flush = |out: &mut Vec<MeshGpuPrimitiveBatch>,
606 vertices: &mut Vec<MeshTriVertex>,
607 palette: &mut Vec<usize>,
608 palette_lookup: &mut HashMap<usize, u16>| {
609 if vertices.is_empty() {
610 return;
611 }
612 let mut runtime_desc = prim.runtime_desc.clone();
613 runtime_desc.vertex_count = vertices.len() as u32;
614 runtime_desc.bone_palette_len = palette.len() as u32;
615 out.push(MeshGpuPrimitiveBatch {
616 vertices: std::mem::take(vertices),
617 frame_cols,
618 bone_cols: palette
619 .iter()
620 .filter_map(|&index| all_bone_cols.get(index).copied())
621 .collect(),
622 skinned: true,
623 texture_path: prim.texture_path.clone(),
624 material: prim.material.clone(),
625 runtime_desc,
626 });
627 palette.clear();
628 palette_lookup.clear();
629 };
630
631 for triangle in prim.vertices.chunks(3) {
632 if triangle.len() != 3 {
633 continue;
634 }
635 let mut needed = Vec::<usize>::new();
636 for vertex in triangle {
637 for lane in 0..4 {
638 if vertex.bone_weights[lane] <= 0.0 {
639 continue;
640 }
641 let bone_index = vertex.bone_indices[lane] as usize;
642 if bone_index >= all_bone_cols.len()
643 || palette_lookup.contains_key(&bone_index)
644 || needed.contains(&bone_index)
645 {
646 continue;
647 }
648 needed.push(bone_index);
649 }
650 }
651
652 if !batch_vertices.is_empty()
653 && palette.len().saturating_add(needed.len()) > MAX_GPU_BONE_PALETTE
654 {
655 flush(out, &mut batch_vertices, &mut palette, &mut palette_lookup);
656 }
657
658 for bone_index in needed {
659 let local_index = palette.len() as u16;
660 palette.push(bone_index);
661 palette_lookup.insert(bone_index, local_index);
662 }
663
664 for source in triangle {
665 let mut vertex = source.clone();
666 for lane in 0..4 {
667 if vertex.bone_weights[lane] <= 0.0 {
668 vertex.bone_indices[lane] = 0;
669 continue;
670 }
671 let global_index = source.bone_indices[lane] as usize;
672 match palette_lookup.get(&global_index).copied() {
673 Some(local_index) => vertex.bone_indices[lane] = local_index,
674 None => {
675 vertex.bone_indices[lane] = 0;
679 vertex.bone_weights[lane] = 0.0;
680 }
681 }
682 }
683 batch_vertices.push(vertex);
684 }
685 }
686
687 flush(out, &mut batch_vertices, &mut palette, &mut palette_lookup);
688}
689
690#[derive(Debug, Clone)]
691struct MeshPrimitive {
692 frame_index: usize,
693 texture_path: Option<PathBuf>,
694 material: MeshMaterial,
695 runtime_desc: MeshPrimitiveRuntimeDesc,
696 vertices: Vec<MeshTriVertex>,
697 bones: Vec<BoneBinding>,
698}
699
700#[derive(Debug, Clone)]
701struct BoneBinding {
702 frame_name: String,
703 frame_index: usize,
704 offset_matrix: Mat4,
705}
706
707#[derive(Debug, Clone)]
708struct FrameNode {
709 name: String,
710 base_local: Mat4,
711 children: Vec<usize>,
712}
713
714#[derive(Debug, Clone, Default)]
715struct AnimationClip {
716 name: String,
717 tracks: HashMap<String, AnimationTrack>,
718 max_time: i64,
719 open_closed: bool,
720}
721
722#[derive(Debug, Clone, Default)]
723struct AnimationTrack {
724 rotation_keys: Vec<QuatKey>,
725 scale_keys: Vec<Vec3Key>,
726 position_keys: Vec<Vec3Key>,
727 matrix_keys: Vec<MatKey>,
728}
729
730#[derive(Debug, Clone, Copy)]
731struct Vec3Key {
732 time: i64,
733 value: [f32; 3],
734}
735
736#[derive(Debug, Clone, Copy)]
737struct QuatKey {
738 time: i64,
739 value: Quat,
740}
741
742#[derive(Debug, Clone, Copy)]
743struct MatKey {
744 time: i64,
745 value: Mat4,
746}
747
748#[derive(Debug, Clone)]
749struct PoseState {
750 combined: Vec<Mat4>,
751 frame_lookup: HashMap<String, usize>,
752}
753
754impl PoseState {
755 fn new(asset: &MeshAsset, time_sec: f32) -> Self {
756 Self::new_with_clip(
757 asset,
758 time_sec,
759 if asset.animations.is_empty() {
760 None
761 } else {
762 Some(0)
763 },
764 )
765 }
766
767 fn new_with_clip(asset: &MeshAsset, time_sec: f32, clip_index: Option<usize>) -> Self {
768 let mut state = MeshAnimationState {
769 time_sec,
770 clip_index,
771 ..Default::default()
772 };
773
774 Self::new_with_state(asset, &state)
775 }
776
777 fn new_with_state(asset: &MeshAsset, state: &MeshAnimationState) -> Self {
778 let frame_lookup: HashMap<String, usize> = asset
779 .frames
780 .iter()
781 .enumerate()
782 .map(|(i, f)| (f.name.clone(), i))
783 .collect();
784 let clip = asset
785 .resolve_clip_index(state.clip_name.as_deref(), state.clip_index)
786 .and_then(|idx| asset.animations.get(idx));
787 let blend_clip = asset
788 .resolve_clip_index(state.blend_clip_name.as_deref(), state.blend_clip_index)
789 .and_then(|idx| asset.animations.get(idx));
790 let prev_clip = asset
791 .resolve_clip_index(state.prev_clip_name.as_deref(), state.prev_clip_index)
792 .and_then(|idx| asset.animations.get(idx));
793 let primary_time = animation_time_for_state(state);
794 let prev_time = state.previous_track_sample_time_sec();
795 let prev_weight = state.previous_track_weight();
796 let cur_weight = state.current_track_weight();
797 let blend_weight = state.blend_weight.clamp(0.0, 1.0);
798 let mut local = Vec::with_capacity(asset.frames.len());
799 for frame in &asset.frames {
800 let current = if let Some(clip) = clip {
801 if let Some(track) = clip.tracks.get(&frame.name) {
802 sample_track(
803 track,
804 frame.base_local,
805 primary_time,
806 clip.max_time,
807 state.looped && clip.open_closed,
808 asset.ticks_per_second,
809 )
810 } else {
811 frame.base_local
812 }
813 } else {
814 frame.base_local
815 };
816 let mut primary = current;
817 if prev_weight > 0.0
818 && let Some(prev_clip) = prev_clip
819 {
820 let previous = if let Some(prev_track) = prev_clip.tracks.get(&frame.name) {
821 sample_track(
822 prev_track,
823 frame.base_local,
824 prev_time,
825 prev_clip.max_time,
826 state.looped && prev_clip.open_closed,
827 asset.ticks_per_second,
828 )
829 } else {
830 frame.base_local
831 };
832 let normalized_prev = if (cur_weight + prev_weight) > 0.0 {
833 prev_weight / (cur_weight + prev_weight)
834 } else {
835 0.0
836 };
837 primary = blend_mats(current, previous, normalized_prev);
838 }
839 let mat = if blend_weight > 0.0 {
840 if let Some(clip_b) = blend_clip {
841 let secondary = if let Some(track_b) = clip_b.tracks.get(&frame.name) {
842 sample_track(
843 track_b,
844 frame.base_local,
845 primary_time,
846 clip_b.max_time,
847 state.looped && clip_b.open_closed,
848 asset.ticks_per_second,
849 )
850 } else {
851 frame.base_local
852 };
853 blend_mats(primary, secondary, blend_weight)
854 } else {
855 primary
856 }
857 } else {
858 primary
859 };
860 local.push(mat);
861 }
862 let mut combined = vec![Mat4::identity(); asset.frames.len()];
863 if !asset.frames.is_empty() {
864 let mut has_parent = vec![false; asset.frames.len()];
865 for frame in &asset.frames {
866 for &child in &frame.children {
867 if child < has_parent.len() {
868 has_parent[child] = true;
869 }
870 }
871 }
872 for frame_idx in 0..asset.frames.len() {
873 if !has_parent[frame_idx] {
874 update_combined_recursive(
875 &asset.frames,
876 &local,
877 &mut combined,
878 frame_idx,
879 None,
880 );
881 }
882 }
883 }
884 Self {
885 combined,
886 frame_lookup,
887 }
888 }
889}
890
891fn update_combined_recursive(
892 frames: &[FrameNode],
893 local: &[Mat4],
894 combined: &mut [Mat4],
895 frame_idx: usize,
896 parent: Option<Mat4>,
897) {
898 let l = local.get(frame_idx).copied().unwrap_or(Mat4::identity());
899 let c = if let Some(p) = parent { l.mul(&p) } else { l };
900 combined[frame_idx] = c;
901 if let Some(frame) = frames.get(frame_idx) {
902 for &child in &frame.children {
903 update_combined_recursive(frames, local, combined, child, Some(c));
904 }
905 }
906}
907
908fn animation_time_for_state(state: &MeshAnimationState) -> f32 {
909 state.current_sample_time_sec()
910}
911
912fn blend_mats(a: Mat4, b: Mat4, w: f32) -> Mat4 {
913 let w = w.clamp(0.0, 1.0);
914 if w <= 0.0 {
915 return a;
916 }
917 if w >= 1.0 {
918 return b;
919 }
920 let (sa, ra, pa) = a.decompose_srt();
921 let (sb, rb, pb) = b.decompose_srt();
922 Mat4::from_scale_rotation_translation(
923 lerp3(sa, sb, w),
924 Quat::nlerp(ra, rb, w).normalize(),
925 lerp3(pa, pb, w),
926 )
927}
928
929fn sample_track(
930 track: &AnimationTrack,
931 base_local: Mat4,
932 time_sec: f32,
933 clip_max_time: i64,
934 looped: bool,
935 ticks_per_second: f32,
936) -> Mat4 {
937 let mut t = (time_sec.max(0.0) * ticks_per_second.max(1.0)) as i64;
938 if clip_max_time > 0 {
939 let max_time = clip_max_time.max(1);
940 if looped {
941 t %= max_time;
942 } else {
943 t = t.clamp(0, max_time);
944 }
945 }
946 if !track.matrix_keys.is_empty() {
947 return sample_mat_keys(&track.matrix_keys, t);
948 }
949 let (base_scale, base_rot, base_pos) = base_local.decompose_srt();
950 let scale = sample_vec3_keys(&track.scale_keys, t).unwrap_or(base_scale);
951 let rot = sample_quat_keys(&track.rotation_keys, t).unwrap_or(base_rot);
952 let pos = sample_vec3_keys(&track.position_keys, t).unwrap_or(base_pos);
953 Mat4::from_scale_rotation_translation(scale, rot, pos)
954}
955
956fn sample_vec3_keys(keys: &[Vec3Key], t: i64) -> Option<[f32; 3]> {
957 if keys.is_empty() {
958 return None;
959 }
960 if keys.len() == 1 {
961 return Some(keys[0].value);
962 }
963 let (a, b, k) = find_key_pair_vec3(keys, t);
964 Some(lerp3(a.value, b.value, k))
965}
966
967fn sample_quat_keys(keys: &[QuatKey], t: i64) -> Option<Quat> {
968 if keys.is_empty() {
969 return None;
970 }
971 if keys.len() == 1 {
972 return Some(keys[0].value.normalize());
973 }
974 let (a, b, k) = find_key_pair_quat(keys, t);
975 Some(Quat::nlerp(a.value, b.value, k).normalize())
976}
977
978fn sample_mat_keys(keys: &[MatKey], t: i64) -> Mat4 {
979 if keys.is_empty() {
980 return Mat4::identity();
981 }
982 if keys.len() == 1 {
983 return keys[0].value;
984 }
985 let (a, b, k) = find_key_pair_mat(keys, t);
986 Mat4::lerp(a.value, b.value, k)
987}
988
989fn find_key_pair_vec3(keys: &[Vec3Key], t: i64) -> (Vec3Key, Vec3Key, f32) {
990 for w in keys.windows(2) {
991 let a = w[0];
992 let b = w[1];
993 if t >= a.time && t <= b.time {
994 let span = (b.time - a.time).max(1) as f32;
995 return (a, b, (t - a.time) as f32 / span);
996 }
997 }
998 let first = keys[0];
999 let last = keys[keys.len() - 1];
1000 if t < first.time {
1001 (first, first, 0.0)
1002 } else {
1003 (last, last, 0.0)
1004 }
1005}
1006
1007fn find_key_pair_quat(keys: &[QuatKey], t: i64) -> (QuatKey, QuatKey, f32) {
1008 for w in keys.windows(2) {
1009 let a = w[0];
1010 let b = w[1];
1011 if t >= a.time && t <= b.time {
1012 let span = (b.time - a.time).max(1) as f32;
1013 return (a, b, (t - a.time) as f32 / span);
1014 }
1015 }
1016 let first = keys[0];
1017 let last = keys[keys.len() - 1];
1018 if t < first.time {
1019 (first, first, 0.0)
1020 } else {
1021 (last, last, 0.0)
1022 }
1023}
1024
1025fn find_key_pair_mat(keys: &[MatKey], t: i64) -> (MatKey, MatKey, f32) {
1026 for w in keys.windows(2) {
1027 let a = w[0];
1028 let b = w[1];
1029 if t >= a.time && t <= b.time {
1030 let span = (b.time - a.time).max(1) as f32;
1031 return (a, b, (t - a.time) as f32 / span);
1032 }
1033 }
1034 let first = keys[0];
1035 let last = keys[keys.len() - 1];
1036 if t < first.time {
1037 (first, first, 0.0)
1038 } else {
1039 (last, last, 0.0)
1040 }
1041}
1042
1043fn skin_matrix_for_bone(bone: &BoneBinding, pose: &PoseState) -> Mat4 {
1044 let combined = pose
1045 .combined
1046 .get(bone.frame_index)
1047 .copied()
1048 .unwrap_or(Mat4::identity());
1049 bone.offset_matrix.mul(&combined)
1050}
1051
1052fn skin_vertex(
1053 src: &MeshTriVertex,
1054 prim: &MeshPrimitive,
1055 pose: &PoseState,
1056) -> ([f32; 3], [f32; 3]) {
1057 let mut pos = [0.0f32; 3];
1058 let mut normal = [0.0f32; 3];
1059 let mut accum = 0.0f32;
1060 for lane in 0..4 {
1061 let weight = src.bone_weights[lane];
1062 if weight <= 0.0 {
1063 continue;
1064 }
1065 let bone_idx = src.bone_indices[lane] as usize;
1066 let Some(bone) = prim.bones.get(bone_idx) else {
1067 continue;
1068 };
1069 let skin_mat = skin_matrix_for_bone(bone, pose);
1070 let p = skin_mat.transform_point(src.pos);
1071 let n = skin_mat.transform_vector(src.normal);
1072 for i in 0..3 {
1073 pos[i] += p[i] * weight;
1074 normal[i] += n[i] * weight;
1075 }
1076 accum += weight;
1077 }
1078 if accum <= 1e-6 {
1079 let frame_world = pose
1080 .combined
1081 .get(prim.frame_index)
1082 .copied()
1083 .unwrap_or(Mat4::identity());
1084 (
1085 frame_world.transform_point(src.pos),
1086 frame_world.transform_vector(src.normal),
1087 )
1088 } else {
1089 (pos, normal)
1090 }
1091}
1092
1093#[derive(Debug, Clone, Copy, Default)]
1094struct Quat {
1095 x: f32,
1096 y: f32,
1097 z: f32,
1098 w: f32,
1099}
1100
1101impl Quat {
1102 fn normalize(self) -> Self {
1103 let len = (self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w).sqrt();
1104 if len <= 1e-8 {
1105 return Self {
1106 x: 0.0,
1107 y: 0.0,
1108 z: 0.0,
1109 w: 1.0,
1110 };
1111 }
1112 let inv = 1.0 / len;
1113 Self {
1114 x: self.x * inv,
1115 y: self.y * inv,
1116 z: self.z * inv,
1117 w: self.w * inv,
1118 }
1119 }
1120
1121 fn nlerp(a: Self, mut b: Self, t: f32) -> Self {
1122 let mut dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
1123 if dot < 0.0 {
1124 dot = -dot;
1125 b = Self {
1126 x: -b.x,
1127 y: -b.y,
1128 z: -b.z,
1129 w: -b.w,
1130 };
1131 }
1132 let k = t.clamp(0.0, 1.0);
1133 Self {
1134 x: a.x + (b.x - a.x) * k,
1135 y: a.y + (b.y - a.y) * k,
1136 z: a.z + (b.z - a.z) * k,
1137 w: a.w + (b.w - a.w) * k,
1138 }
1139 .normalize()
1140 }
1141}
1142
1143#[derive(Debug, Clone, Copy)]
1144struct Mat4 {
1145 m: [[f32; 4]; 4],
1146}
1147
1148impl Mat4 {
1149 fn identity() -> Self {
1150 Self {
1151 m: [
1152 [1.0, 0.0, 0.0, 0.0],
1153 [0.0, 1.0, 0.0, 0.0],
1154 [0.0, 0.0, 1.0, 0.0],
1155 [0.0, 0.0, 0.0, 1.0],
1156 ],
1157 }
1158 }
1159
1160 fn from_x_values(vals: [f32; 16]) -> Self {
1161 Self {
1162 m: [
1163 [vals[0], vals[1], vals[2], vals[3]],
1164 [vals[4], vals[5], vals[6], vals[7]],
1165 [vals[8], vals[9], vals[10], vals[11]],
1166 [vals[12], vals[13], vals[14], vals[15]],
1167 ],
1168 }
1169 }
1170
1171 fn mul(&self, rhs: &Self) -> Self {
1172 let mut out = [[0.0f32; 4]; 4];
1173 for r in 0..4 {
1174 for c in 0..4 {
1175 out[r][c] = self.m[r][0] * rhs.m[0][c]
1176 + self.m[r][1] * rhs.m[1][c]
1177 + self.m[r][2] * rhs.m[2][c]
1178 + self.m[r][3] * rhs.m[3][c];
1179 }
1180 }
1181 Self { m: out }
1182 }
1183
1184 fn transform_point(&self, p: [f32; 3]) -> [f32; 3] {
1185 [
1186 p[0] * self.m[0][0] + p[1] * self.m[1][0] + p[2] * self.m[2][0] + self.m[3][0],
1187 p[0] * self.m[0][1] + p[1] * self.m[1][1] + p[2] * self.m[2][1] + self.m[3][1],
1188 p[0] * self.m[0][2] + p[1] * self.m[1][2] + p[2] * self.m[2][2] + self.m[3][2],
1189 ]
1190 }
1191
1192 fn transform_vector(&self, p: [f32; 3]) -> [f32; 3] {
1193 [
1194 p[0] * self.m[0][0] + p[1] * self.m[1][0] + p[2] * self.m[2][0],
1195 p[0] * self.m[0][1] + p[1] * self.m[1][1] + p[2] * self.m[2][1],
1196 p[0] * self.m[0][2] + p[1] * self.m[1][2] + p[2] * self.m[2][2],
1197 ]
1198 }
1199
1200 fn lerp(a: Self, b: Self, t: f32) -> Self {
1201 let k = t.clamp(0.0, 1.0);
1202 let mut out = [[0.0f32; 4]; 4];
1203 for r in 0..4 {
1204 for c in 0..4 {
1205 out[r][c] = a.m[r][c] + (b.m[r][c] - a.m[r][c]) * k;
1206 }
1207 }
1208 Self { m: out }
1209 }
1210
1211 fn from_scale_rotation_translation(scale: [f32; 3], rot: Quat, pos: [f32; 3]) -> Self {
1212 let q = rot.normalize();
1213 let xx = q.x * q.x;
1214 let yy = q.y * q.y;
1215 let zz = q.z * q.z;
1216 let xy = q.x * q.y;
1217 let xz = q.x * q.z;
1218 let yz = q.y * q.z;
1219 let wx = q.w * q.x;
1220 let wy = q.w * q.y;
1221 let wz = q.w * q.z;
1222 let mut m = Self::identity();
1223 m.m[0][0] = (1.0 - 2.0 * (yy + zz)) * scale[0];
1224 m.m[0][1] = (2.0 * (xy + wz)) * scale[0];
1225 m.m[0][2] = (2.0 * (xz - wy)) * scale[0];
1226 m.m[1][0] = (2.0 * (xy - wz)) * scale[1];
1227 m.m[1][1] = (1.0 - 2.0 * (xx + zz)) * scale[1];
1228 m.m[1][2] = (2.0 * (yz + wx)) * scale[1];
1229 m.m[2][0] = (2.0 * (xz + wy)) * scale[2];
1230 m.m[2][1] = (2.0 * (yz - wx)) * scale[2];
1231 m.m[2][2] = (1.0 - 2.0 * (xx + yy)) * scale[2];
1232 m.m[3][0] = pos[0];
1233 m.m[3][1] = pos[1];
1234 m.m[3][2] = pos[2];
1235 m
1236 }
1237
1238 fn decompose_srt(&self) -> ([f32; 3], Quat, [f32; 3]) {
1239 let pos = [self.m[3][0], self.m[3][1], self.m[3][2]];
1240 let sx = (self.m[0][0] * self.m[0][0]
1241 + self.m[0][1] * self.m[0][1]
1242 + self.m[0][2] * self.m[0][2])
1243 .sqrt()
1244 .max(1e-8);
1245 let sy = (self.m[1][0] * self.m[1][0]
1246 + self.m[1][1] * self.m[1][1]
1247 + self.m[1][2] * self.m[1][2])
1248 .sqrt()
1249 .max(1e-8);
1250 let sz = (self.m[2][0] * self.m[2][0]
1251 + self.m[2][1] * self.m[2][1]
1252 + self.m[2][2] * self.m[2][2])
1253 .sqrt()
1254 .max(1e-8);
1255 let scale = [sx, sy, sz];
1256 let r00 = self.m[0][0] / sx;
1257 let r01 = self.m[0][1] / sx;
1258 let r02 = self.m[0][2] / sx;
1259 let r10 = self.m[1][0] / sy;
1260 let r11 = self.m[1][1] / sy;
1261 let r12 = self.m[1][2] / sy;
1262 let r20 = self.m[2][0] / sz;
1263 let r21 = self.m[2][1] / sz;
1264 let r22 = self.m[2][2] / sz;
1265 let trace = r00 + r11 + r22;
1266 let rot = if trace > 0.0 {
1267 let s = (trace + 1.0).sqrt() * 2.0;
1268 Quat {
1269 w: 0.25 * s,
1270 x: (r21 - r12) / s,
1271 y: (r02 - r20) / s,
1272 z: (r10 - r01) / s,
1273 }
1274 } else if r00 > r11 && r00 > r22 {
1275 let s = (1.0 + r00 - r11 - r22).sqrt() * 2.0;
1276 Quat {
1277 w: (r21 - r12) / s,
1278 x: 0.25 * s,
1279 y: (r01 + r10) / s,
1280 z: (r02 + r20) / s,
1281 }
1282 } else if r11 > r22 {
1283 let s = (1.0 + r11 - r00 - r22).sqrt() * 2.0;
1284 Quat {
1285 w: (r02 - r20) / s,
1286 x: (r01 + r10) / s,
1287 y: 0.25 * s,
1288 z: (r12 + r21) / s,
1289 }
1290 } else {
1291 let s = (1.0 + r22 - r00 - r11).sqrt() * 2.0;
1292 Quat {
1293 w: (r10 - r01) / s,
1294 x: (r02 + r20) / s,
1295 y: (r12 + r21) / s,
1296 z: 0.25 * s,
1297 }
1298 };
1299 (scale, rot.normalize(), pos)
1300 }
1301}
1302
1303fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
1304 [
1305 a[1] * b[2] - a[2] * b[1],
1306 a[2] * b[0] - a[0] * b[2],
1307 a[0] * b[1] - a[1] * b[0],
1308 ]
1309}
1310
1311fn normalize3(v: [f32; 3]) -> [f32; 3] {
1312 let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
1313 if len <= 1e-8 {
1314 [0.0, 0.0, 1.0]
1315 } else {
1316 [v[0] / len, v[1] / len, v[2] / len]
1317 }
1318}
1319
1320fn lerp3(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
1321 let k = t.clamp(0.0, 1.0);
1322 [
1323 a[0] + (b[0] - a[0]) * k,
1324 a[1] + (b[1] - a[1]) * k,
1325 a[2] + (b[2] - a[2]) * k,
1326 ]
1327}
1328
1329#[derive(Debug, Clone)]
1330enum Tok {
1331 Ident(String),
1332 Number(f32),
1333 Str(String),
1334 Sym(char),
1335}
1336
1337struct Cursor {
1338 toks: Vec<Tok>,
1339 i: usize,
1340}
1341
1342impl Cursor {
1343 fn new(toks: Vec<Tok>) -> Self {
1344 Self { toks, i: 0 }
1345 }
1346
1347 fn peek(&self) -> Option<&Tok> {
1348 self.toks.get(self.i)
1349 }
1350
1351 fn next(&mut self) -> Option<Tok> {
1352 let out = self.toks.get(self.i).cloned();
1353 if out.is_some() {
1354 self.i += 1;
1355 }
1356 out
1357 }
1358
1359 fn consume_ident(&mut self, name: &str) -> bool {
1360 match self.peek() {
1361 Some(Tok::Ident(s)) if s == name => {
1362 self.i += 1;
1363 true
1364 }
1365 _ => false,
1366 }
1367 }
1368
1369 fn consume_sym(&mut self, ch: char) -> bool {
1370 match self.peek() {
1371 Some(Tok::Sym(c)) if *c == ch => {
1372 self.i += 1;
1373 true
1374 }
1375 _ => false,
1376 }
1377 }
1378
1379 fn next_number(&mut self) -> Result<f32> {
1380 match self.next() {
1381 Some(Tok::Number(v)) => Ok(v),
1382 other => bail!("expected number, got {:?}", other),
1383 }
1384 }
1385
1386 fn next_i64(&mut self) -> Result<i64> {
1387 Ok(self.next_number()? as i64)
1388 }
1389
1390 fn next_usize(&mut self) -> Result<usize> {
1391 Ok(self.next_number()? as usize)
1392 }
1393
1394 fn next_string(&mut self) -> Result<String> {
1395 match self.next() {
1396 Some(Tok::Str(s)) => Ok(s),
1397 Some(Tok::Ident(s)) => Ok(s),
1398 other => bail!("expected string, got {:?}", other),
1399 }
1400 }
1401
1402 fn optional_name_before_block(&mut self) -> Option<String> {
1403 match self.peek() {
1404 Some(Tok::Ident(s)) if matches!(self.toks.get(self.i + 1), Some(Tok::Sym('{'))) => {
1405 let out = s.clone();
1406 self.i += 1;
1407 Some(out)
1408 }
1409 Some(Tok::Str(s)) if matches!(self.toks.get(self.i + 1), Some(Tok::Sym('{'))) => {
1410 let out = s.clone();
1411 self.i += 1;
1412 Some(out)
1413 }
1414 _ => None,
1415 }
1416 }
1417
1418 fn expect_block_start(&mut self) -> Result<()> {
1419 let _ = self.optional_name_before_block();
1420 if self.consume_sym('{') {
1421 Ok(())
1422 } else {
1423 bail!("expected '{{' at token {}", self.i)
1424 }
1425 }
1426
1427 fn skip_block(&mut self) {
1428 let _ = self.optional_name_before_block();
1429 if !self.consume_sym('{') {
1430 let _ = self.next();
1431 return;
1432 }
1433 let mut depth = 1usize;
1434 while let Some(tok) = self.next() {
1435 match tok {
1436 Tok::Sym('{') => depth += 1,
1437 Tok::Sym('}') => {
1438 depth -= 1;
1439 if depth == 0 {
1440 break;
1441 }
1442 }
1443 _ => {}
1444 }
1445 }
1446 }
1447}
1448
1449fn find_existing_casefold(path: &Path) -> Option<PathBuf> {
1450 crate::resource::resolve_game_file(path).ok().flatten()
1451}
1452
1453fn resolve_relative_casefold(base: &Path, rel: &str) -> Option<PathBuf> {
1454 let normalized = rel.replace('\\', "/");
1455 crate::resource::resolve_game_file(&base.join(normalized))
1456 .ok()
1457 .flatten()
1458}
1459
1460pub fn resolve_mesh_path(project_dir: &Path, append_dir: &str, file_name: &str) -> Result<PathBuf> {
1461 if file_name.trim().is_empty() {
1462 bail!("empty mesh file name")
1463 }
1464 let raw = Path::new(file_name);
1465 if raw.is_absolute() {
1466 let x_raw = if raw.extension().is_some() {
1467 raw.to_path_buf()
1468 } else {
1469 raw.with_extension("x")
1470 };
1471 if let Some(found) = find_existing_casefold(&x_raw) {
1472 return Ok(found);
1473 }
1474 let sgmesh_raw = raw.with_extension("sgmesh");
1475 if let Some(found) = find_existing_casefold(&sgmesh_raw) {
1476 return Ok(found);
1477 }
1478 }
1479 let norm = file_name.replace('\\', "/");
1480 let p = Path::new(&norm);
1481 let x_name = if p.extension().is_some() {
1482 norm.clone()
1483 } else {
1484 format!("{norm}.x")
1485 };
1486 let sgmesh_name = if p
1487 .extension()
1488 .and_then(|s| s.to_str())
1489 .map(|s| s.eq_ignore_ascii_case("sgmesh"))
1490 .unwrap_or(false)
1491 {
1492 norm.clone()
1493 } else {
1494 p.with_extension("sgmesh")
1495 .to_string_lossy()
1496 .replace('\\', "/")
1497 };
1498
1499 for append in crate::resource::ordered_append_dirs(project_dir, append_dir) {
1500 let base = if append.is_empty() {
1501 project_dir.join("x")
1502 } else {
1503 project_dir.join(&append).join("x")
1504 };
1505 if let Some(found) = resolve_relative_casefold(&base, &x_name) {
1506 return Ok(found);
1507 }
1508 }
1509
1510 for append in crate::resource::ordered_append_dirs(project_dir, append_dir) {
1511 let base = if append.is_empty() {
1512 project_dir.join("x")
1513 } else {
1514 project_dir.join(&append).join("x")
1515 };
1516 if let Some(found) = resolve_relative_casefold(&base, &sgmesh_name) {
1517 return Ok(found);
1518 }
1519 }
1520
1521 bail!("mesh asset not found through x resource path: {file_name}")
1522}
1523
1524pub fn load_mesh_asset(project_dir: &Path, append_dir: &str, file_name: &str) -> Result<MeshAsset> {
1525 let path = resolve_mesh_path(project_dir, append_dir, file_name)?;
1526 load_mesh_asset_from_path(&path)
1527}
1528
1529fn load_mesh_asset_from_path(path: &Path) -> Result<MeshAsset> {
1530 let bytes =
1531 crate::resource::read_file_bytes(path).with_context(|| format!("read mesh {:?}", path))?;
1532 let ext = path
1533 .extension()
1534 .and_then(|s| s.to_str())
1535 .unwrap_or("")
1536 .to_ascii_lowercase();
1537 let mut asset = if ext == "sgmesh" {
1538 read_internal_mesh_asset(path)?
1539 } else if ext == "obj" {
1540 parse_obj(&decode_text(&bytes), path)?
1541 } else if ext == "x" {
1542 import_x_scene_bytes(&bytes, path)?.into_mesh_asset(path)?
1543 } else {
1544 let text = decode_text(&bytes);
1545 if text.contains("Mesh") && text.contains("xof") {
1546 import_x_scene_bytes(&bytes, path)?.into_mesh_asset(path)?
1547 } else {
1548 parse_obj(&text, path)?
1549 }
1550 };
1551 finalize_mesh_asset(&mut asset);
1552 Ok(asset)
1553}
1554
1555fn finalize_mesh_asset(asset: &mut MeshAsset) {
1556 if asset.texture_path.is_none() {
1557 asset.texture_path = asset.primitives.iter().find_map(|p| p.texture_path.clone());
1558 }
1559 for prim in &mut asset.primitives {
1560 if prim.runtime_desc.effect_key.is_empty() || prim.runtime_desc.technique_name.is_empty() {
1561 prim.runtime_desc = build_mesh_primitive_runtime_desc(prim);
1562 } else {
1563 prim.runtime_desc.vertex_stride_bytes = std::mem::size_of::<MeshTriVertex>() as u32;
1564 prim.runtime_desc.vertex_count = prim.vertices.len() as u32;
1565 prim.runtime_desc.bone_palette_len = prim.bones.len() as u32;
1566 }
1567 }
1568 if asset.vertices.is_empty() {
1569 asset.vertices = asset.sample_vertices(0.0);
1570 }
1571 asset.bone_count = asset
1572 .primitives
1573 .iter()
1574 .map(|p| p.bones.len())
1575 .max()
1576 .unwrap_or(0);
1577 if asset.ticks_per_second <= 0.0 {
1578 asset.ticks_per_second = 1.0;
1579 }
1580}
1581
1582pub fn internal_mesh_asset_path_for_source(path: &Path) -> PathBuf {
1583 path.with_extension("sgmesh")
1584}
1585
1586pub fn compile_mesh_asset_file(input: &Path, output: &Path) -> Result<()> {
1587 let mut asset = load_mesh_asset_from_path(input)?;
1588 asset.source_path = input.to_path_buf();
1589 write_internal_mesh_asset(output, &asset)
1590}
1591
1592fn decode_text(bytes: &[u8]) -> String {
1593 if bytes.len() >= 2 && bytes[1] == 0 {
1594 let mut u16s = Vec::with_capacity(bytes.len() / 2);
1595 let mut i = 0usize;
1596 while i + 1 < bytes.len() {
1597 u16s.push(u16::from_le_bytes([bytes[i], bytes[i + 1]]));
1598 i += 2;
1599 }
1600 String::from_utf16_lossy(&u16s)
1601 } else {
1602 String::from_utf8_lossy(bytes).into_owned()
1603 }
1604}
1605
1606fn parse_obj(text: &str, path: &Path) -> Result<MeshAsset> {
1607 #[derive(Debug, Clone)]
1608 struct ObjGroup {
1609 material_name: Option<String>,
1610 vertices: Vec<MeshTriVertex>,
1611 }
1612
1613 let mut positions = Vec::<[f32; 3]>::new();
1614 let mut texcoords = Vec::<[f32; 2]>::new();
1615 let mut normals = Vec::<[f32; 3]>::new();
1616 let mut mtllibs = Vec::<PathBuf>::new();
1617 let mut groups = vec![ObjGroup {
1618 material_name: None,
1619 vertices: Vec::new(),
1620 }];
1621 let mut current_group = 0usize;
1622
1623 for line in text.lines() {
1624 let line = line.trim();
1625 if line.is_empty() || line.starts_with('#') {
1626 continue;
1627 }
1628 let mut parts = line.split_whitespace();
1629 let Some(tag) = parts.next() else {
1630 continue;
1631 };
1632 match tag {
1633 "v" => {
1634 let vals: Vec<f32> = parts.filter_map(|s| s.parse::<f32>().ok()).collect();
1635 if vals.len() >= 3 {
1636 positions.push([vals[0], vals[1], vals[2]]);
1637 }
1638 }
1639 "vt" => {
1640 let vals: Vec<f32> = parts.filter_map(|s| s.parse::<f32>().ok()).collect();
1641 if vals.len() >= 2 {
1642 texcoords.push([vals[0], 1.0 - vals[1]]);
1643 }
1644 }
1645 "vn" => {
1646 let vals: Vec<f32> = parts.filter_map(|s| s.parse::<f32>().ok()).collect();
1647 if vals.len() >= 3 {
1648 normals.push(normalize3([vals[0], vals[1], vals[2]]));
1649 }
1650 }
1651 "mtllib" => {
1652 for name in parts {
1653 let cand = if let Some(parent) = path.parent() {
1654 parent.join(name)
1655 } else {
1656 PathBuf::from(name)
1657 };
1658 mtllibs.push(cand);
1659 }
1660 }
1661 "usemtl" => {
1662 let mat = parts.next().map(|s| s.to_string());
1663 if let Some(idx) = groups.iter().position(|g| g.material_name == mat) {
1664 current_group = idx;
1665 } else {
1666 current_group = groups.len();
1667 groups.push(ObjGroup {
1668 material_name: mat,
1669 vertices: Vec::new(),
1670 });
1671 }
1672 }
1673 "f" => {
1674 let items: Vec<String> = parts.map(|s| s.to_string()).collect();
1675 if items.len() < 3 {
1676 continue;
1677 }
1678 let mut face_verts = Vec::<MeshTriVertex>::new();
1679 for item in items {
1680 let comps: Vec<&str> = item.split('/').collect();
1681 let vi = comps
1682 .first()
1683 .and_then(|s| s.parse::<isize>().ok())
1684 .unwrap_or(0);
1685 let ti = comps
1686 .get(1)
1687 .and_then(|s| {
1688 if s.is_empty() {
1689 None
1690 } else {
1691 s.parse::<isize>().ok()
1692 }
1693 })
1694 .unwrap_or(0);
1695 let ni = comps
1696 .get(2)
1697 .and_then(|s| {
1698 if s.is_empty() {
1699 None
1700 } else {
1701 s.parse::<isize>().ok()
1702 }
1703 })
1704 .unwrap_or(0);
1705 let pos = obj_index(&positions, vi)
1706 .copied()
1707 .unwrap_or([0.0, 0.0, 0.0]);
1708 let uv = obj_index(&texcoords, ti).copied().unwrap_or([0.0, 0.0]);
1709 let normal = obj_index(&normals, ni).copied().unwrap_or([0.0, 0.0, 0.0]);
1710 face_verts.push(MeshTriVertex {
1711 pos,
1712 uv,
1713 normal,
1714 tangent: [0.0, 0.0, 0.0],
1715 binormal: [0.0, 0.0, 0.0],
1716 color: [1.0, 1.0, 1.0, 1.0],
1717 bone_indices: [0; 4],
1718 bone_weights: [0.0; 4],
1719 });
1720 }
1721 for i in 1..face_verts.len() - 1 {
1722 let a = face_verts[0].clone();
1723 let b = face_verts[i].clone();
1724 let c = face_verts[i + 1].clone();
1725 let n = triangle_normal(a.pos, b.pos, c.pos);
1726 groups[current_group].vertices.push(MeshTriVertex {
1727 normal: if has_normal(a.normal) { a.normal } else { n },
1728 ..a
1729 });
1730 groups[current_group].vertices.push(MeshTriVertex {
1731 normal: if has_normal(b.normal) { b.normal } else { n },
1732 ..b
1733 });
1734 groups[current_group].vertices.push(MeshTriVertex {
1735 normal: if has_normal(c.normal) { c.normal } else { n },
1736 ..c
1737 });
1738 }
1739 }
1740 _ => {}
1741 }
1742 }
1743
1744 let material_map = parse_obj_material_libs(path, &mtllibs);
1745 let mut primitives = Vec::new();
1746 let mut combined = Vec::new();
1747 let mut fallback_tex = None;
1748 for group in groups {
1749 if group.vertices.is_empty() {
1750 continue;
1751 }
1752 let (material, texture_path) = if let Some(name) = group.material_name.as_deref() {
1753 material_map
1754 .get(name)
1755 .cloned()
1756 .unwrap_or((default_mesh_material(), None))
1757 } else {
1758 (default_mesh_material(), None)
1759 };
1760 if fallback_tex.is_none() {
1761 fallback_tex = texture_path.clone();
1762 }
1763 let mut vertices = group.vertices;
1764 apply_tangent_space(&mut vertices);
1765 combined.extend(vertices.iter().cloned());
1766 primitives.push(MeshPrimitive {
1767 frame_index: 0,
1768 texture_path,
1769 material,
1770 runtime_desc: MeshPrimitiveRuntimeDesc::default(),
1771 vertices,
1772 bones: Vec::new(),
1773 });
1774 }
1775 if primitives.is_empty() {
1776 bail!("no faces in {:?}", path);
1777 }
1778 Ok(MeshAsset {
1779 source_path: path.to_path_buf(),
1780 texture_path: fallback_tex,
1781 vertices: combined,
1782 bone_count: 0,
1783 ticks_per_second: 4800.0,
1784 primitives,
1785 frames: vec![FrameNode {
1786 name: "__root__".to_string(),
1787 base_local: Mat4::identity(),
1788 children: Vec::new(),
1789 }],
1790 animations: Vec::new(),
1791 })
1792}
1793
1794fn parse_obj_material_libs(
1795 mesh_path: &Path,
1796 libs: &[PathBuf],
1797) -> HashMap<String, (MeshMaterial, Option<PathBuf>)> {
1798 let mut out = HashMap::new();
1799 for lib in libs {
1800 if let Ok(text) = crate::resource::read_file_to_string(lib) {
1801 let base = lib
1802 .parent()
1803 .unwrap_or_else(|| mesh_path.parent().unwrap_or(Path::new(".")));
1804 let mut current_name: Option<String> = None;
1805 let mut current_material = default_mesh_material();
1806 let mut current_texture: Option<PathBuf> = None;
1807 for line in text.lines() {
1808 let line = line.trim();
1809 if line.is_empty() || line.starts_with('#') {
1810 continue;
1811 }
1812 let mut parts = line.split_whitespace();
1813 let Some(tag) = parts.next() else {
1814 continue;
1815 };
1816 match tag {
1817 "newmtl" => {
1818 if let Some(name) = current_name.take() {
1819 out.insert(name, (current_material.clone(), current_texture.clone()));
1820 }
1821 current_name = parts.next().map(|s| s.to_string());
1822 current_material = default_mesh_material();
1823 current_texture = None;
1824 }
1825 "Ka" => {
1826 let vals: Vec<f32> = parts.filter_map(|s| s.parse::<f32>().ok()).collect();
1827 if vals.len() >= 3 {
1828 current_material.ambient = [vals[0], vals[1], vals[2], 1.0];
1829 }
1830 }
1831 "Kd" => {
1832 let vals: Vec<f32> = parts.filter_map(|s| s.parse::<f32>().ok()).collect();
1833 if vals.len() >= 3 {
1834 current_material.diffuse = [vals[0], vals[1], vals[2], 1.0];
1835 }
1836 }
1837 "Ks" => {
1838 let vals: Vec<f32> = parts.filter_map(|s| s.parse::<f32>().ok()).collect();
1839 if vals.len() >= 3 {
1840 current_material.specular = [vals[0], vals[1], vals[2], 1.0];
1841 }
1842 }
1843 "Ke" => {
1844 let vals: Vec<f32> = parts.filter_map(|s| s.parse::<f32>().ok()).collect();
1845 if vals.len() >= 3 {
1846 current_material.emissive = [vals[0], vals[1], vals[2], 1.0];
1847 }
1848 }
1849 "Ns" => {
1850 if let Some(v) = parts.next().and_then(|s| s.parse::<f32>().ok()) {
1851 current_material.power = v.max(1.0);
1852 }
1853 }
1854 "illum" => {
1855 if let Some(v) = parts.next().and_then(|s| s.parse::<i32>().ok()) {
1856 current_material.lighting_type = match v {
1857 0 => MeshLightingType::None,
1858 1 => MeshLightingType::Lambert,
1859 2..=10 => MeshLightingType::BlinnPhong,
1860 _ => current_material.lighting_type,
1861 };
1862 }
1863 }
1864 "d" | "Tr" => {
1865 if let Some(v) = parts.next().and_then(|s| s.parse::<f32>().ok()) {
1866 current_material.diffuse[3] = if tag == "Tr" { 1.0 - v } else { v };
1867 }
1868 }
1869 "map_Kd" => {
1870 if let Some(name) = parts.next() {
1871 let tex = base.join(name);
1872 current_texture = crate::resource::resolve_game_file(&tex)
1873 .ok()
1874 .flatten()
1875 .or_else(|| resolve_texture_path(mesh_path, name));
1876 }
1877 }
1878 "map_Bump" | "bump" | "norm" => {
1879 if let Some(name) = parts.next() {
1880 let tex = base.join(name);
1881 current_material.normal_texture_path =
1882 crate::resource::resolve_game_file(&tex)
1883 .ok()
1884 .flatten()
1885 .or_else(|| resolve_texture_path(mesh_path, name));
1886 if current_material.normal_texture_path.is_some() {
1887 current_material.lighting_type = MeshLightingType::Bump;
1888 }
1889 }
1890 }
1891 _ => {}
1892 }
1893 }
1894 if let Some(name) = current_name.take() {
1895 out.insert(name, (current_material, current_texture));
1896 }
1897 }
1898 }
1899 out
1900}
1901
1902fn default_mesh_material() -> MeshMaterial {
1903 MeshMaterial {
1904 diffuse: [1.0, 1.0, 1.0, 1.0],
1905 ambient: [1.0, 1.0, 1.0, 1.0],
1906 specular: [0.0, 0.0, 0.0, 1.0],
1907 emissive: [0.0, 0.0, 0.0, 1.0],
1908 power: 16.0,
1909 lighting_type: MeshLightingType::None,
1910 shading_type: MeshShadingType::None,
1911 shader_option: MESH_SHADER_OPTION_NONE,
1912 rim_light_color: [1.0, 1.0, 1.0, 1.0],
1913 rim_light_power: 1.0,
1914 parallax_max_height: 0.016,
1915 alpha_test_enable: false,
1916 alpha_ref: 0.001,
1917 cull_disable: false,
1918 shadow_map_enable: true,
1919 use_mesh_tex: true,
1920 use_mrbd: false,
1921 mrbd: [0.0, 0.0, 0.0, 0.0],
1922 use_rgb: false,
1923 rgb_rate: [0.0, 0.0, 0.0, 0.0],
1924 add_rgb: [0.0, 0.0, 0.0, 0.0],
1925 use_mul_vertex_color: false,
1926 mul_vertex_color_rate: 1.0,
1927 depth_buffer_shadow_bias: 0.03,
1928 directional_light_ids: vec![10],
1929 point_light_ids: vec![20],
1930 spot_light_ids: vec![30],
1931 normal_texture_path: None,
1932 toon_texture_path: None,
1933 effect_filename: None,
1934 }
1935}
1936
1937pub fn mesh_runtime_material_key(
1938 material: &MeshMaterial,
1939 has_texture: bool,
1940 skinned: bool,
1941) -> MeshRuntimeMaterialKey {
1942 let use_shadow_tex = material.shading_type == MeshShadingType::DepthBuffer;
1943 let use_toon_tex =
1944 material.lighting_type == MeshLightingType::Toon && material.toon_texture_path.is_some();
1945 let use_normal_tex = matches!(
1946 material.lighting_type,
1947 MeshLightingType::Bump | MeshLightingType::Parallax
1948 ) && material.normal_texture_path.is_some();
1949 MeshRuntimeMaterialKey {
1950 use_mesh_tex: has_texture && material.use_mesh_tex,
1951 use_shadow_tex,
1952 use_toon_tex,
1953 use_normal_tex,
1954 use_mul_vertex_color: material.use_mul_vertex_color,
1955 use_mrbd: material.use_mrbd,
1956 use_rgb: material.use_rgb,
1957 lighting_type: material.lighting_type,
1958 shading_type: material.shading_type,
1959 shader_option: material.shader_option,
1960 skinned,
1961 alpha_test_enable: material.alpha_test_enable,
1962 cull_disable: material.cull_disable,
1963 shadow_map_enable: material.shadow_map_enable,
1964 }
1965}
1966
1967pub fn mesh_effect_variant_bits(material: &MeshMaterial, has_texture: bool, skinned: bool) -> u64 {
1968 mesh_effect_variant_bits_from_runtime_key(mesh_runtime_material_key(
1969 material,
1970 has_texture,
1971 skinned,
1972 ))
1973}
1974
1975pub fn mesh_effect_variant_bits_from_runtime_desc(desc: &MeshPrimitiveRuntimeDesc) -> u64 {
1976 mesh_effect_variant_bits_from_runtime_key(desc.material_key)
1977}
1978
1979pub fn mesh_effect_variant_bits_from_runtime_key(key: MeshRuntimeMaterialKey) -> u64 {
1980 let mut bits = 0u64;
1981 if key.use_mesh_tex {
1982 bits |= 1 << 0;
1983 }
1984 if key.use_shadow_tex {
1985 bits |= 1 << 1;
1986 }
1987 if key.use_toon_tex {
1988 bits |= 1 << 2;
1989 }
1990 if key.use_normal_tex {
1991 bits |= 1 << 3;
1992 }
1993 if key.use_mul_vertex_color {
1994 bits |= 1 << 4;
1995 }
1996 if key.use_mrbd {
1997 bits |= 1 << 5;
1998 }
1999 if key.use_rgb {
2000 bits |= 1 << 6;
2001 }
2002 if key.shader_option & MESH_SHADER_OPTION_RIM_LIGHT != 0 {
2003 bits |= 1 << 7;
2004 }
2005 bits |= ((key.lighting_type as u64) & 0xF) << 8;
2006 bits |= ((key.shading_type as u64) & 0xF) << 12;
2007 if key.skinned {
2008 bits |= 1 << 16;
2009 }
2010 if key.alpha_test_enable {
2011 bits |= 1 << 17;
2012 }
2013 if key.cull_disable {
2014 bits |= 1 << 18;
2015 }
2016 if key.shadow_map_enable {
2017 bits |= 1 << 19;
2018 }
2019 bits
2020}
2021
2022pub fn mesh_effect_key_from_variant(bits: u64) -> String {
2023 let key = MeshRuntimeMaterialKey {
2024 use_mesh_tex: (bits & (1 << 0)) != 0,
2025 use_shadow_tex: (bits & (1 << 1)) != 0,
2026 use_toon_tex: (bits & (1 << 2)) != 0,
2027 use_normal_tex: (bits & (1 << 3)) != 0,
2028 use_mul_vertex_color: (bits & (1 << 4)) != 0,
2029 use_mrbd: (bits & (1 << 5)) != 0,
2030 use_rgb: (bits & (1 << 6)) != 0,
2031 lighting_type: match ((bits >> 8) & 0xF) as u32 {
2032 1 => MeshLightingType::Lambert,
2033 2 => MeshLightingType::BlinnPhong,
2034 3 => MeshLightingType::PerPixelBlinnPhong,
2035 4 => MeshLightingType::PerPixelHalfLambert,
2036 5 => MeshLightingType::Toon,
2037 6 => MeshLightingType::FixedFunction,
2038 7 => MeshLightingType::PerPixelFixedFunction,
2039 8 => MeshLightingType::Bump,
2040 9 => MeshLightingType::Parallax,
2041 _ => MeshLightingType::None,
2042 },
2043 shading_type: if ((bits >> 12) & 0xF) as u32 == 1 {
2044 MeshShadingType::DepthBuffer
2045 } else {
2046 MeshShadingType::None
2047 },
2048 shader_option: if (bits & (1 << 7)) != 0 {
2049 MESH_SHADER_OPTION_RIM_LIGHT
2050 } else {
2051 MESH_SHADER_OPTION_NONE
2052 },
2053 skinned: (bits & (1 << 16)) != 0,
2054 alpha_test_enable: (bits & (1 << 17)) != 0,
2055 cull_disable: (bits & (1 << 18)) != 0,
2056 shadow_map_enable: (bits & (1 << 19)) != 0,
2057 };
2058 mesh_effect_filename_from_runtime_key(
2059 if key.skinned {
2060 MeshEffectProfile::SkinnedMesh
2061 } else {
2062 MeshEffectProfile::Mesh
2063 },
2064 key,
2065 )
2066}
2067
2068pub fn mesh_effect_filename_from_runtime_key(
2069 effect_profile: MeshEffectProfile,
2070 key: MeshRuntimeMaterialKey,
2071) -> String {
2072 match effect_profile {
2073 MeshEffectProfile::ShadowMap => {
2074 let mut name = String::from("shadow_map");
2075 if key.skinned {
2076 name.push_str("_skn_p_bw_bi_n.fx");
2077 } else {
2078 name.push_str("_frm_p_n.fx");
2079 }
2080 name
2081 }
2082 MeshEffectProfile::Mesh | MeshEffectProfile::SkinnedMesh | MeshEffectProfile::None => {
2083 let mut name =
2084 if key.skinned || matches!(effect_profile, MeshEffectProfile::SkinnedMesh) {
2085 String::from("skinned")
2086 } else {
2087 String::from("mesh")
2088 };
2089 if key.use_mesh_tex {
2090 name.push_str("_mt");
2091 }
2092 if key.use_shadow_tex {
2093 name.push_str("_st");
2094 }
2095 if key.use_toon_tex {
2096 name.push_str("_tt");
2097 }
2098 if key.use_normal_tex {
2099 name.push_str("_nt");
2100 }
2101 if key.use_mul_vertex_color {
2102 name.push_str("_vc");
2103 }
2104 if key.use_mrbd {
2105 name.push_str("_mrbd");
2106 }
2107 if key.use_rgb {
2108 name.push_str("_rgb");
2109 }
2110 name.push_str(match key.lighting_type {
2111 MeshLightingType::Lambert => "_lmbt",
2112 MeshLightingType::BlinnPhong => "_blph",
2113 MeshLightingType::PerPixelBlinnPhong => "_ppbp",
2114 MeshLightingType::PerPixelHalfLambert => "_pphl",
2115 MeshLightingType::Toon => "_toon",
2116 MeshLightingType::FixedFunction => "_ffp",
2117 MeshLightingType::PerPixelFixedFunction => "_ppfp",
2118 MeshLightingType::Bump => "_bump",
2119 MeshLightingType::Parallax => "_para",
2120 MeshLightingType::None => "_nolt",
2121 });
2122 name.push_str(match key.shading_type {
2123 MeshShadingType::DepthBuffer => "_dpbs",
2124 MeshShadingType::None => "_nost",
2125 });
2126 if key.shader_option & MESH_SHADER_OPTION_RIM_LIGHT != 0 {
2127 name.push_str("_rmlt");
2128 }
2129 name.push_str(".fx");
2130 name
2131 }
2132 }
2133}
2134
2135fn build_mesh_primitive_runtime_desc_from_material(
2136 material: &MeshMaterial,
2137 has_texture: bool,
2138 skinned: bool,
2139 vertex_count: u32,
2140 bone_palette_len: u32,
2141) -> MeshPrimitiveRuntimeDesc {
2142 let material_key = mesh_runtime_material_key(material, has_texture, skinned);
2143 let effect_profile = if skinned {
2144 MeshEffectProfile::SkinnedMesh
2145 } else {
2146 MeshEffectProfile::Mesh
2147 };
2148 MeshPrimitiveRuntimeDesc {
2149 effect_profile,
2150 effect_key: mesh_effect_filename_from_runtime_key(effect_profile, material_key),
2151 technique_name: String::from("tech"),
2152 shadow_effect_key: mesh_effect_filename_from_runtime_key(
2153 MeshEffectProfile::ShadowMap,
2154 MeshRuntimeMaterialKey {
2155 skinned,
2156 shadow_map_enable: true,
2157 ..MeshRuntimeMaterialKey::default()
2158 },
2159 ),
2160 shadow_technique_name: String::from("tech"),
2161 use_mesh_texture_slot: material_key.use_mesh_tex,
2162 use_normal_texture_slot: material_key.use_normal_tex,
2163 use_toon_texture_slot: material_key.use_toon_tex,
2164 use_shadow_texture_slot: material_key.use_shadow_tex,
2165 material_key,
2166 vertex_stride_bytes: std::mem::size_of::<MeshTriVertex>() as u32,
2167 vertex_count,
2168 bone_palette_len,
2169 }
2170}
2171
2172fn build_mesh_primitive_runtime_desc(prim: &MeshPrimitive) -> MeshPrimitiveRuntimeDesc {
2173 build_mesh_primitive_runtime_desc_from_material(
2174 &prim.material,
2175 prim.texture_path.is_some(),
2176 !prim.bones.is_empty(),
2177 prim.vertices.len() as u32,
2178 prim.bones.len() as u32,
2179 )
2180}
2181
2182fn apply_effect_filename(material: &mut MeshMaterial, effect_name: &str) {
2183 let lower = effect_name.to_ascii_lowercase();
2184 material.effect_filename = Some(effect_name.to_string());
2185 if lower.contains("_lmbt") {
2186 material.lighting_type = MeshLightingType::Lambert;
2187 } else if lower.contains("_blph") {
2188 material.lighting_type = MeshLightingType::BlinnPhong;
2189 } else if lower.contains("_ppbp") {
2190 material.lighting_type = MeshLightingType::PerPixelBlinnPhong;
2191 } else if lower.contains("_pphl") {
2192 material.lighting_type = MeshLightingType::PerPixelHalfLambert;
2193 } else if lower.contains("_toon") {
2194 material.lighting_type = MeshLightingType::Toon;
2195 } else if lower.contains("_ffp") {
2196 material.lighting_type = MeshLightingType::FixedFunction;
2197 } else if lower.contains("_ppfp") {
2198 material.lighting_type = MeshLightingType::PerPixelFixedFunction;
2199 } else if lower.contains("_bump") {
2200 material.lighting_type = MeshLightingType::Bump;
2201 } else if lower.contains("_para") {
2202 material.lighting_type = MeshLightingType::Parallax;
2203 } else if lower.contains("_nolt") {
2204 material.lighting_type = MeshLightingType::None;
2205 }
2206 if lower.contains("_dpbs") {
2207 material.shading_type = MeshShadingType::DepthBuffer;
2208 } else if lower.contains("_nost") {
2209 material.shading_type = MeshShadingType::None;
2210 }
2211 if lower.contains("_rmlt") {
2212 material.shader_option |= MESH_SHADER_OPTION_RIM_LIGHT;
2213 }
2214}
2215
2216fn parse_effect_bool(value_str: Option<&str>, values: &[f32]) -> Option<bool> {
2217 if let Some(v) = values.first() {
2218 return Some(*v != 0.0);
2219 }
2220 value_str.map(|s| {
2221 !matches!(
2222 s.to_ascii_lowercase().as_str(),
2223 "0" | "false" | "off" | "none"
2224 )
2225 })
2226}
2227
2228fn parse_effect_int_list(values: &[f32]) -> Vec<i32> {
2229 values.iter().map(|v| *v as i32).collect()
2230}
2231
2232fn assign_effect_param(
2233 material: &mut MeshMaterial,
2234 mesh_path: &Path,
2235 key: &str,
2236 value_str: Option<&str>,
2237 values: &[f32],
2238) {
2239 let key = key.to_ascii_lowercase();
2240 match key.as_str() {
2241 "lighting_type" | "mp__lighting_type" => {
2242 if let Some(v) = values.first() {
2243 material.lighting_type = match *v as i32 {
2244 1 => MeshLightingType::Lambert,
2245 2 => MeshLightingType::BlinnPhong,
2246 3 => MeshLightingType::PerPixelBlinnPhong,
2247 4 => MeshLightingType::PerPixelHalfLambert,
2248 5 => MeshLightingType::Toon,
2249 6 => MeshLightingType::FixedFunction,
2250 7 => MeshLightingType::PerPixelFixedFunction,
2251 8 => MeshLightingType::Bump,
2252 9 => MeshLightingType::Parallax,
2253 _ => MeshLightingType::None,
2254 };
2255 }
2256 }
2257 "shading_type" | "mp__shading_type" => {
2258 if let Some(v) = values.first() {
2259 material.shading_type = if (*v as i32) == 1 {
2260 MeshShadingType::DepthBuffer
2261 } else {
2262 MeshShadingType::None
2263 };
2264 }
2265 }
2266 "shader_option" => {
2267 if let Some(v) = values.first() {
2268 material.shader_option = *v as u32;
2269 }
2270 }
2271 "use_mesh_tex" | "g_usemeshtex" => {
2272 if let Some(v) = parse_effect_bool(value_str, values) {
2273 material.use_mesh_tex = v;
2274 }
2275 }
2276 "use_mrbd" | "g_usemrbd" => {
2277 if let Some(v) = parse_effect_bool(value_str, values) {
2278 material.use_mrbd = v;
2279 }
2280 }
2281 "mrbd" | "g_mrbd" => {
2282 if !values.is_empty() {
2283 material.mrbd = [
2284 values.first().copied().unwrap_or(0.0),
2285 values.get(1).copied().unwrap_or(0.0),
2286 values.get(2).copied().unwrap_or(0.0),
2287 values.get(3).copied().unwrap_or(0.0),
2288 ];
2289 material.use_mrbd = values.iter().any(|v| v.abs() > 1e-6);
2290 }
2291 }
2292 "use_rgb" | "g_usergb" => {
2293 if let Some(v) = parse_effect_bool(value_str, values) {
2294 material.use_rgb = v;
2295 }
2296 }
2297 "rgb_rate" | "g_rgbrate" => {
2298 if !values.is_empty() {
2299 material.rgb_rate = [
2300 values.first().copied().unwrap_or(0.0),
2301 values.get(1).copied().unwrap_or(0.0),
2302 values.get(2).copied().unwrap_or(0.0),
2303 values.get(3).copied().unwrap_or(0.0),
2304 ];
2305 material.use_rgb = true;
2306 }
2307 }
2308 "add_rgb" | "g_addrgb" => {
2309 if !values.is_empty() {
2310 material.add_rgb = [
2311 values.first().copied().unwrap_or(0.0),
2312 values.get(1).copied().unwrap_or(0.0),
2313 values.get(2).copied().unwrap_or(0.0),
2314 values.get(3).copied().unwrap_or(0.0),
2315 ];
2316 material.use_rgb = true;
2317 }
2318 }
2319 "use_mul_vertex_color" | "g_usemulvertexcolor" => {
2320 if let Some(v) = parse_effect_bool(value_str, values) {
2321 material.use_mul_vertex_color = v;
2322 }
2323 }
2324 "mul_vertex_color_rate" | "g_mulvertexcolorrate" | "g_vertexcolorrate" => {
2325 if let Some(v) = values.first() {
2326 material.mul_vertex_color_rate = (*v).clamp(0.0, 8.0);
2327 material.use_mul_vertex_color =
2328 material.use_mul_vertex_color || (*v - 1.0).abs() > 1e-6;
2329 }
2330 }
2331 "depth_buffer_shadow_bias" | "g_depthbuffershadowbias" | "g_dbsbias" => {
2332 if let Some(v) = values.first() {
2333 material.depth_buffer_shadow_bias = *v;
2334 }
2335 }
2336 "directional_light_id_list" | "directionallightidlist" | "g_directionallightidlist" => {
2337 let ids = parse_effect_int_list(values);
2338 if !ids.is_empty() {
2339 material.directional_light_ids = ids;
2340 }
2341 }
2342 "point_light_id_list" | "pointlightidlist" | "g_pointlightidlist" => {
2343 let ids = parse_effect_int_list(values);
2344 if !ids.is_empty() {
2345 material.point_light_ids = ids;
2346 }
2347 }
2348 "spot_light_id_list" | "spotlightidlist" | "g_spotlightidlist" => {
2349 let ids = parse_effect_int_list(values);
2350 if !ids.is_empty() {
2351 material.spot_light_ids = ids;
2352 }
2353 }
2354 "g_rimlightcolor" | "rim_light_color" => {
2355 if values.len() >= 3 {
2356 material.rim_light_color = [
2357 values[0],
2358 values[1],
2359 values[2],
2360 values.get(3).copied().unwrap_or(1.0),
2361 ];
2362 }
2363 }
2364 "g_rimlightpower" | "rim_light_power" => {
2365 if let Some(v) = values.first() {
2366 material.rim_light_power = (*v).max(0.0);
2367 if material.rim_light_power > 0.0 {
2368 material.shader_option |= MESH_SHADER_OPTION_RIM_LIGHT;
2369 }
2370 }
2371 }
2372 "g_parallaxmaxheight" | "parallax_max_height" => {
2373 if let Some(v) = values.first() {
2374 material.parallax_max_height = (*v).max(0.0);
2375 if material.parallax_max_height > 0.0
2376 && material.lighting_type == MeshLightingType::None
2377 {
2378 material.lighting_type = MeshLightingType::Parallax;
2379 }
2380 }
2381 }
2382 "g_materialdiffuse" | "material_diffuse" => {
2383 if values.len() >= 3 {
2384 material.diffuse = [
2385 values[0],
2386 values[1],
2387 values[2],
2388 values.get(3).copied().unwrap_or(material.diffuse[3]),
2389 ];
2390 }
2391 }
2392 "g_materialambient" | "material_ambient" => {
2393 if values.len() >= 3 {
2394 material.ambient = [
2395 values[0],
2396 values[1],
2397 values[2],
2398 values.get(3).copied().unwrap_or(material.ambient[3]),
2399 ];
2400 }
2401 }
2402 "g_materialspecular" | "material_specular" => {
2403 if values.len() >= 3 {
2404 material.specular = [
2405 values[0],
2406 values[1],
2407 values[2],
2408 values.get(3).copied().unwrap_or(material.specular[3]),
2409 ];
2410 }
2411 }
2412 "g_materialemissive" | "material_emissive" => {
2413 if values.len() >= 3 {
2414 material.emissive = [
2415 values[0],
2416 values[1],
2417 values[2],
2418 values.get(3).copied().unwrap_or(material.emissive[3]),
2419 ];
2420 }
2421 }
2422 "g_materialspecularpower" | "material_specular_power" | "material_power" => {
2423 if let Some(v) = values.first() {
2424 material.power = (*v).max(1.0);
2425 }
2426 }
2427 "prerendertype" | "pre_render_type" | "g_prerendertype" | "shadow_map_enable"
2428 | "shadowmapenable" => {
2429 if let Some(v) = values.first() {
2430 material.shadow_map_enable = ((*v as i32) & 1) != 0;
2431 }
2432 }
2433 "alphatestenable" | "alpha_test_enable" | "g_alphatestenable" => {
2434 if let Some(v) = parse_effect_bool(value_str, values) {
2435 material.alpha_test_enable = v;
2436 }
2437 }
2438 "alpharef" | "alpha_ref" | "g_alpharef" => {
2439 if let Some(v) = values.first() {
2440 material.alpha_ref = (*v).clamp(0.0, 1.0);
2441 if material.alpha_ref > 0.0 {
2442 material.alpha_test_enable = true;
2443 }
2444 }
2445 }
2446 "cullmode" | "cull_mode" | "twosided" | "double_sided" => {
2447 if let Some(v) = values.first() {
2448 let vi = *v as i32;
2449 material.cull_disable = vi == 0 || vi == 1;
2450 } else if let Some(s) = value_str {
2451 let s = s.to_ascii_lowercase();
2452 material.cull_disable = matches!(
2453 s.as_str(),
2454 "none" | "off" | "0" | "1" | "two" | "twosided" | "double" | "double_sided"
2455 );
2456 }
2457 }
2458 "normaltexture" | "normal_tex" | "g_normaltexture" | "g_normaltex" | "normalmap"
2459 | "normal_map" => {
2460 if let Some(s) = value_str {
2461 material.normal_texture_path = resolve_texture_path(mesh_path, s);
2462 if material.normal_texture_path.is_some()
2463 && material.lighting_type == MeshLightingType::None
2464 {
2465 material.lighting_type = MeshLightingType::Bump;
2466 }
2467 }
2468 }
2469 "toontexture" | "toon_tex" | "g_toontexture" | "g_toontex" | "toonmap" | "toon_map" => {
2470 if let Some(s) = value_str {
2471 material.toon_texture_path = resolve_texture_path(mesh_path, s);
2472 if material.toon_texture_path.is_some()
2473 && material.lighting_type == MeshLightingType::None
2474 {
2475 material.lighting_type = MeshLightingType::Toon;
2476 }
2477 }
2478 }
2479 _ => {}
2480 }
2481}
2482
2483fn parse_x_effect_instance(
2484 cur: &mut Cursor,
2485 mesh_path: &Path,
2486 material: &mut MeshMaterial,
2487) -> Result<()> {
2488 cur.expect_block_start()?;
2489 if let Ok(effect_name) = cur.next_string() {
2490 apply_effect_filename(material, &effect_name);
2491 }
2492 while let Some(tok) = cur.peek().cloned() {
2493 match tok {
2494 Tok::Sym('}') => {
2495 cur.next();
2496 break;
2497 }
2498 Tok::Ident(_) => {
2499 let block_name = cur.next_string()?;
2500 if !cur.consume_sym('{') {
2501 continue;
2502 }
2503 let mut strings = Vec::<String>::new();
2504 let mut values = Vec::<f32>::new();
2505 let mut depth = 1usize;
2506 while let Some(tok2) = cur.next() {
2507 match tok2 {
2508 Tok::Sym('{') => depth += 1,
2509 Tok::Sym('}') => {
2510 depth -= 1;
2511 if depth == 0 {
2512 break;
2513 }
2514 }
2515 Tok::Str(s) | Tok::Ident(s) => strings.push(s),
2516 Tok::Number(v) => values.push(v),
2517 Tok::Sym(_) => {}
2518 }
2519 }
2520 let value_str = strings
2521 .iter()
2522 .find(|s| s.contains('.') || s.contains('/') || s.contains('\\'))
2523 .map(|s| s.as_str())
2524 .or_else(|| strings.get(1).map(|s| s.as_str()));
2525 assign_effect_param(material, mesh_path, &block_name, value_str, &values);
2526 if let Some(first) = strings.first() {
2527 assign_effect_param(
2528 material,
2529 mesh_path,
2530 first,
2531 strings.get(1).map(|s| s.as_str()),
2532 &values,
2533 );
2534 }
2535 }
2536 _ => {
2537 cur.next();
2538 }
2539 }
2540 }
2541 Ok(())
2542}
2543
2544fn triangle_tangent_binormal(
2545 a: &MeshTriVertex,
2546 b: &MeshTriVertex,
2547 c: &MeshTriVertex,
2548) -> ([f32; 3], [f32; 3]) {
2549 let e1 = [
2550 b.pos[0] - a.pos[0],
2551 b.pos[1] - a.pos[1],
2552 b.pos[2] - a.pos[2],
2553 ];
2554 let e2 = [
2555 c.pos[0] - a.pos[0],
2556 c.pos[1] - a.pos[1],
2557 c.pos[2] - a.pos[2],
2558 ];
2559 let duv1 = [b.uv[0] - a.uv[0], b.uv[1] - a.uv[1]];
2560 let duv2 = [c.uv[0] - a.uv[0], c.uv[1] - a.uv[1]];
2561 let denom = duv1[0] * duv2[1] - duv1[1] * duv2[0];
2562 if denom.abs() <= 1e-8 {
2563 let n = if has_normal(a.normal) {
2564 a.normal
2565 } else {
2566 triangle_normal(a.pos, b.pos, c.pos)
2567 };
2568 let up = if n[1].abs() < 0.999 {
2569 [0.0, 1.0, 0.0]
2570 } else {
2571 [1.0, 0.0, 0.0]
2572 };
2573 let t = normalize3(cross3(up, n));
2574 let b = normalize3(cross3(n, t));
2575 return (t, b);
2576 }
2577 let inv = 1.0 / denom;
2578 let tangent = normalize3([
2579 inv * (duv2[1] * e1[0] - duv1[1] * e2[0]),
2580 inv * (duv2[1] * e1[1] - duv1[1] * e2[1]),
2581 inv * (duv2[1] * e1[2] - duv1[1] * e2[2]),
2582 ]);
2583 let binormal = normalize3([
2584 inv * (-duv2[0] * e1[0] + duv1[0] * e2[0]),
2585 inv * (-duv2[0] * e1[1] + duv1[0] * e2[1]),
2586 inv * (-duv2[0] * e1[2] + duv1[0] * e2[2]),
2587 ]);
2588 (tangent, binormal)
2589}
2590
2591fn apply_tangent_space(vertices: &mut [MeshTriVertex]) {
2592 for tri in vertices.chunks_mut(3) {
2593 if tri.len() != 3 {
2594 continue;
2595 }
2596 let (t, b) = triangle_tangent_binormal(&tri[0], &tri[1], &tri[2]);
2597 for v in tri {
2598 v.tangent = t;
2599 v.binormal = b;
2600 }
2601 }
2602}
2603
2604fn has_normal(n: [f32; 3]) -> bool {
2605 n[0].abs() > 1e-6 || n[1].abs() > 1e-6 || n[2].abs() > 1e-6
2606}
2607
2608fn triangle_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] {
2609 let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
2610 let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
2611 normalize3([
2612 ab[1] * ac[2] - ab[2] * ac[1],
2613 ab[2] * ac[0] - ab[0] * ac[2],
2614 ab[0] * ac[1] - ab[1] * ac[0],
2615 ])
2616}
2617
2618fn obj_index<T>(items: &[T], raw: isize) -> Option<&T> {
2619 if raw == 0 {
2620 return None;
2621 }
2622 if raw > 0 {
2623 items.get((raw - 1) as usize)
2624 } else {
2625 let idx = items.len() as isize + raw;
2626 if idx < 0 {
2627 None
2628 } else {
2629 items.get(idx as usize)
2630 }
2631 }
2632}
2633
2634#[derive(Debug, Clone)]
2635struct ParsedXMaterial {
2636 name: Option<String>,
2637 material: MeshMaterial,
2638 texture_path: Option<PathBuf>,
2639}
2640
2641#[derive(Debug, Clone)]
2642struct XFace {
2643 indices: Vec<usize>,
2644 material_index: usize,
2645}
2646
2647#[derive(Debug, Clone)]
2648struct ImportedXBoneBinding {
2649 frame_name: String,
2650 offset_matrix: Mat4,
2651}
2652
2653#[derive(Debug, Clone)]
2654struct ImportedXPrimitive {
2655 frame_node: usize,
2656 texture_path: Option<PathBuf>,
2657 material: MeshMaterial,
2658 vertices: Vec<MeshTriVertex>,
2659 bones: Vec<ImportedXBoneBinding>,
2660}
2661
2662#[derive(Debug, Clone)]
2663struct ImportedXFrame {
2664 name: String,
2665 parent: Option<usize>,
2666 base_local: Mat4,
2667}
2668
2669#[derive(Debug, Clone, Default)]
2670struct ImportedXScene {
2671 frames: Vec<ImportedXFrame>,
2672 primitives: Vec<ImportedXPrimitive>,
2673 animations: Vec<AnimationClip>,
2674 ticks_per_second: f32,
2675}
2676
2677fn import_x_scene_bytes_shion(bytes: &[u8], path: &Path) -> Result<ImportedXScene> {
2678 let file = shion_xfile::parse_x(bytes)
2679 .with_context(|| format!("parse DirectX .x file with Shion parser: {:?}", path))?;
2680 let scene = shion_xfile::Scene::from_xfile(&file)
2681 .with_context(|| format!("lift DirectX .x semantic scene: {:?}", path))?;
2682 import_x_scene_from_shion_scene(&scene, path)
2683}
2684
2685fn import_x_scene_from_shion_scene(
2686 scene: &shion_xfile::Scene,
2687 path: &Path,
2688) -> Result<ImportedXScene> {
2689 let loose_materials = shion_loose_material_lookup(scene);
2690 let mut out = ImportedXScene {
2691 frames: vec![ImportedXFrame {
2692 name: "__root__".to_string(),
2693 parent: None,
2694 base_local: Mat4::identity(),
2695 }],
2696 primitives: Vec::new(),
2697 animations: shion_animation_clips(scene),
2698 ticks_per_second: scene.anim_ticks_per_second.unwrap_or(4800).max(1) as f32,
2699 };
2700
2701 for frame in &scene.frames {
2702 import_shion_frame(frame, path, &loose_materials, &mut out, 0)?;
2703 }
2704 for mesh in &scene.loose_meshes {
2705 out.primitives
2706 .extend(shion_mesh_primitives(mesh, path, 0, &loose_materials)?);
2707 }
2708 Ok(out)
2709}
2710
2711fn shion_loose_material_lookup(
2712 scene: &shion_xfile::Scene,
2713) -> HashMap<String, shion_xfile::Material> {
2714 let mut out = HashMap::new();
2715 for material in &scene.loose_materials {
2716 if let Some(name) = &material.name {
2717 out.insert(name.clone(), material.clone());
2718 }
2719 }
2720 out
2721}
2722
2723fn import_shion_frame(
2724 frame: &shion_xfile::Frame,
2725 path: &Path,
2726 loose_materials: &HashMap<String, shion_xfile::Material>,
2727 scene: &mut ImportedXScene,
2728 parent_frame: usize,
2729) -> Result<usize> {
2730 let frame_idx = scene.frames.len();
2731 scene.frames.push(ImportedXFrame {
2732 name: frame
2733 .name
2734 .clone()
2735 .unwrap_or_else(|| format!("frame_{}", frame_idx)),
2736 parent: Some(parent_frame),
2737 base_local: frame
2738 .transform
2739 .map(Mat4::from_x_values)
2740 .unwrap_or_else(Mat4::identity),
2741 });
2742
2743 for mesh in &frame.meshes {
2744 scene.primitives.extend(shion_mesh_primitives(
2745 mesh,
2746 path,
2747 frame_idx,
2748 loose_materials,
2749 )?);
2750 }
2751 for child in &frame.child_frames {
2752 import_shion_frame(child, path, loose_materials, scene, frame_idx)?;
2753 }
2754 Ok(frame_idx)
2755}
2756
2757fn shion_mesh_primitives(
2758 mesh: &shion_xfile::Mesh,
2759 path: &Path,
2760 frame_index: usize,
2761 loose_materials: &HashMap<String, shion_xfile::Material>,
2762) -> Result<Vec<ImportedXPrimitive>> {
2763 let material_slots = shion_material_slots(mesh, path, loose_materials);
2764 let face_material_indices = mesh
2765 .material_list
2766 .as_ref()
2767 .map(|list| list.face_indexes.as_slice())
2768 .unwrap_or(&[]);
2769 let texcoords = mesh.texcoords.as_deref().unwrap_or(&[]);
2770 let vertex_colors = shion_vertex_color_lookup(mesh);
2771 let (influences, bones) = shion_skin_bindings(mesh);
2772 let mut groups: HashMap<usize, Vec<MeshTriVertex>> = HashMap::new();
2773
2774 for (face_idx, face) in mesh.faces.iter().enumerate() {
2775 if face.len() < 3 {
2776 continue;
2777 }
2778 let material_index = face_material_indices.get(face_idx).copied().unwrap_or(0) as usize;
2779 for tri in 1..face.len() - 1 {
2780 let face_corners = [0usize, tri, tri + 1];
2781 let vertex_indices = [
2782 face[face_corners[0]] as usize,
2783 face[face_corners[1]] as usize,
2784 face[face_corners[2]] as usize,
2785 ];
2786 let tri_n = triangle_normal(
2787 mesh.vertices
2788 .get(vertex_indices[0])
2789 .copied()
2790 .unwrap_or([0.0, 0.0, 0.0]),
2791 mesh.vertices
2792 .get(vertex_indices[1])
2793 .copied()
2794 .unwrap_or([0.0, 0.0, 0.0]),
2795 mesh.vertices
2796 .get(vertex_indices[2])
2797 .copied()
2798 .unwrap_or([0.0, 0.0, 0.0]),
2799 );
2800 let group = groups.entry(material_index).or_default();
2801 for (&vertex_index, &corner_index) in vertex_indices.iter().zip(face_corners.iter()) {
2802 let pos = mesh
2803 .vertices
2804 .get(vertex_index)
2805 .copied()
2806 .unwrap_or([0.0, 0.0, 0.0]);
2807 let uv = texcoords.get(vertex_index).copied().unwrap_or([0.0, 0.0]);
2808 let normal = shion_face_corner_normal(mesh, face_idx, corner_index)
2809 .or_else(|| shion_vertex_normal(mesh, vertex_index))
2810 .unwrap_or(tri_n);
2811 let color = vertex_colors
2812 .get(&vertex_index)
2813 .copied()
2814 .unwrap_or([1.0, 1.0, 1.0, 1.0]);
2815 let (bone_indices, bone_weights) = packed_influences(influences.get(&vertex_index));
2816 group.push(MeshTriVertex {
2817 pos,
2818 uv,
2819 normal,
2820 tangent: [0.0, 0.0, 0.0],
2821 binormal: [0.0, 0.0, 0.0],
2822 color,
2823 bone_indices,
2824 bone_weights,
2825 });
2826 }
2827 }
2828 }
2829
2830 let mut out = Vec::new();
2831 for (material_index, vertices) in groups {
2832 if vertices.is_empty() {
2833 continue;
2834 }
2835 let slot = material_slots
2836 .get(material_index)
2837 .cloned()
2838 .unwrap_or_else(|| ParsedXMaterial {
2839 name: None,
2840 material: default_mesh_material(),
2841 texture_path: None,
2842 });
2843 let mut vertices = vertices;
2844 apply_tangent_space(&mut vertices);
2845 out.push(ImportedXPrimitive {
2846 frame_node: frame_index,
2847 texture_path: slot.texture_path.clone(),
2848 material: slot.material,
2849 vertices,
2850 bones: bones.clone(),
2851 });
2852 }
2853 Ok(out)
2854}
2855
2856fn shion_material_slots(
2857 mesh: &shion_xfile::Mesh,
2858 path: &Path,
2859 loose_materials: &HashMap<String, shion_xfile::Material>,
2860) -> Vec<ParsedXMaterial> {
2861 let mut slots = Vec::new();
2862 if let Some(list) = &mesh.material_list {
2863 for material in &list.materials {
2864 slots.push(shion_material_to_parsed(material, path));
2865 }
2866 for reference in &list.material_references {
2867 if let Some(name) = &reference.name
2868 && let Some(material) = loose_materials.get(name)
2869 {
2870 slots.push(shion_material_to_parsed(material, path));
2871 continue;
2872 }
2873 slots.push(ParsedXMaterial {
2874 name: reference.name.clone(),
2875 material: default_mesh_material(),
2876 texture_path: None,
2877 });
2878 }
2879 while slots.len() < list.material_count as usize {
2880 slots.push(ParsedXMaterial {
2881 name: None,
2882 material: default_mesh_material(),
2883 texture_path: None,
2884 });
2885 }
2886 }
2887 if slots.is_empty() {
2888 slots.push(ParsedXMaterial {
2889 name: None,
2890 material: default_mesh_material(),
2891 texture_path: None,
2892 });
2893 }
2894
2895 for effect in &mesh.effect_instances {
2896 for slot in &mut slots {
2897 shion_apply_effect_instance(&mut slot.material, path, effect);
2898 }
2899 }
2900 slots
2901}
2902
2903fn shion_material_to_parsed(material: &shion_xfile::Material, path: &Path) -> ParsedXMaterial {
2904 let mut out = default_mesh_material();
2905 out.diffuse = material.face_color;
2906 out.ambient = material.face_color;
2907 out.specular = [
2908 material.specular_color[0],
2909 material.specular_color[1],
2910 material.specular_color[2],
2911 1.0,
2912 ];
2913 out.emissive = [
2914 material.emissive_color[0],
2915 material.emissive_color[1],
2916 material.emissive_color[2],
2917 1.0,
2918 ];
2919 out.power = material.power.max(1.0);
2920
2921 let mut texture_path = material
2922 .texture_filenames
2923 .iter()
2924 .find_map(|tex| resolve_texture_path(path, tex));
2925 for effect in &material.effect_instances {
2926 shion_apply_effect_instance(&mut out, path, effect);
2927 }
2928 if texture_path.is_none() {
2929 texture_path = material.effect_instances.iter().find_map(|effect| {
2930 effect
2931 .strings
2932 .iter()
2933 .find_map(|param| resolve_texture_path(path, ¶m.value))
2934 .or_else(|| {
2935 effect
2936 .legacy_strings
2937 .iter()
2938 .find_map(|param| resolve_texture_path(path, ¶m.value))
2939 })
2940 });
2941 }
2942
2943 ParsedXMaterial {
2944 name: material.name.clone(),
2945 material: out,
2946 texture_path,
2947 }
2948}
2949
2950fn shion_apply_effect_instance(
2951 material: &mut MeshMaterial,
2952 path: &Path,
2953 effect: &shion_xfile::EffectInstance,
2954) {
2955 if !effect.effect_filename.trim().is_empty() {
2956 apply_effect_filename(material, &effect.effect_filename);
2957 }
2958 for param in &effect.strings {
2959 assign_effect_param(material, path, ¶m.param_name, Some(¶m.value), &[]);
2960 }
2961 for param in &effect.dwords {
2962 assign_effect_param(
2963 material,
2964 path,
2965 ¶m.param_name,
2966 None,
2967 &[param.value as f32],
2968 );
2969 }
2970 for param in &effect.floats {
2971 assign_effect_param(material, path, ¶m.param_name, None, ¶m.values);
2972 }
2973 for param in &effect.legacy_strings {
2974 assign_effect_param(material, path, "effect_string", Some(¶m.value), &[]);
2975 }
2976 for param in &effect.legacy_dwords {
2977 assign_effect_param(material, path, "effect_dword", None, &[param.value as f32]);
2978 }
2979 for param in &effect.legacy_floats {
2980 assign_effect_param(material, path, "effect_floats", None, ¶m.values);
2981 }
2982}
2983
2984fn shion_vertex_color_lookup(mesh: &shion_xfile::Mesh) -> HashMap<usize, [f32; 4]> {
2985 let mut out = HashMap::new();
2986 if let Some(colors) = &mesh.vertex_colors {
2987 for color in colors {
2988 out.insert(color.index as usize, color.rgba);
2989 }
2990 }
2991 out
2992}
2993
2994fn shion_face_corner_normal(
2995 mesh: &shion_xfile::Mesh,
2996 face_index: usize,
2997 corner_index: usize,
2998) -> Option<[f32; 3]> {
2999 let normals = mesh.normals.as_ref()?;
3000 let normal_index = (*normals.face_normals.get(face_index)?.get(corner_index)?) as usize;
3001 normals.normals.get(normal_index).copied()
3002}
3003
3004fn shion_vertex_normal(mesh: &shion_xfile::Mesh, vertex_index: usize) -> Option<[f32; 3]> {
3005 let normals = mesh.normals.as_ref()?;
3006 normals.normals.get(vertex_index).copied()
3007}
3008
3009type VertexBoneInfluences = HashMap<usize, Vec<(usize, f32)>>;
3010
3011fn shion_skin_bindings(
3012 mesh: &shion_xfile::Mesh,
3013) -> (VertexBoneInfluences, Vec<ImportedXBoneBinding>) {
3014 let mut influences = VertexBoneInfluences::new();
3015 let mut bones = Vec::new();
3016 for skin in &mesh.skin_weights {
3017 let bone_index = bones.len();
3018 bones.push(ImportedXBoneBinding {
3019 frame_name: skin.transform_node_name.clone(),
3020 offset_matrix: Mat4::from_x_values(skin.matrix_offset),
3021 });
3022 for (&vertex_index, &weight) in skin.vertex_indices.iter().zip(skin.weights.iter()) {
3023 influences
3024 .entry(vertex_index as usize)
3025 .or_default()
3026 .push((bone_index, weight));
3027 }
3028 }
3029 (influences, bones)
3030}
3031
3032fn shion_animation_clips(scene: &shion_xfile::Scene) -> Vec<AnimationClip> {
3033 let mut clips = Vec::new();
3034 for (set_index, set) in scene.animation_sets.iter().enumerate() {
3035 let mut clip = AnimationClip {
3036 name: set
3037 .name
3038 .clone()
3039 .unwrap_or_else(|| format!("anim_{}", set_index)),
3040 open_closed: true,
3041 ..Default::default()
3042 };
3043 for animation in &set.animations {
3044 if let Some(options) = &animation.options {
3045 clip.open_closed = options.open_closed != 0;
3046 }
3047 let Some(target_name) = animation.target_name.as_deref() else {
3048 continue;
3049 };
3050 let track = clip.tracks.entry(target_name.to_string()).or_default();
3051 for block in &animation.keys {
3052 for key in &block.keys {
3053 let time = key.time as i64;
3054 clip.max_time = clip.max_time.max(time);
3055 match block.key_type {
3056 0 => {
3057 if key.values.len() >= 4 {
3058 track.rotation_keys.push(QuatKey {
3059 time,
3060 value: Quat {
3061 x: key.values[1],
3062 y: key.values[2],
3063 z: key.values[3],
3064 w: key.values[0],
3065 }
3066 .normalize(),
3067 });
3068 }
3069 }
3070 1 => {
3071 track.scale_keys.push(Vec3Key {
3072 time,
3073 value: [
3074 key.values.first().copied().unwrap_or(1.0),
3075 key.values.get(1).copied().unwrap_or(1.0),
3076 key.values.get(2).copied().unwrap_or(1.0),
3077 ],
3078 });
3079 }
3080 2 => {
3081 track.position_keys.push(Vec3Key {
3082 time,
3083 value: [
3084 key.values.first().copied().unwrap_or(0.0),
3085 key.values.get(1).copied().unwrap_or(0.0),
3086 key.values.get(2).copied().unwrap_or(0.0),
3087 ],
3088 });
3089 }
3090 3 | 4 => {
3091 let mut m = [0.0f32; 16];
3094 for (dst, src) in m.iter_mut().zip(key.values.iter().copied()) {
3095 *dst = src;
3096 }
3097 track.matrix_keys.push(MatKey {
3098 time,
3099 value: Mat4::from_x_values(m),
3100 });
3101 }
3102 _ => {}
3103 }
3104 }
3105 }
3106 }
3107 if !clip.tracks.is_empty() {
3108 clips.push(clip);
3109 }
3110 }
3111 clips
3112}
3113
3114impl ImportedXScene {
3115 fn into_mesh_asset(self, path: &Path) -> Result<MeshAsset> {
3116 let mut children_map: Vec<Vec<usize>> = vec![Vec::new(); self.frames.len().max(1)];
3117 for (idx, frame) in self.frames.iter().enumerate() {
3118 if let Some(parent) = frame.parent
3119 && parent < children_map.len()
3120 {
3121 children_map[parent].push(idx);
3122 }
3123 }
3124 let frames = self
3125 .frames
3126 .iter()
3127 .enumerate()
3128 .map(|(idx, frame)| FrameNode {
3129 name: frame.name.clone(),
3130 base_local: frame.base_local,
3131 children: children_map.get(idx).cloned().unwrap_or_default(),
3132 })
3133 .collect::<Vec<_>>();
3134 let frame_lookup: HashMap<String, usize> = frames
3135 .iter()
3136 .enumerate()
3137 .map(|(i, f)| (f.name.clone(), i))
3138 .collect();
3139 let primitives = self
3140 .primitives
3141 .into_iter()
3142 .map(|prim| MeshPrimitive {
3143 frame_index: prim.frame_node,
3144 texture_path: prim.texture_path,
3145 material: prim.material,
3146 runtime_desc: MeshPrimitiveRuntimeDesc::default(),
3147 vertices: prim.vertices,
3148 bones: prim
3149 .bones
3150 .into_iter()
3151 .map(|bone| BoneBinding {
3152 frame_index: frame_lookup.get(&bone.frame_name).copied().unwrap_or(0),
3153 frame_name: bone.frame_name,
3154 offset_matrix: bone.offset_matrix,
3155 })
3156 .collect(),
3157 })
3158 .collect::<Vec<_>>();
3159 let texture_path = primitives.iter().find_map(|p| p.texture_path.clone());
3160 let mut asset = MeshAsset {
3161 source_path: path.to_path_buf(),
3162 texture_path,
3163 vertices: Vec::new(),
3164 bone_count: primitives.iter().map(|p| p.bones.len()).max().unwrap_or(0),
3165 ticks_per_second: self.ticks_per_second.max(1.0),
3166 primitives,
3167 frames,
3168 animations: self.animations,
3169 };
3170 asset.vertices = asset.sample_vertices(0.0);
3171 if asset.vertices.is_empty() {
3172 bail!("no usable Mesh block in {:?}", path)
3173 }
3174 Ok(asset)
3175 }
3176}
3177
3178fn import_x_scene_tokens(toks: Vec<Tok>, path: &Path) -> Result<ImportedXScene> {
3179 let mut cur = Cursor::new(toks);
3180 let mut scene = ImportedXScene {
3181 frames: vec![ImportedXFrame {
3182 name: "__root__".to_string(),
3183 parent: None,
3184 base_local: Mat4::identity(),
3185 }],
3186 primitives: Vec::new(),
3187 animations: Vec::new(),
3188 ticks_per_second: 4800.0,
3189 };
3190 while let Some(tok) = cur.peek().cloned() {
3191 match tok {
3192 Tok::Ident(name) if name == "Frame" => {
3193 cur.next();
3194 import_x_frame_block(&mut cur, path, &mut scene, 0)?;
3195 }
3196 Tok::Ident(name) if name == "Mesh" => {
3197 cur.next();
3198 let mesh_prims = parse_x_mesh_block(&mut cur, path, 0)?;
3199 scene
3200 .primitives
3201 .extend(mesh_prims.into_iter().map(imported_primitive_from_mesh));
3202 }
3203 Tok::Ident(name) if name == "AnimationSet" => {
3204 cur.next();
3205 parse_x_animation_set(&mut cur, &mut scene.animations)?;
3206 }
3207 Tok::Ident(name) if name == "AnimTicksPerSecond" => {
3208 cur.next();
3209 scene.ticks_per_second = parse_x_anim_ticks_per_second(&mut cur)
3210 .unwrap_or(scene.ticks_per_second)
3211 .max(1.0);
3212 }
3213 Tok::Ident(_) => {
3214 cur.next();
3215 cur.skip_block();
3216 }
3217 _ => {
3218 cur.next();
3219 }
3220 }
3221 }
3222 Ok(scene)
3223}
3224
3225fn parse_x_tokens(toks: Vec<Tok>, path: &Path) -> Result<MeshAsset> {
3226 import_x_scene_tokens(toks, path)?.into_mesh_asset(path)
3227}
3228
3229fn import_x_scene_text(text: &str, path: &Path) -> Result<ImportedXScene> {
3230 import_x_scene_tokens(tokenize_x(text), path)
3231}
3232
3233fn import_x_scene_binary(bytes: &[u8], path: &Path, float_bits: u32) -> Result<ImportedXScene> {
3234 import_x_scene_tokens(tokenize_x_binary(bytes, float_bits)?, path)
3235}
3236
3237fn import_x_scene_bytes(bytes: &[u8], path: &Path) -> Result<ImportedXScene> {
3238 import_x_scene_bytes_shion(bytes, path)
3239}
3240
3241fn parse_x_text(text: &str, path: &Path) -> Result<MeshAsset> {
3242 import_x_scene_text(text, path)?.into_mesh_asset(path)
3243}
3244
3245fn parse_x_binary(bytes: &[u8], path: &Path, float_bits: u32) -> Result<MeshAsset> {
3246 import_x_scene_binary(bytes, path, float_bits)?.into_mesh_asset(path)
3247}
3248
3249fn parse_x_bytes(bytes: &[u8], path: &Path) -> Result<MeshAsset> {
3250 import_x_scene_bytes(bytes, path)?.into_mesh_asset(path)
3251}
3252
3253fn imported_primitive_from_mesh(prim: MeshPrimitive) -> ImportedXPrimitive {
3254 ImportedXPrimitive {
3255 frame_node: prim.frame_index,
3256 texture_path: prim.texture_path,
3257 material: prim.material,
3258 vertices: prim.vertices,
3259 bones: prim
3260 .bones
3261 .into_iter()
3262 .map(|bone| ImportedXBoneBinding {
3263 frame_name: bone.frame_name,
3264 offset_matrix: bone.offset_matrix,
3265 })
3266 .collect(),
3267 }
3268}
3269
3270fn import_x_frame_block(
3271 cur: &mut Cursor,
3272 path: &Path,
3273 scene: &mut ImportedXScene,
3274 parent_frame: usize,
3275) -> Result<usize> {
3276 let name = cur
3277 .optional_name_before_block()
3278 .unwrap_or_else(|| format!("frame_{}", scene.frames.len()));
3279 cur.expect_block_start()?;
3280 let frame_idx = scene.frames.len();
3281 scene.frames.push(ImportedXFrame {
3282 name,
3283 parent: Some(parent_frame),
3284 base_local: Mat4::identity(),
3285 });
3286 while let Some(tok) = cur.peek().cloned() {
3287 match tok {
3288 Tok::Sym('}') => {
3289 cur.next();
3290 break;
3291 }
3292 Tok::Ident(name) if name == "FrameTransformMatrix" => {
3293 cur.next();
3294 scene.frames[frame_idx].base_local = parse_x_matrix_block(cur)?;
3295 }
3296 Tok::Ident(name) if name == "Frame" => {
3297 cur.next();
3298 let _ = import_x_frame_block(cur, path, scene, frame_idx)?;
3299 }
3300 Tok::Ident(name) if name == "Mesh" => {
3301 cur.next();
3302 let mesh_prims = parse_x_mesh_block(cur, path, frame_idx)?;
3303 scene
3304 .primitives
3305 .extend(mesh_prims.into_iter().map(imported_primitive_from_mesh));
3306 }
3307 Tok::Ident(name) if name == "AnimationSet" => {
3308 cur.next();
3309 cur.skip_block();
3310 }
3311 Tok::Ident(_) => {
3312 cur.next();
3313 cur.skip_block();
3314 }
3315 _ => {
3316 cur.next();
3317 }
3318 }
3319 }
3320 Ok(frame_idx)
3321}
3322
3323fn parse_x_mesh_block(
3324 cur: &mut Cursor,
3325 path: &Path,
3326 frame_index: usize,
3327) -> Result<Vec<MeshPrimitive>> {
3328 cur.expect_block_start()?;
3329 let vertex_count = cur.next_usize()?;
3330 let mut positions = Vec::with_capacity(vertex_count);
3331 for _ in 0..vertex_count {
3332 positions.push([cur.next_number()?, cur.next_number()?, cur.next_number()?]);
3333 }
3334 let face_count = cur.next_usize()?;
3335 let mut faces = Vec::<XFace>::with_capacity(face_count);
3336 for _ in 0..face_count {
3337 let n = cur.next_usize()?;
3338 let mut face = Vec::with_capacity(n);
3339 for _ in 0..n {
3340 face.push(cur.next_usize()?);
3341 }
3342 faces.push(XFace {
3343 indices: face,
3344 material_index: 0,
3345 });
3346 }
3347
3348 let mut texcoords = Vec::<[f32; 2]>::new();
3349 let mut normals = Vec::<[f32; 3]>::new();
3350 let mut vertex_colors = HashMap::<usize, [f32; 4]>::new();
3351 let mut influences: HashMap<usize, Vec<(usize, f32)>> = HashMap::new();
3352 let mut bones = Vec::<BoneBinding>::new();
3353 let mut bone_map: HashMap<String, usize> = HashMap::new();
3354 let mut texture_path: Option<PathBuf> = None;
3355 let mut materials = vec![ParsedXMaterial {
3356 name: None,
3357 material: default_mesh_material(),
3358 texture_path: None,
3359 }];
3360
3361 while let Some(tok) = cur.peek().cloned() {
3362 match tok {
3363 Tok::Sym('}') => {
3364 cur.next();
3365 break;
3366 }
3367 Tok::Ident(name) if name == "MeshTextureCoords" => {
3368 cur.next();
3369 texcoords = parse_x_texcoords(cur)?;
3370 }
3371 Tok::Ident(name) if name == "MeshNormals" => {
3372 cur.next();
3373 normals = parse_x_normals(cur)?;
3374 }
3375 Tok::Ident(name) if name == "MeshVertexColors" => {
3376 cur.next();
3377 vertex_colors = parse_x_vertex_colors(cur)?;
3378 }
3379 Tok::Ident(name) if name == "SkinWeights" => {
3380 cur.next();
3381 parse_x_skin_weights(cur, &mut influences, &mut bones, &mut bone_map)?;
3382 }
3383 Tok::Ident(name) if name == "TextureFilename" => {
3384 cur.next();
3385 texture_path = parse_x_texture_filename(cur, path);
3386 }
3387 Tok::Ident(name) if name == "MeshMaterialList" => {
3388 cur.next();
3389 let (face_mtls, parsed_mtls) = parse_x_material_list(cur, path)?;
3390 for (face, midx) in faces.iter_mut().zip(face_mtls) {
3391 face.material_index = midx;
3392 }
3393 if !parsed_mtls.is_empty() {
3394 materials = parsed_mtls;
3395 }
3396 }
3397 Tok::Ident(_) => {
3398 cur.next();
3399 cur.skip_block();
3400 }
3401 _ => {
3402 cur.next();
3403 }
3404 }
3405 }
3406
3407 let mut groups: HashMap<usize, Vec<MeshTriVertex>> = HashMap::new();
3408 for face in faces {
3409 if face.indices.len() < 3 {
3410 continue;
3411 }
3412 for tri in 1..face.indices.len() - 1 {
3413 let idxs = [face.indices[0], face.indices[tri], face.indices[tri + 1]];
3414 let tri_n = triangle_normal(
3415 positions.get(idxs[0]).copied().unwrap_or([0.0, 0.0, 0.0]),
3416 positions.get(idxs[1]).copied().unwrap_or([0.0, 0.0, 0.0]),
3417 positions.get(idxs[2]).copied().unwrap_or([0.0, 0.0, 0.0]),
3418 );
3419 let group = groups.entry(face.material_index).or_default();
3420 for &idx in &idxs {
3421 let pos = positions.get(idx).copied().unwrap_or([0.0, 0.0, 0.0]);
3422 let uv = texcoords.get(idx).copied().unwrap_or([0.0, 0.0]);
3423 let normal = normals.get(idx).copied().unwrap_or(tri_n);
3424 let (bone_indices, bone_weights) = packed_influences(influences.get(&idx));
3425 let color = vertex_colors
3426 .get(&idx)
3427 .copied()
3428 .unwrap_or([1.0, 1.0, 1.0, 1.0]);
3429 group.push(MeshTriVertex {
3430 pos,
3431 uv,
3432 normal,
3433 tangent: [0.0, 0.0, 0.0],
3434 binormal: [0.0, 0.0, 0.0],
3435 color,
3436 bone_indices,
3437 bone_weights,
3438 });
3439 }
3440 }
3441 }
3442
3443 let mut out = Vec::new();
3444 for (midx, vertices) in groups {
3445 if vertices.is_empty() {
3446 continue;
3447 }
3448 let parsed = materials.get(midx).cloned().unwrap_or(ParsedXMaterial {
3449 name: None,
3450 material: default_mesh_material(),
3451 texture_path: None,
3452 });
3453 let mut vertices = vertices;
3454 apply_tangent_space(&mut vertices);
3455 out.push(MeshPrimitive {
3456 frame_index,
3457 texture_path: parsed.texture_path.clone().or_else(|| texture_path.clone()),
3458 material: parsed.material,
3459 runtime_desc: MeshPrimitiveRuntimeDesc::default(),
3460 vertices,
3461 bones: bones.clone(),
3462 });
3463 }
3464 Ok(out)
3465}
3466
3467fn parse_x_material_list(
3468 cur: &mut Cursor,
3469 mesh_path: &Path,
3470) -> Result<(Vec<usize>, Vec<ParsedXMaterial>)> {
3471 cur.expect_block_start()?;
3472 let material_count = cur.next_usize()?;
3473 let face_count = cur.next_usize()?;
3474 let mut face_mtls = Vec::with_capacity(face_count);
3475 for _ in 0..face_count {
3476 face_mtls.push(cur.next_usize()?);
3477 }
3478 let mut materials = Vec::new();
3479 let mut named_materials = HashMap::<String, ParsedXMaterial>::new();
3480 while let Some(tok) = cur.peek().cloned() {
3481 match tok {
3482 Tok::Sym('}') => {
3483 cur.next();
3484 break;
3485 }
3486 Tok::Ident(name) if name == "Material" => {
3487 cur.next();
3488 let parsed = parse_x_material(cur, mesh_path)?;
3489 if let Some(name) = parsed.name.clone() {
3490 named_materials.insert(name, parsed.clone());
3491 }
3492 materials.push(parsed);
3493 }
3494 Tok::Sym('{') => {
3495 cur.next();
3496 let ref_name = match cur.peek() {
3497 Some(Tok::Ident(s)) => Some(s.clone()),
3498 Some(Tok::Str(s)) => Some(s.clone()),
3499 _ => None,
3500 };
3501 if ref_name.is_some() {
3502 let _ = cur.next();
3503 }
3504 while !cur.consume_sym('}') {
3505 if cur.next().is_none() {
3506 break;
3507 }
3508 }
3509 let parsed = ref_name
3510 .and_then(|name| named_materials.get(&name).cloned())
3511 .unwrap_or(ParsedXMaterial {
3512 name: None,
3513 material: default_mesh_material(),
3514 texture_path: None,
3515 });
3516 materials.push(parsed);
3517 }
3518 _ => {
3519 cur.next();
3520 }
3521 }
3522 }
3523 while materials.len() < material_count {
3524 materials.push(ParsedXMaterial {
3525 name: None,
3526 material: default_mesh_material(),
3527 texture_path: None,
3528 });
3529 }
3530 Ok((face_mtls, materials))
3531}
3532
3533fn parse_x_material(cur: &mut Cursor, mesh_path: &Path) -> Result<ParsedXMaterial> {
3534 let name = cur.optional_name_before_block();
3535 cur.expect_block_start()?;
3536 let diffuse = [
3537 cur.next_number()?,
3538 cur.next_number()?,
3539 cur.next_number()?,
3540 cur.next_number()?,
3541 ];
3542 let power = cur.next_number()?.max(1.0);
3543 let specular = [
3544 cur.next_number()?,
3545 cur.next_number()?,
3546 cur.next_number()?,
3547 1.0,
3548 ];
3549 let emissive = [
3550 cur.next_number()?,
3551 cur.next_number()?,
3552 cur.next_number()?,
3553 1.0,
3554 ];
3555 let mut material = default_mesh_material();
3556 material.diffuse = diffuse;
3557 material.ambient = [diffuse[0], diffuse[1], diffuse[2], diffuse[3]];
3558 material.specular = specular;
3559 material.emissive = emissive;
3560 material.power = power;
3561 let mut texture_path = None;
3562 while let Some(tok) = cur.peek().cloned() {
3563 match tok {
3564 Tok::Sym('}') => {
3565 cur.next();
3566 break;
3567 }
3568 Tok::Ident(name) if name == "TextureFilename" => {
3569 cur.next();
3570 texture_path = parse_x_texture_filename(cur, mesh_path);
3571 }
3572 Tok::Ident(name) if name == "EffectInstance" => {
3573 cur.next();
3574 parse_x_effect_instance(cur, mesh_path, &mut material)?;
3575 }
3576 Tok::Ident(_) => {
3577 cur.next();
3578 cur.skip_block();
3579 }
3580 _ => {
3581 cur.next();
3582 }
3583 }
3584 }
3585 Ok(ParsedXMaterial {
3586 name,
3587 material,
3588 texture_path,
3589 })
3590}
3591
3592fn parse_x_matrix_block(cur: &mut Cursor) -> Result<Mat4> {
3593 cur.expect_block_start()?;
3594 let mut vals = [0.0f32; 16];
3595 for v in &mut vals {
3596 *v = cur.next_number()?;
3597 }
3598 let _ = cur.consume_sym('}');
3599 Ok(Mat4::from_x_values(vals))
3600}
3601
3602fn parse_x_texcoords(cur: &mut Cursor) -> Result<Vec<[f32; 2]>> {
3603 cur.expect_block_start()?;
3604 let count = cur.next_usize()?;
3605 let mut out = Vec::with_capacity(count);
3606 for _ in 0..count {
3607 out.push([cur.next_number()?, cur.next_number()?]);
3608 }
3609 let _ = cur.consume_sym('}');
3610 Ok(out)
3611}
3612
3613fn parse_x_normals(cur: &mut Cursor) -> Result<Vec<[f32; 3]>> {
3614 cur.expect_block_start()?;
3615 let count = cur.next_usize()?;
3616 let mut out = Vec::with_capacity(count);
3617 for _ in 0..count {
3618 out.push(normalize3([
3619 cur.next_number()?,
3620 cur.next_number()?,
3621 cur.next_number()?,
3622 ]));
3623 }
3624 let face_count = cur.next_usize()?;
3625 for _ in 0..face_count {
3626 let n = cur.next_usize()?;
3627 for _ in 0..n {
3628 let _ = cur.next_usize()?;
3629 }
3630 }
3631 let _ = cur.consume_sym('}');
3632 Ok(out)
3633}
3634
3635fn parse_x_vertex_colors(cur: &mut Cursor) -> Result<HashMap<usize, [f32; 4]>> {
3636 cur.expect_block_start()?;
3637 let count = cur.next_usize()?;
3638 let mut out = HashMap::with_capacity(count);
3639 for _ in 0..count {
3640 let vidx = cur.next_usize()?;
3641 let rgba = [
3642 cur.next_number()?,
3643 cur.next_number()?,
3644 cur.next_number()?,
3645 cur.next_number()?,
3646 ];
3647 out.insert(vidx, rgba);
3648 }
3649 let _ = cur.consume_sym('}');
3650 Ok(out)
3651}
3652
3653fn parse_x_skin_weights(
3654 cur: &mut Cursor,
3655 influences: &mut HashMap<usize, Vec<(usize, f32)>>,
3656 bones: &mut Vec<BoneBinding>,
3657 bone_map: &mut HashMap<String, usize>,
3658) -> Result<()> {
3659 cur.expect_block_start()?;
3660 let bone_name = cur.next_string()?;
3661 let bone_idx = if let Some(idx) = bone_map.get(&bone_name).copied() {
3662 idx
3663 } else {
3664 let idx = bones.len();
3665 bones.push(BoneBinding {
3666 frame_name: bone_name.clone(),
3667 frame_index: 0,
3668 offset_matrix: Mat4::identity(),
3669 });
3670 bone_map.insert(bone_name.clone(), idx);
3671 idx
3672 };
3673 let count = cur.next_usize()?;
3674 let mut verts = Vec::with_capacity(count);
3675 for _ in 0..count {
3676 verts.push(cur.next_usize()?);
3677 }
3678 let mut weights = Vec::with_capacity(count);
3679 for _ in 0..count {
3680 weights.push(cur.next_number()?);
3681 }
3682 let mut vals = [0.0f32; 16];
3683 for v in &mut vals {
3684 *v = cur.next_number()?;
3685 }
3686 bones[bone_idx].offset_matrix = Mat4::from_x_values(vals);
3687 let _ = cur.consume_sym('}');
3688 for (vidx, weight) in verts.into_iter().zip(weights) {
3689 influences.entry(vidx).or_default().push((bone_idx, weight));
3690 }
3691 Ok(())
3692}
3693
3694fn parse_x_texture_filename(cur: &mut Cursor, mesh_path: &Path) -> Option<PathBuf> {
3695 if cur.expect_block_start().is_err() {
3696 return None;
3697 }
3698 let name = cur.next_string().ok()?;
3699 let _ = cur.consume_sym('}');
3700 resolve_texture_path(mesh_path, &name)
3701}
3702
3703fn parse_x_anim_ticks_per_second(cur: &mut Cursor) -> Result<f32> {
3704 cur.expect_block_start()?;
3705 let ticks = cur.next_number()?.max(1.0);
3706 let _ = cur.consume_sym('}');
3707 Ok(ticks)
3708}
3709
3710fn parse_x_animation_options(cur: &mut Cursor, clip: &mut AnimationClip) -> Result<()> {
3711 cur.expect_block_start()?;
3712 clip.open_closed = cur.next_i64().unwrap_or(1) != 0;
3713 let _ = cur.next_i64();
3714 let _ = cur.consume_sym('}');
3715 Ok(())
3716}
3717
3718fn parse_x_animation_set(cur: &mut Cursor, out: &mut Vec<AnimationClip>) -> Result<()> {
3719 let name = cur
3720 .optional_name_before_block()
3721 .unwrap_or_else(|| format!("anim_{}", out.len()));
3722 cur.expect_block_start()?;
3723 let mut clip = AnimationClip {
3724 name,
3725 open_closed: true,
3726 ..Default::default()
3727 };
3728 while let Some(tok) = cur.peek().cloned() {
3729 match tok {
3730 Tok::Sym('}') => {
3731 cur.next();
3732 break;
3733 }
3734 Tok::Ident(id) if id == "Animation" => {
3735 cur.next();
3736 parse_x_animation(cur, &mut clip)?;
3737 }
3738 Tok::Ident(id) if id == "AnimationOptions" => {
3739 cur.next();
3740 parse_x_animation_options(cur, &mut clip)?;
3741 }
3742 Tok::Ident(_) => {
3743 cur.next();
3744 cur.skip_block();
3745 }
3746 _ => {
3747 cur.next();
3748 }
3749 }
3750 }
3751 if !clip.tracks.is_empty() {
3752 out.push(clip);
3753 }
3754 Ok(())
3755}
3756
3757fn parse_x_animation(cur: &mut Cursor, clip: &mut AnimationClip) -> Result<()> {
3758 let _anim_name = cur.optional_name_before_block();
3759 cur.expect_block_start()?;
3760 let mut target_name: Option<String> = None;
3761 while let Some(tok) = cur.peek().cloned() {
3762 match tok {
3763 Tok::Sym('}') => {
3764 cur.next();
3765 break;
3766 }
3767 Tok::Sym('{') => {
3768 cur.next();
3769 if target_name.is_none() {
3770 target_name = Some(cur.next_string()?);
3771 }
3772 while !cur.consume_sym('}') {
3773 let _ = cur.next();
3774 }
3775 }
3776 Tok::Ident(id) if id == "AnimationKey" => {
3777 cur.next();
3778 if let Some(target) = target_name.clone() {
3779 parse_x_animation_key(cur, clip, &target)?;
3780 } else {
3781 cur.skip_block();
3782 }
3783 }
3784 Tok::Ident(_) => {
3785 cur.next();
3786 cur.skip_block();
3787 }
3788 _ => {
3789 cur.next();
3790 }
3791 }
3792 }
3793 Ok(())
3794}
3795
3796fn parse_x_animation_key(
3797 cur: &mut Cursor,
3798 clip: &mut AnimationClip,
3799 target_name: &str,
3800) -> Result<()> {
3801 cur.expect_block_start()?;
3802 let key_type = cur.next_i64()?;
3803 let count = cur.next_usize()?;
3804 let track = clip.tracks.entry(target_name.to_string()).or_default();
3805 for _ in 0..count {
3806 let time = cur.next_i64()?;
3807 let nvals = cur.next_usize()?;
3808 clip.max_time = clip.max_time.max(time);
3809 match key_type {
3810 0 => {
3811 let vals = read_n_numbers(cur, nvals)?;
3812 let q = if vals.len() >= 4 {
3813 Quat {
3815 x: vals[1],
3816 y: vals[2],
3817 z: vals[3],
3818 w: vals[0],
3819 }
3820 .normalize()
3821 } else {
3822 Quat {
3823 x: 0.0,
3824 y: 0.0,
3825 z: 0.0,
3826 w: 1.0,
3827 }
3828 };
3829 track.rotation_keys.push(QuatKey { time, value: q });
3830 }
3831 1 => {
3832 let vals = read_n_numbers(cur, nvals)?;
3833 let v = [
3834 vals.first().copied().unwrap_or(1.0),
3835 vals.get(1).copied().unwrap_or(1.0),
3836 vals.get(2).copied().unwrap_or(1.0),
3837 ];
3838 track.scale_keys.push(Vec3Key { time, value: v });
3839 }
3840 2 => {
3841 let vals = read_n_numbers(cur, nvals)?;
3842 let v = [
3843 vals.first().copied().unwrap_or(0.0),
3844 vals.get(1).copied().unwrap_or(0.0),
3845 vals.get(2).copied().unwrap_or(0.0),
3846 ];
3847 track.position_keys.push(Vec3Key { time, value: v });
3848 }
3849 3 | 4 => {
3850 let vals = read_n_numbers(cur, nvals)?;
3853 let mut m = [0.0f32; 16];
3854 for (dst, src) in m.iter_mut().zip(vals) {
3855 *dst = src;
3856 }
3857 track.matrix_keys.push(MatKey {
3858 time,
3859 value: Mat4::from_x_values(m),
3860 });
3861 }
3862 _ => {
3863 let _ = read_n_numbers(cur, nvals)?;
3864 }
3865 }
3866 }
3867 let _ = cur.consume_sym('}');
3868 Ok(())
3869}
3870
3871fn read_n_numbers(cur: &mut Cursor, n: usize) -> Result<Vec<f32>> {
3872 let mut out = Vec::with_capacity(n);
3873 for _ in 0..n {
3874 out.push(cur.next_number()?);
3875 }
3876 Ok(out)
3877}
3878
3879fn packed_influences(src: Option<&Vec<(usize, f32)>>) -> ([u16; 4], [f32; 4]) {
3880 let mut bone_indices = [0u16; 4];
3881 let mut bone_weights = [0.0f32; 4];
3882 let Some(src) = src else {
3883 return (bone_indices, bone_weights);
3884 };
3885 let mut vals = src.clone();
3886 vals.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
3887 let mut sum = 0.0f32;
3888 for (dst_idx, (bone, weight)) in vals.into_iter().take(4).enumerate() {
3889 bone_indices[dst_idx] = bone as u16;
3890 bone_weights[dst_idx] = weight.max(0.0);
3891 sum += bone_weights[dst_idx];
3892 }
3893 if sum > 0.0 {
3894 for w in &mut bone_weights {
3895 *w /= sum;
3896 }
3897 }
3898 (bone_indices, bone_weights)
3899}
3900
3901fn resolve_texture_path(mesh_path: &Path, tex_name: &str) -> Option<PathBuf> {
3902 let p = Path::new(tex_name);
3903 if p.is_absolute()
3904 && let Some(found) = find_existing_casefold(p)
3905 {
3906 return Some(found);
3907 }
3908 let mut candidates = Vec::new();
3909 if let Some(parent) = mesh_path.parent() {
3910 if let Some(found) = resolve_relative_casefold(parent, tex_name) {
3911 return Some(found);
3912 }
3913 candidates.push(parent.join(tex_name));
3914 }
3915 candidates.push(PathBuf::from(tex_name));
3916 if p.extension().is_none() {
3917 for ext in ["png", "bmp", "jpg", "jpeg", "g00"] {
3918 if let Some(parent) = mesh_path.parent() {
3919 candidates.push(parent.join(format!("{}.{}", tex_name, ext)));
3920 }
3921 }
3922 }
3923 candidates
3924 .into_iter()
3925 .find_map(|p| find_existing_casefold(&p))
3926}
3927
3928fn tokenize_x(text: &str) -> Vec<Tok> {
3929 let mut out = Vec::new();
3930 let chars: Vec<char> = text.chars().collect();
3931 let mut i = 0usize;
3932 while i < chars.len() {
3933 let ch = chars[i];
3934 if ch == '/' && i + 1 < chars.len() && chars[i + 1] == '/' {
3935 while i < chars.len() && chars[i] != '\n' {
3936 i += 1;
3937 }
3938 continue;
3939 }
3940 if ch.is_whitespace() || ch == ';' || ch == ',' {
3941 i += 1;
3942 continue;
3943 }
3944 if ch == '{' || ch == '}' {
3945 out.push(Tok::Sym(ch));
3946 i += 1;
3947 continue;
3948 }
3949 if ch == '"' {
3950 i += 1;
3951 let start = i;
3952 while i < chars.len() && chars[i] != '"' {
3953 i += 1;
3954 }
3955 out.push(Tok::Str(chars[start..i].iter().collect()));
3956 if i < chars.len() {
3957 i += 1;
3958 }
3959 continue;
3960 }
3961 if ch.is_ascii_digit() || ch == '-' || ch == '+' || ch == '.' {
3962 let start = i;
3963 i += 1;
3964 while i < chars.len() {
3965 let c = chars[i];
3966 if c.is_ascii_digit() || matches!(c, '.' | '-' | '+' | 'e' | 'E') {
3967 i += 1;
3968 } else {
3969 break;
3970 }
3971 }
3972 let s: String = chars[start..i].iter().collect();
3973 if let Ok(v) = s.parse::<f32>() {
3974 out.push(Tok::Number(v));
3975 }
3976 continue;
3977 }
3978 if ch.is_alphanumeric() || ch == '_' || ch == '.' {
3979 let start = i;
3980 i += 1;
3981 while i < chars.len()
3982 && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == '.')
3983 {
3984 i += 1;
3985 }
3986 out.push(Tok::Ident(chars[start..i].iter().collect()));
3987 continue;
3988 }
3989 i += 1;
3990 }
3991 out
3992}
3993
3994fn tokenize_x_binary(bytes: &[u8], float_bits: u32) -> Result<Vec<Tok>> {
3995 let mut out = Vec::new();
3996 let mut i = 0usize;
3997 while i + 2 <= bytes.len() {
3998 let token = u16::from_le_bytes([bytes[i], bytes[i + 1]]);
3999 i += 2;
4000 match token {
4001 1 => {
4002 let count = read_u32_le(bytes, &mut i)? as usize;
4003 let raw = read_bytes(bytes, &mut i, count)?;
4004 out.push(Tok::Ident(trim_x_string(raw)));
4005 }
4006 2 => {
4007 let count = read_u32_le(bytes, &mut i)? as usize;
4008 let raw = read_bytes(bytes, &mut i, count)?;
4009 out.push(Tok::Str(trim_x_string(raw)));
4010 let terminator = read_u32_le(bytes, &mut i)?;
4013 match terminator {
4014 19 | 20 => {}
4017 other => bail!(
4018 "invalid .x binary string terminator {} at offset {}",
4019 other,
4020 i.saturating_sub(4)
4021 ),
4022 }
4023 }
4024 3 => {
4025 let v = read_u32_le(bytes, &mut i)? as i32;
4026 out.push(Tok::Number(v as f32));
4027 }
4028 5 => {
4029 let _ = read_bytes(bytes, &mut i, 16)?;
4030 }
4031 6 => {
4032 let count = read_u32_le(bytes, &mut i)? as usize;
4033 for _ in 0..count {
4034 let v = read_u32_le(bytes, &mut i)? as i32;
4035 out.push(Tok::Number(v as f32));
4036 }
4037 }
4038 7 => {
4039 let count = read_u32_le(bytes, &mut i)? as usize;
4040 if float_bits >= 64 {
4041 for _ in 0..count {
4042 let v = read_f64_le(bytes, &mut i)?;
4043 out.push(Tok::Number(v as f32));
4044 }
4045 } else {
4046 for _ in 0..count {
4047 let v = read_f32_le(bytes, &mut i)?;
4048 out.push(Tok::Number(v));
4049 }
4050 }
4051 }
4052 10 => out.push(Tok::Sym('{')),
4053 11 => out.push(Tok::Sym('}')),
4054 12 => out.push(Tok::Sym('(')),
4055 13 => out.push(Tok::Sym(')')),
4056 14 => out.push(Tok::Sym('[')),
4057 15 => out.push(Tok::Sym(']')),
4058 16 => out.push(Tok::Sym('<')),
4059 17 => out.push(Tok::Sym('>')),
4060 18 => out.push(Tok::Sym('.')),
4061 19 | 20 => {}
4063 31 => out.push(Tok::Ident("template".to_string())),
4064 40 => out.push(Tok::Ident("WORD".to_string())),
4065 41 => out.push(Tok::Ident("DWORD".to_string())),
4066 42 => out.push(Tok::Ident("FLOAT".to_string())),
4067 43 => out.push(Tok::Ident("DOUBLE".to_string())),
4068 44 => out.push(Tok::Ident("CHAR".to_string())),
4069 45 => out.push(Tok::Ident("UCHAR".to_string())),
4070 46 => out.push(Tok::Ident("SWORD".to_string())),
4071 47 => out.push(Tok::Ident("SDWORD".to_string())),
4072 48 => out.push(Tok::Ident("VOID".to_string())),
4073 49 => out.push(Tok::Ident("LPSTR".to_string())),
4074 50 => out.push(Tok::Ident("UNICODE".to_string())),
4075 51 => out.push(Tok::Ident("CSTRING".to_string())),
4076 52 => out.push(Tok::Ident("ARRAY".to_string())),
4077 _ => {
4078 bail!(
4079 "unsupported .x binary token {} at offset {}",
4080 token,
4081 i.saturating_sub(2)
4082 );
4083 }
4084 }
4085 }
4086 Ok(out)
4087}
4088
4089fn read_bytes<'a>(bytes: &'a [u8], i: &mut usize, n: usize) -> Result<&'a [u8]> {
4090 if bytes.len().saturating_sub(*i) < n {
4091 bail!("unexpected eof in .x binary stream")
4092 }
4093 let out = &bytes[*i..*i + n];
4094 *i += n;
4095 Ok(out)
4096}
4097
4098fn read_u32_le(bytes: &[u8], i: &mut usize) -> Result<u32> {
4099 let raw = read_bytes(bytes, i, 4)?;
4100 Ok(u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]))
4101}
4102
4103fn read_f32_le(bytes: &[u8], i: &mut usize) -> Result<f32> {
4104 let raw = read_bytes(bytes, i, 4)?;
4105 Ok(f32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]))
4106}
4107
4108fn read_f64_le(bytes: &[u8], i: &mut usize) -> Result<f64> {
4109 let raw = read_bytes(bytes, i, 8)?;
4110 Ok(f64::from_le_bytes([
4111 raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7],
4112 ]))
4113}
4114
4115fn trim_x_string(raw: &[u8]) -> String {
4116 let s = String::from_utf8_lossy(raw).to_string();
4117 s.trim_end_matches('\0').to_string()
4118}
4119
4120pub const SIGLUS_INTERNAL_MESH_MAGIC: &[u8; 8] = b"SGMESH10";
4121pub const SIGLUS_INTERNAL_MESH_VERSION: u32 = 3;
4122
4123pub fn write_internal_mesh_asset(path: &Path, asset: &MeshAsset) -> Result<()> {
4124 let mut writer = AssetWriter::default();
4125 writer.write_bytes(SIGLUS_INTERNAL_MESH_MAGIC)?;
4126 writer.write_u32(SIGLUS_INTERNAL_MESH_VERSION)?;
4127 let base_dir = asset.source_path.parent().unwrap_or_else(|| Path::new(""));
4128 writer.write_string(&normalize_runtime_path_string(&asset.source_path))?;
4129 writer.write_opt_path(asset.texture_path.as_deref(), base_dir)?;
4130 writer.write_u32(asset.bone_count as u32)?;
4131 writer.write_f32(asset.ticks_per_second)?;
4132
4133 writer.write_u32(asset.frames.len() as u32)?;
4134 for frame in &asset.frames {
4135 writer.write_string(&frame.name)?;
4136 writer.write_mat4(frame.base_local)?;
4137 writer.write_u32(frame.children.len() as u32)?;
4138 for &child in &frame.children {
4139 writer.write_u32(child as u32)?;
4140 }
4141 }
4142
4143 writer.write_u32(asset.primitives.len() as u32)?;
4144 for prim in &asset.primitives {
4145 writer.write_u32(prim.frame_index as u32)?;
4146 writer.write_opt_path(prim.texture_path.as_deref(), base_dir)?;
4147 writer.write_mesh_material(&prim.material, base_dir)?;
4148 writer.write_u32(prim.vertices.len() as u32)?;
4149 for vertex in &prim.vertices {
4150 writer.write_vertex(vertex)?;
4151 }
4152 writer.write_u32(prim.bones.len() as u32)?;
4153 for bone in &prim.bones {
4154 writer.write_string(&bone.frame_name)?;
4155 writer.write_u32(bone.frame_index as u32)?;
4156 writer.write_mat4(bone.offset_matrix)?;
4157 }
4158 writer.write_mesh_runtime_desc(&prim.runtime_desc)?;
4159 }
4160
4161 writer.write_u32(asset.animations.len() as u32)?;
4162 for clip in &asset.animations {
4163 writer.write_string(&clip.name)?;
4164 writer.write_i64(clip.max_time)?;
4165 writer.write_bool(clip.open_closed)?;
4166 writer.write_u32(clip.tracks.len() as u32)?;
4167 for (track_name, track) in &clip.tracks {
4168 writer.write_string(track_name)?;
4169 writer.write_u32(track.rotation_keys.len() as u32)?;
4170 for key in &track.rotation_keys {
4171 writer.write_i64(key.time)?;
4172 writer.write_quat(key.value)?;
4173 }
4174 writer.write_u32(track.scale_keys.len() as u32)?;
4175 for key in &track.scale_keys {
4176 writer.write_i64(key.time)?;
4177 writer.write_vec3(key.value)?;
4178 }
4179 writer.write_u32(track.position_keys.len() as u32)?;
4180 for key in &track.position_keys {
4181 writer.write_i64(key.time)?;
4182 writer.write_vec3(key.value)?;
4183 }
4184 writer.write_u32(track.matrix_keys.len() as u32)?;
4185 for key in &track.matrix_keys {
4186 writer.write_i64(key.time)?;
4187 writer.write_mat4(key.value)?;
4188 }
4189 }
4190 }
4191
4192 if let Some(parent) = path.parent() {
4193 fs::create_dir_all(parent)
4194 .with_context(|| format!("create mesh asset dir {:?}", parent))?;
4195 }
4196 fs::write(path, writer.finish())
4197 .with_context(|| format!("write internal mesh asset {:?}", path))?;
4198 Ok(())
4199}
4200
4201pub fn read_internal_mesh_asset(path: &Path) -> Result<MeshAsset> {
4202 let bytes = crate::resource::read_file_bytes(path)
4203 .with_context(|| format!("read internal mesh asset {:?}", path))?;
4204 let mut reader = AssetReader::new(&bytes);
4205 let magic = reader.read_fixed::<8>()?;
4206 if &magic != SIGLUS_INTERNAL_MESH_MAGIC {
4207 bail!("invalid internal mesh asset magic in {:?}", path);
4208 }
4209 let version = reader.read_u32()?;
4210 if version == 0 || version > SIGLUS_INTERNAL_MESH_VERSION {
4211 bail!(
4212 "unsupported internal mesh asset version {} in {:?}",
4213 version,
4214 path
4215 );
4216 }
4217 let base_dir = path.parent().unwrap_or_else(|| Path::new(""));
4218 let source_path = resolve_runtime_asset_path(base_dir, &reader.read_string()?);
4219 let texture_path = reader.read_opt_path(base_dir)?;
4220 let bone_count = reader.read_u32()? as usize;
4221 let ticks_per_second = reader.read_f32()?;
4222
4223 let frame_count = reader.read_u32()? as usize;
4224 let mut frames = Vec::with_capacity(frame_count);
4225 for _ in 0..frame_count {
4226 let name = reader.read_string()?;
4227 let base_local = reader.read_mat4()?;
4228 let child_count = reader.read_u32()? as usize;
4229 let mut children = Vec::with_capacity(child_count);
4230 for _ in 0..child_count {
4231 children.push(reader.read_u32()? as usize);
4232 }
4233 frames.push(FrameNode {
4234 name,
4235 base_local,
4236 children,
4237 });
4238 }
4239
4240 let prim_count = reader.read_u32()? as usize;
4241 let mut primitives = Vec::with_capacity(prim_count);
4242 for _ in 0..prim_count {
4243 let frame_index = reader.read_u32()? as usize;
4244 let texture_path = reader.read_opt_path(base_dir)?;
4245 let material = reader.read_mesh_material(base_dir)?;
4246 let vertex_count = reader.read_u32()? as usize;
4247 let mut vertices = Vec::with_capacity(vertex_count);
4248 for _ in 0..vertex_count {
4249 vertices.push(reader.read_vertex()?);
4250 }
4251 let bone_binding_count = reader.read_u32()? as usize;
4252 let mut bones = Vec::with_capacity(bone_binding_count);
4253 for _ in 0..bone_binding_count {
4254 bones.push(BoneBinding {
4255 frame_name: reader.read_string()?,
4256 frame_index: reader.read_u32()? as usize,
4257 offset_matrix: reader.read_mat4()?,
4258 });
4259 }
4260 let runtime_desc = if version >= 2 {
4261 reader.read_mesh_runtime_desc(version)?
4262 } else {
4263 build_mesh_primitive_runtime_desc_from_material(
4264 &material,
4265 texture_path.is_some(),
4266 !bones.is_empty(),
4267 vertex_count as u32,
4268 bone_binding_count as u32,
4269 )
4270 };
4271 primitives.push(MeshPrimitive {
4272 frame_index,
4273 texture_path,
4274 material,
4275 runtime_desc,
4276 vertices,
4277 bones,
4278 });
4279 }
4280
4281 let anim_count = reader.read_u32()? as usize;
4282 let mut animations = Vec::with_capacity(anim_count);
4283 for _ in 0..anim_count {
4284 let name = reader.read_string()?;
4285 let max_time = reader.read_i64()?;
4286 let open_closed = reader.read_bool()?;
4287 let track_count = reader.read_u32()? as usize;
4288 let mut tracks = HashMap::new();
4289 for _ in 0..track_count {
4290 let track_name = reader.read_string()?;
4291 let rotation_count = reader.read_u32()? as usize;
4292 let mut rotation_keys = Vec::with_capacity(rotation_count);
4293 for _ in 0..rotation_count {
4294 rotation_keys.push(QuatKey {
4295 time: reader.read_i64()?,
4296 value: reader.read_quat()?,
4297 });
4298 }
4299 let scale_count = reader.read_u32()? as usize;
4300 let mut scale_keys = Vec::with_capacity(scale_count);
4301 for _ in 0..scale_count {
4302 scale_keys.push(Vec3Key {
4303 time: reader.read_i64()?,
4304 value: reader.read_vec3()?,
4305 });
4306 }
4307 let position_count = reader.read_u32()? as usize;
4308 let mut position_keys = Vec::with_capacity(position_count);
4309 for _ in 0..position_count {
4310 position_keys.push(Vec3Key {
4311 time: reader.read_i64()?,
4312 value: reader.read_vec3()?,
4313 });
4314 }
4315 let matrix_count = reader.read_u32()? as usize;
4316 let mut matrix_keys = Vec::with_capacity(matrix_count);
4317 for _ in 0..matrix_count {
4318 matrix_keys.push(MatKey {
4319 time: reader.read_i64()?,
4320 value: reader.read_mat4()?,
4321 });
4322 }
4323 tracks.insert(
4324 track_name,
4325 AnimationTrack {
4326 rotation_keys,
4327 scale_keys,
4328 position_keys,
4329 matrix_keys,
4330 },
4331 );
4332 }
4333 animations.push(AnimationClip {
4334 name,
4335 tracks,
4336 max_time,
4337 open_closed,
4338 });
4339 }
4340
4341 let mut asset = MeshAsset {
4342 source_path,
4343 texture_path,
4344 vertices: Vec::new(),
4345 bone_count,
4346 ticks_per_second,
4347 primitives,
4348 frames,
4349 animations,
4350 };
4351 finalize_mesh_asset(&mut asset);
4352 Ok(asset)
4353}
4354
4355fn normalize_runtime_path_string(path: &Path) -> String {
4356 path.to_string_lossy().replace('\\', "/")
4357}
4358
4359fn normalize_runtime_rel_path(path: &Path, base_dir: &Path) -> String {
4360 if let Ok(rel) = path.strip_prefix(base_dir) {
4361 return normalize_runtime_path_string(rel);
4362 }
4363 normalize_runtime_path_string(path)
4364}
4365
4366fn resolve_runtime_asset_path(base_dir: &Path, raw: &str) -> PathBuf {
4367 let path = Path::new(raw);
4368 if path.is_absolute() {
4369 return path.to_path_buf();
4370 }
4371 base_dir.join(path)
4372}
4373
4374#[derive(Default)]
4375struct AssetWriter {
4376 bytes: Vec<u8>,
4377}
4378
4379impl AssetWriter {
4380 fn finish(self) -> Vec<u8> {
4381 self.bytes
4382 }
4383
4384 fn write_bytes(&mut self, raw: &[u8]) -> Result<()> {
4385 self.bytes.write_all(raw)?;
4386 Ok(())
4387 }
4388
4389 fn write_u8(&mut self, value: u8) -> Result<()> {
4390 self.bytes.write_all(&[value])?;
4391 Ok(())
4392 }
4393
4394 fn write_bool(&mut self, value: bool) -> Result<()> {
4395 self.write_u8(if value { 1 } else { 0 })
4396 }
4397
4398 fn write_u16(&mut self, value: u16) -> Result<()> {
4399 self.bytes.write_all(&value.to_le_bytes())?;
4400 Ok(())
4401 }
4402
4403 fn write_u32(&mut self, value: u32) -> Result<()> {
4404 self.bytes.write_all(&value.to_le_bytes())?;
4405 Ok(())
4406 }
4407
4408 fn write_i32(&mut self, value: i32) -> Result<()> {
4409 self.bytes.write_all(&value.to_le_bytes())?;
4410 Ok(())
4411 }
4412
4413 fn write_i64(&mut self, value: i64) -> Result<()> {
4414 self.bytes.write_all(&value.to_le_bytes())?;
4415 Ok(())
4416 }
4417
4418 fn write_f32(&mut self, value: f32) -> Result<()> {
4419 self.bytes.write_all(&value.to_le_bytes())?;
4420 Ok(())
4421 }
4422
4423 fn write_string(&mut self, value: &str) -> Result<()> {
4424 let raw = value.as_bytes();
4425 self.write_u32(raw.len() as u32)?;
4426 self.bytes.write_all(raw)?;
4427 Ok(())
4428 }
4429
4430 fn write_opt_string(&mut self, value: Option<&str>) -> Result<()> {
4431 match value {
4432 Some(v) => {
4433 self.write_bool(true)?;
4434 self.write_string(v)?;
4435 Ok(())
4436 }
4437 None => self.write_bool(false),
4438 }
4439 }
4440
4441 fn write_opt_path(&mut self, value: Option<&Path>, base_dir: &Path) -> Result<()> {
4442 self.write_opt_string(
4443 value
4444 .map(|p| normalize_runtime_rel_path(p, base_dir))
4445 .as_deref(),
4446 )
4447 }
4448
4449 fn write_vec3(&mut self, value: [f32; 3]) -> Result<()> {
4450 for v in value {
4451 self.write_f32(v)?;
4452 }
4453 Ok(())
4454 }
4455
4456 fn write_vec4(&mut self, value: [f32; 4]) -> Result<()> {
4457 for v in value {
4458 self.write_f32(v)?;
4459 }
4460 Ok(())
4461 }
4462
4463 fn write_mat4(&mut self, value: Mat4) -> Result<()> {
4464 for row in value.m {
4465 self.write_vec4(row)?;
4466 }
4467 Ok(())
4468 }
4469
4470 fn write_quat(&mut self, value: Quat) -> Result<()> {
4471 self.write_f32(value.x)?;
4472 self.write_f32(value.y)?;
4473 self.write_f32(value.z)?;
4474 self.write_f32(value.w)?;
4475 Ok(())
4476 }
4477
4478 fn write_vertex(&mut self, value: &MeshTriVertex) -> Result<()> {
4479 self.write_vec3(value.pos)?;
4480 self.write_f32(value.uv[0])?;
4481 self.write_f32(value.uv[1])?;
4482 self.write_vec3(value.normal)?;
4483 self.write_vec3(value.tangent)?;
4484 self.write_vec3(value.binormal)?;
4485 self.write_vec4(value.color)?;
4486 for index in value.bone_indices {
4487 self.write_u16(index)?;
4488 }
4489 for weight in value.bone_weights {
4490 self.write_f32(weight)?;
4491 }
4492 Ok(())
4493 }
4494
4495 fn write_mesh_material(&mut self, value: &MeshMaterial, base_dir: &Path) -> Result<()> {
4496 self.write_vec4(value.diffuse)?;
4497 self.write_vec4(value.ambient)?;
4498 self.write_vec4(value.specular)?;
4499 self.write_vec4(value.emissive)?;
4500 self.write_f32(value.power)?;
4501 self.write_u32(value.lighting_type as u32)?;
4502 self.write_u32(value.shading_type as u32)?;
4503 self.write_u32(value.shader_option)?;
4504 self.write_vec4(value.rim_light_color)?;
4505 self.write_f32(value.rim_light_power)?;
4506 self.write_f32(value.parallax_max_height)?;
4507 self.write_bool(value.alpha_test_enable)?;
4508 self.write_f32(value.alpha_ref)?;
4509 self.write_bool(value.cull_disable)?;
4510 self.write_bool(value.shadow_map_enable)?;
4511 self.write_bool(value.use_mesh_tex)?;
4512 self.write_bool(value.use_mrbd)?;
4513 self.write_vec4(value.mrbd)?;
4514 self.write_bool(value.use_rgb)?;
4515 self.write_vec4(value.rgb_rate)?;
4516 self.write_vec4(value.add_rgb)?;
4517 self.write_bool(value.use_mul_vertex_color)?;
4518 self.write_f32(value.mul_vertex_color_rate)?;
4519 self.write_f32(value.depth_buffer_shadow_bias)?;
4520 self.write_u32(value.directional_light_ids.len() as u32)?;
4521 for id in &value.directional_light_ids {
4522 self.write_i32(*id)?;
4523 }
4524 self.write_u32(value.point_light_ids.len() as u32)?;
4525 for id in &value.point_light_ids {
4526 self.write_i32(*id)?;
4527 }
4528 self.write_u32(value.spot_light_ids.len() as u32)?;
4529 for id in &value.spot_light_ids {
4530 self.write_i32(*id)?;
4531 }
4532 self.write_opt_path(value.normal_texture_path.as_deref(), base_dir)?;
4533 self.write_opt_path(value.toon_texture_path.as_deref(), base_dir)?;
4534 self.write_opt_string(value.effect_filename.as_deref())?;
4535 Ok(())
4536 }
4537
4538 fn write_mesh_runtime_material_key(&mut self, value: MeshRuntimeMaterialKey) -> Result<()> {
4539 self.write_bool(value.use_mesh_tex)?;
4540 self.write_bool(value.use_shadow_tex)?;
4541 self.write_bool(value.use_toon_tex)?;
4542 self.write_bool(value.use_normal_tex)?;
4543 self.write_bool(value.use_mul_vertex_color)?;
4544 self.write_bool(value.use_mrbd)?;
4545 self.write_bool(value.use_rgb)?;
4546 self.write_u32(value.lighting_type as u32)?;
4547 self.write_u32(value.shading_type as u32)?;
4548 self.write_u32(value.shader_option)?;
4549 self.write_bool(value.skinned)?;
4550 self.write_bool(value.alpha_test_enable)?;
4551 self.write_bool(value.cull_disable)?;
4552 self.write_bool(value.shadow_map_enable)?;
4553 Ok(())
4554 }
4555
4556 fn write_mesh_runtime_desc(&mut self, value: &MeshPrimitiveRuntimeDesc) -> Result<()> {
4557 self.write_u32(value.effect_profile as u32)?;
4558 self.write_string(&value.effect_key)?;
4559 self.write_string(&value.technique_name)?;
4560 self.write_string(&value.shadow_effect_key)?;
4561 self.write_string(&value.shadow_technique_name)?;
4562 self.write_bool(value.use_mesh_texture_slot)?;
4563 self.write_bool(value.use_normal_texture_slot)?;
4564 self.write_bool(value.use_toon_texture_slot)?;
4565 self.write_bool(value.use_shadow_texture_slot)?;
4566 self.write_mesh_runtime_material_key(value.material_key)?;
4567 self.write_u32(value.vertex_stride_bytes)?;
4568 self.write_u32(value.vertex_count)?;
4569 self.write_u32(value.bone_palette_len)?;
4570 Ok(())
4571 }
4572}
4573
4574struct AssetReader<'a> {
4575 cursor: IoCursor<&'a [u8]>,
4576}
4577
4578impl<'a> AssetReader<'a> {
4579 fn new(bytes: &'a [u8]) -> Self {
4580 Self {
4581 cursor: IoCursor::new(bytes),
4582 }
4583 }
4584
4585 fn read_exact_into<const N: usize>(&mut self) -> Result<[u8; N]> {
4586 let mut raw = [0u8; N];
4587 self.cursor.read_exact(&mut raw)?;
4588 Ok(raw)
4589 }
4590
4591 fn read_fixed<const N: usize>(&mut self) -> Result<[u8; N]> {
4592 self.read_exact_into::<N>()
4593 }
4594
4595 fn read_u8(&mut self) -> Result<u8> {
4596 Ok(self.read_exact_into::<1>()?[0])
4597 }
4598
4599 fn read_bool(&mut self) -> Result<bool> {
4600 Ok(self.read_u8()? != 0)
4601 }
4602
4603 fn read_u16(&mut self) -> Result<u16> {
4604 Ok(u16::from_le_bytes(self.read_exact_into::<2>()?))
4605 }
4606
4607 fn read_u32(&mut self) -> Result<u32> {
4608 Ok(u32::from_le_bytes(self.read_exact_into::<4>()?))
4609 }
4610
4611 fn read_i32(&mut self) -> Result<i32> {
4612 Ok(i32::from_le_bytes(self.read_exact_into::<4>()?))
4613 }
4614
4615 fn read_i64(&mut self) -> Result<i64> {
4616 Ok(i64::from_le_bytes(self.read_exact_into::<8>()?))
4617 }
4618
4619 fn read_f32(&mut self) -> Result<f32> {
4620 Ok(f32::from_le_bytes(self.read_exact_into::<4>()?))
4621 }
4622
4623 fn read_string(&mut self) -> Result<String> {
4624 let len = self.read_u32()? as usize;
4625 let mut raw = vec![0u8; len];
4626 self.cursor.read_exact(&mut raw)?;
4627 Ok(String::from_utf8_lossy(&raw).to_string())
4628 }
4629
4630 fn read_opt_string(&mut self) -> Result<Option<String>> {
4631 if self.read_bool()? {
4632 Ok(Some(self.read_string()?))
4633 } else {
4634 Ok(None)
4635 }
4636 }
4637
4638 fn read_opt_path(&mut self, base_dir: &Path) -> Result<Option<PathBuf>> {
4639 Ok(self
4640 .read_opt_string()?
4641 .map(|raw| resolve_runtime_asset_path(base_dir, &raw)))
4642 }
4643
4644 fn read_vec3(&mut self) -> Result<[f32; 3]> {
4645 Ok([self.read_f32()?, self.read_f32()?, self.read_f32()?])
4646 }
4647
4648 fn read_vec4(&mut self) -> Result<[f32; 4]> {
4649 Ok([
4650 self.read_f32()?,
4651 self.read_f32()?,
4652 self.read_f32()?,
4653 self.read_f32()?,
4654 ])
4655 }
4656
4657 fn read_mat4(&mut self) -> Result<Mat4> {
4658 Ok(Mat4 {
4659 m: [
4660 self.read_vec4()?,
4661 self.read_vec4()?,
4662 self.read_vec4()?,
4663 self.read_vec4()?,
4664 ],
4665 })
4666 }
4667
4668 fn read_quat(&mut self) -> Result<Quat> {
4669 Ok(Quat {
4670 x: self.read_f32()?,
4671 y: self.read_f32()?,
4672 z: self.read_f32()?,
4673 w: self.read_f32()?,
4674 })
4675 }
4676
4677 fn read_vertex(&mut self) -> Result<MeshTriVertex> {
4678 Ok(MeshTriVertex {
4679 pos: self.read_vec3()?,
4680 uv: [self.read_f32()?, self.read_f32()?],
4681 normal: self.read_vec3()?,
4682 tangent: self.read_vec3()?,
4683 binormal: self.read_vec3()?,
4684 color: self.read_vec4()?,
4685 bone_indices: [
4686 self.read_u16()?,
4687 self.read_u16()?,
4688 self.read_u16()?,
4689 self.read_u16()?,
4690 ],
4691 bone_weights: [
4692 self.read_f32()?,
4693 self.read_f32()?,
4694 self.read_f32()?,
4695 self.read_f32()?,
4696 ],
4697 })
4698 }
4699
4700 fn read_mesh_runtime_material_key(&mut self) -> Result<MeshRuntimeMaterialKey> {
4701 Ok(MeshRuntimeMaterialKey {
4702 use_mesh_tex: self.read_bool()?,
4703 use_shadow_tex: self.read_bool()?,
4704 use_toon_tex: self.read_bool()?,
4705 use_normal_tex: self.read_bool()?,
4706 use_mul_vertex_color: self.read_bool()?,
4707 use_mrbd: self.read_bool()?,
4708 use_rgb: self.read_bool()?,
4709 lighting_type: match self.read_u32()? {
4710 1 => MeshLightingType::Lambert,
4711 2 => MeshLightingType::BlinnPhong,
4712 3 => MeshLightingType::PerPixelBlinnPhong,
4713 4 => MeshLightingType::PerPixelHalfLambert,
4714 5 => MeshLightingType::Toon,
4715 6 => MeshLightingType::FixedFunction,
4716 7 => MeshLightingType::PerPixelFixedFunction,
4717 8 => MeshLightingType::Bump,
4718 9 => MeshLightingType::Parallax,
4719 _ => MeshLightingType::None,
4720 },
4721 shading_type: if self.read_u32()? == 1 {
4722 MeshShadingType::DepthBuffer
4723 } else {
4724 MeshShadingType::None
4725 },
4726 shader_option: self.read_u32()?,
4727 skinned: self.read_bool()?,
4728 alpha_test_enable: self.read_bool()?,
4729 cull_disable: self.read_bool()?,
4730 shadow_map_enable: self.read_bool()?,
4731 })
4732 }
4733
4734 fn read_mesh_runtime_desc(&mut self, version: u32) -> Result<MeshPrimitiveRuntimeDesc> {
4735 let effect_profile = match self.read_u32()? {
4736 1 => MeshEffectProfile::Mesh,
4737 2 => MeshEffectProfile::SkinnedMesh,
4738 3 => MeshEffectProfile::ShadowMap,
4739 _ => MeshEffectProfile::None,
4740 };
4741 let effect_key = self.read_string()?;
4742 let technique_name = self.read_string()?;
4743 let (shadow_effect_key, shadow_technique_name) = if version >= 3 {
4744 (self.read_string()?, self.read_string()?)
4745 } else {
4746 let skinned = effect_profile == MeshEffectProfile::SkinnedMesh
4747 || effect_key.starts_with("skinned");
4748 (
4749 mesh_effect_filename_from_runtime_key(
4750 MeshEffectProfile::ShadowMap,
4751 MeshRuntimeMaterialKey {
4752 skinned,
4753 shadow_map_enable: true,
4754 ..MeshRuntimeMaterialKey::default()
4755 },
4756 ),
4757 String::from("tech"),
4758 )
4759 };
4760 Ok(MeshPrimitiveRuntimeDesc {
4761 effect_profile,
4762 effect_key,
4763 technique_name,
4764 shadow_effect_key,
4765 shadow_technique_name,
4766 use_mesh_texture_slot: self.read_bool()?,
4767 use_normal_texture_slot: self.read_bool()?,
4768 use_toon_texture_slot: self.read_bool()?,
4769 use_shadow_texture_slot: self.read_bool()?,
4770 material_key: self.read_mesh_runtime_material_key()?,
4771 vertex_stride_bytes: self.read_u32()?,
4772 vertex_count: self.read_u32()?,
4773 bone_palette_len: self.read_u32()?,
4774 })
4775 }
4776
4777 fn read_mesh_material(&mut self, base_dir: &Path) -> Result<MeshMaterial> {
4778 let diffuse = self.read_vec4()?;
4779 let ambient = self.read_vec4()?;
4780 let specular = self.read_vec4()?;
4781 let emissive = self.read_vec4()?;
4782 let power = self.read_f32()?;
4783 let lighting_type = match self.read_u32()? {
4784 1 => MeshLightingType::Lambert,
4785 2 => MeshLightingType::BlinnPhong,
4786 3 => MeshLightingType::PerPixelBlinnPhong,
4787 4 => MeshLightingType::PerPixelHalfLambert,
4788 5 => MeshLightingType::Toon,
4789 6 => MeshLightingType::FixedFunction,
4790 7 => MeshLightingType::PerPixelFixedFunction,
4791 8 => MeshLightingType::Bump,
4792 9 => MeshLightingType::Parallax,
4793 _ => MeshLightingType::None,
4794 };
4795 let shading_type = match self.read_u32()? {
4796 1 => MeshShadingType::DepthBuffer,
4797 _ => MeshShadingType::None,
4798 };
4799 let shader_option = self.read_u32()?;
4800 let rim_light_color = self.read_vec4()?;
4801 let rim_light_power = self.read_f32()?;
4802 let parallax_max_height = self.read_f32()?;
4803 let alpha_test_enable = self.read_bool()?;
4804 let alpha_ref = self.read_f32()?;
4805 let cull_disable = self.read_bool()?;
4806 let shadow_map_enable = self.read_bool()?;
4807 let use_mesh_tex = self.read_bool()?;
4808 let use_mrbd = self.read_bool()?;
4809 let mrbd = self.read_vec4()?;
4810 let use_rgb = self.read_bool()?;
4811 let rgb_rate = self.read_vec4()?;
4812 let add_rgb = self.read_vec4()?;
4813 let use_mul_vertex_color = self.read_bool()?;
4814 let mul_vertex_color_rate = self.read_f32()?;
4815 let depth_buffer_shadow_bias = self.read_f32()?;
4816 let directional_count = self.read_u32()? as usize;
4817 let mut directional_light_ids = Vec::with_capacity(directional_count);
4818 for _ in 0..directional_count {
4819 directional_light_ids.push(self.read_i32()?);
4820 }
4821 let point_count = self.read_u32()? as usize;
4822 let mut point_light_ids = Vec::with_capacity(point_count);
4823 for _ in 0..point_count {
4824 point_light_ids.push(self.read_i32()?);
4825 }
4826 let spot_count = self.read_u32()? as usize;
4827 let mut spot_light_ids = Vec::with_capacity(spot_count);
4828 for _ in 0..spot_count {
4829 spot_light_ids.push(self.read_i32()?);
4830 }
4831 let normal_texture_path = self.read_opt_path(base_dir)?;
4832 let toon_texture_path = self.read_opt_path(base_dir)?;
4833 let effect_filename = self.read_opt_string()?;
4834 Ok(MeshMaterial {
4835 diffuse,
4836 ambient,
4837 specular,
4838 emissive,
4839 power,
4840 lighting_type,
4841 shading_type,
4842 shader_option,
4843 rim_light_color,
4844 rim_light_power,
4845 parallax_max_height,
4846 alpha_test_enable,
4847 alpha_ref,
4848 cull_disable,
4849 shadow_map_enable,
4850 use_mesh_tex,
4851 use_mrbd,
4852 mrbd,
4853 use_rgb,
4854 rgb_rate,
4855 add_rgb,
4856 use_mul_vertex_color,
4857 mul_vertex_color_rate,
4858 depth_buffer_shadow_bias,
4859 directional_light_ids,
4860 point_light_ids,
4861 spot_light_ids,
4862 normal_texture_path,
4863 toon_texture_path,
4864 effect_filename,
4865 })
4866 }
4867}
4868
4869#[cfg(test)]
4870mod tests {
4871 use super::*;
4872
4873 #[test]
4874 fn binary_string_consumes_dword_terminator() {
4875 let mut bytes = Vec::new();
4876 bytes.extend_from_slice(&2u16.to_le_bytes());
4877 bytes.extend_from_slice(&3u32.to_le_bytes());
4878 bytes.extend_from_slice(b"abc");
4879 bytes.extend_from_slice(&20u32.to_le_bytes());
4880
4881 let tokens = tokenize_x_binary(&bytes, 32).expect("binary .x string token");
4882 assert_eq!(tokens.len(), 1);
4883 match &tokens[0] {
4884 Tok::Str(value) => assert_eq!(value, "abc"),
4885 other => panic!("unexpected token: {other:?}"),
4886 }
4887 }
4888
4889 #[test]
4890 fn skinned_primitive_is_split_into_bone_palette_batches() {
4891 let bone_count = MAX_GPU_BONE_PALETTE + 6;
4892 let mut vertices = Vec::new();
4893 for triangle in 0..24usize {
4894 for lane in 0..3usize {
4895 let bone = (triangle * 3 + lane) % bone_count;
4896 vertices.push(MeshTriVertex {
4897 bone_indices: [bone as u16, 0, 0, 0],
4898 bone_weights: [1.0, 0.0, 0.0, 0.0],
4899 ..MeshTriVertex::default()
4900 });
4901 }
4902 }
4903 let prim = MeshPrimitive {
4904 frame_index: 0,
4905 texture_path: None,
4906 material: default_mesh_material(),
4907 runtime_desc: MeshPrimitiveRuntimeDesc::default(),
4908 vertices: vertices.clone(),
4909 bones: (0..bone_count)
4910 .map(|index| BoneBinding {
4911 frame_name: format!("bone-{index}"),
4912 frame_index: index,
4913 offset_matrix: Mat4::identity(),
4914 })
4915 .collect(),
4916 };
4917 let bone_cols = vec![Mat4::identity().m; bone_count];
4918 let mut batches = Vec::new();
4919 append_gpu_batches_for_primitive(&mut batches, &prim, Mat4::identity().m, &bone_cols);
4920
4921 assert!(batches.len() >= 2);
4922 assert_eq!(
4923 batches
4924 .iter()
4925 .map(|batch| batch.vertices.len())
4926 .sum::<usize>(),
4927 vertices.len()
4928 );
4929 for batch in &batches {
4930 assert!(batch.bone_cols.len() <= MAX_GPU_BONE_PALETTE);
4931 assert_eq!(
4932 batch.runtime_desc.vertex_count as usize,
4933 batch.vertices.len()
4934 );
4935 assert_eq!(
4936 batch.runtime_desc.bone_palette_len as usize,
4937 batch.bone_cols.len()
4938 );
4939 for vertex in &batch.vertices {
4940 for lane in 0..4 {
4941 if vertex.bone_weights[lane] > 0.0 {
4942 assert!((vertex.bone_indices[lane] as usize) < batch.bone_cols.len());
4943 }
4944 }
4945 }
4946 }
4947 }
4948
4949 #[test]
4950 fn animation_key_type_three_is_matrix() {
4951 let mut tokens = vec![
4952 Tok::Sym('{'),
4953 Tok::Number(3.0),
4954 Tok::Number(1.0),
4955 Tok::Number(0.0),
4956 Tok::Number(16.0),
4957 ];
4958 for value in [
4959 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
4960 ] {
4961 tokens.push(Tok::Number(value));
4962 }
4963 tokens.push(Tok::Sym('}'));
4964
4965 let mut cursor = Cursor::new(tokens);
4966 let mut clip = AnimationClip::default();
4967 parse_x_animation_key(&mut cursor, &mut clip, "Bone").expect("matrix animation key");
4968
4969 let track = clip.tracks.get("Bone").expect("Bone track");
4970 assert_eq!(track.matrix_keys.len(), 1);
4971 assert_eq!(track.matrix_keys[0].time, 0);
4972 }
4973}