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