finaly got orientation figured out I think

This commit is contained in:
Robin Appelman 2024-08-28 23:35:27 +02:00
commit 2dffd8d7cd
9 changed files with 171 additions and 75 deletions

View file

@ -1,11 +1,11 @@
use cgmath::Euler; use cgmath::{Euler, Matrix4};
use std::env::args; use std::env::args;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use vmdl::mdl::Mdl; use vmdl::mdl::Mdl;
use vmdl::vtx::Vtx; use vmdl::vtx::Vtx;
use vmdl::vvd::Vvd; use vmdl::vvd::Vvd;
use vmdl::Model; use vmdl::{Model, Quaternion};
fn main() -> Result<(), vmdl::ModelError> { fn main() -> Result<(), vmdl::ModelError> {
let mut args = args(); let mut args = args();
@ -19,25 +19,33 @@ fn main() -> Result<(), vmdl::ModelError> {
let data = fs::read(path.with_extension("vvd"))?; let data = fs::read(path.with_extension("vvd"))?;
let vvd = Vvd::read(&data)?; let vvd = Vvd::read(&data)?;
// dbg!(&mdl.header2); // dbg!(&mdl.header);
// for bone in &mdl.bones { // for bone in &mdl.bones {
// println!( // println!(
// "{}: from {} at\n\t{:?}\n\t{:?}\n\t{:?}", // "{}: from {} at\n\t{:?}\n\t{:?}\n\t{:?}\n\t{:?}",
// bone.name, bone.parent, bone.rot, bone.q_alignment, bone.pose_to_bone // bone.name, bone.parent, bone.rot, bone.rot_scale, bone.quaternion, bone.pose_to_bone
// ); // );
// } // }
dbg!(&mdl.local_animations[0]); // dbg!(&mdl.bones[0]);
dbg!(&mdl.local_animations[0].animations[0]);
let transform = mdl let transform = mdl
.local_animations .local_animations
.first() .get(0)
.map(|a| a.animations[0].rotation(0)) .map(|a| a.animations[0].rotation(0))
.unwrap(); .unwrap();
dbg!(transform); dbg!(transform);
dbg!(Euler::from(cgmath::Quaternion::from(transform))); dbg!(Euler::from(cgmath::Quaternion::from(transform)));
// dbg!(&mdl.body_table_by_name);
// dbg!(&mdl.attachments); // dbg!(&mdl.attachments);
let _model = Model::from_parts(mdl, vtx, vvd); let model = Model::from_parts(mdl, vtx, vvd);
dbg!(model.animations().nth(1).unwrap().get_bone_transform(1, 0));
// dbg!(model.root_transform());
// dbg!(model.idle_transform());
// dbg!(Euler::from(cgmath::Quaternion::from(
// model.idle_transform()
// )));
// dbg!(Euler::from(Quaternion::from(model.root_transform()))); // dbg!(Euler::from(Quaternion::from(model.root_transform())));
// for strip in model.vertex_strips() { // for strip in model.vertex_strips() {
// for vertex in strip { // for vertex in strip {

View file

@ -250,9 +250,7 @@ fn model_to_model(model: &Model, loader: &Loader, skin: usize) -> CpuModel {
let skin = model.skin_tables().nth(skin).unwrap(); 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 let geometries = model
.meshes() .meshes()

View file

@ -43,11 +43,21 @@ pub struct Quaternion48 {
z: u16, z: u16,
} }
impl Default for Quaternion48 {
fn default() -> Self {
Quaternion48 {
x: 32768,
y: 32768,
z: 16384,
}
}
}
impl ReadableRelative for Quaternion48 {} impl ReadableRelative for Quaternion48 {}
fn calc_w(x: f32, y: f32, z: f32, w_neg: bool) -> f32 { fn calc_w(x: f32, y: f32, z: f32, w_neg: bool) -> f32 {
let w_sign = if w_neg { -1.0 } else { 1.0 }; let w_sign = if w_neg { -1.0 } else { 1.0 };
f32::sqrt(1.0 - ((x * x) - (y * y) - (z * z))) * w_sign f32::sqrt(1.0 - (x * x) - (y * y) - (z * z)) * w_sign
} }
impl Quaternion48 { impl Quaternion48 {
@ -92,14 +102,13 @@ pub struct Quaternion64(u64);
impl ReadableRelative for Quaternion64 {} impl ReadableRelative for Quaternion64 {}
impl Quaternion64 { impl Quaternion64 {
const MASK_21_BIT: u64 = 0b111111111111111111111; const MASK_21_BIT: u64 = 0b11111_11111111_11111111;
const W_NEG_MASK: u64 = 0x80_00_00_00_00_00_00_00; const W_NEG_MASK: u64 = 0x80_00_00_00_00_00_00_00;
fn val(&self, offset: i32) -> f32 { fn val(&self, offset: i32) -> f32 {
let raw = (self.0 >> offset) & Self::MASK_21_BIT; let raw = ((self.0) >> offset) & Self::MASK_21_BIT;
(raw as f32 - 1048576.0) / 1048576.5 (raw as f32 - 1048576.0) / 1048576.5
} }
pub fn x(&self) -> f32 { pub fn x(&self) -> f32 {
self.val(0) self.val(0)
} }
@ -121,7 +130,7 @@ impl Quaternion64 {
impl From<Quaternion64> for Quaternion { impl From<Quaternion64> for Quaternion {
fn from(value: Quaternion64) -> Self { fn from(value: Quaternion64) -> Self {
let normalized = Vector4::new(value.x(), value.y(), value.z(), value.w()).normalize(); let normalized = Vector4::new(value.x(), value.y(), value.z(), value.w());
Quaternion { Quaternion {
x: normalized.x, x: normalized.x,
y: normalized.y, y: normalized.y,

View file

@ -7,12 +7,12 @@ pub mod vtx;
pub mod vvd; pub mod vvd;
pub use crate::mdl::Mdl; pub use crate::mdl::Mdl;
use crate::mdl::{Bone, ModelFlags, PoseParameterDescription, TextureInfo}; use crate::mdl::{AnimationDescription, Bone, ModelFlags, PoseParameterDescription, TextureInfo};
pub use crate::vtx::Vtx; pub use crate::vtx::Vtx;
use crate::vvd::Vertex; use crate::vvd::Vertex;
pub use crate::vvd::Vvd; pub use crate::vvd::Vvd;
use bytemuck::{pod_read_unaligned, Contiguous, Pod}; use bytemuck::{pod_read_unaligned, Contiguous, Pod};
use cgmath::{Matrix4, SquareMatrix}; use cgmath::{Matrix4, SquareMatrix, Transform, Vector3};
pub use error::*; pub use error::*;
pub use handle::Handle; pub use handle::Handle;
use itertools::Either; use itertools::Either;
@ -85,6 +85,10 @@ impl Model {
} }
} }
pub fn animations(&self) -> impl Iterator<Item = &AnimationDescription> {
self.mdl.local_animations.iter()
}
pub fn meshes(&self) -> impl Iterator<Item = Mesh> { pub fn meshes(&self) -> impl Iterator<Item = Mesh> {
let mdl_meshes = self let mdl_meshes = self
.mdl .mdl
@ -140,9 +144,13 @@ impl Model {
} }
self.bones() self.bones()
.find(|bone| bone.name != "root") .next()
.map(|bone| bone.pose_to_bone) .map(|bone| {
.map(Matrix4::from) // let inv = Matrix4::from(bone.pose_to_bone)
// .inverse_transform()
// .unwrap();
Matrix4::from(bone.rot)
})
.unwrap_or_else(Matrix4::identity) .unwrap_or_else(Matrix4::identity)
} }
@ -153,8 +161,9 @@ impl Model {
self.mdl self.mdl
.local_animations .local_animations
.first() .iter()
.and_then(|desc| desc.animations.first()) .filter_map(|desc| desc.animations.iter().find(|animation| animation.bone == 0))
.find(|anim| anim.rotation_looks_valid())
.map(|animation| animation.rotation(0)) .map(|animation| animation.rotation(0))
.map(Matrix4::from) .map(Matrix4::from)
.unwrap_or_else(Matrix4::identity) .unwrap_or_else(Matrix4::identity)
@ -169,24 +178,23 @@ impl Model {
} }
pub fn vertex_to_world_space(&self, vertex: &Vertex) -> Vector { pub fn vertex_to_world_space(&self, vertex: &Vertex) -> Vector {
// vertex.position.transformed(self.root_transform()) let transform = self.idle_transform() * self.root_transform();
vertex.position.transformed(self.idle_transform()) transform
.transform_vector(Vector3::from(vertex.position))
.into()
}
// let mut pos = Vector3::from(vertex.position); fn bone_transform(
// for weights in vertex.bone_weights.weights() { &self,
// if let Some(bone) = self.mdl.bones.get(weights.bone_id as usize) { bone_id: u8,
// let transform = Quaternion::from(bone.rot); bone: &Bone,
// if bone.parent == 0 { animation: &AnimationDescription,
// if bone.name == "joint1" { weight: f32,
// dbg!(&bone.name, bone.rot, transform); frame: usize,
// } ) -> Matrix4<f32> {
// let transform = Matrix4::from(transform); let animation_transform = weight * animation.get_bone_transform(bone_id, frame);
// pos = transform.transform_vector(pos); let bone_origin = Matrix4::from(bone.pose_to_bone);
// // pos = bone.pose_to_bone.transform(pos); bone_origin.inverse_transform().unwrap() * animation_transform * bone_origin
// }
// }
// }
// pos.into()
} }
} }

View file

@ -20,6 +20,7 @@ pub struct Mdl {
pub header2: Option<StudioHeader2>, pub header2: Option<StudioHeader2>,
pub bones: Vec<Bone>, pub bones: Vec<Bone>,
pub bone_controllers: Vec<BoneController>, pub bone_controllers: Vec<BoneController>,
pub body_table_by_name: Vec<u8>,
pub body_parts: Vec<BodyPart>, pub body_parts: Vec<BodyPart>,
pub textures: Vec<TextureInfo>, pub textures: Vec<TextureInfo>,
pub texture_paths: Vec<String>, pub texture_paths: Vec<String>,
@ -60,6 +61,7 @@ impl Mdl {
let skin_table = read_relative::<u16, _>(data, header.skin_reference_indexes())?; let skin_table = read_relative::<u16, _>(data, header.skin_reference_indexes())?;
let bones = read_relative(data, header.bone_indexes())?; let bones = read_relative(data, header.bone_indexes())?;
let bone_controllers = read_relative(data, header.bone_controller_indexes())?; let bone_controllers = read_relative(data, header.bone_controller_indexes())?;
let body_table_by_name = read_relative(data, header.bone_table_by_name_indexes())?;
let surface_prop = read_single(data, header.surface_prop_index)?; let surface_prop = read_single(data, header.surface_prop_index)?;
let key_values = (header.key_value_size > 0) let key_values = (header.key_value_size > 0)
@ -91,6 +93,7 @@ impl Mdl {
name, name,
bones, bones,
bone_controllers, bone_controllers,
body_table_by_name,
body_parts: header body_parts: header
.body_part_indexes() .body_part_indexes()
.map(|index| { .map(|index| {

View file

@ -6,6 +6,7 @@ use crate::{
}; };
use bitflags::bitflags; use bitflags::bitflags;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use cgmath::{Matrix4, SquareMatrix};
use std::mem::size_of; use std::mem::size_of;
#[derive(Debug, Clone, Copy, Zeroable, Pod)] #[derive(Debug, Clone, Copy, Zeroable, Pod)]
@ -84,10 +85,20 @@ static_assertions::const_assert_eq!(size_of::<AnimationDescriptionHeader>(), 100
pub struct AnimationDescription { pub struct AnimationDescription {
pub name: String, pub name: String,
pub fps: f32, pub fps: f32,
pub frame_count: i32, pub frame_count: usize,
pub animations: Vec<Animation>, pub animations: Vec<Animation>,
} }
impl AnimationDescription {
pub fn get_bone_transform(&self, bone: u8, frame: usize) -> Matrix4<f32> {
let Some(animation) = self.animations.iter().find(|anim| anim.bone == bone) else {
return Matrix4::identity();
};
Matrix4::from_translation(animation.position(frame).into())
* Matrix4::from(animation.rotation(frame))
}
}
impl ReadRelative for AnimationDescription { impl ReadRelative for AnimationDescription {
type Header = AnimationDescriptionHeader; type Header = AnimationDescriptionHeader;
@ -110,7 +121,7 @@ impl ReadRelative for AnimationDescription {
Ok(AnimationDescription { Ok(AnimationDescription {
name: read_single(data, header.name_offset)?, name: read_single(data, header.name_offset)?,
fps: header.fps, fps: header.fps,
frame_count: header.frame_count, frame_count: header.frame_count as usize,
animations, animations,
}) })
} }
@ -234,20 +245,43 @@ impl<'a> FrameValues<'a> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum RotationData { pub enum RotationData {
Quaternion48(Quaternion48), Quaternion48(Quaternion),
Quaternion64(Quaternion64), Quaternion64(Quaternion),
RotationValues(Vec<RadianEuler>), Animated(Vec<RadianEuler>),
None, None,
} }
impl From<Quaternion48> for RotationData {
fn from(value: Quaternion48) -> Self {
let q = Quaternion::from(value);
RotationData::Quaternion48(q)
}
}
impl From<Quaternion64> for RotationData {
fn from(value: Quaternion64) -> Self {
let q = Quaternion::from(value);
RotationData::Quaternion64(q)
}
}
impl From<Vec<RadianEuler>> for RotationData {
fn from(value: Vec<RadianEuler>) -> Self {
// axis get fixed up when applying the scale
RotationData::Animated(value)
}
}
impl RotationData { impl RotationData {
pub fn rotation(&self, frame: usize) -> Quaternion { pub fn rotation(&self, frame: usize) -> Quaternion {
match self { match self {
RotationData::Quaternion48(q) => Quaternion::from(*q), RotationData::Quaternion48(q) => *q,
RotationData::Quaternion64(q) => Quaternion::from(*q), RotationData::Quaternion64(q) => *q,
RotationData::RotationValues(values) => { RotationData::Animated(values) => values
values.get(frame).copied().unwrap_or_default().into() .get(frame)
} .copied()
.unwrap_or_else(|| values.last().copied().unwrap_or_default())
.into(),
RotationData::None => Quaternion::default(), RotationData::None => Quaternion::default(),
} }
} }
@ -256,18 +290,19 @@ impl RotationData {
match self { match self {
RotationData::Quaternion48(_) => size_of::<Quaternion48>(), RotationData::Quaternion48(_) => size_of::<Quaternion48>(),
RotationData::Quaternion64(_) => size_of::<Quaternion64>(), RotationData::Quaternion64(_) => size_of::<Quaternion64>(),
RotationData::RotationValues(_) => size_of::<AnimationValuePointer>(), RotationData::Animated(_) => size_of::<AnimationValuePointer>(),
RotationData::None => 0, RotationData::None => 0,
} }
} }
fn set_scale(&mut self, scale: Vector) { fn set_scale(&mut self, scale: Vector) {
if let RotationData::RotationValues(values) = self { if let RotationData::Animated(values) = self {
values.iter_mut().for_each(|value| { values.iter_mut().for_each(|value| {
// scale and fixup the angles
*value = RadianEuler { *value = RadianEuler {
x: value.x * scale.x, y: value.x * scale.x,
y: value.y * scale.y, z: value.y * scale.y,
z: value.z * scale.z, x: value.z * scale.z,
} }
}); });
} }
@ -317,6 +352,10 @@ impl Animation {
self.rotation_data.rotation(frame) self.rotation_data.rotation(frame)
} }
pub(crate) fn rotation_looks_valid(&self) -> bool {
true
}
pub fn position(&self, frame: usize) -> Vector { pub fn position(&self, frame: usize) -> Vector {
self.position_data.position(frame) self.position_data.position(frame)
} }
@ -343,17 +382,17 @@ fn read_animation(
let offset = size_of::<AnimationHeader>(); let offset = size_of::<AnimationHeader>();
let rotation_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT) { let rotation_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT) {
RotationData::Quaternion48(read_single(data, offset)?) RotationData::from(read_single::<Quaternion48, _>(data, offset)?)
} else if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT2) { } else if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWROT2) {
RotationData::Quaternion64(read_single(data, offset)?) RotationData::from(read_single::<Quaternion64, _>(data, offset)?)
} else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMROT) { } else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMROT) {
let pointers: AnimationValuePointer = read_single(data, offset)?; let pointers: AnimationValuePointer = read_single(data, offset)?;
let value_data = &data[offset..]; let value_data = &data[offset..];
let values = (0..frames) let values: Vec<RadianEuler> = (0..frames)
.map(|frame| read_animation_values(value_data, frame, pointers)) .map(|frame| read_animation_values(value_data, frame, pointers))
.map(|r| r.map(|[x, y, z]| RadianEuler { x, y, z })) .map(|r| r.map(|[x, y, z]| RadianEuler { x, z, y }))
.collect::<Result<_, ModelError>>()?; .collect::<Result<_, ModelError>>()?;
RotationData::RotationValues(values) RotationData::from(values)
} else { } else {
RotationData::None RotationData::None
}; };
@ -362,8 +401,8 @@ fn read_animation(
let position_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWPOS) { let position_data = if header.flags.contains(AnimationFlags::STUDIO_ANIM_RAWPOS) {
PositionData::Vector48(read_single(data, position_offset)?) PositionData::Vector48(read_single(data, position_offset)?)
} else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMPOS) { } else if header.flags.contains(AnimationFlags::STUDIO_ANIM_ANIMPOS) {
let pointers: AnimationValuePointer = read_single(data, offset)?; let pointers: AnimationValuePointer = read_single(data, position_offset)?;
let value_data = &data[offset..]; let value_data = &data[position_offset..];
let values = (0..frames) let values = (0..frames)
.map(|frame| read_animation_values(value_data, frame, pointers)) .map(|frame| read_animation_values(value_data, frame, pointers))
.map(|r| r.map(Vector::from)) .map(|r| r.map(Vector::from))

View file

@ -139,8 +139,7 @@ pub struct StudioHeader {
anim_block_model: i32, // Placeholder for mutable-void* anim_block_model: i32, // Placeholder for mutable-void*
// Points to a series of bytes? bone_table_by_name_index: i32,
bone_table_name_index: i32,
vertex_base: i32, // Placeholder for void* vertex_base: i32, // Placeholder for void*
offset_base: i32, // Placeholder for void* offset_base: i32, // Placeholder for void*
@ -155,9 +154,9 @@ pub struct StudioHeader {
num_allowed_root_lods: u8, num_allowed_root_lods: u8,
#[allow(dead_code)] #[allow(dead_code)]
unused0: u8, // ?? unused0: u8,
#[allow(dead_code)] #[allow(dead_code)]
unused1: i32, // ?? unused1: i32,
flex_controller_ui_count: i32, flex_controller_ui_count: i32,
flex_controller_ui_index: i32, flex_controller_ui_index: i32,
@ -219,6 +218,14 @@ impl StudioHeader {
) )
} }
pub fn bone_table_by_name_indexes(&self) -> impl Iterator<Item = usize> {
index_range(
self.bone_table_by_name_index,
self.bone_count,
size_of::<u8>(),
)
}
pub fn hitbox_set_indexes(&self) -> impl Iterator<Item = usize> { pub fn hitbox_set_indexes(&self) -> impl Iterator<Item = usize> {
index_range( index_range(
self.hitbox_set_offset, self.hitbox_set_offset,
@ -235,10 +242,6 @@ impl StudioHeader {
) )
} }
pub fn local_sequence_indexes(&self) -> impl Iterator<Item = usize> {
index_range(self.local_seq_offset, self.local_seq_count, 1)
}
pub fn texture_indexes(&self) -> impl Iterator<Item = usize> { pub fn texture_indexes(&self) -> impl Iterator<Item = usize> {
index_range( index_range(
self.texture_offset, self.texture_offset,

View file

@ -130,10 +130,33 @@ impl From<cgmath::Quaternion<f32>> for Quaternion {
impl From<Quaternion> for cgmath::Matrix4<f32> { impl From<Quaternion> for cgmath::Matrix4<f32> {
fn from(q: Quaternion) -> Self { fn from(q: Quaternion) -> Self {
// cgmath::Quaternion::from(Quaternion {
// x: q.z,
// y: -q.y,
// z: q.x,
// w: q.w,
// })
// .into()
cgmath::Quaternion::from(q).into() cgmath::Quaternion::from(q).into()
} }
} }
impl Mul for Quaternion {
type Output = Quaternion;
fn mul(self, rhs: Self) -> Self::Output {
(cgmath::Quaternion::from(self) * cgmath::Quaternion::from(rhs)).into()
}
}
impl Mul<RadianEuler> for Quaternion {
type Output = Quaternion;
fn mul(self, rhs: RadianEuler) -> Self::Output {
(cgmath::Quaternion::from(self) * cgmath::Quaternion::from(rhs)).into()
}
}
#[derive(Debug, Clone, Copy, Zeroable, Pod, Default)] #[derive(Debug, Clone, Copy, Zeroable, Pod, Default)]
#[repr(C)] #[repr(C)]
pub struct RadianEuler { pub struct RadianEuler {
@ -165,10 +188,9 @@ impl From<RadianEuler> for Euler<Deg<f32>> {
impl From<RadianEuler> for cgmath::Quaternion<f32> { impl From<RadianEuler> for cgmath::Quaternion<f32> {
fn from(value: RadianEuler) -> Self { fn from(value: RadianEuler) -> Self {
// angles are applied in roll, pitch, yaw order // angles are applied in roll, pitch, yaw order
// additionally the access are remapped
cgmath::Quaternion::from_angle_y(Rad(value.y)) cgmath::Quaternion::from_angle_y(Rad(value.y))
* cgmath::Quaternion::from_angle_x(Rad(-value.z)) * cgmath::Quaternion::from_angle_x(Rad(-value.x))
* cgmath::Quaternion::from_angle_z(Rad(value.x)) * cgmath::Quaternion::from_angle_z(Rad(value.z))
} }
} }
@ -178,6 +200,12 @@ impl From<RadianEuler> for Quaternion {
} }
} }
impl From<RadianEuler> for Matrix4<f32> {
fn from(value: RadianEuler) -> Self {
cgmath::Quaternion::from(value).into()
}
}
/// Fixed length, null-terminated string /// Fixed length, null-terminated string
#[derive(Debug, Clone, Default, Copy)] #[derive(Debug, Clone, Default, Copy)]
pub struct FixedString<const LEN: usize>(ArrayString<LEN>); pub struct FixedString<const LEN: usize>(ArrayString<LEN>);

View file

@ -89,7 +89,7 @@ pub struct BoneWeights {
impl BoneWeights { impl BoneWeights {
pub fn weights(&self) -> impl Iterator<Item = BoneWeight> + '_ { pub fn weights(&self) -> impl Iterator<Item = BoneWeight> + '_ {
(0..min(self.bone_count as usize, 3)).map(|i| BoneWeight { (0..min(self.bone_count as usize, 3)).map(|i| BoneWeight {
weight: self.weight[i], weight: self.weight[i] / self.bone_count as f32,
bone_id: self.bone[i], bone_id: self.bone[i],
}) })
} }