use crate::chunks::animation::{M2AnimationBlock, M2AnimationTrack};
use crate::chunks::infrastructure::ChunkReader;
use crate::chunks::particle_emitter::M2ParticleEmitter;
use crate::chunks::texture_animation::M2TextureAnimation;
use crate::common::M2Parse;
use crate::error::Result;
use crate::io_ext::{ReadExt, WriteExt};
use std::io::{Read, Seek, Write};
fn create_empty_animation_block<T: M2Parse>() -> M2AnimationBlock<T> {
let track = M2AnimationTrack::default();
M2AnimationBlock::new(track)
}
#[derive(Debug, Clone)]
pub struct ExtendedParticleData {
pub version: u8,
pub enhanced_emitters: Vec<EnhancedEmitter>,
pub particle_systems: Vec<AdvancedParticleSystem>,
}
impl ExtendedParticleData {
pub fn parse_expt<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let version = 1;
let mut enhanced_emitters = Vec::new();
let mut particle_systems = Vec::new();
while !reader.is_at_end()? {
let emitter_type = reader.read_u8()?;
match emitter_type {
0 => {
let enhanced = EnhancedEmitter::parse(reader)?;
enhanced_emitters.push(enhanced);
}
1 => {
let system = AdvancedParticleSystem::parse(reader)?;
particle_systems.push(system);
}
_ => {
let skip_size = reader.read_u32_le()?;
let mut skip_buffer = vec![0u8; skip_size as usize];
reader.read_exact(&mut skip_buffer)?;
}
}
}
Ok(Self {
version,
enhanced_emitters,
particle_systems,
})
}
pub fn parse_exp2<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let version = 2;
let mut enhanced_emitters = Vec::new();
let mut particle_systems = Vec::new();
let emitter_count = reader.read_u32_le()?;
let system_count = reader.read_u32_le()?;
for _ in 0..emitter_count {
let enhanced = EnhancedEmitter::parse_v2(reader)?;
enhanced_emitters.push(enhanced);
}
for _ in 0..system_count {
let system = AdvancedParticleSystem::parse_v2(reader)?;
particle_systems.push(system);
}
Ok(Self {
version,
enhanced_emitters,
particle_systems,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
if self.version == 1 {
for emitter in &self.enhanced_emitters {
writer.write_u8(0)?; emitter.write(writer)?;
}
for system in &self.particle_systems {
writer.write_u8(1)?; system.write(writer)?;
}
} else {
writer.write_u32_le(self.enhanced_emitters.len() as u32)?;
writer.write_u32_le(self.particle_systems.len() as u32)?;
for emitter in &self.enhanced_emitters {
emitter.write_v2(writer)?;
}
for system in &self.particle_systems {
system.write_v2(writer)?;
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct EnhancedEmitter {
pub base_emitter: M2ParticleEmitter,
pub extended_properties: ExtendedEmitterProperties,
}
impl EnhancedEmitter {
pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
let extended_properties = ExtendedEmitterProperties {
enhanced_blending_mode: reader.read_u8()?,
particle_sorting_mode: reader.read_u8()?,
texture_scaling_factor: reader.read_f32_le()?,
advanced_physics_enabled: reader.read_u8()? != 0,
collision_detection_enabled: reader.read_u8()? != 0,
wind_influence_factor: reader.read_f32_le()?,
};
let base_emitter = M2ParticleEmitter {
id: 0,
flags: crate::chunks::particle_emitter::M2ParticleFlags::empty(),
position: crate::common::C3Vector {
x: 0.0,
y: 0.0,
z: 0.0,
},
bone_index: 0,
texture_index: 0,
model_filename: crate::common::M2Array::new(0, 0),
parent_emitter: 0,
geometry_model_unknown: 0,
fallback_model_filename: None,
blending_type: 0,
emitter_type: crate::chunks::particle_emitter::M2ParticleEmitterType::Point,
particle_type: 0,
head_or_tail: 0,
texture_file_data_ids: None,
texture_tile_coordinates: crate::common::M2Array::new(0, 0),
enable_encryption: None,
multi_texture_param0: None,
multi_texture_param1: None,
lifetime: 0.0,
emission_rate: 0.0,
emission_area_length: 0.0,
emission_area_width: 0.0,
emission_velocity: 0.0,
min_lifetime: 0.0,
max_lifetime: 0.0,
min_emission_rate: 0.0,
max_emission_rate: 0.0,
min_emission_area_length: 0.0,
max_emission_area_length: 0.0,
min_emission_area_width: 0.0,
max_emission_area_width: 0.0,
min_emission_velocity: 0.0,
max_emission_velocity: 0.0,
position_variation: 0.0,
min_position_variation: 0.0,
max_position_variation: 0.0,
initial_size: 0.0,
min_initial_size: 0.0,
max_initial_size: 0.0,
size_variation: 0.0,
min_size_variation: 0.0,
max_size_variation: 0.0,
horizontal_range: 0.0,
min_horizontal_range: 0.0,
max_horizontal_range: 0.0,
vertical_range: 0.0,
min_vertical_range: 0.0,
max_vertical_range: 0.0,
gravity: 0.0,
min_gravity: 0.0,
max_gravity: 0.0,
initial_velocity: 0.0,
min_initial_velocity: 0.0,
max_initial_velocity: 0.0,
speed_variation: 0.0,
min_speed_variation: 0.0,
max_speed_variation: 0.0,
rotation_speed: 0.0,
min_rotation_speed: 0.0,
max_rotation_speed: 0.0,
initial_rotation: 0.0,
min_initial_rotation: 0.0,
max_initial_rotation: 0.0,
mid_point_color: crate::chunks::color_animation::M2Color::transparent(),
color_animation_speed: 0.0,
color_median_time: 0.0,
lifespan_unused: 0.0,
emission_rate_unused: 0.0,
unknown_1: 0,
unknown_2: 0.0,
emission_speed_animation: create_empty_animation_block(),
emission_rate_animation: create_empty_animation_block(),
emission_area_animation: create_empty_animation_block(),
xy_scale_animation: create_empty_animation_block(),
z_scale_animation: create_empty_animation_block(),
color_animation: create_empty_animation_block(),
transparency_animation: create_empty_animation_block(),
size_animation: create_empty_animation_block(),
intensity_animation: create_empty_animation_block(),
z_source_animation: create_empty_animation_block(),
particle_initial_state: None,
particle_initial_state_variation: None,
particle_convergence_time: None,
physics_parameters: None,
};
Ok(Self {
base_emitter,
extended_properties,
})
}
pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
let mut emitter = Self::parse(reader)?;
emitter.extended_properties.advanced_physics_enabled = true;
emitter.extended_properties.collision_detection_enabled = reader.read_u8()? != 0;
Ok(emitter)
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u8(self.extended_properties.enhanced_blending_mode)?;
writer.write_u8(self.extended_properties.particle_sorting_mode)?;
writer.write_f32_le(self.extended_properties.texture_scaling_factor)?;
writer.write_u8(if self.extended_properties.advanced_physics_enabled {
1
} else {
0
})?;
writer.write_u8(if self.extended_properties.collision_detection_enabled {
1
} else {
0
})?;
writer.write_f32_le(self.extended_properties.wind_influence_factor)?;
Ok(())
}
pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
self.write(writer)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ExtendedEmitterProperties {
pub enhanced_blending_mode: u8,
pub particle_sorting_mode: u8,
pub texture_scaling_factor: f32,
pub advanced_physics_enabled: bool,
pub collision_detection_enabled: bool,
pub wind_influence_factor: f32,
}
#[derive(Debug, Clone)]
pub struct AdvancedParticleSystem {
pub system_id: u32,
pub max_particles: u32,
pub spawn_pattern: u8,
pub physics_properties: ParticlePhysicsProperties,
}
impl AdvancedParticleSystem {
pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
let system_id = reader.read_u32_le()?;
let max_particles = reader.read_u32_le()?;
let spawn_pattern = reader.read_u8()?;
let physics_properties = ParticlePhysicsProperties {
air_resistance: reader.read_f32_le()?,
bounce_factor: reader.read_f32_le()?,
friction_coefficient: reader.read_f32_le()?,
};
Ok(Self {
system_id,
max_particles,
spawn_pattern,
physics_properties,
})
}
pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
let mut system = Self::parse(reader)?;
system.physics_properties.air_resistance = reader.read_f32_le()?;
Ok(system)
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32_le(self.system_id)?;
writer.write_u32_le(self.max_particles)?;
writer.write_u8(self.spawn_pattern)?;
writer.write_f32_le(self.physics_properties.air_resistance)?;
writer.write_f32_le(self.physics_properties.bounce_factor)?;
writer.write_f32_le(self.physics_properties.friction_coefficient)?;
Ok(())
}
pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
self.write(writer)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ParticlePhysicsProperties {
pub air_resistance: f32,
pub bounce_factor: f32,
pub friction_coefficient: f32,
}
#[derive(Debug, Clone)]
pub struct ParentAnimationBlacklist {
pub blacklisted_sequences: Vec<u16>,
}
impl ParentAnimationBlacklist {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let count = reader.chunk_size() / 2; let mut blacklisted_sequences = Vec::with_capacity(count as usize);
for _ in 0..count {
blacklisted_sequences.push(reader.read_u16_le()?);
}
Ok(Self {
blacklisted_sequences,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
for &sequence_id in &self.blacklisted_sequences {
writer.write_u16_le(sequence_id)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ParentAnimationData {
pub texture_weights: Vec<TextureWeight>,
pub blending_modes: Vec<BlendMode>,
}
impl ParentAnimationData {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let weight_count = reader.read_u32_le()?;
let mut texture_weights = Vec::with_capacity(weight_count as usize);
for _ in 0..weight_count {
let weight = TextureWeight {
texture_index: reader.read_u16_le()?,
weight_factor: reader.read_f32_le()?,
blend_operation: reader.read_u8()?,
};
texture_weights.push(weight);
}
let mode_count = reader.read_u32_le()?;
let mut blending_modes = Vec::with_capacity(mode_count as usize);
for _ in 0..mode_count {
let mode = BlendMode {
source_blend: reader.read_u8()?,
dest_blend: reader.read_u8()?,
alpha_test_threshold: reader.read_f32_le()?,
};
blending_modes.push(mode);
}
Ok(Self {
texture_weights,
blending_modes,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32_le(self.texture_weights.len() as u32)?;
for weight in &self.texture_weights {
writer.write_u16_le(weight.texture_index)?;
writer.write_f32_le(weight.weight_factor)?;
writer.write_u8(weight.blend_operation)?;
}
writer.write_u32_le(self.blending_modes.len() as u32)?;
for mode in &self.blending_modes {
writer.write_u8(mode.source_blend)?;
writer.write_u8(mode.dest_blend)?;
writer.write_f32_le(mode.alpha_test_threshold)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TextureWeight {
pub texture_index: u16,
pub weight_factor: f32,
pub blend_operation: u8,
}
#[derive(Debug, Clone)]
pub struct BlendMode {
pub source_blend: u8,
pub dest_blend: u8,
pub alpha_test_threshold: f32,
}
#[derive(Debug, Clone)]
pub struct WaterfallEffect {
pub version: u8,
pub parameters: WaterfallParameters,
}
impl WaterfallEffect {
pub fn parse<R: Read + std::io::Seek>(
reader: &mut ChunkReader<R>,
version: u8,
) -> Result<Self> {
let parameters = match version {
1 => WaterfallParameters::parse_v1(reader)?,
2 => WaterfallParameters::parse_v2(reader)?,
3 => WaterfallParameters::parse_v3(reader)?,
_ => {
return Err(crate::error::M2Error::ParseError(format!(
"Unsupported waterfall effect version: {}",
version
)));
}
};
Ok(Self {
version,
parameters,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
match self.version {
1 => self.parameters.write_v1(writer),
2 => self.parameters.write_v2(writer),
3 => self.parameters.write_v3(writer),
_ => Err(crate::error::M2Error::ParseError(format!(
"Unsupported waterfall effect version: {}",
self.version
))),
}
}
}
#[derive(Debug, Clone)]
pub struct WaterfallParameters {
pub flow_velocity: f32,
pub turbulence: f32,
pub foam_intensity: f32,
pub additional_params: Vec<f32>,
}
impl WaterfallParameters {
pub fn parse_v1<R: Read>(reader: &mut R) -> Result<Self> {
let flow_velocity = reader.read_f32_le()?;
let turbulence = reader.read_f32_le()?;
let foam_intensity = reader.read_f32_le()?;
Ok(Self {
flow_velocity,
turbulence,
foam_intensity,
additional_params: Vec::new(),
})
}
pub fn parse_v2<R: Read>(reader: &mut R) -> Result<Self> {
let mut params = Self::parse_v1(reader)?;
params.additional_params.push(reader.read_f32_le()?); params.additional_params.push(reader.read_f32_le()?);
Ok(params)
}
pub fn parse_v3<R: Read>(reader: &mut R) -> Result<Self> {
let mut params = Self::parse_v2(reader)?;
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)
}
pub fn write_v1<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_f32_le(self.flow_velocity)?;
writer.write_f32_le(self.turbulence)?;
writer.write_f32_le(self.foam_intensity)?;
Ok(())
}
pub fn write_v2<W: Write>(&self, writer: &mut W) -> Result<()> {
self.write_v1(writer)?;
if self.additional_params.len() >= 2 {
writer.write_f32_le(self.additional_params[0])?; writer.write_f32_le(self.additional_params[1])?; } else {
writer.write_f32_le(0.0)?;
writer.write_f32_le(0.0)?;
}
Ok(())
}
pub fn write_v3<W: Write>(&self, writer: &mut W) -> Result<()> {
self.write_v2(writer)?;
if self.additional_params.len() >= 5 {
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 {
writer.write_f32_le(0.0)?;
writer.write_f32_le(0.0)?;
writer.write_f32_le(1.0)?; }
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct EdgeFadeData {
pub fade_distances: Vec<f32>,
pub fade_factors: Vec<f32>,
}
impl EdgeFadeData {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let distance_count = reader.read_u32_le()?;
let mut fade_distances = Vec::with_capacity(distance_count as usize);
for _ in 0..distance_count {
fade_distances.push(reader.read_f32_le()?);
}
let factor_count = reader.read_u32_le()?;
let mut fade_factors = Vec::with_capacity(factor_count as usize);
for _ in 0..factor_count {
fade_factors.push(reader.read_f32_le()?);
}
Ok(Self {
fade_distances,
fade_factors,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32_le(self.fade_distances.len() as u32)?;
for &distance in &self.fade_distances {
writer.write_f32_le(distance)?;
}
writer.write_u32_le(self.fade_factors.len() as u32)?;
for &factor in &self.fade_factors {
writer.write_f32_le(factor)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ModelAlphaData {
pub alpha_test_threshold: f32,
pub blend_mode: AlphaBlendMode,
}
impl ModelAlphaData {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let alpha_test_threshold = reader.read_f32_le()?;
let blend_mode_value = reader.read_u8()?;
let blend_mode =
AlphaBlendMode::from_u8(blend_mode_value).unwrap_or(AlphaBlendMode::Normal);
Ok(Self {
alpha_test_threshold,
blend_mode,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_f32_le(self.alpha_test_threshold)?;
writer.write_u8(self.blend_mode as u8)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AlphaBlendMode {
Normal = 0,
Additive = 1,
Multiplicative = 2,
AlphaTest = 3,
}
impl AlphaBlendMode {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Normal),
1 => Some(Self::Additive),
2 => Some(Self::Multiplicative),
3 => Some(Self::AlphaTest),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct LightingDetails {
pub ambient_factor: f32,
pub diffuse_factor: f32,
pub specular_factor: f32,
}
impl LightingDetails {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let ambient_factor = reader.read_f32_le()?;
let diffuse_factor = reader.read_f32_le()?;
let specular_factor = reader.read_f32_le()?;
Ok(Self {
ambient_factor,
diffuse_factor,
specular_factor,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_f32_le(self.ambient_factor)?;
writer.write_f32_le(self.diffuse_factor)?;
writer.write_f32_le(self.specular_factor)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct RecursiveParticleIds {
pub model_ids: Vec<u32>,
}
impl RecursiveParticleIds {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let count = reader.chunk_size() / 4; let mut model_ids = Vec::with_capacity(count as usize);
for _ in 0..count {
model_ids.push(reader.read_u32_le()?);
}
Ok(Self { model_ids })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
for &id in &self.model_ids {
writer.write_u32_le(id)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct GeometryParticleIds {
pub model_ids: Vec<u32>,
}
impl GeometryParticleIds {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let count = reader.chunk_size() / 4; let mut model_ids = Vec::with_capacity(count as usize);
for _ in 0..count {
model_ids.push(reader.read_u32_le()?);
}
Ok(Self { model_ids })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
for &id in &self.model_ids {
writer.write_u32_le(id)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TextureAnimationChunk {
pub texture_animations: Vec<ExtendedTextureAnimation>,
}
impl TextureAnimationChunk {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let count = reader.read_u32_le()?;
let mut texture_animations = Vec::with_capacity(count as usize);
for _ in 0..count {
let extended_anim = ExtendedTextureAnimation::parse(reader)?;
texture_animations.push(extended_anim);
}
Ok(Self { texture_animations })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32_le(self.texture_animations.len() as u32)?;
for anim in &self.texture_animations {
anim.write(writer)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ExtendedTextureAnimation {
pub base_animation: M2TextureAnimation,
pub extended_properties: ExtendedAnimationProperties,
}
impl ExtendedTextureAnimation {
pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let base_animation = M2TextureAnimation::parse(reader)?;
let extended_properties = ExtendedAnimationProperties {
flow_direction: [
reader.read_f32_le()?,
reader.read_f32_le()?,
reader.read_f32_le()?,
],
speed_multiplier: reader.read_f32_le()?,
turbulence_factor: reader.read_f32_le()?,
animation_mode: ExtendedAnimationMode::from_u8(reader.read_u8()?)?,
loop_behavior: LoopBehavior::from_u8(reader.read_u8()?)?,
blend_mode: TextureBlendMode::from_u8(reader.read_u8()?)?,
_padding: reader.read_u8()?, };
Ok(Self {
base_animation,
extended_properties,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
self.base_animation.write(writer)?;
writer.write_f32_le(self.extended_properties.flow_direction[0])?;
writer.write_f32_le(self.extended_properties.flow_direction[1])?;
writer.write_f32_le(self.extended_properties.flow_direction[2])?;
writer.write_f32_le(self.extended_properties.speed_multiplier)?;
writer.write_f32_le(self.extended_properties.turbulence_factor)?;
writer.write_u8(self.extended_properties.animation_mode as u8)?;
writer.write_u8(self.extended_properties.loop_behavior as u8)?;
writer.write_u8(self.extended_properties.blend_mode as u8)?;
writer.write_u8(self.extended_properties._padding)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ExtendedAnimationProperties {
pub flow_direction: [f32; 3],
pub speed_multiplier: f32,
pub turbulence_factor: f32,
pub animation_mode: ExtendedAnimationMode,
pub loop_behavior: LoopBehavior,
pub blend_mode: TextureBlendMode,
pub _padding: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtendedAnimationMode {
StandardScroll = 0,
FlowingLiquid = 1,
TurbulentFlow = 2,
Vortex = 3,
Wave = 4,
}
impl ExtendedAnimationMode {
pub fn from_u8(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::StandardScroll),
1 => Ok(Self::FlowingLiquid),
2 => Ok(Self::TurbulentFlow),
3 => Ok(Self::Vortex),
4 => Ok(Self::Wave),
_ => Err(crate::error::M2Error::ParseError(format!(
"Unknown extended animation mode: {}",
value
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopBehavior {
Infinite = 0,
Once = 1,
PingPong = 2,
Reverse = 3,
}
impl LoopBehavior {
pub fn from_u8(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::Infinite),
1 => Ok(Self::Once),
2 => Ok(Self::PingPong),
3 => Ok(Self::Reverse),
_ => Err(crate::error::M2Error::ParseError(format!(
"Unknown loop behavior: {}",
value
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextureBlendMode {
Normal = 0,
Additive = 1,
Multiply = 2,
Screen = 3,
Overlay = 4,
}
impl TextureBlendMode {
pub fn from_u8(value: u8) -> Result<Self> {
match value {
0 => Ok(Self::Normal),
1 => Ok(Self::Additive),
2 => Ok(Self::Multiply),
3 => Ok(Self::Screen),
4 => Ok(Self::Overlay),
_ => Err(crate::error::M2Error::ParseError(format!(
"Unknown texture blend mode: {}",
value
))),
}
}
}
#[derive(Debug, Clone)]
pub struct ParticleGeosetData {
pub geoset_assignments: Vec<ParticleGeosetEntry>,
}
impl ParticleGeosetData {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let count = reader.chunk_size() / 2; let mut geoset_assignments = Vec::with_capacity(count as usize);
for _ in 0..count {
let geoset = reader.read_u16_le()?;
geoset_assignments.push(ParticleGeosetEntry { geoset });
}
Ok(Self { geoset_assignments })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
for entry in &self.geoset_assignments {
writer.write_u16_le(entry.geoset)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ParticleGeosetEntry {
pub geoset: u16,
}
#[derive(Debug, Clone)]
pub struct DbocChunk {
pub data: Vec<u8>,
}
impl DbocChunk {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let mut data = vec![0u8; reader.chunk_size() as usize];
reader.read_exact(&mut data)?;
Ok(Self { data })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&self.data)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AfraChunk {
pub data: Vec<u8>,
}
impl AfraChunk {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let mut data = vec![0u8; reader.chunk_size() as usize];
reader.read_exact(&mut data)?;
Ok(Self { data })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&self.data)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct DpivChunk {
pub vertex_pos_count: u32,
pub vertex_pos_offset: u32,
pub face_norm_count: u32,
pub face_norm_offset: u32,
pub index_count: u32,
pub index_offset: u32,
pub flags_count: u32,
pub flags_offset: u32,
pub vertex_positions: Vec<[f32; 3]>,
pub face_normals: Vec<[f32; 3]>,
pub indices: Vec<u16>,
pub flags: Vec<u16>,
}
impl DpivChunk {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let chunk_start = reader.current_position()?;
let vertex_pos_count = reader.read_u32_le()?;
let vertex_pos_offset = reader.read_u32_le()?;
let face_norm_count = reader.read_u32_le()?;
let face_norm_offset = reader.read_u32_le()?;
let index_count = reader.read_u32_le()?;
let index_offset = reader.read_u32_le()?;
let flags_count = reader.read_u32_le()?;
let flags_offset = reader.read_u32_le()?;
reader.seek_to_position(chunk_start + vertex_pos_offset as u64)?;
let mut vertex_positions = Vec::with_capacity(vertex_pos_count as usize);
for _ in 0..vertex_pos_count {
let pos = [
reader.read_f32_le()?,
reader.read_f32_le()?,
reader.read_f32_le()?,
];
vertex_positions.push(pos);
}
reader.seek_to_position(chunk_start + face_norm_offset as u64)?;
let mut face_normals = Vec::with_capacity(face_norm_count as usize);
for _ in 0..face_norm_count {
let normal = [
reader.read_f32_le()?,
reader.read_f32_le()?,
reader.read_f32_le()?,
];
face_normals.push(normal);
}
reader.seek_to_position(chunk_start + index_offset as u64)?;
let mut indices = Vec::with_capacity(index_count as usize);
for _ in 0..index_count {
indices.push(reader.read_u16_le()?);
}
reader.seek_to_position(chunk_start + flags_offset as u64)?;
let mut flags = Vec::with_capacity(flags_count as usize);
for _ in 0..flags_count {
flags.push(reader.read_u16_le()?);
}
Ok(Self {
vertex_pos_count,
vertex_pos_offset,
face_norm_count,
face_norm_offset,
index_count,
index_offset,
flags_count,
flags_offset,
vertex_positions,
face_normals,
indices,
flags,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32_le(self.vertex_pos_count)?;
writer.write_u32_le(self.vertex_pos_offset)?;
writer.write_u32_le(self.face_norm_count)?;
writer.write_u32_le(self.face_norm_offset)?;
writer.write_u32_le(self.index_count)?;
writer.write_u32_le(self.index_offset)?;
writer.write_u32_le(self.flags_count)?;
writer.write_u32_le(self.flags_offset)?;
for pos in &self.vertex_positions {
writer.write_f32_le(pos[0])?;
writer.write_f32_le(pos[1])?;
writer.write_f32_le(pos[2])?;
}
for normal in &self.face_normals {
writer.write_f32_le(normal[0])?;
writer.write_f32_le(normal[1])?;
writer.write_f32_le(normal[2])?;
}
for &index in &self.indices {
writer.write_u16_le(index)?;
}
for &flag in &self.flags {
writer.write_u16_le(flag)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ParentSequenceBounds {
pub sequence_bounds: Vec<SequenceBounds>,
}
#[derive(Debug, Clone)]
pub struct SequenceBounds {
pub min_bounds: [f32; 3],
pub max_bounds: [f32; 3],
pub radius: f32,
}
impl ParentSequenceBounds {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let mut sequence_bounds = Vec::new();
while !reader.is_at_end()? {
let min_bounds = [
reader.read_f32_le()?,
reader.read_f32_le()?,
reader.read_f32_le()?,
];
let max_bounds = [
reader.read_f32_le()?,
reader.read_f32_le()?,
reader.read_f32_le()?,
];
let radius = reader.read_f32_le()?;
sequence_bounds.push(SequenceBounds {
min_bounds,
max_bounds,
radius,
});
}
Ok(Self { sequence_bounds })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
for bounds in &self.sequence_bounds {
writer.write_f32_le(bounds.min_bounds[0])?;
writer.write_f32_le(bounds.min_bounds[1])?;
writer.write_f32_le(bounds.min_bounds[2])?;
writer.write_f32_le(bounds.max_bounds[0])?;
writer.write_f32_le(bounds.max_bounds[1])?;
writer.write_f32_le(bounds.max_bounds[2])?;
writer.write_f32_le(bounds.radius)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ParentEventData {
pub event_entries: Vec<ParentEventEntry>,
}
#[derive(Debug, Clone)]
pub struct ParentEventEntry {
pub event_id: u32,
pub data: Vec<u8>,
pub timestamp: u32,
}
impl ParentEventData {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let mut event_entries = Vec::new();
while !reader.is_at_end()? {
let event_id = reader.read_u32_le()?;
let data_size = reader.read_u32_le()?;
let timestamp = reader.read_u32_le()?;
let mut data = vec![0u8; data_size as usize];
reader.read_exact(&mut data)?;
event_entries.push(ParentEventEntry {
event_id,
data,
timestamp,
});
}
Ok(Self { event_entries })
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
for entry in &self.event_entries {
writer.write_u32_le(entry.event_id)?;
writer.write_u32_le(entry.data.len() as u32)?;
writer.write_u32_le(entry.timestamp)?;
writer.write_all(&entry.data)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct CollisionMeshData {
pub vertices: Vec<[f32; 3]>,
pub faces: Vec<CollisionFace>,
pub materials: Vec<CollisionMaterial>,
}
#[derive(Debug, Clone)]
pub struct CollisionFace {
pub indices: [u16; 3],
pub material_index: u16,
}
#[derive(Debug, Clone)]
pub struct CollisionMaterial {
pub flags: u32,
pub friction: f32,
pub restitution: f32,
}
impl CollisionMeshData {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let vertex_count = reader.read_u32_le()?;
let face_count = reader.read_u32_le()?;
let material_count = reader.read_u32_le()?;
let mut vertices = Vec::with_capacity(vertex_count as usize);
for _ in 0..vertex_count {
vertices.push([
reader.read_f32_le()?,
reader.read_f32_le()?,
reader.read_f32_le()?,
]);
}
let mut faces = Vec::with_capacity(face_count as usize);
for _ in 0..face_count {
faces.push(CollisionFace {
indices: [
reader.read_u16_le()?,
reader.read_u16_le()?,
reader.read_u16_le()?,
],
material_index: reader.read_u16_le()?,
});
}
let mut materials = Vec::with_capacity(material_count as usize);
for _ in 0..material_count {
materials.push(CollisionMaterial {
flags: reader.read_u32_le()?,
friction: reader.read_f32_le()?,
restitution: reader.read_f32_le()?,
});
}
Ok(Self {
vertices,
faces,
materials,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32_le(self.vertices.len() as u32)?;
writer.write_u32_le(self.faces.len() as u32)?;
writer.write_u32_le(self.materials.len() as u32)?;
for vertex in &self.vertices {
writer.write_f32_le(vertex[0])?;
writer.write_f32_le(vertex[1])?;
writer.write_f32_le(vertex[2])?;
}
for face in &self.faces {
writer.write_u16_le(face.indices[0])?;
writer.write_u16_le(face.indices[1])?;
writer.write_u16_le(face.indices[2])?;
writer.write_u16_le(face.material_index)?;
}
for material in &self.materials {
writer.write_u32_le(material.flags)?;
writer.write_f32_le(material.friction)?;
writer.write_f32_le(material.restitution)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PhysicsFileDataChunk {
pub physics_data: Vec<u8>,
pub properties: PhysicsProperties,
}
#[derive(Debug, Clone)]
pub struct PhysicsProperties {
pub mass: f32,
pub center_of_mass: [f32; 3],
pub inertia_tensor: [f32; 9],
pub flags: u32,
}
impl PhysicsFileDataChunk {
pub fn parse<R: Read + std::io::Seek>(reader: &mut ChunkReader<R>) -> Result<Self> {
let mass = reader.read_f32_le()?;
let center_of_mass = [
reader.read_f32_le()?,
reader.read_f32_le()?,
reader.read_f32_le()?,
];
let mut inertia_tensor = [0.0f32; 9];
for item in &mut inertia_tensor {
*item = reader.read_f32_le()?;
}
let flags = reader.read_u32_le()?;
let mut physics_data = Vec::new();
reader.read_to_end(&mut physics_data)?;
Ok(Self {
physics_data,
properties: PhysicsProperties {
mass,
center_of_mass,
inertia_tensor,
flags,
},
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_f32_le(self.properties.mass)?;
writer.write_f32_le(self.properties.center_of_mass[0])?;
writer.write_f32_le(self.properties.center_of_mass[1])?;
writer.write_f32_le(self.properties.center_of_mass[2])?;
for &tensor_val in &self.properties.inertia_tensor {
writer.write_f32_le(tensor_val)?;
}
writer.write_u32_le(self.properties.flags)?;
writer.write_all(&self.physics_data)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunks::infrastructure::{ChunkHeader, ChunkReader};
use std::io::Cursor;
#[test]
fn test_parent_animation_blacklist() {
let data = vec![
0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, ];
let header = ChunkHeader {
magic: *b"PABC",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let blacklist = ParentAnimationBlacklist::parse(&mut chunk_reader).unwrap();
assert_eq!(blacklist.blacklisted_sequences, vec![1, 5, 10]);
}
#[test]
fn test_waterfall_effect_v1() {
let data = vec![
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x40, 0x3F, ];
let header = ChunkHeader {
magic: *b"WFV1",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let effect = WaterfallEffect::parse(&mut chunk_reader, 1).unwrap();
assert_eq!(effect.version, 1);
assert_eq!(effect.parameters.flow_velocity, 1.0);
assert!(effect.parameters.additional_params.is_empty());
}
#[test]
fn test_edge_fade_data() {
let mut data = Vec::new();
data.extend_from_slice(&2u32.to_le_bytes());
data.extend_from_slice(&10.0f32.to_le_bytes());
data.extend_from_slice(&20.0f32.to_le_bytes());
data.extend_from_slice(&2u32.to_le_bytes());
data.extend_from_slice(&0.5f32.to_le_bytes());
data.extend_from_slice(&0.8f32.to_le_bytes());
let header = ChunkHeader {
magic: *b"EDGF",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let fade_data = EdgeFadeData::parse(&mut chunk_reader).unwrap();
assert_eq!(fade_data.fade_distances, vec![10.0, 20.0]);
assert_eq!(fade_data.fade_factors, vec![0.5, 0.8]);
}
#[test]
fn test_alpha_blend_mode_conversion() {
assert_eq!(AlphaBlendMode::from_u8(0), Some(AlphaBlendMode::Normal));
assert_eq!(AlphaBlendMode::from_u8(1), Some(AlphaBlendMode::Additive));
assert_eq!(
AlphaBlendMode::from_u8(2),
Some(AlphaBlendMode::Multiplicative)
);
assert_eq!(AlphaBlendMode::from_u8(3), Some(AlphaBlendMode::AlphaTest));
assert_eq!(AlphaBlendMode::from_u8(99), None);
}
#[test]
fn test_particle_model_ids() {
let data = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, ];
let header = ChunkHeader {
magic: *b"RPID",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let rpid = RecursiveParticleIds::parse(&mut chunk_reader).unwrap();
assert_eq!(rpid.model_ids.len(), 2);
assert_eq!(rpid.model_ids[0], 0x04030201); assert_eq!(rpid.model_ids[1], 0x08070605);
}
#[test]
fn test_particle_geoset_data() {
let data = vec![
0x01, 0x00, 0x05, 0x00, 0x0A, 0x00, ];
let header = ChunkHeader {
magic: *b"PGD1",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let pgd1 = ParticleGeosetData::parse(&mut chunk_reader).unwrap();
assert_eq!(pgd1.geoset_assignments.len(), 3);
assert_eq!(pgd1.geoset_assignments[0].geoset, 1);
assert_eq!(pgd1.geoset_assignments[1].geoset, 5);
assert_eq!(pgd1.geoset_assignments[2].geoset, 10);
}
#[test]
fn test_parent_sequence_bounds() {
let mut data = Vec::new();
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 {
magic: *b"PSBC",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let psbc = ParentSequenceBounds::parse(&mut chunk_reader).unwrap();
assert_eq!(psbc.sequence_bounds.len(), 1);
assert_eq!(psbc.sequence_bounds[0].min_bounds, [-10.0, -5.0, -2.0]);
assert_eq!(psbc.sequence_bounds[0].max_bounds, [10.0, 5.0, 2.0]);
assert_eq!(psbc.sequence_bounds[0].radius, 15.0);
}
#[test]
fn test_parent_event_data() {
let mut data = Vec::new();
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 {
magic: *b"PEDC",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let pedc = ParentEventData::parse(&mut chunk_reader).unwrap();
assert_eq!(pedc.event_entries.len(), 1);
assert_eq!(pedc.event_entries[0].event_id, 1);
assert_eq!(pedc.event_entries[0].timestamp, 1000);
assert_eq!(pedc.event_entries[0].data, vec![0x01, 0x02, 0x03, 0x04]);
}
#[test]
fn test_collision_mesh_data() {
let mut data = Vec::new();
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());
data.extend_from_slice(&2.0f32.to_le_bytes());
data.extend_from_slice(&3.0f32.to_le_bytes());
data.extend_from_slice(&0u16.to_le_bytes());
data.extend_from_slice(&1u16.to_le_bytes());
data.extend_from_slice(&2u16.to_le_bytes());
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 {
magic: *b"PCOL",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let pcol = CollisionMeshData::parse(&mut chunk_reader).unwrap();
assert_eq!(pcol.vertices.len(), 1);
assert_eq!(pcol.faces.len(), 1);
assert_eq!(pcol.materials.len(), 1);
assert_eq!(pcol.vertices[0], [1.0, 2.0, 3.0]);
assert_eq!(pcol.materials[0].friction, 0.5);
}
#[test]
fn test_texture_animation_chunk() {
let mut data = Vec::new();
data.extend_from_slice(&1u32.to_le_bytes());
data.extend_from_slice(&1u16.to_le_bytes()); data.extend_from_slice(&0u16.to_le_bytes());
for _ in 0..5 {
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()); }
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 {
magic: *b"TXAC",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let txac = TextureAnimationChunk::parse(&mut chunk_reader).unwrap();
assert_eq!(txac.texture_animations.len(), 1);
assert_eq!(
txac.texture_animations[0]
.extended_properties
.animation_mode,
ExtendedAnimationMode::FlowingLiquid
);
assert_eq!(
txac.texture_animations[0]
.extended_properties
.speed_multiplier,
1.5
);
}
#[test]
fn test_dboc_chunk() {
let data = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10,
];
let header = ChunkHeader {
magic: *b"DBOC",
size: data.len() as u32,
};
let cursor = Cursor::new(data.clone());
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let dboc = DbocChunk::parse(&mut chunk_reader).unwrap();
assert_eq!(dboc.data, data);
}
#[test]
fn test_extended_animation_mode_conversion() {
assert_eq!(
ExtendedAnimationMode::from_u8(0).unwrap(),
ExtendedAnimationMode::StandardScroll
);
assert_eq!(
ExtendedAnimationMode::from_u8(1).unwrap(),
ExtendedAnimationMode::FlowingLiquid
);
assert_eq!(
ExtendedAnimationMode::from_u8(4).unwrap(),
ExtendedAnimationMode::Wave
);
assert!(ExtendedAnimationMode::from_u8(99).is_err());
}
#[test]
fn test_physics_file_data_chunk() {
let mut data = Vec::new();
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 {
data.extend_from_slice(&((i + 1) as f32).to_le_bytes());
}
data.extend_from_slice(&0x12345678u32.to_le_bytes());
data.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
let header = ChunkHeader {
magic: *b"PFDC",
size: data.len() as u32,
};
let cursor = Cursor::new(data);
let mut chunk_reader = ChunkReader::new(cursor, header).unwrap();
let pfdc = PhysicsFileDataChunk::parse(&mut chunk_reader).unwrap();
assert_eq!(pfdc.properties.mass, 10.0);
assert_eq!(pfdc.properties.center_of_mass, [1.0, 2.0, 3.0]);
assert_eq!(pfdc.properties.flags, 0x12345678);
assert_eq!(pfdc.physics_data, vec![0xAA, 0xBB, 0xCC, 0xDD]);
}
}