mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 08:34:23 +02:00
read animation data
This commit is contained in:
parent
67fed7fbaf
commit
5e48fd2a6f
13 changed files with 472 additions and 73 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2804,6 +2804,7 @@ dependencies = [
|
|||
"criterion",
|
||||
"gltf",
|
||||
"gltf-json",
|
||||
"half",
|
||||
"iai",
|
||||
"image",
|
||||
"itertools 0.13.0",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ tracing = "0.1.40"
|
|||
bytemuck = { version = "1.17.0", features = ["derive"] }
|
||||
cgmath = "0.18.0"
|
||||
num_enum = "0.7.3"
|
||||
half = "2.4.1"
|
||||
|
||||
[dev-dependencies]
|
||||
three-d = { version = "0.14.1", features = ["egui-gui"] }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use cgmath::{Euler, Matrix4, Quaternion};
|
||||
use cgmath::Euler;
|
||||
use std::env::args;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -21,16 +21,23 @@ fn main() -> Result<(), vmdl::ModelError> {
|
|||
|
||||
// dbg!(&mdl.header2);
|
||||
|
||||
for bone in &mdl.bones {
|
||||
println!(
|
||||
"{}: from {} at\n\t{:?}\n\t{:?}\n\t{:?}",
|
||||
bone.name, bone.parent, bone.rot, bone.q_alignment, bone.pose_to_bone
|
||||
);
|
||||
}
|
||||
dbg!(&mdl.animation_blocks);
|
||||
// for bone in &mdl.bones {
|
||||
// println!(
|
||||
// "{}: from {} at\n\t{:?}\n\t{:?}\n\t{:?}",
|
||||
// bone.name, bone.parent, bone.rot, bone.q_alignment, bone.pose_to_bone
|
||||
// );
|
||||
// }
|
||||
dbg!(&mdl.local_animations[0]);
|
||||
let transform = mdl
|
||||
.local_animations
|
||||
.first()
|
||||
.map(|a| a.animations[0].rotation(0))
|
||||
.unwrap();
|
||||
dbg!(transform);
|
||||
dbg!(Euler::from(cgmath::Quaternion::from(transform)));
|
||||
|
||||
// dbg!(&mdl.attachments);
|
||||
let model = Model::from_parts(mdl, vtx, vvd);
|
||||
let _model = Model::from_parts(mdl, vtx, vvd);
|
||||
// dbg!(Euler::from(Quaternion::from(model.root_transform())));
|
||||
// for strip in model.vertex_strips() {
|
||||
// for vertex in strip {
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ fn model_to_model(model: &Model, loader: &Loader, skin: usize) -> CpuModel {
|
|||
let skin = model.skin_tables().nth(skin).unwrap();
|
||||
|
||||
let transforms = model.root_transform();
|
||||
let transforms = Matrix4::identity();
|
||||
let transforms = dbg!(model.idle_transform());
|
||||
// let transforms = Matrix4::identity();
|
||||
|
||||
let geometries = model
|
||||
.meshes()
|
||||
|
|
|
|||
132
src/compressed_vector.rs
Normal file
132
src/compressed_vector.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
use crate::{Quaternion, ReadableRelative, Vector};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use cgmath::{InnerSpace, Vector4};
|
||||
use half::f16;
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Vector48 {
|
||||
x: u16,
|
||||
y: u16,
|
||||
z: u16,
|
||||
}
|
||||
|
||||
impl ReadableRelative for Vector48 {}
|
||||
|
||||
impl Vector48 {
|
||||
pub fn x(&self) -> f32 {
|
||||
f16::from_bits(self.x).into()
|
||||
}
|
||||
pub fn y(&self) -> f32 {
|
||||
f16::from_bits(self.y).into()
|
||||
}
|
||||
pub fn z(&self) -> f32 {
|
||||
f16::from_bits(self.z).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector48> for Vector {
|
||||
fn from(value: Vector48) -> Self {
|
||||
Vector {
|
||||
x: value.x(),
|
||||
y: value.y(),
|
||||
z: value.z(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Quaternion48 {
|
||||
x: u16,
|
||||
y: u16,
|
||||
z: u16,
|
||||
}
|
||||
|
||||
impl ReadableRelative for Quaternion48 {}
|
||||
|
||||
fn calc_w(x: f32, y: f32, z: f32, w_neg: bool) -> f32 {
|
||||
let w_sign = if w_neg { -1.0 } else { 1.0 };
|
||||
f32::sqrt(1.0 - ((x * x) - (y * y) - (z * z))) * w_sign
|
||||
}
|
||||
|
||||
impl Quaternion48 {
|
||||
const Z_MASK: u16 = 0x7F_FF;
|
||||
const W_NEG_MASK: u16 = 0x80_00;
|
||||
|
||||
pub fn x(&self) -> f32 {
|
||||
((self.x as f32) - 32768.0) / 32768.0
|
||||
}
|
||||
pub fn y(&self) -> f32 {
|
||||
((self.y as f32) - 32768.0) / 32768.0
|
||||
}
|
||||
pub fn z(&self) -> f32 {
|
||||
(((self.z & Self::Z_MASK) as f32) - 16384.0) / 16384.0
|
||||
}
|
||||
pub fn w(&self) -> f32 {
|
||||
calc_w(
|
||||
self.x(),
|
||||
self.y(),
|
||||
self.z(),
|
||||
self.z & Self::W_NEG_MASK == Self::W_NEG_MASK,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Quaternion48> for Quaternion {
|
||||
fn from(value: Quaternion48) -> Self {
|
||||
let normalized = Vector4::new(value.x(), value.y(), value.z(), value.w()).normalize();
|
||||
Quaternion {
|
||||
x: normalized.x,
|
||||
y: normalized.y,
|
||||
z: normalized.z,
|
||||
w: normalized.w,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Quaternion64(u64);
|
||||
|
||||
impl ReadableRelative for Quaternion64 {}
|
||||
|
||||
impl Quaternion64 {
|
||||
const MASK_21_BIT: u64 = 0b111111111111111111111;
|
||||
const W_NEG_MASK: u64 = 0x80_00_00_00_00_00_00_00;
|
||||
|
||||
fn val(&self, offset: i32) -> f32 {
|
||||
let raw = (self.0 >> offset) & Self::MASK_21_BIT;
|
||||
(raw as f32 - 1048576.0) / 1048576.5
|
||||
}
|
||||
|
||||
pub fn x(&self) -> f32 {
|
||||
self.val(0)
|
||||
}
|
||||
pub fn y(&self) -> f32 {
|
||||
self.val(21)
|
||||
}
|
||||
pub fn z(&self) -> f32 {
|
||||
self.val(42)
|
||||
}
|
||||
pub fn w(&self) -> f32 {
|
||||
calc_w(
|
||||
self.x(),
|
||||
self.y(),
|
||||
self.z(),
|
||||
self.0 & Self::W_NEG_MASK == Self::W_NEG_MASK,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Quaternion64> for Quaternion {
|
||||
fn from(value: Quaternion64) -> Self {
|
||||
let normalized = Vector4::new(value.x(), value.y(), value.z(), value.w()).normalize();
|
||||
Quaternion {
|
||||
x: normalized.x,
|
||||
y: normalized.y,
|
||||
z: normalized.z,
|
||||
w: normalized.w,
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/lib.rs
21
src/lib.rs
|
|
@ -1,3 +1,4 @@
|
|||
mod compressed_vector;
|
||||
mod error;
|
||||
mod handle;
|
||||
pub mod mdl;
|
||||
|
|
@ -11,7 +12,7 @@ pub use crate::vtx::Vtx;
|
|||
use crate::vvd::Vertex;
|
||||
pub use crate::vvd::Vvd;
|
||||
use bytemuck::{pod_read_unaligned, Contiguous, Pod};
|
||||
use cgmath::{Matrix4, SquareMatrix, Transform, Vector3};
|
||||
use cgmath::{Matrix4, SquareMatrix};
|
||||
pub use error::*;
|
||||
pub use handle::Handle;
|
||||
use itertools::Either;
|
||||
|
|
@ -145,6 +146,20 @@ impl Model {
|
|||
.unwrap_or_else(Matrix4::identity)
|
||||
}
|
||||
|
||||
pub fn idle_transform(&self) -> Matrix4<f32> {
|
||||
if self.mdl.header.flags.contains(ModelFlags::STATIC_PROP) {
|
||||
return Matrix4::identity();
|
||||
}
|
||||
|
||||
self.mdl
|
||||
.local_animations
|
||||
.first()
|
||||
.and_then(|desc| desc.animations.first())
|
||||
.map(|animation| animation.rotation(0))
|
||||
.map(Matrix4::from)
|
||||
.unwrap_or_else(Matrix4::identity)
|
||||
}
|
||||
|
||||
pub fn surface_prop(&self) -> &str {
|
||||
self.mdl.surface_prop.as_str()
|
||||
}
|
||||
|
|
@ -154,7 +169,9 @@ impl Model {
|
|||
}
|
||||
|
||||
pub fn vertex_to_world_space(&self, vertex: &Vertex) -> Vector {
|
||||
vertex.position.transformed(self.root_transform())
|
||||
// vertex.position.transformed(self.root_transform())
|
||||
vertex.position.transformed(self.idle_transform())
|
||||
|
||||
// let mut pos = Vector3::from(vertex.position);
|
||||
// for weights in vertex.bone_weights.weights() {
|
||||
// if let Some(bone) = self.mdl.bones.get(weights.bone_id as usize) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
mod raw;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
pub use raw::header::*;
|
||||
pub use raw::header2::*;
|
||||
pub use raw::*;
|
||||
|
|
@ -63,7 +62,16 @@ impl Mdl {
|
|||
let key_values = (header.key_value_size > 0)
|
||||
.then(|| read_single(data, header.key_value_index))
|
||||
.transpose()?;
|
||||
let local_animations = read_relative(data, header.local_animation_indexes())?;
|
||||
let mut local_animations: Vec<AnimationDescription> =
|
||||
read_relative(data, header.local_animation_indexes())?;
|
||||
local_animations
|
||||
.iter_mut()
|
||||
.flat_map(|desc| desc.animations.iter_mut())
|
||||
.for_each(|animation| {
|
||||
if let Some(bone) = bones.get(animation.bone as usize) {
|
||||
animation.set_scales(bone);
|
||||
}
|
||||
});
|
||||
let animation_block_source: String = read_single(data, header.anim_blocks_name_index)?;
|
||||
let animation_blocks = read_relative(data, header.animation_block_indexes())?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
use crate::vvd::Vertex;
|
||||
use crate::{ModelError, ReadRelative, ReadableRelative};
|
||||
use crate::compressed_vector::{Quaternion48, Quaternion64, Vector48};
|
||||
use crate::mdl::Bone;
|
||||
use crate::{
|
||||
read_single, ModelError, Quaternion, RadianEuler, ReadRelative, Readable, ReadableRelative,
|
||||
Vector,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::mem::size_of;
|
||||
|
|
@ -29,14 +33,8 @@ impl ReadRelative for PoseParameterDescription {
|
|||
type Header = PoseParameterDescriptionHeader;
|
||||
|
||||
fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
|
||||
let name_bytes = data
|
||||
.get(header.name_index as usize..)
|
||||
.ok_or(ModelError::OutOfBounds {
|
||||
data: "pose name",
|
||||
offset: header.name_index as usize,
|
||||
})?;
|
||||
Ok(PoseParameterDescription {
|
||||
name: String::read(name_bytes, ())?,
|
||||
name: read_single(data, header.name_index)?,
|
||||
flags: header.flags,
|
||||
start: header.start,
|
||||
end: header.end,
|
||||
|
|
@ -87,26 +85,33 @@ pub struct AnimationDescription {
|
|||
pub name: String,
|
||||
pub fps: f32,
|
||||
pub frame_count: i32,
|
||||
pub animation_block: i32,
|
||||
pub animation: i32,
|
||||
pub animations: Vec<Animation>,
|
||||
}
|
||||
|
||||
impl ReadRelative for AnimationDescription {
|
||||
type Header = AnimationDescriptionHeader;
|
||||
|
||||
fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
|
||||
let name_bytes =
|
||||
data.get(header.name_offset as usize..)
|
||||
.ok_or(ModelError::OutOfBounds {
|
||||
data: "animation name",
|
||||
offset: header.name_offset as usize,
|
||||
})?;
|
||||
let mut animations = Vec::with_capacity(1);
|
||||
let mut offset = header.animation_index as usize;
|
||||
loop {
|
||||
let (animation, next_offset) = if header.animation_block == 0 {
|
||||
read_animation(data, offset, header.frame_count as usize)?
|
||||
} else {
|
||||
todo!("read animation from animation block");
|
||||
};
|
||||
animations.push(animation);
|
||||
if next_offset == 0 {
|
||||
break;
|
||||
}
|
||||
offset += next_offset;
|
||||
}
|
||||
|
||||
Ok(AnimationDescription {
|
||||
name: String::read(name_bytes, ())?,
|
||||
name: read_single(data, header.name_offset)?,
|
||||
fps: header.fps,
|
||||
frame_count: header.frame_count,
|
||||
animation_block: header.animation_block,
|
||||
animation: header.animation_index,
|
||||
animations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -147,3 +152,234 @@ bitflags! {
|
|||
const STUDIO_ANIM_RAWROT2 = 0x00000020;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
struct AnimationValuePointer([u16; 3]);
|
||||
impl ReadableRelative for AnimationValuePointer {}
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug, Default)]
|
||||
#[repr(C)]
|
||||
struct ValueHeader {
|
||||
valid: u8,
|
||||
total: u8,
|
||||
}
|
||||
impl ReadableRelative for ValueHeader {}
|
||||
|
||||
fn read_animation_values(
|
||||
data: &[u8], // data starting at the AnimationValuePointer
|
||||
frame: usize,
|
||||
base_pointers: AnimationValuePointer,
|
||||
) -> Result<[f32; 3], ModelError> {
|
||||
let mut result = [0.0; 3];
|
||||
for (out, base_pointer) in result.iter_mut().zip(base_pointers.0) {
|
||||
if base_pointer == 0 {
|
||||
*out = 0.0;
|
||||
} else {
|
||||
let header: ValueHeader = read_single(data, base_pointer)?;
|
||||
let values = FrameValues {
|
||||
header,
|
||||
data: &data[base_pointer as usize..],
|
||||
};
|
||||
*out = values.get(frame as u8).map(|val| val as f32)?;
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// I hate this data structure
|
||||
///
|
||||
/// Seems to be an array of
|
||||
///
|
||||
/// FrameValues {
|
||||
/// header: ValueHeader,
|
||||
/// values: [u16; self.header.valid]
|
||||
/// }
|
||||
///
|
||||
/// each item containing `header.total` worth of frames (for frames larger than `header.valid` it re-uses the last valid data)
|
||||
/// when looking up frame `k` we skip through the list of values until we find the value range for the frame
|
||||
struct FrameValues<'a> {
|
||||
header: ValueHeader,
|
||||
data: &'a [u8], // data starting at self.header
|
||||
}
|
||||
|
||||
impl<'a> FrameValues<'a> {
|
||||
pub fn get(&self, index: u8) -> Result<u16, ModelError> {
|
||||
if self.header.total <= index {
|
||||
let offset_count = self.header.valid + 1;
|
||||
let offset = (offset_count as usize) * size_of::<u16>();
|
||||
let next_header: ValueHeader = read_single(self.data, offset)?;
|
||||
let next = FrameValues {
|
||||
header: next_header,
|
||||
data: &self.data[offset..],
|
||||
};
|
||||
if next_header.total == 0 {
|
||||
return Err(ModelError::OutOfBounds {
|
||||
data: "animation value",
|
||||
offset,
|
||||
});
|
||||
}
|
||||
next.get(index - self.header.total)
|
||||
} else {
|
||||
let offset_count = if self.header.valid > index {
|
||||
index + 1
|
||||
} else {
|
||||
self.header.valid
|
||||
};
|
||||
let offset = (offset_count as usize) * size_of::<u16>();
|
||||
read_single(self.data, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RotationData {
|
||||
Quaternion48(Quaternion48),
|
||||
Quaternion64(Quaternion64),
|
||||
RotationValues(Vec<RadianEuler>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl RotationData {
|
||||
pub fn rotation(&self, frame: usize) -> Quaternion {
|
||||
match self {
|
||||
RotationData::Quaternion48(q) => Quaternion::from(*q),
|
||||
RotationData::Quaternion64(q) => Quaternion::from(*q),
|
||||
RotationData::RotationValues(values) => {
|
||||
values.get(frame).copied().unwrap_or_default().into()
|
||||
}
|
||||
RotationData::None => Quaternion::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
RotationData::Quaternion48(_) => size_of::<Quaternion48>(),
|
||||
RotationData::Quaternion64(_) => size_of::<Quaternion64>(),
|
||||
RotationData::RotationValues(_) => size_of::<AnimationValuePointer>(),
|
||||
RotationData::None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_scale(&mut self, scale: Vector) {
|
||||
if let RotationData::RotationValues(values) = self {
|
||||
values.iter_mut().for_each(|value| {
|
||||
*value = RadianEuler {
|
||||
x: value.x * scale.x,
|
||||
y: value.y * scale.y,
|
||||
z: value.z * scale.z,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PositionData {
|
||||
Vector48(Vector48),
|
||||
PositionValues(Vec<Vector>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl PositionData {
|
||||
pub fn position(&self, frame: usize) -> Vector {
|
||||
match self {
|
||||
PositionData::Vector48(vector) => Vector::from(*vector),
|
||||
PositionData::PositionValues(values) => values.get(frame).copied().unwrap_or_default(),
|
||||
PositionData::None => Vector::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_scale(&mut self, scale: Vector) {
|
||||
if let PositionData::PositionValues(values) = self {
|
||||
values.iter_mut().for_each(|value| {
|
||||
*value = Vector {
|
||||
x: value.x * scale.x,
|
||||
y: value.y * scale.y,
|
||||
z: value.z * scale.z,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per bone animation data
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Animation {
|
||||
pub bone: u8,
|
||||
pub flags: AnimationFlags,
|
||||
rotation_data: RotationData,
|
||||
position_data: PositionData,
|
||||
}
|
||||
|
||||
impl Animation {
|
||||
pub fn rotation(&self, frame: usize) -> Quaternion {
|
||||
self.rotation_data.rotation(frame)
|
||||
}
|
||||
|
||||
pub fn position(&self, frame: usize) -> Vector {
|
||||
self.position_data.position(frame)
|
||||
}
|
||||
|
||||
pub(crate) fn set_scales(&mut self, bone: &Bone) {
|
||||
self.rotation_data.set_scale(bone.rot_scale);
|
||||
self.position_data.set_scale(bone.pos_scale);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_animation(
|
||||
data: &[u8],
|
||||
header_offset: usize,
|
||||
frames: usize,
|
||||
) -> Result<(Animation, usize), ModelError> {
|
||||
let data = data
|
||||
.get(header_offset..)
|
||||
.ok_or_else(|| ModelError::OutOfBounds {
|
||||
data: "animation data",
|
||||
offset: header_offset,
|
||||
})?;
|
||||
let header = <AnimationHeader as Readable>::read(data)?;
|
||||
|
||||
let offset = size_of::<AnimationHeader>();
|
||||
|
||||
let rotation_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT) {
|
||||
RotationData::Quaternion48(read_single(data, offset)?)
|
||||
} else if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT2) {
|
||||
RotationData::Quaternion64(read_single(data, offset)?)
|
||||
} else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMROT) {
|
||||
let pointers: AnimationValuePointer = read_single(data, offset)?;
|
||||
let value_data = &data[offset..];
|
||||
let values = (0..frames)
|
||||
.map(|frame| read_animation_values(value_data, frame, pointers))
|
||||
.map(|r| r.map(|[x, y, z]| RadianEuler { x, y, z }))
|
||||
.collect::<Result<_, ModelError>>()?;
|
||||
RotationData::RotationValues(values)
|
||||
} else {
|
||||
RotationData::None
|
||||
};
|
||||
|
||||
let position_offset = offset + rotation_data.size();
|
||||
let position_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWPOS) {
|
||||
PositionData::Vector48(read_single(data, position_offset)?)
|
||||
} else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMPOS) {
|
||||
let pointers: AnimationValuePointer = read_single(data, offset)?;
|
||||
let value_data = &data[offset..];
|
||||
let values = (0..frames)
|
||||
.map(|frame| read_animation_values(value_data, frame, pointers))
|
||||
.map(|r| r.map(Vector::from))
|
||||
.collect::<Result<_, ModelError>>()?;
|
||||
PositionData::PositionValues(values)
|
||||
} else {
|
||||
PositionData::None
|
||||
};
|
||||
|
||||
Ok((
|
||||
Animation {
|
||||
bone: header.bone,
|
||||
flags: header.flags,
|
||||
rotation_data,
|
||||
position_data,
|
||||
},
|
||||
header.next_offset as usize,
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use crate::{ModelError, Quaternion, RadianEuler, ReadRelative, Readable, Transform3x4, Vector};
|
||||
use crate::{
|
||||
read_single, ModelError, Quaternion, RadianEuler, ReadRelative, Readable, Transform3x4, Vector,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
|
@ -58,19 +60,6 @@ impl ReadRelative for Bone {
|
|||
type Header = BoneHeader;
|
||||
|
||||
fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
|
||||
let name_bytes =
|
||||
data.get(header.sz_name_index as usize..)
|
||||
.ok_or(ModelError::OutOfBounds {
|
||||
data: "bone name",
|
||||
offset: header.sz_name_index as usize,
|
||||
})?;
|
||||
let surface_prop_bytes =
|
||||
data.get(header.surface_prop_idx as usize..)
|
||||
.ok_or(ModelError::OutOfBounds {
|
||||
data: "bone surface property",
|
||||
offset: header.surface_prop_idx as usize,
|
||||
})?;
|
||||
|
||||
let prop_type = ProceduralBoneType::try_from(header.proc_type).ok();
|
||||
let proc_bytes = (header.proc_index != 0)
|
||||
.then(|| {
|
||||
|
|
@ -103,7 +92,7 @@ impl ReadRelative for Bone {
|
|||
.transpose()?;
|
||||
|
||||
Ok(Bone {
|
||||
name: String::read(name_bytes, ())?,
|
||||
name: read_single(data, header.sz_name_index)?,
|
||||
parent: header.parent,
|
||||
bone_controller: header.bone_controller,
|
||||
pos: header.pos,
|
||||
|
|
@ -116,7 +105,7 @@ impl ReadRelative for Bone {
|
|||
flags: header.flags,
|
||||
procedural_rules,
|
||||
physics_bone: header.physics_bone,
|
||||
surface_prop: String::read(surface_prop_bytes, ())?,
|
||||
surface_prop: read_single(data, header.surface_prop_idx)?,
|
||||
contents: header.contents,
|
||||
})
|
||||
}
|
||||
|
|
@ -336,17 +325,10 @@ impl ReadRelative for SourceBoneTransform {
|
|||
type Header = SourceBoneTransformHeader;
|
||||
|
||||
fn read(data: &[u8], header: Self::Header) -> Result<Self, ModelError> {
|
||||
let name_bytes =
|
||||
data.get(header.sz_name_index as usize..)
|
||||
.ok_or(ModelError::OutOfBounds {
|
||||
data: "source bone transform name",
|
||||
offset: header.sz_name_index as usize,
|
||||
})?;
|
||||
|
||||
Ok(SourceBoneTransform {
|
||||
name: String::read(name_bytes, ())?,
|
||||
name: read_single(data, header.sz_name_index)?,
|
||||
pre_transform: header.pre_transform,
|
||||
post_transform: header.post_transform,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::mdl::raw::*;
|
||||
use crate::mdl::StudioHeader2;
|
||||
use crate::{index_range, read_single, Vector};
|
||||
use crate::{index_range, Vector};
|
||||
use std::mem::size_of;
|
||||
|
||||
pub const FILETYPE_ID: i32 = i32::from_be_bytes(*b"IDST");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::mem::size_of;
|
||||
use crate::mdl::SourceBoneTransformHeader;
|
||||
use crate::{index_range, ReadableRelative};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::mem::size_of;
|
||||
use std::ops::Range;
|
||||
use crate::mdl::{BoneHeader, SourceBoneTransformHeader};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
|
|
@ -29,7 +29,11 @@ impl ReadableRelative for StudioHeader2 {}
|
|||
|
||||
impl StudioHeader2 {
|
||||
pub fn source_bone_transforms(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.source_bone_transform_index, self.source_bone_transform_count, size_of::<SourceBoneTransformHeader>())
|
||||
index_range(
|
||||
self.source_bone_transform_index,
|
||||
self.source_bone_transform_count,
|
||||
size_of::<SourceBoneTransformHeader>(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bone_flex_drivers(&self) -> Range<i32> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Vector;
|
||||
use crate::{index_range, FixedString, Transform3x4};
|
||||
use crate::{ModelError, ReadRelative, Vector};
|
||||
use bitflags::bitflags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::mem::size_of;
|
||||
|
|
@ -145,7 +145,11 @@ pub struct HitBoxSetHeader {
|
|||
|
||||
impl HitBoxSetHeader {
|
||||
pub fn hitbox_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.hitbox_offset, self.hitbox_count, size_of::<BoundingBoxHeader>())
|
||||
index_range(
|
||||
self.hitbox_offset,
|
||||
self.hitbox_count,
|
||||
size_of::<BoundingBoxHeader>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,4 +163,4 @@ pub struct BoundingBoxHeader {
|
|||
pub bounding_box_max: Vector,
|
||||
pub name_index: i32,
|
||||
padding: [i32; 8],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::fmt;
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::{Add, Mul};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod, PartialEq, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Vector {
|
||||
pub x: f32,
|
||||
|
|
@ -100,6 +100,17 @@ pub struct Quaternion {
|
|||
pub w: f32,
|
||||
}
|
||||
|
||||
impl Default for Quaternion {
|
||||
fn default() -> Self {
|
||||
Quaternion {
|
||||
x: 1.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
w: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Quaternion> for cgmath::Quaternion<f32> {
|
||||
fn from(q: Quaternion) -> Self {
|
||||
[q.x, q.y, q.z, q.w].into()
|
||||
|
|
@ -123,7 +134,7 @@ impl From<Quaternion> for cgmath::Matrix4<f32> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod, Default)]
|
||||
#[repr(C)]
|
||||
pub struct RadianEuler {
|
||||
pub x: f32,
|
||||
|
|
@ -244,14 +255,10 @@ impl Transform3x4 {
|
|||
// mat
|
||||
let quat = cgmath::Quaternion::from(mat);
|
||||
let euler = Euler::from(quat);
|
||||
#[cfg(debug_assertions)]
|
||||
dbg!(euler);
|
||||
let mapped_rotation = cgmath::Quaternion::from_angle_x(-euler.z)
|
||||
* cgmath::Quaternion::from_angle_y(euler.y)
|
||||
* cgmath::Quaternion::from_angle_z(euler.x);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
dbg!(Euler::from(mapped_rotation));
|
||||
mapped_rotation.into()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue