wow_m2/chunks/
rendering_enhancements.rs

1//! Advanced rendering enhancement chunks for Legion+ M2 models
2//!
3//! This module provides support for advanced rendering features introduced in
4//! Legion and later expansions, including extended particle systems, waterfall
5//! effects, edge fading, model alpha calculations, and lighting details.
6
7use 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
16/// Helper function to create empty animation blocks for compatibility
17fn create_empty_animation_block<T: M2Parse>() -> M2AnimationBlock<T> {
18    let track = M2AnimationTrack::default();
19    M2AnimationBlock::new(track)
20}
21
22/// Extended particle data for EXPT chunks (version 1)
23#[derive(Debug, Clone)]
24pub struct ExtendedParticleData {
25    /// Version identifier (1 for EXPT, 2 for EXP2)
26    pub version: u8,
27    /// Enhanced particle emitters
28    pub enhanced_emitters: Vec<EnhancedEmitter>,
29    /// Advanced particle systems
30    pub particle_systems: Vec<AdvancedParticleSystem>,
31}
32
33impl ExtendedParticleData {
34    /// Parse EXPT chunk (version 1)
35    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        // EXPT format: sequence of enhanced emitter definitions
41        while !reader.is_at_end()? {
42            let emitter_type = reader.read_u8()?;
43            match emitter_type {
44                0 => {
45                    // Enhanced emitter
46                    let enhanced = EnhancedEmitter::parse(reader)?;
47                    enhanced_emitters.push(enhanced);
48                }
49                1 => {
50                    // Advanced particle system
51                    let system = AdvancedParticleSystem::parse(reader)?;
52                    particle_systems.push(system);
53                }
54                _ => {
55                    // Skip unknown types for forward compatibility
56                    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    /// Parse EXP2 chunk (version 2)
71    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        // EXP2 format: enhanced version with additional properties
77        let emitter_count = reader.read_u32_le()?;
78        let system_count = reader.read_u32_le()?;
79
80        // Read enhanced emitters
81        for _ in 0..emitter_count {
82            let enhanced = EnhancedEmitter::parse_v2(reader)?;
83            enhanced_emitters.push(enhanced);
84        }
85
86        // Read particle systems
87        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    /// Write extended particle data to a writer
100    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
101        if self.version == 1 {
102            // EXPT format
103            for emitter in &self.enhanced_emitters {
104                writer.write_u8(0)?; // Enhanced emitter type
105                emitter.write(writer)?;
106            }
107
108            for system in &self.particle_systems {
109                writer.write_u8(1)?; // Advanced particle system type
110                system.write(writer)?;
111            }
112        } else {
113            // EXP2 format
114            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/// Enhanced particle emitter with extended properties
131#[derive(Debug, Clone)]
132pub struct EnhancedEmitter {
133    /// Base emitter from legacy system
134    pub base_emitter: M2ParticleEmitter,
135    /// Extended properties
136    pub extended_properties: ExtendedEmitterProperties,
137}
138
139impl EnhancedEmitter {
140    /// Parse version 1 enhanced emitter
141    pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
142        // For now, create minimal structure
143        // In a complete implementation, this would parse the full enhanced emitter format
144        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        // Create a minimal base emitter for compatibility
154        // In a real implementation, this would be parsed from the chunk data
155        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    /// Parse version 2 enhanced emitter (EXP2)
253    pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
254        // Version 2 has additional fields
255        let mut emitter = Self::parse(reader)?;
256
257        // Additional EXP2 fields (placeholder implementation)
258        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    /// Write enhanced emitter (version 1)
265    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    /// Write enhanced emitter (version 2)
284    pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
285        self.write(writer)?;
286        // Additional EXP2 fields would go here
287        Ok(())
288    }
289}
290
291/// Extended properties for enhanced emitters
292#[derive(Debug, Clone)]
293pub struct ExtendedEmitterProperties {
294    /// Enhanced blending mode
295    pub enhanced_blending_mode: u8,
296    /// Particle sorting mode for depth/alpha handling
297    pub particle_sorting_mode: u8,
298    /// Texture scaling factor
299    pub texture_scaling_factor: f32,
300    /// Enable advanced physics simulation
301    pub advanced_physics_enabled: bool,
302    /// Enable particle collision detection
303    pub collision_detection_enabled: bool,
304    /// Factor for wind influence on particles
305    pub wind_influence_factor: f32,
306}
307
308/// Advanced particle system with additional capabilities
309#[derive(Debug, Clone)]
310pub struct AdvancedParticleSystem {
311    /// System identifier
312    pub system_id: u32,
313    /// Maximum number of particles
314    pub max_particles: u32,
315    /// Particle spawn pattern type
316    pub spawn_pattern: u8,
317    /// System-wide physics properties
318    pub physics_properties: ParticlePhysicsProperties,
319}
320
321impl AdvancedParticleSystem {
322    /// Parse version 1 advanced particle system
323    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    /// Parse version 2 advanced particle system (EXP2)
343    pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
344        let mut system = Self::parse(reader)?;
345
346        // Additional EXP2 fields
347        system.physics_properties.air_resistance = reader.read_f32_le()?;
348
349        Ok(system)
350    }
351
352    /// Write advanced particle system (version 1)
353    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    /// Write advanced particle system (version 2)
364    pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
365        self.write(writer)?;
366        // Additional EXP2 fields would go here
367        Ok(())
368    }
369}
370
371/// Physics properties for particle systems
372#[derive(Debug, Clone)]
373pub struct ParticlePhysicsProperties {
374    /// Air resistance factor
375    pub air_resistance: f32,
376    /// Bounce factor for collision
377    pub bounce_factor: f32,
378    /// Friction coefficient
379    pub friction_coefficient: f32,
380}
381
382/// Parent animation blacklist (PABC chunk)
383#[derive(Debug, Clone)]
384pub struct ParentAnimationBlacklist {
385    /// Animation sequences that should be blacklisted
386    pub blacklisted_sequences: Vec<u16>,
387}
388
389impl ParentAnimationBlacklist {
390    /// Parse PABC chunk
391    pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
392        let count = reader.chunk_size() / 2; // Each sequence ID is 2 bytes
393        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    /// Write PABC chunk
405    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/// Parent animation data (PADC chunk)
414#[derive(Debug, Clone)]
415pub struct ParentAnimationData {
416    /// Texture weight assignments
417    pub texture_weights: Vec<TextureWeight>,
418    /// Blending modes for animations
419    pub blending_modes: Vec<BlendMode>,
420}
421
422impl ParentAnimationData {
423    /// Parse PADC chunk
424    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    /// Write PADC chunk
456    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/// Texture weight assignment for parent animations
476#[derive(Debug, Clone)]
477pub struct TextureWeight {
478    /// Index of the texture to weight
479    pub texture_index: u16,
480    /// Weight factor (0.0 to 1.0)
481    pub weight_factor: f32,
482    /// Blend operation type
483    pub blend_operation: u8,
484}
485
486/// Blending mode configuration
487#[derive(Debug, Clone)]
488pub struct BlendMode {
489    /// Source blend factor
490    pub source_blend: u8,
491    /// Destination blend factor
492    pub dest_blend: u8,
493    /// Alpha test threshold
494    pub alpha_test_threshold: f32,
495}
496
497/// Waterfall effect data (WFV1/WFV2/WFV3 chunks)
498#[derive(Debug, Clone)]
499pub struct WaterfallEffect {
500    /// Version (1, 2, or 3)
501    pub version: u8,
502    /// Effect parameters
503    pub parameters: WaterfallParameters,
504}
505
506impl WaterfallEffect {
507    /// Parse waterfall effect chunk
508    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    /// Write waterfall effect chunk
531    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/// Parameters for waterfall effects
545#[derive(Debug, Clone)]
546pub struct WaterfallParameters {
547    /// Flow velocity
548    pub flow_velocity: f32,
549    /// Turbulence factor
550    pub turbulence: f32,
551    /// Foam intensity
552    pub foam_intensity: f32,
553    /// Version-specific additional parameters
554    pub additional_params: Vec<f32>,
555}
556
557impl WaterfallParameters {
558    /// Parse version 1 parameters
559    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    /// Parse version 2 parameters
573    pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
574        let mut params = Self::parse_v1(reader)?;
575
576        // Version 2 adds splash parameters
577        params.additional_params.push(reader.read_f32_le()?); // splash_intensity
578        params.additional_params.push(reader.read_f32_le()?); // splash_radius
579
580        Ok(params)
581    }
582
583    /// Parse version 3 parameters
584    pub fn parse_v3<R: Read>(reader: &mut R) -> Result<Self> {
585        let mut params = Self::parse_v2(reader)?;
586
587        // Version 3 adds advanced flow control
588        params.additional_params.push(reader.read_f32_le()?); // flow_direction_x
589        params.additional_params.push(reader.read_f32_le()?); // flow_direction_y
590        params.additional_params.push(reader.read_f32_le()?); // flow_direction_z
591
592        Ok(params)
593    }
594
595    /// Write version 1 parameters
596    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    /// Write version 2 parameters
604    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])?; // splash_intensity
609            writer.write_f32_le(self.additional_params[1])?; // splash_radius
610        } else {
611            writer.write_f32_le(0.0)?;
612            writer.write_f32_le(0.0)?;
613        }
614
615        Ok(())
616    }
617
618    /// Write version 3 parameters
619    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])?; // flow_direction_x
624            writer.write_f32_le(self.additional_params[3])?; // flow_direction_y  
625            writer.write_f32_le(self.additional_params[4])?; // flow_direction_z
626        } else {
627            writer.write_f32_le(0.0)?;
628            writer.write_f32_le(0.0)?;
629            writer.write_f32_le(1.0)?; // Default downward flow
630        }
631
632        Ok(())
633    }
634}
635
636/// Edge fade rendering data (EDGF chunk)
637#[derive(Debug, Clone)]
638pub struct EdgeFadeData {
639    /// Fade distances for different LOD levels
640    pub fade_distances: Vec<f32>,
641    /// Fade factors for smooth transitions
642    pub fade_factors: Vec<f32>,
643}
644
645impl EdgeFadeData {
646    /// Parse EDGF chunk
647    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    /// Write EDGF chunk
669    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/// Model alpha calculation data (NERF chunk)
685#[derive(Debug, Clone)]
686pub struct ModelAlphaData {
687    /// Alpha test threshold
688    pub alpha_test_threshold: f32,
689    /// Alpha blend mode
690    pub blend_mode: AlphaBlendMode,
691}
692
693impl ModelAlphaData {
694    /// Parse NERF chunk
695    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    /// Write NERF chunk
708    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/// Alpha blending modes for model rendering
716#[derive(Debug, Clone, Copy, PartialEq, Eq)]
717pub enum AlphaBlendMode {
718    /// Normal alpha blending
719    Normal = 0,
720    /// Additive blending
721    Additive = 1,
722    /// Multiplicative blending
723    Multiplicative = 2,
724    /// Alpha test only
725    AlphaTest = 3,
726}
727
728impl AlphaBlendMode {
729    /// Convert from u8 value
730    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/// Lighting detail data (DETL chunk)
742#[derive(Debug, Clone)]
743pub struct LightingDetails {
744    /// Ambient lighting factor
745    pub ambient_factor: f32,
746    /// Diffuse lighting factor
747    pub diffuse_factor: f32,
748    /// Specular lighting factor
749    pub specular_factor: f32,
750}
751
752impl LightingDetails {
753    /// Parse DETL chunk
754    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    /// Write DETL chunk
767    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/// Recursive particle model IDs (RPID chunk)
776#[derive(Debug, Clone)]
777pub struct RecursiveParticleIds {
778    /// FileDataIDs of models to load recursively
779    pub model_ids: Vec<u32>,
780}
781
782impl RecursiveParticleIds {
783    /// Parse RPID chunk
784    pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
785        let count = reader.chunk_size() / 4; // Each ID is 4 bytes
786        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    /// Write RPID chunk
796    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/// Geometry particle model IDs (GPID chunk)
805#[derive(Debug, Clone)]
806pub struct GeometryParticleIds {
807    /// FileDataIDs of geometry models for particles
808    pub model_ids: Vec<u32>,
809}
810
811impl GeometryParticleIds {
812    /// Parse GPID chunk
813    pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
814        let count = reader.chunk_size() / 4; // Each ID is 4 bytes
815        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    /// Write GPID chunk
825    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/// TXAC texture animation chunk data
834#[derive(Debug, Clone)]
835pub struct TextureAnimationChunk {
836    /// Extended texture animations beyond the standard set
837    pub texture_animations: Vec<ExtendedTextureAnimation>,
838}
839
840impl TextureAnimationChunk {
841    /// Parse TXAC chunk
842    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    /// Write TXAC chunk
855    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/// Extended texture animation for TXAC chunk
867#[derive(Debug, Clone)]
868pub struct ExtendedTextureAnimation {
869    /// Base texture animation
870    pub base_animation: M2TextureAnimation,
871    /// Extended properties for advanced effects
872    pub extended_properties: ExtendedAnimationProperties,
873}
874
875impl ExtendedTextureAnimation {
876    /// Parse extended texture animation
877    pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
878        // Parse base texture animation first
879        let base_animation = M2TextureAnimation::parse(reader)?;
880
881        // Parse extended properties
882        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()?, // Padding for alignment
894        };
895
896        Ok(Self {
897            base_animation,
898            extended_properties,
899        })
900    }
901
902    /// Write extended texture animation
903    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
904        // Write base animation first
905        self.base_animation.write(writer)?;
906
907        // Write extended properties
908        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/// Extended properties for texture animations
923#[derive(Debug, Clone)]
924pub struct ExtendedAnimationProperties {
925    /// Flow direction vector (x, y, z)
926    pub flow_direction: [f32; 3],
927    /// Speed multiplier for animation
928    pub speed_multiplier: f32,
929    /// Turbulence factor for realistic flow
930    pub turbulence_factor: f32,
931    /// Animation mode
932    pub animation_mode: ExtendedAnimationMode,
933    /// Loop behavior
934    pub loop_behavior: LoopBehavior,
935    /// Texture blend mode
936    pub blend_mode: TextureBlendMode,
937    /// Padding for alignment
938    pub _padding: u8,
939}
940
941/// Extended animation modes for TXAC
942#[derive(Debug, Clone, Copy, PartialEq, Eq)]
943pub enum ExtendedAnimationMode {
944    /// Standard scrolling (legacy compatibility)
945    StandardScroll = 0,
946    /// Flowing liquid animation
947    FlowingLiquid = 1,
948    /// Turbulent flow with noise
949    TurbulentFlow = 2,
950    /// Whirlpool/vortex animation
951    Vortex = 3,
952    /// Wave motion
953    Wave = 4,
954}
955
956impl ExtendedAnimationMode {
957    /// Convert from u8 value
958    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/// Loop behavior for animations
974#[derive(Debug, Clone, Copy, PartialEq, Eq)]
975pub enum LoopBehavior {
976    /// Loop infinitely
977    Infinite = 0,
978    /// Play once and stop
979    Once = 1,
980    /// Ping-pong (forward then reverse)
981    PingPong = 2,
982    /// Reverse loop
983    Reverse = 3,
984}
985
986impl LoopBehavior {
987    /// Convert from u8 value
988    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/// Texture blend modes for TXAC
1003#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1004pub enum TextureBlendMode {
1005    /// Normal blend
1006    Normal = 0,
1007    /// Additive blend
1008    Additive = 1,
1009    /// Multiply blend
1010    Multiply = 2,
1011    /// Screen blend
1012    Screen = 3,
1013    /// Overlay blend
1014    Overlay = 4,
1015}
1016
1017impl TextureBlendMode {
1018    /// Convert from u8 value
1019    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/// PGD1 particle geoset data chunk
1035#[derive(Debug, Clone)]
1036pub struct ParticleGeosetData {
1037    /// Geoset assignments for particle emitters
1038    pub geoset_assignments: Vec<ParticleGeosetEntry>,
1039}
1040
1041impl ParticleGeosetData {
1042    /// Parse PGD1 chunk
1043    pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1044        let count = reader.chunk_size() / 2; // Each entry is 2 bytes (u16)
1045        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    /// Write PGD1 chunk
1056    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/// Particle geoset entry for PGD1 chunk
1065#[derive(Debug, Clone)]
1066pub struct ParticleGeosetEntry {
1067    /// Geoset ID that this particle emitter belongs to
1068    pub geoset: u16,
1069}
1070
1071/// DBOC chunk data (purpose currently unknown)
1072#[derive(Debug, Clone)]
1073pub struct DbocChunk {
1074    /// Raw data for DBOC chunk (16 bytes observed)
1075    pub data: Vec<u8>,
1076}
1077
1078impl DbocChunk {
1079    /// Parse DBOC chunk
1080    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    /// Write DBOC chunk
1088    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1089        writer.write_all(&self.data)?;
1090        Ok(())
1091    }
1092}
1093
1094/// AFRA chunk data (purpose unknown, not observed in files yet)
1095#[derive(Debug, Clone)]
1096pub struct AfraChunk {
1097    /// Raw data for AFRA chunk
1098    pub data: Vec<u8>,
1099}
1100
1101impl AfraChunk {
1102    /// Parse AFRA chunk
1103    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    /// Write AFRA chunk
1111    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
1112        writer.write_all(&self.data)?;
1113        Ok(())
1114    }
1115}
1116
1117/// DPIV chunk data (collision mesh for player housing)
1118#[derive(Debug, Clone)]
1119pub struct DpivChunk {
1120    /// Vertex position count
1121    pub vertex_pos_count: u32,
1122    /// Vertex position offset
1123    pub vertex_pos_offset: u32,
1124    /// Face normal count
1125    pub face_norm_count: u32,
1126    /// Face normal offset
1127    pub face_norm_offset: u32,
1128    /// Index count
1129    pub index_count: u32,
1130    /// Index offset
1131    pub index_offset: u32,
1132    /// Flags count
1133    pub flags_count: u32,
1134    /// Flags offset
1135    pub flags_offset: u32,
1136    /// Vertex positions
1137    pub vertex_positions: Vec<[f32; 3]>,
1138    /// Face normals
1139    pub face_normals: Vec<[f32; 3]>,
1140    /// Indices
1141    pub indices: Vec<u16>,
1142    /// Flags
1143    pub flags: Vec<u16>,
1144}
1145
1146impl DpivChunk {
1147    /// Parse DPIV chunk
1148    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        // Read vertex positions
1161        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        // Read face normals
1173        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        // Read indices
1185        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        // Read flags
1192        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    /// Write DPIV chunk
1215    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        // Note: For write, we would need to calculate proper offsets and write data
1226        // This is a simplified implementation that assumes sequential data layout
1227        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/// PSBC - Parent Sequence Bounds Chunk
1252#[derive(Debug, Clone)]
1253pub struct ParentSequenceBounds {
1254    /// Animation sequence bounds data
1255    pub sequence_bounds: Vec<SequenceBounds>,
1256}
1257
1258/// Sequence bounds information
1259#[derive(Debug, Clone)]
1260pub struct SequenceBounds {
1261    /// Minimum bounds
1262    pub min_bounds: [f32; 3],
1263    /// Maximum bounds
1264    pub max_bounds: [f32; 3],
1265    /// Radius
1266    pub radius: f32,
1267}
1268
1269impl ParentSequenceBounds {
1270    /// Parse PSBC chunk
1271    pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
1272        let mut sequence_bounds = Vec::new();
1273
1274        // Each sequence bound is 28 bytes (6 floats + 1 float)
1275        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    /// Write PSBC chunk
1299    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/// PEDC - Parent Event Data Chunk
1314#[derive(Debug, Clone)]
1315pub struct ParentEventData {
1316    /// Event data entries
1317    pub event_entries: Vec<ParentEventEntry>,
1318}
1319
1320/// Parent event entry
1321#[derive(Debug, Clone)]
1322pub struct ParentEventEntry {
1323    /// Event identifier
1324    pub event_id: u32,
1325    /// Data blob
1326    pub data: Vec<u8>,
1327    /// Timestamp
1328    pub timestamp: u32,
1329}
1330
1331impl ParentEventData {
1332    /// Parse PEDC chunk
1333    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    /// Write PEDC chunk
1355    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/// PCOL - Collision Mesh Data Chunk
1367#[derive(Debug, Clone)]
1368pub struct CollisionMeshData {
1369    /// Collision vertices
1370    pub vertices: Vec<[f32; 3]>,
1371    /// Collision faces
1372    pub faces: Vec<CollisionFace>,
1373    /// Material properties
1374    pub materials: Vec<CollisionMaterial>,
1375}
1376
1377/// Collision face
1378#[derive(Debug, Clone)]
1379pub struct CollisionFace {
1380    /// Vertex indices
1381    pub indices: [u16; 3],
1382    /// Material index
1383    pub material_index: u16,
1384}
1385
1386/// Collision material properties
1387#[derive(Debug, Clone)]
1388pub struct CollisionMaterial {
1389    /// Material flags
1390    pub flags: u32,
1391    /// Friction coefficient
1392    pub friction: f32,
1393    /// Restitution coefficient
1394    pub restitution: f32,
1395}
1396
1397impl CollisionMeshData {
1398    /// Parse PCOL chunk
1399    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    /// Write PCOL chunk
1442    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/// PFDC - Physics File Data Chunk
1471#[derive(Debug, Clone)]
1472pub struct PhysicsFileDataChunk {
1473    /// Raw physics file data
1474    pub physics_data: Vec<u8>,
1475    /// Physics properties
1476    pub properties: PhysicsProperties,
1477}
1478
1479/// Physics properties
1480#[derive(Debug, Clone)]
1481pub struct PhysicsProperties {
1482    /// Mass
1483    pub mass: f32,
1484    /// Center of mass
1485    pub center_of_mass: [f32; 3],
1486    /// Inertia tensor
1487    pub inertia_tensor: [f32; 9],
1488    /// Physics flags
1489    pub flags: u32,
1490}
1491
1492impl PhysicsFileDataChunk {
1493    /// Parse PFDC chunk
1494    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        // Read remaining data as physics blob
1510        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    /// Write PFDC chunk
1525    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, // Sequence 1
1552            0x05, 0x00, // Sequence 5
1553            0x0A, 0x00, // Sequence 10
1554        ];
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, // flow_velocity: 1.0
1571            0x00, 0x00, 0x00, 0x3F, // turbulence: 0.5
1572            0x00, 0x00, 0x40, 0x3F, // foam_intensity: 0.75
1573        ];
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        // Distance count
1592        data.extend_from_slice(&2u32.to_le_bytes());
1593        // Distances
1594        data.extend_from_slice(&10.0f32.to_le_bytes());
1595        data.extend_from_slice(&20.0f32.to_le_bytes());
1596        // Factor count
1597        data.extend_from_slice(&2u32.to_le_bytes());
1598        // Factors
1599        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, // ID: 67305985
1630            0x05, 0x06, 0x07, 0x08, // ID: 134678021
1631        ];
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); // Little-endian
1643        assert_eq!(rpid.model_ids[1], 0x08070605);
1644    }
1645
1646    #[test]
1647    fn test_particle_geoset_data() {
1648        let data = vec![
1649            0x01, 0x00, // Geoset 1
1650            0x05, 0x00, // Geoset 5
1651            0x0A, 0x00, // Geoset 10
1652        ];
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        // First sequence bounds (28 bytes)
1673        data.extend_from_slice(&(-10.0f32).to_le_bytes()); // min_x
1674        data.extend_from_slice(&(-5.0f32).to_le_bytes()); // min_y
1675        data.extend_from_slice(&(-2.0f32).to_le_bytes()); // min_z
1676        data.extend_from_slice(&10.0f32.to_le_bytes()); // max_x
1677        data.extend_from_slice(&5.0f32.to_le_bytes()); // max_y
1678        data.extend_from_slice(&2.0f32.to_le_bytes()); // max_z
1679        data.extend_from_slice(&15.0f32.to_le_bytes()); // radius
1680
1681        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        // First event
1700        data.extend_from_slice(&1u32.to_le_bytes()); // event_id
1701        data.extend_from_slice(&4u32.to_le_bytes()); // data_size
1702        data.extend_from_slice(&1000u32.to_le_bytes()); // timestamp
1703        data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]); // data
1704
1705        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        // Header
1724        data.extend_from_slice(&1u32.to_le_bytes()); // vertex_count
1725        data.extend_from_slice(&1u32.to_le_bytes()); // face_count
1726        data.extend_from_slice(&1u32.to_le_bytes()); // material_count
1727
1728        // Vertex
1729        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        // Face
1734        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()); // material_index
1738
1739        // Material
1740        data.extend_from_slice(&1u32.to_le_bytes()); // flags
1741        data.extend_from_slice(&0.5f32.to_le_bytes()); // friction
1742        data.extend_from_slice(&0.8f32.to_le_bytes()); // restitution
1743
1744        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        // Count of texture animations
1764        data.extend_from_slice(&1u32.to_le_bytes());
1765
1766        // Base texture animation data (simplified)
1767        data.extend_from_slice(&1u16.to_le_bytes()); // Animation type (Scroll)
1768        data.extend_from_slice(&0u16.to_le_bytes()); // Padding
1769
1770        // Add minimal animation block data (would be more complex in reality)
1771        for _ in 0..5 {
1772            // Each animation block: interpolation_type, global_sequence, timestamps, values
1773            data.extend_from_slice(&0u16.to_le_bytes()); // Interpolation type
1774            data.extend_from_slice(&(-1i16).to_le_bytes()); // Global sequence
1775            data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges count
1776            data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges offset
1777            data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps count
1778            data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps offset
1779            data.extend_from_slice(&0u32.to_le_bytes()); // Values count
1780            data.extend_from_slice(&0u32.to_le_bytes()); // Values offset
1781        }
1782
1783        // Extended properties
1784        data.extend_from_slice(&1.0f32.to_le_bytes()); // Flow direction X
1785        data.extend_from_slice(&0.0f32.to_le_bytes()); // Flow direction Y
1786        data.extend_from_slice(&0.0f32.to_le_bytes()); // Flow direction Z
1787        data.extend_from_slice(&1.5f32.to_le_bytes()); // Speed multiplier
1788        data.extend_from_slice(&0.2f32.to_le_bytes()); // Turbulence factor
1789        data.extend_from_slice(&1u8.to_le_bytes()); // Animation mode (FlowingLiquid)
1790        data.extend_from_slice(&0u8.to_le_bytes()); // Loop behavior (Infinite)
1791        data.extend_from_slice(&0u8.to_le_bytes()); // Blend mode (Normal)
1792        data.extend_from_slice(&0u8.to_le_bytes()); // Padding
1793
1794        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        ]; // 16 bytes
1823
1824        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        // Physics properties
1857        data.extend_from_slice(&10.0f32.to_le_bytes()); // mass
1858        data.extend_from_slice(&1.0f32.to_le_bytes()); // center_of_mass[0]
1859        data.extend_from_slice(&2.0f32.to_le_bytes()); // center_of_mass[1]
1860        data.extend_from_slice(&3.0f32.to_le_bytes()); // center_of_mass[2]
1861
1862        // Inertia tensor (9 floats)
1863        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()); // flags
1868
1869        // Additional physics data
1870        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}