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

@ -43,11 +43,21 @@ pub struct Quaternion48 {
z: u16,
}
impl Default for Quaternion48 {
fn default() -> Self {
Quaternion48 {
x: 32768,
y: 32768,
z: 16384,
}
}
}
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
f32::sqrt(1.0 - (x * x) - (y * y) - (z * z)) * w_sign
}
impl Quaternion48 {
@ -92,14 +102,13 @@ pub struct Quaternion64(u64);
impl ReadableRelative for 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;
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
}
pub fn x(&self) -> f32 {
self.val(0)
}
@ -121,7 +130,7 @@ impl Quaternion64 {
impl From<Quaternion64> for Quaternion {
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 {
x: normalized.x,
y: normalized.y,

View file

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

View file

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

View file

@ -6,6 +6,7 @@ use crate::{
};
use bitflags::bitflags;
use bytemuck::{Pod, Zeroable};
use cgmath::{Matrix4, SquareMatrix};
use std::mem::size_of;
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
@ -84,10 +85,20 @@ static_assertions::const_assert_eq!(size_of::<AnimationDescriptionHeader>(), 100
pub struct AnimationDescription {
pub name: String,
pub fps: f32,
pub frame_count: i32,
pub frame_count: usize,
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 {
type Header = AnimationDescriptionHeader;
@ -110,7 +121,7 @@ impl ReadRelative for AnimationDescription {
Ok(AnimationDescription {
name: read_single(data, header.name_offset)?,
fps: header.fps,
frame_count: header.frame_count,
frame_count: header.frame_count as usize,
animations,
})
}
@ -234,20 +245,43 @@ impl<'a> FrameValues<'a> {
#[derive(Clone, Debug)]
pub enum RotationData {
Quaternion48(Quaternion48),
Quaternion64(Quaternion64),
RotationValues(Vec<RadianEuler>),
Quaternion48(Quaternion),
Quaternion64(Quaternion),
Animated(Vec<RadianEuler>),
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 {
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::Quaternion48(q) => *q,
RotationData::Quaternion64(q) => *q,
RotationData::Animated(values) => values
.get(frame)
.copied()
.unwrap_or_else(|| values.last().copied().unwrap_or_default())
.into(),
RotationData::None => Quaternion::default(),
}
}
@ -256,18 +290,19 @@ impl RotationData {
match self {
RotationData::Quaternion48(_) => size_of::<Quaternion48>(),
RotationData::Quaternion64(_) => size_of::<Quaternion64>(),
RotationData::RotationValues(_) => size_of::<AnimationValuePointer>(),
RotationData::Animated(_) => size_of::<AnimationValuePointer>(),
RotationData::None => 0,
}
}
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| {
// scale and fixup the angles
*value = RadianEuler {
x: value.x * scale.x,
y: value.y * scale.y,
z: value.z * scale.z,
y: value.x * scale.x,
z: value.y * scale.y,
x: value.z * scale.z,
}
});
}
@ -317,6 +352,10 @@ impl Animation {
self.rotation_data.rotation(frame)
}
pub(crate) fn rotation_looks_valid(&self) -> bool {
true
}
pub fn position(&self, frame: usize) -> Vector {
self.position_data.position(frame)
}
@ -343,17 +382,17 @@ fn read_animation(
let offset = size_of::<AnimationHeader>();
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) {
RotationData::Quaternion64(read_single(data, offset)?)
RotationData::from(read_single::<Quaternion64, _>(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)
let values: Vec<RadianEuler> = (0..frames)
.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>>()?;
RotationData::RotationValues(values)
RotationData::from(values)
} else {
RotationData::None
};
@ -362,8 +401,8 @@ fn read_animation(
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 pointers: AnimationValuePointer = read_single(data, position_offset)?;
let value_data = &data[position_offset..];
let values = (0..frames)
.map(|frame| read_animation_values(value_data, frame, pointers))
.map(|r| r.map(Vector::from))

View file

@ -139,8 +139,7 @@ pub struct StudioHeader {
anim_block_model: i32, // Placeholder for mutable-void*
// Points to a series of bytes?
bone_table_name_index: i32,
bone_table_by_name_index: i32,
vertex_base: i32, // Placeholder for void*
offset_base: i32, // Placeholder for void*
@ -155,9 +154,9 @@ pub struct StudioHeader {
num_allowed_root_lods: u8,
#[allow(dead_code)]
unused0: u8, // ??
unused0: u8,
#[allow(dead_code)]
unused1: i32, // ??
unused1: i32,
flex_controller_ui_count: 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> {
index_range(
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> {
index_range(
self.texture_offset,

View file

@ -130,10 +130,33 @@ impl From<cgmath::Quaternion<f32>> for Quaternion {
impl From<Quaternion> for cgmath::Matrix4<f32> {
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()
}
}
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)]
#[repr(C)]
pub struct RadianEuler {
@ -165,10 +188,9 @@ impl From<RadianEuler> for Euler<Deg<f32>> {
impl From<RadianEuler> for cgmath::Quaternion<f32> {
fn from(value: RadianEuler) -> Self {
// 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_x(Rad(-value.z))
* cgmath::Quaternion::from_angle_z(Rad(value.x))
* cgmath::Quaternion::from_angle_x(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
#[derive(Debug, Clone, Default, Copy)]
pub struct FixedString<const LEN: usize>(ArrayString<LEN>);

View file

@ -89,7 +89,7 @@ pub struct BoneWeights {
impl BoneWeights {
pub fn weights(&self) -> impl Iterator<Item = 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],
})
}