mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 16:44:11 +02:00
procedural bone data
This commit is contained in:
parent
c58d911bf6
commit
d128a94584
7 changed files with 291 additions and 122 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2889,6 +2889,7 @@ dependencies = [
|
|||
"itertools 0.12.0",
|
||||
"main_error",
|
||||
"miette",
|
||||
"num_enum 0.7.1",
|
||||
"static_assertions",
|
||||
"tf-asset-loader",
|
||||
"thiserror",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ itertools = "0.12.0"
|
|||
tracing = "0.1.37"
|
||||
bytemuck = { version = "1.14.0", features = ["derive"] }
|
||||
cgmath = "0.18.0"
|
||||
num_enum = "0.7.1"
|
||||
|
||||
[dev-dependencies]
|
||||
three-d = { version = "0.14.0", features = ["egui-gui"] }
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ fn main() -> Result<(), vmdl::ModelError> {
|
|||
let _vvd = Vvd::read(&data)?;
|
||||
|
||||
for bone in mdl.bones {
|
||||
println!("{}: from {} at {:?}", bone.name, bone.parent, bone.rot);
|
||||
println!(
|
||||
"{}: from {} at\n\t{:?}\n\t{:?}",
|
||||
bone.name, bone.parent, bone.rot, bone.procedural_rules
|
||||
);
|
||||
}
|
||||
|
||||
// let model = Model::from_parts(mdl, vtx, vvd);
|
||||
|
|
|
|||
|
|
@ -250,12 +250,7 @@ fn model_to_model(model: &Model, loader: &Loader, skin: usize) -> CpuModel {
|
|||
|
||||
let skin = model.skin_tables().nth(skin).unwrap();
|
||||
|
||||
let transforms = model
|
||||
.bones()
|
||||
.filter(|bone| bone.name == "root")
|
||||
.next()
|
||||
.map(|bone| Mat4::from(cgmath::Quaternion::from(bone.rot)))
|
||||
.unwrap_or_else(|| Matrix4::identity());
|
||||
let transforms = model.root_transform();
|
||||
|
||||
let geometries = model
|
||||
.meshes()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub use crate::vtx::Vtx;
|
|||
use crate::vvd::Vertex;
|
||||
pub use crate::vvd::Vvd;
|
||||
use bytemuck::{pod_read_unaligned, Pod};
|
||||
use cgmath::{Matrix4, SquareMatrix};
|
||||
pub use error::*;
|
||||
pub use handle::Handle;
|
||||
use itertools::Either;
|
||||
|
|
@ -141,6 +142,14 @@ impl Model {
|
|||
pub fn bones(&self) -> impl Iterator<Item = &Bone> {
|
||||
self.mdl.bones.iter()
|
||||
}
|
||||
|
||||
pub fn root_transform(&self) -> Matrix4<f32> {
|
||||
self.bones()
|
||||
.next()
|
||||
.map(|bone| Quaternion::from(bone.rot))
|
||||
.map(|rotation| Matrix4::from(rotation))
|
||||
.unwrap_or_else(|| Matrix4::identity())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SkinTable<'a> {
|
||||
|
|
|
|||
271
src/mdl/raw/bones.rs
Normal file
271
src/mdl/raw/bones.rs
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
use crate::{ModelError, Quaternion, RadianEuler, ReadRelative, Readable, Vector};
|
||||
use bitflags::bitflags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct BoneHeader {
|
||||
pub sz_name_index: i32,
|
||||
pub parent: i32, // parent bone
|
||||
pub bone_controller: [i32; 6], // bone controller index, -1 == none
|
||||
|
||||
pub pos: Vector,
|
||||
pub quaternion: Quaternion,
|
||||
pub rot: RadianEuler,
|
||||
pub pos_scale: Vector,
|
||||
pub rot_scale: Vector,
|
||||
|
||||
pub pose_to_bone: [[f32; 3]; 4], // 3x4 matrix
|
||||
pub q_alignment: Quaternion,
|
||||
pub flags: BoneFlags,
|
||||
pub proc_type: i32,
|
||||
pub proc_index: i32, // procedural rule
|
||||
pub physics_bone: i32, // index into physically simulated bone
|
||||
pub surface_prop_idx: i32, // index into string table for property name
|
||||
pub contents: i32, // See BSPFlags.h for the contents flags
|
||||
|
||||
#[allow(dead_code)]
|
||||
reserved: [i32; 8], // remove as appropriate
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(size_of::<BoneHeader>(), 216);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Bone {
|
||||
pub name: String,
|
||||
pub parent: i32, // parent bone
|
||||
pub bone_controller: [i32; 6], // bone controller index, -1 == none
|
||||
|
||||
pub pos: Vector,
|
||||
pub quaternion: Quaternion,
|
||||
pub rot: RadianEuler,
|
||||
pub pos_scale: Vector,
|
||||
pub rot_scale: Vector,
|
||||
|
||||
pub pose_to_bone: [[f32; 3]; 4], // 3x4 matrix
|
||||
pub q_alignment: Quaternion,
|
||||
pub flags: BoneFlags,
|
||||
pub procedural_rules: Option<ProceduralBone>,
|
||||
pub physics_bone: i32, // index into physically simulated bone
|
||||
pub surface_prop: String,
|
||||
pub contents: i32, // See BSPFlags.h for the contents flags
|
||||
}
|
||||
|
||||
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_else(|| 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_else(|| 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(|| {
|
||||
data.get(header.proc_index as usize..)
|
||||
.ok_or_else(|| ModelError::OutOfBounds {
|
||||
data: "bone surface property",
|
||||
offset: header.proc_index as usize,
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let procedural_rules = prop_type
|
||||
.zip(proc_bytes)
|
||||
.map(|(ty, bytes)| {
|
||||
Result::<_, ModelError>::Ok(match ty {
|
||||
ProceduralBoneType::AxisInterp => {
|
||||
ProceduralBone::AxisInterp(AxisInterpBone::read(bytes)?)
|
||||
}
|
||||
ProceduralBoneType::QuaternionInterp => {
|
||||
ProceduralBone::QuaternionInterp(QuaternionInterpBone::read(bytes)?)
|
||||
}
|
||||
ProceduralBoneType::AiMatBone => {
|
||||
ProceduralBone::AiMatBone(AiMatBone::read(bytes)?)
|
||||
}
|
||||
ProceduralBoneType::AiMatAttach => {
|
||||
ProceduralBone::AiMatAttach(AiMatBone::read(bytes)?)
|
||||
}
|
||||
ProceduralBoneType::Jiggle => ProceduralBone::Jiggle(JiggleBone::read(bytes)?),
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(Bone {
|
||||
name: String::read(name_bytes, ())?,
|
||||
parent: header.parent,
|
||||
bone_controller: header.bone_controller,
|
||||
pos: header.pos,
|
||||
quaternion: header.quaternion,
|
||||
rot: header.rot,
|
||||
pos_scale: header.pos_scale,
|
||||
rot_scale: header.rot_scale,
|
||||
pose_to_bone: header.pose_to_bone,
|
||||
q_alignment: header.q_alignment,
|
||||
flags: header.flags,
|
||||
procedural_rules,
|
||||
physics_bone: header.physics_bone,
|
||||
surface_prop: String::read(surface_prop_bytes, ())?,
|
||||
contents: header.contents,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct BoneFlags(u32);
|
||||
|
||||
bitflags! {
|
||||
impl BoneFlags: u32 {
|
||||
const BONE_PHYSICALLY_SIMULATED = 0x00000001;
|
||||
const BONE_PHYSICS_PROCEDURAL = 0x00000002;
|
||||
const BONE_ALWAYS_PROCEDURAL = 0x00000004;
|
||||
const BONE_SCREEN_ALIGN_SPHERE = 0x00000008;
|
||||
const BONE_SCREEN_ALIGN_CYLINDER = 0x00000010;
|
||||
|
||||
const BONE_USED_BY_HITBOX = 0x00000100;
|
||||
const BONE_USED_BY_ATTACHMENT = 0x00000200;
|
||||
|
||||
const BONE_USED_BY_VERTEX_LOD0 = 0x00000400;
|
||||
const BONE_USED_BY_VERTEX_LOD1 = 0x00000800;
|
||||
const BONE_USED_BY_VERTEX_LOD2 = 0x00001000;
|
||||
const BONE_USED_BY_VERTEX_LOD3 = 0x00002000;
|
||||
const BONE_USED_BY_VERTEX_LOD4 = 0x00004000;
|
||||
const BONE_USED_BY_VERTEX_LOD5 = 0x00008000;
|
||||
const BONE_USED_BY_VERTEX_LOD6 = 0x00010000;
|
||||
const BONE_USED_BY_VERTEX_LOD7 = 0x00020000;
|
||||
const BONE_USED_BY_BONE_MERGE = 0x00040000;
|
||||
|
||||
const BONE_TYPE_MASK = 0x00F00000;
|
||||
const BONE_FIXED_ALIGNMENT = 0x00100000;
|
||||
|
||||
const BONE_HAS_SAVEFRAME_POS = 0x00200000;
|
||||
const BONE_HAS_SAVEFRAME_ROT = 0x00400000;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ProceduralBone {
|
||||
AxisInterp(AxisInterpBone),
|
||||
QuaternionInterp(QuaternionInterpBone),
|
||||
AiMatBone(AiMatBone),
|
||||
AiMatAttach(AiMatBone),
|
||||
Jiggle(JiggleBone),
|
||||
}
|
||||
|
||||
#[derive(TryFromPrimitive, Copy, Clone)]
|
||||
#[repr(i32)]
|
||||
pub enum ProceduralBoneType {
|
||||
AxisInterp = 1,
|
||||
QuaternionInterp = 2,
|
||||
AiMatBone = 3,
|
||||
AiMatAttach = 4,
|
||||
Jiggle = 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct AxisInterpBone {
|
||||
pub control: i32,
|
||||
pub axis: i32,
|
||||
pub position: [Vector; 6], // X+, X-, Y+, Y-, Z+, Z-
|
||||
pub quaternion: [Quaternion; 6], // X+, X-, Y+, Y-, Z+, Z-
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct QuaternionInterpBone {
|
||||
/// 1 / radian angle of trigger influence
|
||||
pub inverse_tolerance: f32,
|
||||
/// angle to match
|
||||
pub trigger: Quaternion,
|
||||
/// new position
|
||||
pub position: Vector,
|
||||
/// new angle
|
||||
pub quaternion: Quaternion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct AiMatBone {
|
||||
pub parent: i32,
|
||||
pub aim: i32,
|
||||
pub aim_vector: Vector,
|
||||
pub up_vector: Vector,
|
||||
pub base_position: Vector,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct JiggleBone {
|
||||
pub flags: JiggleBoneFlags,
|
||||
pub length: f32,
|
||||
pub tip_mass: f32,
|
||||
|
||||
pub yaw_stiffness: f32,
|
||||
pub yaw_damping: f32,
|
||||
pub pitch_stiffness: f32,
|
||||
pub pitch_damping: f32,
|
||||
pub along_stiffness: f32,
|
||||
pub along_damping: f32,
|
||||
|
||||
pub angle_limit: f32,
|
||||
|
||||
pub min_yaw: f32,
|
||||
pub max_yaw: f32,
|
||||
pub yaw_friction: f32,
|
||||
pub yaw_bound: f32,
|
||||
|
||||
pub min_pitch: f32,
|
||||
pub max_pitch: f32,
|
||||
pub pitch_friction: f32,
|
||||
pub pitch_bounce: f32,
|
||||
|
||||
pub base_mass: f32,
|
||||
pub base_stiffness: f32,
|
||||
pub base_damping: f32,
|
||||
pub base_min_left: f32,
|
||||
pub base_max_left: f32,
|
||||
pub base_left_friction: f32,
|
||||
pub base_min_up: f32,
|
||||
pub base_max_up: f32,
|
||||
pub base_up_friction: f32,
|
||||
pub base_min_forward: f32,
|
||||
pub base_max_forward: f32,
|
||||
pub base_forward_friction: f32,
|
||||
|
||||
pub boing_impact_speed: f32,
|
||||
pub boing_impact_angle: f32,
|
||||
pub boing_damping_rate: f32,
|
||||
pub boing_frequency: f32,
|
||||
pub boing_amplitute: f32,
|
||||
}
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct JiggleBoneFlags(u32);
|
||||
|
||||
bitflags! {
|
||||
impl JiggleBoneFlags: u32 {
|
||||
const JIGGLE_IS_FLEXIBLE = 0x01;
|
||||
const JIGGLE_IS_RIGID = 0x02;
|
||||
const JIGGLE_HAS_YAW_CONSTRAINT = 0x04;
|
||||
const JIGGLE_HAS_PITCH_CONSTRAINT = 0x08;
|
||||
const JIGGLE_HAS_ANGLE_CONSTRAINT = 0x10;
|
||||
const JIGGLE_HAS_LENGTH_CONSTRAINT = 0x20;
|
||||
const JIGGLE_HAS_BASE_SPRING = 0x40;
|
||||
/// simple squash and stretch sinusoid "boing"
|
||||
const JIGGLE_IS_BOING = 0x80;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +1,13 @@
|
|||
use crate::{index_range, FixedString, ModelError, ReadRelative};
|
||||
use crate::{Quaternion, RadianEuler, Vector};
|
||||
use crate::Vector;
|
||||
use crate::{index_range, FixedString};
|
||||
use bitflags::bitflags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::mem::size_of;
|
||||
|
||||
mod bones;
|
||||
pub mod header;
|
||||
pub mod header2;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct BoneHeader {
|
||||
pub sz_name_index: i32,
|
||||
pub parent: i32, // parent bone
|
||||
pub bone_controller: [i32; 6], // bone controller index, -1 == none
|
||||
|
||||
pub pos: Vector,
|
||||
pub quaternion: Quaternion,
|
||||
pub rot: RadianEuler,
|
||||
pub pos_scale: Vector,
|
||||
pub rot_scale: Vector,
|
||||
|
||||
pub pose_to_bone: [[f32; 3]; 4], // 3x4 matrix
|
||||
pub q_alignment: Quaternion,
|
||||
pub flags: BoneFlags,
|
||||
pub proc_type: i32,
|
||||
pub proc_index: i32, // procedural rule
|
||||
pub physics_bone: i32, // index into physically simulated bone
|
||||
pub surface_prop_idx: i32, // index into string table for property name
|
||||
pub contents: i32, // See BSPFlags.h for the contents flags
|
||||
|
||||
#[allow(dead_code)]
|
||||
reserved: [i32; 8], // remove as appropriate
|
||||
}
|
||||
|
||||
static_assertions::const_assert_eq!(size_of::<BoneHeader>(), 216);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Bone {
|
||||
pub name: String,
|
||||
pub parent: i32, // parent bone
|
||||
pub bone_controller: [i32; 6], // bone controller index, -1 == none
|
||||
|
||||
pub pos: Vector,
|
||||
pub quaternion: Quaternion,
|
||||
pub rot: RadianEuler,
|
||||
pub pos_scale: Vector,
|
||||
pub rot_scale: Vector,
|
||||
|
||||
pub pose_to_bone: [[f32; 3]; 4], // 3x4 matrix
|
||||
pub q_alignment: Quaternion,
|
||||
pub flags: BoneFlags,
|
||||
pub proc_type: i32,
|
||||
pub proc_index: i32, // procedural rule
|
||||
pub physics_bone: i32, // index into physically simulated bone
|
||||
pub surface_prop_idx: i32, // index into string table for property name
|
||||
pub contents: i32, // See BSPFlags.h for the contents flags
|
||||
}
|
||||
|
||||
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..)
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(Bone {
|
||||
name: String::read(name_bytes, ())?,
|
||||
parent: header.parent,
|
||||
bone_controller: header.bone_controller,
|
||||
pos: header.pos,
|
||||
quaternion: header.quaternion,
|
||||
rot: header.rot,
|
||||
pos_scale: header.pos_scale,
|
||||
rot_scale: header.rot_scale,
|
||||
pose_to_bone: header.pose_to_bone,
|
||||
q_alignment: header.q_alignment,
|
||||
flags: header.flags,
|
||||
proc_type: header.proc_type,
|
||||
proc_index: header.proc_index,
|
||||
physics_bone: header.physics_bone,
|
||||
surface_prop_idx: header.surface_prop_idx,
|
||||
contents: header.contents,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroable, Pod, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct BoneFlags(u32);
|
||||
|
||||
bitflags! {
|
||||
impl BoneFlags: u32 {
|
||||
const BONE_PHYSICALLY_SIMULATED = 0x00000001;
|
||||
const BONE_PHYSICS_PROCEDURAL = 0x00000002;
|
||||
const BONE_ALWAYS_PROCEDURAL = 0x00000004;
|
||||
const BONE_SCREEN_ALIGN_SPHERE = 0x00000008;
|
||||
const BONE_SCREEN_ALIGN_CYLINDER = 0x00000010;
|
||||
|
||||
const BONE_USED_BY_HITBOX = 0x00000100;
|
||||
const BONE_USED_BY_ATTACHMENT = 0x00000200;
|
||||
|
||||
const BONE_USED_BY_VERTEX_LOD0 = 0x00000400;
|
||||
const BONE_USED_BY_VERTEX_LOD1 = 0x00000800;
|
||||
const BONE_USED_BY_VERTEX_LOD2 = 0x00001000;
|
||||
const BONE_USED_BY_VERTEX_LOD3 = 0x00002000;
|
||||
const BONE_USED_BY_VERTEX_LOD4 = 0x00004000;
|
||||
const BONE_USED_BY_VERTEX_LOD5 = 0x00008000;
|
||||
const BONE_USED_BY_VERTEX_LOD6 = 0x00010000;
|
||||
const BONE_USED_BY_VERTEX_LOD7 = 0x00020000;
|
||||
const BONE_USED_BY_BONE_MERGE = 0x00040000;
|
||||
|
||||
const BONE_TYPE_MASK = 0x00F00000;
|
||||
const BONE_FIXED_ALIGNMENT = 0x00100000;
|
||||
|
||||
const BONE_HAS_SAVEFRAME_POS = 0x00200000;
|
||||
const BONE_HAS_SAVEFRAME_ROT = 0x00400000;
|
||||
}
|
||||
}
|
||||
pub use bones::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue