1use crate::chunks::animation::{M2AnimationBlock, M2AnimationTrack};
8use crate::chunks::infrastructure::ChunkReader;
9use crate::chunks::particle_emitter::M2ParticleEmitter;
10use crate::chunks::texture_animation::M2TextureAnimation;
11use crate::common::M2Parse;
12use crate::error::Result;
13use crate::io_ext::{ReadExt, WriteExt};
14use std::io::{Read, Seek, Write};
15
16fn create_empty_animation_block<T: M2Parse>() -> M2AnimationBlock<T> {
18 let track = M2AnimationTrack::default();
19 M2AnimationBlock::new(track)
20}
21
22#[derive(Debug, Clone)]
24pub struct ExtendedParticleData {
25 pub version: u8,
27 pub enhanced_emitters: Vec<EnhancedEmitter>,
29 pub particle_systems: Vec<AdvancedParticleSystem>,
31}
32
33impl ExtendedParticleData {
34 pub fn parse_expt<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
36 let version = 1;
37 let mut enhanced_emitters = Vec::new();
38 let mut particle_systems = Vec::new();
39
40 while !reader.is_at_end()? {
42 let emitter_type = reader.read_u8()?;
43 match emitter_type {
44 0 => {
45 let enhanced = EnhancedEmitter::parse(reader)?;
47 enhanced_emitters.push(enhanced);
48 }
49 1 => {
50 let system = AdvancedParticleSystem::parse(reader)?;
52 particle_systems.push(system);
53 }
54 _ => {
55 let skip_size = reader.read_u32_le()?;
57 let mut skip_buffer = vec![0u8; skip_size as usize];
58 reader.read_exact(&mut skip_buffer)?;
59 }
60 }
61 }
62
63 Ok(Self {
64 version,
65 enhanced_emitters,
66 particle_systems,
67 })
68 }
69
70 pub fn parse_exp2<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
72 let version = 2;
73 let mut enhanced_emitters = Vec::new();
74 let mut particle_systems = Vec::new();
75
76 let emitter_count = reader.read_u32_le()?;
78 let system_count = reader.read_u32_le()?;
79
80 for _ in 0..emitter_count {
82 let enhanced = EnhancedEmitter::parse_v2(reader)?;
83 enhanced_emitters.push(enhanced);
84 }
85
86 for _ in 0..system_count {
88 let system = AdvancedParticleSystem::parse_v2(reader)?;
89 particle_systems.push(system);
90 }
91
92 Ok(Self {
93 version,
94 enhanced_emitters,
95 particle_systems,
96 })
97 }
98
99 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
101 if self.version == 1 {
102 for emitter in &self.enhanced_emitters {
104 writer.write_u8(0)?; emitter.write(writer)?;
106 }
107
108 for system in &self.particle_systems {
109 writer.write_u8(1)?; system.write(writer)?;
111 }
112 } else {
113 writer.write_u32_le(self.enhanced_emitters.len() as u32)?;
115 writer.write_u32_le(self.particle_systems.len() as u32)?;
116
117 for emitter in &self.enhanced_emitters {
118 emitter.write_v2(writer)?;
119 }
120
121 for system in &self.particle_systems {
122 system.write_v2(writer)?;
123 }
124 }
125
126 Ok(())
127 }
128}
129
130#[derive(Debug, Clone)]
132pub struct EnhancedEmitter {
133 pub base_emitter: M2ParticleEmitter,
135 pub extended_properties: ExtendedEmitterProperties,
137}
138
139impl EnhancedEmitter {
140 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
142 let extended_properties = ExtendedEmitterProperties {
145 enhanced_blending_mode: reader.read_u8()?,
146 particle_sorting_mode: reader.read_u8()?,
147 texture_scaling_factor: reader.read_f32_le()?,
148 advanced_physics_enabled: reader.read_u8()? != 0,
149 collision_detection_enabled: reader.read_u8()? != 0,
150 wind_influence_factor: reader.read_f32_le()?,
151 };
152
153 let base_emitter = M2ParticleEmitter {
156 id: 0,
157 flags: crate::chunks::particle_emitter::M2ParticleFlags::empty(),
158 position: crate::common::C3Vector {
159 x: 0.0,
160 y: 0.0,
161 z: 0.0,
162 },
163 bone_index: 0,
164 texture_index: 0,
165 model_filename: crate::common::M2Array::new(0, 0),
166 parent_emitter: 0,
167 geometry_model_unknown: 0,
168 fallback_model_filename: None,
169 blending_type: 0,
170 emitter_type: crate::chunks::particle_emitter::M2ParticleEmitterType::Point,
171 particle_type: 0,
172 head_or_tail: 0,
173 texture_file_data_ids: None,
174 texture_tile_coordinates: crate::common::M2Array::new(0, 0),
175 enable_encryption: None,
176 multi_texture_param0: None,
177 multi_texture_param1: None,
178 lifetime: 0.0,
179 emission_rate: 0.0,
180 emission_area_length: 0.0,
181 emission_area_width: 0.0,
182 emission_velocity: 0.0,
183 min_lifetime: 0.0,
184 max_lifetime: 0.0,
185 min_emission_rate: 0.0,
186 max_emission_rate: 0.0,
187 min_emission_area_length: 0.0,
188 max_emission_area_length: 0.0,
189 min_emission_area_width: 0.0,
190 max_emission_area_width: 0.0,
191 min_emission_velocity: 0.0,
192 max_emission_velocity: 0.0,
193 position_variation: 0.0,
194 min_position_variation: 0.0,
195 max_position_variation: 0.0,
196 initial_size: 0.0,
197 min_initial_size: 0.0,
198 max_initial_size: 0.0,
199 size_variation: 0.0,
200 min_size_variation: 0.0,
201 max_size_variation: 0.0,
202 horizontal_range: 0.0,
203 min_horizontal_range: 0.0,
204 max_horizontal_range: 0.0,
205 vertical_range: 0.0,
206 min_vertical_range: 0.0,
207 max_vertical_range: 0.0,
208 gravity: 0.0,
209 min_gravity: 0.0,
210 max_gravity: 0.0,
211 initial_velocity: 0.0,
212 min_initial_velocity: 0.0,
213 max_initial_velocity: 0.0,
214 speed_variation: 0.0,
215 min_speed_variation: 0.0,
216 max_speed_variation: 0.0,
217 rotation_speed: 0.0,
218 min_rotation_speed: 0.0,
219 max_rotation_speed: 0.0,
220 initial_rotation: 0.0,
221 min_initial_rotation: 0.0,
222 max_initial_rotation: 0.0,
223 mid_point_color: crate::chunks::color_animation::M2Color::transparent(),
224 color_animation_speed: 0.0,
225 color_median_time: 0.0,
226 lifespan_unused: 0.0,
227 emission_rate_unused: 0.0,
228 unknown_1: 0,
229 unknown_2: 0.0,
230 emission_speed_animation: create_empty_animation_block(),
231 emission_rate_animation: create_empty_animation_block(),
232 emission_area_animation: create_empty_animation_block(),
233 xy_scale_animation: create_empty_animation_block(),
234 z_scale_animation: create_empty_animation_block(),
235 color_animation: create_empty_animation_block(),
236 transparency_animation: create_empty_animation_block(),
237 size_animation: create_empty_animation_block(),
238 intensity_animation: create_empty_animation_block(),
239 z_source_animation: create_empty_animation_block(),
240 particle_initial_state: None,
241 particle_initial_state_variation: None,
242 particle_convergence_time: None,
243 physics_parameters: None,
244 };
245
246 Ok(Self {
247 base_emitter,
248 extended_properties,
249 })
250 }
251
252 pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
254 let mut emitter = Self::parse(reader)?;
256
257 emitter.extended_properties.advanced_physics_enabled = true;
259 emitter.extended_properties.collision_detection_enabled = reader.read_u8()? != 0;
260
261 Ok(emitter)
262 }
263
264 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
266 writer.write_u8(self.extended_properties.enhanced_blending_mode)?;
267 writer.write_u8(self.extended_properties.particle_sorting_mode)?;
268 writer.write_f32_le(self.extended_properties.texture_scaling_factor)?;
269 writer.write_u8(if self.extended_properties.advanced_physics_enabled {
270 1
271 } else {
272 0
273 })?;
274 writer.write_u8(if self.extended_properties.collision_detection_enabled {
275 1
276 } else {
277 0
278 })?;
279 writer.write_f32_le(self.extended_properties.wind_influence_factor)?;
280 Ok(())
281 }
282
283 pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
285 self.write(writer)?;
286 Ok(())
288 }
289}
290
291#[derive(Debug, Clone)]
293pub struct ExtendedEmitterProperties {
294 pub enhanced_blending_mode: u8,
296 pub particle_sorting_mode: u8,
298 pub texture_scaling_factor: f32,
300 pub advanced_physics_enabled: bool,
302 pub collision_detection_enabled: bool,
304 pub wind_influence_factor: f32,
306}
307
308#[derive(Debug, Clone)]
310pub struct AdvancedParticleSystem {
311 pub system_id: u32,
313 pub max_particles: u32,
315 pub spawn_pattern: u8,
317 pub physics_properties: ParticlePhysicsProperties,
319}
320
321impl AdvancedParticleSystem {
322 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
324 let system_id = reader.read_u32_le()?;
325 let max_particles = reader.read_u32_le()?;
326 let spawn_pattern = reader.read_u8()?;
327
328 let physics_properties = ParticlePhysicsProperties {
329 air_resistance: reader.read_f32_le()?,
330 bounce_factor: reader.read_f32_le()?,
331 friction_coefficient: reader.read_f32_le()?,
332 };
333
334 Ok(Self {
335 system_id,
336 max_particles,
337 spawn_pattern,
338 physics_properties,
339 })
340 }
341
342 pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
344 let mut system = Self::parse(reader)?;
345
346 system.physics_properties.air_resistance = reader.read_f32_le()?;
348
349 Ok(system)
350 }
351
352 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
354 writer.write_u32_le(self.system_id)?;
355 writer.write_u32_le(self.max_particles)?;
356 writer.write_u8(self.spawn_pattern)?;
357 writer.write_f32_le(self.physics_properties.air_resistance)?;
358 writer.write_f32_le(self.physics_properties.bounce_factor)?;
359 writer.write_f32_le(self.physics_properties.friction_coefficient)?;
360 Ok(())
361 }
362
363 pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
365 self.write(writer)?;
366 Ok(())
368 }
369}
370
371#[derive(Debug, Clone)]
373pub struct ParticlePhysicsProperties {
374 pub air_resistance: f32,
376 pub bounce_factor: f32,
378 pub friction_coefficient: f32,
380}
381
382#[derive(Debug, Clone)]
384pub struct ParentAnimationBlacklist {
385 pub blacklisted_sequences: Vec<u16>,
387}
388
389impl ParentAnimationBlacklist {
390 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
392 let count = reader.chunk_size() / 2; let mut blacklisted_sequences = Vec::with_capacity(count as usize);
394
395 for _ in 0..count {
396 blacklisted_sequences.push(reader.read_u16_le()?);
397 }
398
399 Ok(Self {
400 blacklisted_sequences,
401 })
402 }
403
404 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
406 for &sequence_id in &self.blacklisted_sequences {
407 writer.write_u16_le(sequence_id)?;
408 }
409 Ok(())
410 }
411}
412
413#[derive(Debug, Clone)]
415pub struct ParentAnimationData {
416 pub texture_weights: Vec<TextureWeight>,
418 pub blending_modes: Vec<BlendMode>,
420}
421
422impl ParentAnimationData {
423 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
425 let weight_count = reader.read_u32_le()?;
426 let mut texture_weights = Vec::with_capacity(weight_count as usize);
427
428 for _ in 0..weight_count {
429 let weight = TextureWeight {
430 texture_index: reader.read_u16_le()?,
431 weight_factor: reader.read_f32_le()?,
432 blend_operation: reader.read_u8()?,
433 };
434 texture_weights.push(weight);
435 }
436
437 let mode_count = reader.read_u32_le()?;
438 let mut blending_modes = Vec::with_capacity(mode_count as usize);
439
440 for _ in 0..mode_count {
441 let mode = BlendMode {
442 source_blend: reader.read_u8()?,
443 dest_blend: reader.read_u8()?,
444 alpha_test_threshold: reader.read_f32_le()?,
445 };
446 blending_modes.push(mode);
447 }
448
449 Ok(Self {
450 texture_weights,
451 blending_modes,
452 })
453 }
454
455 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
457 writer.write_u32_le(self.texture_weights.len() as u32)?;
458 for weight in &self.texture_weights {
459 writer.write_u16_le(weight.texture_index)?;
460 writer.write_f32_le(weight.weight_factor)?;
461 writer.write_u8(weight.blend_operation)?;
462 }
463
464 writer.write_u32_le(self.blending_modes.len() as u32)?;
465 for mode in &self.blending_modes {
466 writer.write_u8(mode.source_blend)?;
467 writer.write_u8(mode.dest_blend)?;
468 writer.write_f32_le(mode.alpha_test_threshold)?;
469 }
470
471 Ok(())
472 }
473}
474
475#[derive(Debug, Clone)]
477pub struct TextureWeight {
478 pub texture_index: u16,
480 pub weight_factor: f32,
482 pub blend_operation: u8,
484}
485
486#[derive(Debug, Clone)]
488pub struct BlendMode {
489 pub source_blend: u8,
491 pub dest_blend: u8,
493 pub alpha_test_threshold: f32,
495}
496
497#[derive(Debug, Clone)]
499pub struct WaterfallEffect {
500 pub version: u8,
502 pub parameters: WaterfallParameters,
504}
505
506impl WaterfallEffect {
507 pub fn parse<R: Read + std::io::Seek>(
509 reader: &mut ChunkReader<R>,
510 version: u8,
511 ) -> Result<Self> {
512 let parameters = match version {
513 1 => WaterfallParameters::parse_v1(reader)?,
514 2 => WaterfallParameters::parse_v2(reader)?,
515 3 => WaterfallParameters::parse_v3(reader)?,
516 _ => {
517 return Err(crate::error::M2Error::ParseError(format!(
518 "Unsupported waterfall effect version: {}",
519 version
520 )));
521 }
522 };
523
524 Ok(Self {
525 version,
526 parameters,
527 })
528 }
529
530 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
532 match self.version {
533 1 => self.parameters.write_v1(writer),
534 2 => self.parameters.write_v2(writer),
535 3 => self.parameters.write_v3(writer),
536 _ => Err(crate::error::M2Error::ParseError(format!(
537 "Unsupported waterfall effect version: {}",
538 self.version
539 ))),
540 }
541 }
542}
543
544#[derive(Debug, Clone)]
546pub struct WaterfallParameters {
547 pub flow_velocity: f32,
549 pub turbulence: f32,
551 pub foam_intensity: f32,
553 pub additional_params: Vec<f32>,
555}
556
557impl WaterfallParameters {
558 pub fn parse_v1<R: Read>(reader: &mut R) -> Result<Self> {
560 let flow_velocity = reader.read_f32_le()?;
561 let turbulence = reader.read_f32_le()?;
562 let foam_intensity = reader.read_f32_le()?;
563
564 Ok(Self {
565 flow_velocity,
566 turbulence,
567 foam_intensity,
568 additional_params: Vec::new(),
569 })
570 }
571
572 pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
574 let mut params = Self::parse_v1(reader)?;
575
576 params.additional_params.push(reader.read_f32_le()?); params.additional_params.push(reader.read_f32_le()?); Ok(params)
581 }
582
583 pub fn parse_v3<R: Read>(reader: &mut R) -> Result<Self> {
585 let mut params = Self::parse_v2(reader)?;
586
587 params.additional_params.push(reader.read_f32_le()?); params.additional_params.push(reader.read_f32_le()?); params.additional_params.push(reader.read_f32_le()?); Ok(params)
593 }
594
595 pub fn write_v1<W: Write>(&self, writer: &mut W) -> Result<()> {
597 writer.write_f32_le(self.flow_velocity)?;
598 writer.write_f32_le(self.turbulence)?;
599 writer.write_f32_le(self.foam_intensity)?;
600 Ok(())
601 }
602
603 pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
605 self.write_v1(writer)?;
606
607 if self.additional_params.len() >= 2 {
608 writer.write_f32_le(self.additional_params[0])?; writer.write_f32_le(self.additional_params[1])?; } else {
611 writer.write_f32_le(0.0)?;
612 writer.write_f32_le(0.0)?;
613 }
614
615 Ok(())
616 }
617
618 pub fn write_v3<W: Write>(&self, writer: &mut W) -> Result<()> {
620 self.write_v2(writer)?;
621
622 if self.additional_params.len() >= 5 {
623 writer.write_f32_le(self.additional_params[2])?; writer.write_f32_le(self.additional_params[3])?; writer.write_f32_le(self.additional_params[4])?; } else {
627 writer.write_f32_le(0.0)?;
628 writer.write_f32_le(0.0)?;
629 writer.write_f32_le(1.0)?; }
631
632 Ok(())
633 }
634}
635
636#[derive(Debug, Clone)]
638pub struct EdgeFadeData {
639 pub fade_distances: Vec<f32>,
641 pub fade_factors: Vec<f32>,
643}
644
645impl EdgeFadeData {
646 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
648 let distance_count = reader.read_u32_le()?;
649 let mut fade_distances = Vec::with_capacity(distance_count as usize);
650
651 for _ in 0..distance_count {
652 fade_distances.push(reader.read_f32_le()?);
653 }
654
655 let factor_count = reader.read_u32_le()?;
656 let mut fade_factors = Vec::with_capacity(factor_count as usize);
657
658 for _ in 0..factor_count {
659 fade_factors.push(reader.read_f32_le()?);
660 }
661
662 Ok(Self {
663 fade_distances,
664 fade_factors,
665 })
666 }
667
668 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
670 writer.write_u32_le(self.fade_distances.len() as u32)?;
671 for &distance in &self.fade_distances {
672 writer.write_f32_le(distance)?;
673 }
674
675 writer.write_u32_le(self.fade_factors.len() as u32)?;
676 for &factor in &self.fade_factors {
677 writer.write_f32_le(factor)?;
678 }
679
680 Ok(())
681 }
682}
683
684#[derive(Debug, Clone)]
686pub struct ModelAlphaData {
687 pub alpha_test_threshold: f32,
689 pub blend_mode: AlphaBlendMode,
691}
692
693impl ModelAlphaData {
694 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
696 let alpha_test_threshold = reader.read_f32_le()?;
697 let blend_mode_value = reader.read_u8()?;
698 let blend_mode =
699 AlphaBlendMode::from_u8(blend_mode_value).unwrap_or(AlphaBlendMode::Normal);
700
701 Ok(Self {
702 alpha_test_threshold,
703 blend_mode,
704 })
705 }
706
707 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
709 writer.write_f32_le(self.alpha_test_threshold)?;
710 writer.write_u8(self.blend_mode as u8)?;
711 Ok(())
712 }
713}
714
715#[derive(Debug, Clone, Copy, PartialEq, Eq)]
717pub enum AlphaBlendMode {
718 Normal = 0,
720 Additive = 1,
722 Multiplicative = 2,
724 AlphaTest = 3,
726}
727
728impl AlphaBlendMode {
729 pub fn from_u8(value: u8) -> Option<Self> {
731 match value {
732 0 => Some(Self::Normal),
733 1 => Some(Self::Additive),
734 2 => Some(Self::Multiplicative),
735 3 => Some(Self::AlphaTest),
736 _ => None,
737 }
738 }
739}
740
741#[derive(Debug, Clone)]
743pub struct LightingDetails {
744 pub ambient_factor: f32,
746 pub diffuse_factor: f32,
748 pub specular_factor: f32,
750}
751
752impl LightingDetails {
753 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
755 let ambient_factor = reader.read_f32_le()?;
756 let diffuse_factor = reader.read_f32_le()?;
757 let specular_factor = reader.read_f32_le()?;
758
759 Ok(Self {
760 ambient_factor,
761 diffuse_factor,
762 specular_factor,
763 })
764 }
765
766 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
768 writer.write_f32_le(self.ambient_factor)?;
769 writer.write_f32_le(self.diffuse_factor)?;
770 writer.write_f32_le(self.specular_factor)?;
771 Ok(())
772 }
773}
774
775#[derive(Debug, Clone)]
777pub struct RecursiveParticleIds {
778 pub model_ids: Vec<u32>,
780}
781
782impl RecursiveParticleIds {
783 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
785 let count = reader.chunk_size() / 4; let mut model_ids = Vec::with_capacity(count as usize);
787
788 for _ in 0..count {
789 model_ids.push(reader.read_u32_le()?);
790 }
791
792 Ok(Self { model_ids })
793 }
794
795 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
797 for &id in &self.model_ids {
798 writer.write_u32_le(id)?;
799 }
800 Ok(())
801 }
802}
803
804#[derive(Debug, Clone)]
806pub struct GeometryParticleIds {
807 pub model_ids: Vec<u32>,
809}
810
811impl GeometryParticleIds {
812 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
814 let count = reader.chunk_size() / 4; let mut model_ids = Vec::with_capacity(count as usize);
816
817 for _ in 0..count {
818 model_ids.push(reader.read_u32_le()?);
819 }
820
821 Ok(Self { model_ids })
822 }
823
824 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
826 for &id in &self.model_ids {
827 writer.write_u32_le(id)?;
828 }
829 Ok(())
830 }
831}
832
833#[derive(Debug, Clone)]
835pub struct TextureAnimationChunk {
836 pub texture_animations: Vec<ExtendedTextureAnimation>,
838}
839
840impl TextureAnimationChunk {
841 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
843 let count = reader.read_u32_le()?;
844 let mut texture_animations = Vec::with_capacity(count as usize);
845
846 for _ in 0..count {
847 let extended_anim = ExtendedTextureAnimation::parse(reader)?;
848 texture_animations.push(extended_anim);
849 }
850
851 Ok(Self { texture_animations })
852 }
853
854 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
856 writer.write_u32_le(self.texture_animations.len() as u32)?;
857
858 for anim in &self.texture_animations {
859 anim.write(writer)?;
860 }
861
862 Ok(())
863 }
864}
865
866#[derive(Debug, Clone)]
868pub struct ExtendedTextureAnimation {
869 pub base_animation: M2TextureAnimation,
871 pub extended_properties: ExtendedAnimationProperties,
873}
874
875impl ExtendedTextureAnimation {
876 pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
878 let base_animation = M2TextureAnimation::parse(reader)?;
880
881 let extended_properties = ExtendedAnimationProperties {
883 flow_direction: [
884 reader.read_f32_le()?,
885 reader.read_f32_le()?,
886 reader.read_f32_le()?,
887 ],
888 speed_multiplier: reader.read_f32_le()?,
889 turbulence_factor: reader.read_f32_le()?,
890 animation_mode: ExtendedAnimationMode::from_u8(reader.read_u8()?)?,
891 loop_behavior: LoopBehavior::from_u8(reader.read_u8()?)?,
892 blend_mode: TextureBlendMode::from_u8(reader.read_u8()?)?,
893 _padding: reader.read_u8()?, };
895
896 Ok(Self {
897 base_animation,
898 extended_properties,
899 })
900 }
901
902 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
904 self.base_animation.write(writer)?;
906
907 writer.write_f32_le(self.extended_properties.flow_direction[0])?;
909 writer.write_f32_le(self.extended_properties.flow_direction[1])?;
910 writer.write_f32_le(self.extended_properties.flow_direction[2])?;
911 writer.write_f32_le(self.extended_properties.speed_multiplier)?;
912 writer.write_f32_le(self.extended_properties.turbulence_factor)?;
913 writer.write_u8(self.extended_properties.animation_mode as u8)?;
914 writer.write_u8(self.extended_properties.loop_behavior as u8)?;
915 writer.write_u8(self.extended_properties.blend_mode as u8)?;
916 writer.write_u8(self.extended_properties._padding)?;
917
918 Ok(())
919 }
920}
921
922#[derive(Debug, Clone)]
924pub struct ExtendedAnimationProperties {
925 pub flow_direction: [f32; 3],
927 pub speed_multiplier: f32,
929 pub turbulence_factor: f32,
931 pub animation_mode: ExtendedAnimationMode,
933 pub loop_behavior: LoopBehavior,
935 pub blend_mode: TextureBlendMode,
937 pub _padding: u8,
939}
940
941#[derive(Debug, Clone, Copy, PartialEq, Eq)]
943pub enum ExtendedAnimationMode {
944 StandardScroll = 0,
946 FlowingLiquid = 1,
948 TurbulentFlow = 2,
950 Vortex = 3,
952 Wave = 4,
954}
955
956impl ExtendedAnimationMode {
957 pub fn from_u8(value: u8) -> Result<Self> {
959 match value {
960 0 => Ok(Self::StandardScroll),
961 1 => Ok(Self::FlowingLiquid),
962 2 => Ok(Self::TurbulentFlow),
963 3 => Ok(Self::Vortex),
964 4 => Ok(Self::Wave),
965 _ => Err(crate::error::M2Error::ParseError(format!(
966 "Unknown extended animation mode: {}",
967 value
968 ))),
969 }
970 }
971}
972
973#[derive(Debug, Clone, Copy, PartialEq, Eq)]
975pub enum LoopBehavior {
976 Infinite = 0,
978 Once = 1,
980 PingPong = 2,
982 Reverse = 3,
984}
985
986impl LoopBehavior {
987 pub fn from_u8(value: u8) -> Result<Self> {
989 match value {
990 0 => Ok(Self::Infinite),
991 1 => Ok(Self::Once),
992 2 => Ok(Self::PingPong),
993 3 => Ok(Self::Reverse),
994 _ => Err(crate::error::M2Error::ParseError(format!(
995 "Unknown loop behavior: {}",
996 value
997 ))),
998 }
999 }
1000}
1001
1002#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1004pub enum TextureBlendMode {
1005 Normal = 0,
1007 Additive = 1,
1009 Multiply = 2,
1011 Screen = 3,
1013 Overlay = 4,
1015}
1016
1017impl TextureBlendMode {
1018 pub fn from_u8(value: u8) -> Result<Self> {
1020 match value {
1021 0 => Ok(Self::Normal),
1022 1 => Ok(Self::Additive),
1023 2 => Ok(Self::Multiply),
1024 3 => Ok(Self::Screen),
1025 4 => Ok(Self::Overlay),
1026 _ => Err(crate::error::M2Error::ParseError(format!(
1027 "Unknown texture blend mode: {}",
1028 value
1029 ))),
1030 }
1031 }
1032}
1033
1034#[derive(Debug, Clone)]
1036pub struct ParticleGeosetData {
1037 pub geoset_assignments: Vec<ParticleGeosetEntry>,
1039}
1040
1041impl ParticleGeosetData {
1042 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1044 let count = reader.chunk_size() / 2; let mut geoset_assignments = Vec::with_capacity(count as usize);
1046
1047 for _ in 0..count {
1048 let geoset = reader.read_u16_le()?;
1049 geoset_assignments.push(ParticleGeosetEntry { geoset });
1050 }
1051
1052 Ok(Self { geoset_assignments })
1053 }
1054
1055 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1057 for entry in &self.geoset_assignments {
1058 writer.write_u16_le(entry.geoset)?;
1059 }
1060 Ok(())
1061 }
1062}
1063
1064#[derive(Debug, Clone)]
1066pub struct ParticleGeosetEntry {
1067 pub geoset: u16,
1069}
1070
1071#[derive(Debug, Clone)]
1073pub struct DbocChunk {
1074 pub data: Vec<u8>,
1076}
1077
1078impl DbocChunk {
1079 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1081 let mut data = vec![0u8; reader.chunk_size() as usize];
1082 reader.read_exact(&mut data)?;
1083
1084 Ok(Self { data })
1085 }
1086
1087 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1089 writer.write_all(&self.data)?;
1090 Ok(())
1091 }
1092}
1093
1094#[derive(Debug, Clone)]
1096pub struct AfraChunk {
1097 pub data: Vec<u8>,
1099}
1100
1101impl AfraChunk {
1102 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1104 let mut data = vec![0u8; reader.chunk_size() as usize];
1105 reader.read_exact(&mut data)?;
1106
1107 Ok(Self { data })
1108 }
1109
1110 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1112 writer.write_all(&self.data)?;
1113 Ok(())
1114 }
1115}
1116
1117#[derive(Debug, Clone)]
1119pub struct DpivChunk {
1120 pub vertex_pos_count: u32,
1122 pub vertex_pos_offset: u32,
1124 pub face_norm_count: u32,
1126 pub face_norm_offset: u32,
1128 pub index_count: u32,
1130 pub index_offset: u32,
1132 pub flags_count: u32,
1134 pub flags_offset: u32,
1136 pub vertex_positions: Vec<[f32; 3]>,
1138 pub face_normals: Vec<[f32; 3]>,
1140 pub indices: Vec<u16>,
1142 pub flags: Vec<u16>,
1144}
1145
1146impl DpivChunk {
1147 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1149 let chunk_start = reader.current_position()?;
1150
1151 let vertex_pos_count = reader.read_u32_le()?;
1152 let vertex_pos_offset = reader.read_u32_le()?;
1153 let face_norm_count = reader.read_u32_le()?;
1154 let face_norm_offset = reader.read_u32_le()?;
1155 let index_count = reader.read_u32_le()?;
1156 let index_offset = reader.read_u32_le()?;
1157 let flags_count = reader.read_u32_le()?;
1158 let flags_offset = reader.read_u32_le()?;
1159
1160 reader.seek_to_position(chunk_start + vertex_pos_offset as u64)?;
1162 let mut vertex_positions = Vec::with_capacity(vertex_pos_count as usize);
1163 for _ in 0..vertex_pos_count {
1164 let pos = [
1165 reader.read_f32_le()?,
1166 reader.read_f32_le()?,
1167 reader.read_f32_le()?,
1168 ];
1169 vertex_positions.push(pos);
1170 }
1171
1172 reader.seek_to_position(chunk_start + face_norm_offset as u64)?;
1174 let mut face_normals = Vec::with_capacity(face_norm_count as usize);
1175 for _ in 0..face_norm_count {
1176 let normal = [
1177 reader.read_f32_le()?,
1178 reader.read_f32_le()?,
1179 reader.read_f32_le()?,
1180 ];
1181 face_normals.push(normal);
1182 }
1183
1184 reader.seek_to_position(chunk_start + index_offset as u64)?;
1186 let mut indices = Vec::with_capacity(index_count as usize);
1187 for _ in 0..index_count {
1188 indices.push(reader.read_u16_le()?);
1189 }
1190
1191 reader.seek_to_position(chunk_start + flags_offset as u64)?;
1193 let mut flags = Vec::with_capacity(flags_count as usize);
1194 for _ in 0..flags_count {
1195 flags.push(reader.read_u16_le()?);
1196 }
1197
1198 Ok(Self {
1199 vertex_pos_count,
1200 vertex_pos_offset,
1201 face_norm_count,
1202 face_norm_offset,
1203 index_count,
1204 index_offset,
1205 flags_count,
1206 flags_offset,
1207 vertex_positions,
1208 face_normals,
1209 indices,
1210 flags,
1211 })
1212 }
1213
1214 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1216 writer.write_u32_le(self.vertex_pos_count)?;
1217 writer.write_u32_le(self.vertex_pos_offset)?;
1218 writer.write_u32_le(self.face_norm_count)?;
1219 writer.write_u32_le(self.face_norm_offset)?;
1220 writer.write_u32_le(self.index_count)?;
1221 writer.write_u32_le(self.index_offset)?;
1222 writer.write_u32_le(self.flags_count)?;
1223 writer.write_u32_le(self.flags_offset)?;
1224
1225 for pos in &self.vertex_positions {
1228 writer.write_f32_le(pos[0])?;
1229 writer.write_f32_le(pos[1])?;
1230 writer.write_f32_le(pos[2])?;
1231 }
1232
1233 for normal in &self.face_normals {
1234 writer.write_f32_le(normal[0])?;
1235 writer.write_f32_le(normal[1])?;
1236 writer.write_f32_le(normal[2])?;
1237 }
1238
1239 for &index in &self.indices {
1240 writer.write_u16_le(index)?;
1241 }
1242
1243 for &flag in &self.flags {
1244 writer.write_u16_le(flag)?;
1245 }
1246
1247 Ok(())
1248 }
1249}
1250
1251#[derive(Debug, Clone)]
1253pub struct ParentSequenceBounds {
1254 pub sequence_bounds: Vec<SequenceBounds>,
1256}
1257
1258#[derive(Debug, Clone)]
1260pub struct SequenceBounds {
1261 pub min_bounds: [f32; 3],
1263 pub max_bounds: [f32; 3],
1265 pub radius: f32,
1267}
1268
1269impl ParentSequenceBounds {
1270 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1272 let mut sequence_bounds = Vec::new();
1273
1274 while !reader.is_at_end()? {
1276 let min_bounds = [
1277 reader.read_f32_le()?,
1278 reader.read_f32_le()?,
1279 reader.read_f32_le()?,
1280 ];
1281 let max_bounds = [
1282 reader.read_f32_le()?,
1283 reader.read_f32_le()?,
1284 reader.read_f32_le()?,
1285 ];
1286 let radius = reader.read_f32_le()?;
1287
1288 sequence_bounds.push(SequenceBounds {
1289 min_bounds,
1290 max_bounds,
1291 radius,
1292 });
1293 }
1294
1295 Ok(Self { sequence_bounds })
1296 }
1297
1298 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1300 for bounds in &self.sequence_bounds {
1301 writer.write_f32_le(bounds.min_bounds[0])?;
1302 writer.write_f32_le(bounds.min_bounds[1])?;
1303 writer.write_f32_le(bounds.min_bounds[2])?;
1304 writer.write_f32_le(bounds.max_bounds[0])?;
1305 writer.write_f32_le(bounds.max_bounds[1])?;
1306 writer.write_f32_le(bounds.max_bounds[2])?;
1307 writer.write_f32_le(bounds.radius)?;
1308 }
1309 Ok(())
1310 }
1311}
1312
1313#[derive(Debug, Clone)]
1315pub struct ParentEventData {
1316 pub event_entries: Vec<ParentEventEntry>,
1318}
1319
1320#[derive(Debug, Clone)]
1322pub struct ParentEventEntry {
1323 pub event_id: u32,
1325 pub data: Vec<u8>,
1327 pub timestamp: u32,
1329}
1330
1331impl ParentEventData {
1332 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1334 let mut event_entries = Vec::new();
1335
1336 while !reader.is_at_end()? {
1337 let event_id = reader.read_u32_le()?;
1338 let data_size = reader.read_u32_le()?;
1339 let timestamp = reader.read_u32_le()?;
1340
1341 let mut data = vec![0u8; data_size as usize];
1342 reader.read_exact(&mut data)?;
1343
1344 event_entries.push(ParentEventEntry {
1345 event_id,
1346 data,
1347 timestamp,
1348 });
1349 }
1350
1351 Ok(Self { event_entries })
1352 }
1353
1354 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1356 for entry in &self.event_entries {
1357 writer.write_u32_le(entry.event_id)?;
1358 writer.write_u32_le(entry.data.len() as u32)?;
1359 writer.write_u32_le(entry.timestamp)?;
1360 writer.write_all(&entry.data)?;
1361 }
1362 Ok(())
1363 }
1364}
1365
1366#[derive(Debug, Clone)]
1368pub struct CollisionMeshData {
1369 pub vertices: Vec<[f32; 3]>,
1371 pub faces: Vec<CollisionFace>,
1373 pub materials: Vec<CollisionMaterial>,
1375}
1376
1377#[derive(Debug, Clone)]
1379pub struct CollisionFace {
1380 pub indices: [u16; 3],
1382 pub material_index: u16,
1384}
1385
1386#[derive(Debug, Clone)]
1388pub struct CollisionMaterial {
1389 pub flags: u32,
1391 pub friction: f32,
1393 pub restitution: f32,
1395}
1396
1397impl CollisionMeshData {
1398 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1400 let vertex_count = reader.read_u32_le()?;
1401 let face_count = reader.read_u32_le()?;
1402 let material_count = reader.read_u32_le()?;
1403
1404 let mut vertices = Vec::with_capacity(vertex_count as usize);
1405 for _ in 0..vertex_count {
1406 vertices.push([
1407 reader.read_f32_le()?,
1408 reader.read_f32_le()?,
1409 reader.read_f32_le()?,
1410 ]);
1411 }
1412
1413 let mut faces = Vec::with_capacity(face_count as usize);
1414 for _ in 0..face_count {
1415 faces.push(CollisionFace {
1416 indices: [
1417 reader.read_u16_le()?,
1418 reader.read_u16_le()?,
1419 reader.read_u16_le()?,
1420 ],
1421 material_index: reader.read_u16_le()?,
1422 });
1423 }
1424
1425 let mut materials = Vec::with_capacity(material_count as usize);
1426 for _ in 0..material_count {
1427 materials.push(CollisionMaterial {
1428 flags: reader.read_u32_le()?,
1429 friction: reader.read_f32_le()?,
1430 restitution: reader.read_f32_le()?,
1431 });
1432 }
1433
1434 Ok(Self {
1435 vertices,
1436 faces,
1437 materials,
1438 })
1439 }
1440
1441 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1443 writer.write_u32_le(self.vertices.len() as u32)?;
1444 writer.write_u32_le(self.faces.len() as u32)?;
1445 writer.write_u32_le(self.materials.len() as u32)?;
1446
1447 for vertex in &self.vertices {
1448 writer.write_f32_le(vertex[0])?;
1449 writer.write_f32_le(vertex[1])?;
1450 writer.write_f32_le(vertex[2])?;
1451 }
1452
1453 for face in &self.faces {
1454 writer.write_u16_le(face.indices[0])?;
1455 writer.write_u16_le(face.indices[1])?;
1456 writer.write_u16_le(face.indices[2])?;
1457 writer.write_u16_le(face.material_index)?;
1458 }
1459
1460 for material in &self.materials {
1461 writer.write_u32_le(material.flags)?;
1462 writer.write_f32_le(material.friction)?;
1463 writer.write_f32_le(material.restitution)?;
1464 }
1465
1466 Ok(())
1467 }
1468}
1469
1470#[derive(Debug, Clone)]
1472pub struct PhysicsFileDataChunk {
1473 pub physics_data: Vec<u8>,
1475 pub properties: PhysicsProperties,
1477}
1478
1479#[derive(Debug, Clone)]
1481pub struct PhysicsProperties {
1482 pub mass: f32,
1484 pub center_of_mass: [f32; 3],
1486 pub inertia_tensor: [f32; 9],
1488 pub flags: u32,
1490}
1491
1492impl PhysicsFileDataChunk {
1493 pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1495 let mass = reader.read_f32_le()?;
1496 let center_of_mass = [
1497 reader.read_f32_le()?,
1498 reader.read_f32_le()?,
1499 reader.read_f32_le()?,
1500 ];
1501
1502 let mut inertia_tensor = [0.0f32; 9];
1503 for item in &mut inertia_tensor {
1504 *item = reader.read_f32_le()?;
1505 }
1506
1507 let flags = reader.read_u32_le()?;
1508
1509 let mut physics_data = Vec::new();
1511 reader.read_to_end(&mut physics_data)?;
1512
1513 Ok(Self {
1514 physics_data,
1515 properties: PhysicsProperties {
1516 mass,
1517 center_of_mass,
1518 inertia_tensor,
1519 flags,
1520 },
1521 })
1522 }
1523
1524 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1526 writer.write_f32_le(self.properties.mass)?;
1527 writer.write_f32_le(self.properties.center_of_mass[0])?;
1528 writer.write_f32_le(self.properties.center_of_mass[1])?;
1529 writer.write_f32_le(self.properties.center_of_mass[2])?;
1530
1531 for &tensor_val in &self.properties.inertia_tensor {
1532 writer.write_f32_le(tensor_val)?;
1533 }
1534
1535 writer.write_u32_le(self.properties.flags)?;
1536 writer.write_all(&self.physics_data)?;
1537
1538 Ok(())
1539 }
1540}
1541
1542#[cfg(test)]
1543mod tests {
1544 use super::*;
1545 use crate::chunks::infrastructure::{ChunkHeader, ChunkReader};
1546 use std::io::Cursor;
1547
1548 #[test]
1549 fn test_parent_animation_blacklist() {
1550 let data = vec![
1551 0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, ];
1555
1556 let header = ChunkHeader {
1557 magic: *b"PABC",
1558 size: data.len() as u32,
1559 };
1560 let cursor = Cursor::new(data);
1561 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1562
1563 let blacklist = ParentAnimationBlacklist::parse(&mut chunk_reader).unwrap();
1564 assert_eq!(blacklist.blacklisted_sequences, vec![1, 5, 10]);
1565 }
1566
1567 #[test]
1568 fn test_waterfall_effect_v1() {
1569 let data = vec![
1570 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x40, 0x3F, ];
1574
1575 let header = ChunkHeader {
1576 magic: *b"WFV1",
1577 size: data.len() as u32,
1578 };
1579 let cursor = Cursor::new(data);
1580 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1581
1582 let effect = WaterfallEffect::parse(&mut chunk_reader, 1).unwrap();
1583 assert_eq!(effect.version, 1);
1584 assert_eq!(effect.parameters.flow_velocity, 1.0);
1585 assert!(effect.parameters.additional_params.is_empty());
1586 }
1587
1588 #[test]
1589 fn test_edge_fade_data() {
1590 let mut data = Vec::new();
1591 data.extend_from_slice(&2u32.to_le_bytes());
1593 data.extend_from_slice(&10.0f32.to_le_bytes());
1595 data.extend_from_slice(&20.0f32.to_le_bytes());
1596 data.extend_from_slice(&2u32.to_le_bytes());
1598 data.extend_from_slice(&0.5f32.to_le_bytes());
1600 data.extend_from_slice(&0.8f32.to_le_bytes());
1601
1602 let header = ChunkHeader {
1603 magic: *b"EDGF",
1604 size: data.len() as u32,
1605 };
1606 let cursor = Cursor::new(data);
1607 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1608
1609 let fade_data = EdgeFadeData::parse(&mut chunk_reader).unwrap();
1610 assert_eq!(fade_data.fade_distances, vec![10.0, 20.0]);
1611 assert_eq!(fade_data.fade_factors, vec![0.5, 0.8]);
1612 }
1613
1614 #[test]
1615 fn test_alpha_blend_mode_conversion() {
1616 assert_eq!(AlphaBlendMode::from_u8(0), Some(AlphaBlendMode::Normal));
1617 assert_eq!(AlphaBlendMode::from_u8(1), Some(AlphaBlendMode::Additive));
1618 assert_eq!(
1619 AlphaBlendMode::from_u8(2),
1620 Some(AlphaBlendMode::Multiplicative)
1621 );
1622 assert_eq!(AlphaBlendMode::from_u8(3), Some(AlphaBlendMode::AlphaTest));
1623 assert_eq!(AlphaBlendMode::from_u8(99), None);
1624 }
1625
1626 #[test]
1627 fn test_particle_model_ids() {
1628 let data = vec![
1629 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ];
1632
1633 let header = ChunkHeader {
1634 magic: *b"RPID",
1635 size: data.len() as u32,
1636 };
1637 let cursor = Cursor::new(data);
1638 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1639
1640 let rpid = RecursiveParticleIds::parse(&mut chunk_reader).unwrap();
1641 assert_eq!(rpid.model_ids.len(), 2);
1642 assert_eq!(rpid.model_ids[0], 0x04030201); assert_eq!(rpid.model_ids[1], 0x08070605);
1644 }
1645
1646 #[test]
1647 fn test_particle_geoset_data() {
1648 let data = vec![
1649 0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, ];
1653
1654 let header = ChunkHeader {
1655 magic: *b"PGD1",
1656 size: data.len() as u32,
1657 };
1658 let cursor = Cursor::new(data);
1659 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1660
1661 let pgd1 = ParticleGeosetData::parse(&mut chunk_reader).unwrap();
1662 assert_eq!(pgd1.geoset_assignments.len(), 3);
1663 assert_eq!(pgd1.geoset_assignments[0].geoset, 1);
1664 assert_eq!(pgd1.geoset_assignments[1].geoset, 5);
1665 assert_eq!(pgd1.geoset_assignments[2].geoset, 10);
1666 }
1667
1668 #[test]
1669 fn test_parent_sequence_bounds() {
1670 let mut data = Vec::new();
1671
1672 data.extend_from_slice(&(-10.0f32).to_le_bytes()); data.extend_from_slice(&(-5.0f32).to_le_bytes()); data.extend_from_slice(&(-2.0f32).to_le_bytes()); data.extend_from_slice(&10.0f32.to_le_bytes()); data.extend_from_slice(&5.0f32.to_le_bytes()); data.extend_from_slice(&2.0f32.to_le_bytes()); data.extend_from_slice(&15.0f32.to_le_bytes()); let header = ChunkHeader {
1682 magic: *b"PSBC",
1683 size: data.len() as u32,
1684 };
1685 let cursor = Cursor::new(data);
1686 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1687
1688 let psbc = ParentSequenceBounds::parse(&mut chunk_reader).unwrap();
1689 assert_eq!(psbc.sequence_bounds.len(), 1);
1690 assert_eq!(psbc.sequence_bounds[0].min_bounds, [-10.0, -5.0, -2.0]);
1691 assert_eq!(psbc.sequence_bounds[0].max_bounds, [10.0, 5.0, 2.0]);
1692 assert_eq!(psbc.sequence_bounds[0].radius, 15.0);
1693 }
1694
1695 #[test]
1696 fn test_parent_event_data() {
1697 let mut data = Vec::new();
1698
1699 data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&1000u32.to_le_bytes()); data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]); let header = ChunkHeader {
1706 magic: *b"PEDC",
1707 size: data.len() as u32,
1708 };
1709 let cursor = Cursor::new(data);
1710 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1711
1712 let pedc = ParentEventData::parse(&mut chunk_reader).unwrap();
1713 assert_eq!(pedc.event_entries.len(), 1);
1714 assert_eq!(pedc.event_entries[0].event_id, 1);
1715 assert_eq!(pedc.event_entries[0].timestamp, 1000);
1716 assert_eq!(pedc.event_entries[0].data, vec![0x01, 0x02, 0x03, 0x04]);
1717 }
1718
1719 #[test]
1720 fn test_collision_mesh_data() {
1721 let mut data = Vec::new();
1722
1723 data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&1.0f32.to_le_bytes());
1730 data.extend_from_slice(&2.0f32.to_le_bytes());
1731 data.extend_from_slice(&3.0f32.to_le_bytes());
1732
1733 data.extend_from_slice(&0u16.to_le_bytes());
1735 data.extend_from_slice(&1u16.to_le_bytes());
1736 data.extend_from_slice(&2u16.to_le_bytes());
1737 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0.5f32.to_le_bytes()); data.extend_from_slice(&0.8f32.to_le_bytes()); let header = ChunkHeader {
1745 magic: *b"PCOL",
1746 size: data.len() as u32,
1747 };
1748 let cursor = Cursor::new(data);
1749 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1750
1751 let pcol = CollisionMeshData::parse(&mut chunk_reader).unwrap();
1752 assert_eq!(pcol.vertices.len(), 1);
1753 assert_eq!(pcol.faces.len(), 1);
1754 assert_eq!(pcol.materials.len(), 1);
1755 assert_eq!(pcol.vertices[0], [1.0, 2.0, 3.0]);
1756 assert_eq!(pcol.materials[0].friction, 0.5);
1757 }
1758
1759 #[test]
1760 fn test_texture_animation_chunk() {
1761 let mut data = Vec::new();
1762
1763 data.extend_from_slice(&1u32.to_le_bytes());
1765
1766 data.extend_from_slice(&1u16.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes()); for _ in 0..5 {
1772 data.extend_from_slice(&0u16.to_le_bytes()); data.extend_from_slice(&(-1i16).to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); }
1782
1783 data.extend_from_slice(&1.0f32.to_le_bytes()); data.extend_from_slice(&0.0f32.to_le_bytes()); data.extend_from_slice(&0.0f32.to_le_bytes()); data.extend_from_slice(&1.5f32.to_le_bytes()); data.extend_from_slice(&0.2f32.to_le_bytes()); data.extend_from_slice(&1u8.to_le_bytes()); data.extend_from_slice(&0u8.to_le_bytes()); data.extend_from_slice(&0u8.to_le_bytes()); data.extend_from_slice(&0u8.to_le_bytes()); let header = ChunkHeader {
1795 magic: *b"TXAC",
1796 size: data.len() as u32,
1797 };
1798 let cursor = Cursor::new(data);
1799 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1800
1801 let txac = TextureAnimationChunk::parse(&mut chunk_reader).unwrap();
1802 assert_eq!(txac.texture_animations.len(), 1);
1803 assert_eq!(
1804 txac.texture_animations[0]
1805 .extended_properties
1806 .animation_mode,
1807 ExtendedAnimationMode::FlowingLiquid
1808 );
1809 assert_eq!(
1810 txac.texture_animations[0]
1811 .extended_properties
1812 .speed_multiplier,
1813 1.5
1814 );
1815 }
1816
1817 #[test]
1818 fn test_dboc_chunk() {
1819 let data = vec![
1820 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
1821 0x0F, 0x10,
1822 ]; let header = ChunkHeader {
1825 magic: *b"DBOC",
1826 size: data.len() as u32,
1827 };
1828 let cursor = Cursor::new(data.clone());
1829 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1830
1831 let dboc = DbocChunk::parse(&mut chunk_reader).unwrap();
1832 assert_eq!(dboc.data, data);
1833 }
1834
1835 #[test]
1836 fn test_extended_animation_mode_conversion() {
1837 assert_eq!(
1838 ExtendedAnimationMode::from_u8(0).unwrap(),
1839 ExtendedAnimationMode::StandardScroll
1840 );
1841 assert_eq!(
1842 ExtendedAnimationMode::from_u8(1).unwrap(),
1843 ExtendedAnimationMode::FlowingLiquid
1844 );
1845 assert_eq!(
1846 ExtendedAnimationMode::from_u8(4).unwrap(),
1847 ExtendedAnimationMode::Wave
1848 );
1849 assert!(ExtendedAnimationMode::from_u8(99).is_err());
1850 }
1851
1852 #[test]
1853 fn test_physics_file_data_chunk() {
1854 let mut data = Vec::new();
1855
1856 data.extend_from_slice(&10.0f32.to_le_bytes()); data.extend_from_slice(&1.0f32.to_le_bytes()); data.extend_from_slice(&2.0f32.to_le_bytes()); data.extend_from_slice(&3.0f32.to_le_bytes()); for i in 0..9 {
1864 data.extend_from_slice(&((i + 1) as f32).to_le_bytes());
1865 }
1866
1867 data.extend_from_slice(&0x12345678u32.to_le_bytes()); data.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
1871
1872 let header = ChunkHeader {
1873 magic: *b"PFDC",
1874 size: data.len() as u32,
1875 };
1876 let cursor = Cursor::new(data);
1877 let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
1878
1879 let pfdc = PhysicsFileDataChunk::parse(&mut chunk_reader).unwrap();
1880 assert_eq!(pfdc.properties.mass, 10.0);
1881 assert_eq!(pfdc.properties.center_of_mass, [1.0, 2.0, 3.0]);
1882 assert_eq!(pfdc.properties.flags, 0x12345678);
1883 assert_eq!(pfdc.physics_data, vec![0xAA, 0xBB, 0xCC, 0xDD]);
1884 }
1885}