1use crate::io_ext::{ReadExt, WriteExt};
2use std::io::{Read, Write};
3
4use crate::common::C3Vector;
5use crate::error::Result;
6use crate::version::M2Version;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum M2PhysicsShapeType {
11 None = 0,
13 Sphere = 1,
15 Capsule = 2,
17 Plane = 3,
19 Box = 4,
21}
22
23impl M2PhysicsShapeType {
24 pub fn from_u8(value: u8) -> Option<Self> {
26 match value {
27 0 => Some(Self::None),
28 1 => Some(Self::Sphere),
29 2 => Some(Self::Capsule),
30 3 => Some(Self::Plane),
31 4 => Some(Self::Box),
32 _ => None,
33 }
34 }
35}
36
37bitflags::bitflags! {
38 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
40 pub struct M2PhysicsFlags: u16 {
41 const GENERAL_COLLISION = 0x1;
43 const COLLISION_TRIGGER = 0x2;
45 const UNKNOWN_WOD_1 = 0x4;
47 const UNKNOWN_WOD_2 = 0x8;
49 const UNKNOWN_WOD_3 = 0x10;
51 const ANIMATED_BY_PHYSICS = 0x20;
53 const UNKNOWN_0x40 = 0x40;
55 const PRECISE_COLLISION = 0x80;
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct M2PhysicsJoint {
63 pub body1: u32,
65 pub body2: u32,
67 pub joint_type: u32,
69 pub position: C3Vector,
71 pub orientation: C3Vector,
73 pub lower_limits: C3Vector,
75 pub upper_limits: C3Vector,
77 pub spring_coefficients: C3Vector,
79 pub damping_coefficients: C3Vector,
81}
82
83impl M2PhysicsJoint {
84 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
86 let body1 = reader.read_u32_le()?;
87 let body2 = reader.read_u32_le()?;
88 let joint_type = reader.read_u32_le()?;
89
90 let position = C3Vector::parse(reader)?;
91 let orientation = C3Vector::parse(reader)?;
92 let lower_limits = C3Vector::parse(reader)?;
93 let upper_limits = C3Vector::parse(reader)?;
94 let spring_coefficients = C3Vector::parse(reader)?;
95 let damping_coefficients = C3Vector::parse(reader)?;
96
97 Ok(Self {
98 body1,
99 body2,
100 joint_type,
101 position,
102 orientation,
103 lower_limits,
104 upper_limits,
105 spring_coefficients,
106 damping_coefficients,
107 })
108 }
109
110 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
112 writer.write_u32_le(self.body1)?;
113 writer.write_u32_le(self.body2)?;
114 writer.write_u32_le(self.joint_type)?;
115
116 self.position.write(writer)?;
117 self.orientation.write(writer)?;
118 self.lower_limits.write(writer)?;
119 self.upper_limits.write(writer)?;
120 self.spring_coefficients.write(writer)?;
121 self.damping_coefficients.write(writer)?;
122
123 Ok(())
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct M2PhysicsShape {
130 pub shape_type: M2PhysicsShapeType,
132 pub bone_index: u16,
134 pub flags: M2PhysicsFlags,
136 pub position: C3Vector,
138 pub orientation: C3Vector,
140 pub dimensions: [f32; 5],
142}
143
144impl M2PhysicsShape {
145 pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
147 let shape_type_raw = reader.read_u8()?;
148 let shape_type =
149 M2PhysicsShapeType::from_u8(shape_type_raw).unwrap_or(M2PhysicsShapeType::None);
150
151 reader.read_u8()?; let bone_index = reader.read_u16_le()?;
154 let flags = M2PhysicsFlags::from_bits_retain(reader.read_u16_le()?);
155
156 reader.read_u16_le()?; let position = C3Vector::parse(reader)?;
159 let orientation = C3Vector::parse(reader)?;
160
161 let mut dimensions = [0.0; 5];
162 for item in &mut dimensions {
163 *item = reader.read_f32_le()?;
164 }
165
166 Ok(Self {
167 shape_type,
168 bone_index,
169 flags,
170 position,
171 orientation,
172 dimensions,
173 })
174 }
175
176 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
178 writer.write_u8(self.shape_type as u8)?;
179 writer.write_u8(0)?; writer.write_u16_le(self.bone_index)?;
182 writer.write_u16_le(self.flags.bits())?;
183 writer.write_u16_le(0)?; self.position.write(writer)?;
186 self.orientation.write(writer)?;
187
188 for &dim in &self.dimensions {
189 writer.write_f32_le(dim)?;
190 }
191
192 Ok(())
193 }
194
195 pub fn size_in_bytes() -> usize {
197 2 + 2 + 2 + 2 + 3 * 4 + 3 * 4 + 5 * 4 }
205}
206
207#[derive(Debug, Clone)]
210pub struct M2PhysicsData {
211 pub shapes: Vec<M2PhysicsShape>,
213 pub bodies: Vec<u32>,
215 pub joints: Vec<M2PhysicsJoint>,
217}
218
219impl M2PhysicsData {
220 pub fn parse<R: Read + std::io::Seek>(reader: &mut R, version: u32) -> Result<Self> {
222 if let Some(m2_version) = M2Version::from_header_version(version)
224 && m2_version < M2Version::MoP
225 {
226 return Ok(Self {
227 shapes: Vec::new(),
228 bodies: Vec::new(),
229 joints: Vec::new(),
230 });
231 }
232
233 let shapes_count = reader.read_u32_le()?;
234 let shapes_offset = reader.read_u32_le()?;
235
236 let bodies_count = reader.read_u32_le()?;
237 let bodies_offset = reader.read_u32_le()?;
238
239 let joints_count = reader.read_u32_le()?;
240 let joints_offset = reader.read_u32_le()?;
241
242 let mut shapes = Vec::with_capacity(shapes_count as usize);
244 if shapes_count > 0 {
245 reader.seek(std::io::SeekFrom::Start(shapes_offset as u64))?;
246 for _ in 0..shapes_count {
247 shapes.push(M2PhysicsShape::parse(reader)?);
248 }
249 }
250
251 let mut bodies = Vec::with_capacity(bodies_count as usize);
253 if bodies_count > 0 {
254 reader.seek(std::io::SeekFrom::Start(bodies_offset as u64))?;
255 for _ in 0..bodies_count {
256 bodies.push(reader.read_u32_le()?);
257 }
258 }
259
260 let mut joints = Vec::with_capacity(joints_count as usize);
262 if joints_count > 0 {
263 reader.seek(std::io::SeekFrom::Start(joints_offset as u64))?;
264 for _ in 0..joints_count {
265 joints.push(M2PhysicsJoint::parse(reader)?);
266 }
267 }
268
269 Ok(Self {
270 shapes,
271 bodies,
272 joints,
273 })
274 }
275
276 pub fn write<W: Write + std::io::Seek>(&self, writer: &mut W, version: u32) -> Result<()> {
278 if let Some(m2_version) = M2Version::from_header_version(version)
280 && m2_version < M2Version::MoP
281 {
282 return Ok(());
283 }
284
285 let header_pos = writer.stream_position()?;
286
287 writer.write_u32_le(self.shapes.len() as u32)?;
289 writer.write_u32_le(0)?; writer.write_u32_le(self.bodies.len() as u32)?;
292 writer.write_u32_le(0)?; writer.write_u32_le(self.joints.len() as u32)?;
295 writer.write_u32_le(0)?; if !self.shapes.is_empty() {
299 let shapes_offset = writer.stream_position()?;
300
301 writer.seek(std::io::SeekFrom::Start(header_pos + 4))?;
303 writer.write_u32_le(shapes_offset as u32)?;
304 writer.seek(std::io::SeekFrom::Start(shapes_offset))?;
305
306 for shape in &self.shapes {
307 shape.write(writer)?;
308 }
309 }
310
311 if !self.bodies.is_empty() {
313 let bodies_offset = writer.stream_position()?;
314
315 writer.seek(std::io::SeekFrom::Start(header_pos + 12))?;
317 writer.write_u32_le(bodies_offset as u32)?;
318 writer.seek(std::io::SeekFrom::Start(bodies_offset))?;
319
320 for &body in &self.bodies {
321 writer.write_u32_le(body)?;
322 }
323 }
324
325 if !self.joints.is_empty() {
327 let joints_offset = writer.stream_position()?;
328
329 writer.seek(std::io::SeekFrom::Start(header_pos + 20))?;
331 writer.write_u32_le(joints_offset as u32)?;
332 writer.seek(std::io::SeekFrom::Start(joints_offset))?;
333
334 for joint in &self.joints {
335 joint.write(writer)?;
336 }
337 }
338
339 Ok(())
340 }
341
342 pub fn convert(&self, source_version: M2Version, target_version: M2Version) -> Self {
344 if target_version < M2Version::MoP {
345 Self {
347 shapes: Vec::new(),
348 bodies: Vec::new(),
349 joints: Vec::new(),
350 }
351 } else if source_version < M2Version::MoP {
352 self.clone()
355 } else {
356 self.clone()
358 }
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use std::io::Cursor;
366
367 #[test]
368 fn test_physics_shape_parse_write() {
369 let shape = M2PhysicsShape {
370 shape_type: M2PhysicsShapeType::Sphere,
371 bone_index: 1,
372 flags: M2PhysicsFlags::GENERAL_COLLISION,
373 position: C3Vector {
374 x: 1.0,
375 y: 2.0,
376 z: 3.0,
377 },
378 orientation: C3Vector {
379 x: 0.0,
380 y: 0.0,
381 z: 0.0,
382 },
383 dimensions: [1.0, 0.0, 0.0, 0.0, 0.0], };
385
386 let mut data = Vec::new();
387 shape.write(&mut data).unwrap();
388
389 let mut cursor = Cursor::new(data);
390 let parsed_shape = M2PhysicsShape::parse(&mut cursor).unwrap();
391
392 assert_eq!(parsed_shape.shape_type, M2PhysicsShapeType::Sphere);
393 assert_eq!(parsed_shape.bone_index, 1);
394 assert_eq!(parsed_shape.flags, M2PhysicsFlags::GENERAL_COLLISION);
395 assert_eq!(parsed_shape.position.x, 1.0);
396 assert_eq!(parsed_shape.position.y, 2.0);
397 assert_eq!(parsed_shape.position.z, 3.0);
398 assert_eq!(parsed_shape.dimensions[0], 1.0); }
400
401 #[test]
402 fn test_physics_joint_parse_write() {
403 let joint = M2PhysicsJoint {
404 body1: 0,
405 body2: 1,
406 joint_type: 2, position: C3Vector {
408 x: 1.0,
409 y: 2.0,
410 z: 3.0,
411 },
412 orientation: C3Vector {
413 x: 0.0,
414 y: 0.0,
415 z: 0.0,
416 },
417 lower_limits: C3Vector {
418 x: -1.0,
419 y: -1.0,
420 z: -1.0,
421 },
422 upper_limits: C3Vector {
423 x: 1.0,
424 y: 1.0,
425 z: 1.0,
426 },
427 spring_coefficients: C3Vector {
428 x: 0.0,
429 y: 0.0,
430 z: 0.0,
431 },
432 damping_coefficients: C3Vector {
433 x: 0.5,
434 y: 0.5,
435 z: 0.5,
436 },
437 };
438
439 let mut data = Vec::new();
440 joint.write(&mut data).unwrap();
441
442 let mut cursor = Cursor::new(data);
443 let parsed_joint = M2PhysicsJoint::parse(&mut cursor).unwrap();
444
445 assert_eq!(parsed_joint.body1, 0);
446 assert_eq!(parsed_joint.body2, 1);
447 assert_eq!(parsed_joint.joint_type, 2);
448 assert_eq!(parsed_joint.position.x, 1.0);
449 assert_eq!(parsed_joint.position.y, 2.0);
450 assert_eq!(parsed_joint.position.z, 3.0);
451 assert_eq!(parsed_joint.lower_limits.x, -1.0);
452 assert_eq!(parsed_joint.upper_limits.x, 1.0);
453 assert_eq!(parsed_joint.damping_coefficients.x, 0.5);
454 }
455}