mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 16:44:11 +02:00
animation fixes
This commit is contained in:
parent
2d01af156e
commit
2309050a25
10 changed files with 397 additions and 66 deletions
|
|
@ -6,7 +6,7 @@ description = "Rust parser for valve model files."
|
|||
repository = "https://github.com/icewind1991/vmdl"
|
||||
license = "MIT"
|
||||
exclude = ["data"]
|
||||
rust-version = "1.74.0"
|
||||
rust-version = "1.76.0"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.6"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use cgmath::Matrix4;
|
||||
use std::env::args;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -25,9 +26,32 @@ fn main() -> Result<(), vmdl::ModelError> {
|
|||
);
|
||||
}
|
||||
|
||||
dbg!(Matrix4::from(mdl.bones[1].pose_to_bone.clone()));
|
||||
|
||||
// for animation_desc in &mdl.local_animations {
|
||||
// println!(
|
||||
// "{}: {} frames at {}fps",
|
||||
// animation_desc.name, animation_desc.frame_count, animation_desc.fps,
|
||||
// );
|
||||
// for animation in &animation_desc.animations {
|
||||
// println!(
|
||||
// "\tbone {:.2} frame 0:\n\t\trot: {:?}\n\t\tpos: {:?}",
|
||||
// animation.bone,
|
||||
// animation.rotation(0),
|
||||
// animation.position(0),
|
||||
// );
|
||||
// println!(
|
||||
// "\tbone {:.2} frame 1:\n\t\trot: {:?}\n\t\tpos: {:?}",
|
||||
// animation.bone,
|
||||
// animation.rotation(10),
|
||||
// animation.position(10),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
let model = Model::from_parts(mdl, vtx, vvd);
|
||||
dbg!(model.root_transform());
|
||||
dbg!(model.idle_transform());
|
||||
|
||||
let _ = model;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ mod material;
|
|||
use crate::error::Error;
|
||||
use crate::material::{load_material_fallback, MaterialData};
|
||||
use cgmath::{vec3, Matrix4, SquareMatrix};
|
||||
use std::collections::HashMap;
|
||||
use std::env::args_os;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tf_asset_loader::Loader;
|
||||
use three_d::{
|
||||
AmbientLight, Camera, ClearState, ColorMaterial, CpuMaterial, CpuMesh, CpuModel, CpuTexture,
|
||||
DepthMaterial, DirectionalLight, FrameOutput, Light, NormalMaterial, ORMMaterial, OrbitControl,
|
||||
PhysicalMaterial, PositionMaterial, UVMaterial, Vec2, Vec4, Window, WindowSettings,
|
||||
AmbientLight, Camera, ClearState, ColorMaterial, Context, CpuMaterial, CpuMesh, CpuModel,
|
||||
CpuTexture, DepthMaterial, DirectionalLight, FrameOutput, Light, NormalMaterial, ORMMaterial,
|
||||
OrbitControl, PhysicalMaterial, PositionMaterial, UVMaterial, Vec2, Vec4, Window,
|
||||
WindowSettings,
|
||||
};
|
||||
use three_d_asset::{
|
||||
degrees, Geometry, Mat4, Positions, Primitive, Srgba, TextureData, Vec3, Viewport,
|
||||
|
|
@ -64,8 +67,7 @@ fn main() -> Result<(), Error> {
|
|||
|
||||
let loader = Loader::new().expect("loader");
|
||||
let skin_count = source_model.skin_tables().count();
|
||||
|
||||
let cpu_models = (0..skin_count).map(|skin| model_to_model(&source_model, &loader, skin));
|
||||
let animation_count = source_model.animations().count();
|
||||
|
||||
let ph_material = PhysicalMaterial {
|
||||
albedo: Srgba {
|
||||
|
|
@ -77,10 +79,6 @@ fn main() -> Result<(), Error> {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let models: Vec<three_d::Model<PhysicalMaterial>> = cpu_models
|
||||
.map(|cpu_model| three_d::Model::new(&context, &cpu_model).expect("failed to load model"))
|
||||
.collect();
|
||||
|
||||
let mut directional = [
|
||||
DirectionalLight::new(&context, 1.0, Srgba::WHITE, vec3(1.0, -1.0, 0.0)),
|
||||
DirectionalLight::new(&context, 1.0, Srgba::WHITE, vec3(1.0, 1.0, 0.0)),
|
||||
|
|
@ -98,9 +96,19 @@ fn main() -> Result<(), Error> {
|
|||
let mut fov = 60.0;
|
||||
let mut debug_type = DebugType::NONE;
|
||||
let mut skin_index = 0;
|
||||
let mut animation_index = 0;
|
||||
let mut frame = 0;
|
||||
let mut skip_count = 0;
|
||||
let mut playing = false;
|
||||
|
||||
let model_builder = ModelBuilder::new(source_model, loader);
|
||||
|
||||
window.render_loop(move |mut frame_input| {
|
||||
let model = &models[skin_index];
|
||||
let model_key = ModelKey::new(skin_index, animation_index, frame);
|
||||
|
||||
let model = model_builder.get(&context, model_key);
|
||||
let frame_count = model_builder.frame_count(animation_index);
|
||||
|
||||
let mut change = frame_input.first_frame;
|
||||
let mut panel_width = frame_input.viewport.width;
|
||||
change |= gui.update(
|
||||
|
|
@ -142,6 +150,14 @@ fn main() -> Result<(), Error> {
|
|||
ui.add(Slider::new(&mut depth_max, 1.0..=30.0).text("Depth max"));
|
||||
ui.add(Slider::new(&mut fov, 45.0..=90.0).text("FOV"));
|
||||
|
||||
ui.label("Animation");
|
||||
ui.add(
|
||||
Slider::new(&mut animation_index, 0..=(animation_count - 1))
|
||||
.text("Animation"),
|
||||
);
|
||||
ui.add(Slider::new(&mut frame, 0..=(frame_count - 1)).text("Frame"));
|
||||
ui.add(Checkbox::new(&mut playing, "Play"));
|
||||
|
||||
ui.label("Position");
|
||||
ui.add(Label::new(format!("\tx: {}", camera.position().x)));
|
||||
ui.add(Label::new(format!("\ty: {}", camera.position().y)));
|
||||
|
|
@ -150,6 +166,17 @@ fn main() -> Result<(), Error> {
|
|||
panel_width = gui_context.used_size().x as u32;
|
||||
},
|
||||
);
|
||||
if playing {
|
||||
skip_count += 1;
|
||||
if skip_count > 4 {
|
||||
frame += 1;
|
||||
if frame >= frame_count {
|
||||
frame = 0;
|
||||
}
|
||||
change = true;
|
||||
skip_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let viewport = Viewport {
|
||||
x: panel_width as i32,
|
||||
|
|
@ -224,7 +251,7 @@ fn main() -> Result<(), Error> {
|
|||
model.iter().map(|gm| &gm.geometry),
|
||||
lights,
|
||||
),
|
||||
DebugType::NONE => target.render(&camera, model, lights),
|
||||
DebugType::NONE => target.render(&camera, model.as_ref(), lights),
|
||||
}
|
||||
.write(|| gui.render())
|
||||
.expect("failed to render");
|
||||
|
|
@ -249,17 +276,28 @@ pub fn map_coords<C: Into<Vec3>>(vec: C) -> Vec3 {
|
|||
}
|
||||
}
|
||||
|
||||
fn model_to_model(model: &Model, loader: &Loader, skin: usize) -> CpuModel {
|
||||
fn model_to_model(
|
||||
model: &Model,
|
||||
loader: &Loader,
|
||||
skin: usize,
|
||||
animation: usize,
|
||||
frame: usize,
|
||||
) -> CpuModel {
|
||||
let skin = model.skin_tables().nth(skin).unwrap();
|
||||
|
||||
let transforms = Matrix4::identity();
|
||||
|
||||
let animation = model
|
||||
.animations()
|
||||
.nth(animation)
|
||||
.unwrap_or_else(|| model.animations().next().unwrap());
|
||||
|
||||
let geometries = model
|
||||
.meshes()
|
||||
.map(|mesh| {
|
||||
let positions: Vec<Vec3> = mesh
|
||||
.vertices()
|
||||
.map(|vertex| model.apply_root_transform(vertex.position))
|
||||
.map(|vertex| model.apply_animation(animation, vertex, frame))
|
||||
.map(|position| map_coords(position) * 10.0)
|
||||
.map(|vertex: Vec3| (transforms * vertex.extend(1.0)).truncate())
|
||||
.collect();
|
||||
|
|
@ -348,3 +386,64 @@ fn convert_texture(texture: material::TextureData, keep_alpha: bool) -> CpuTextu
|
|||
..CpuTexture::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
struct ModelKey {
|
||||
skin: usize,
|
||||
animation: usize,
|
||||
frame: usize,
|
||||
}
|
||||
|
||||
impl ModelKey {
|
||||
pub fn new(skin: usize, animation: usize, frame: usize) -> Self {
|
||||
ModelKey {
|
||||
skin,
|
||||
animation,
|
||||
frame,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModelBuilder {
|
||||
source: Model,
|
||||
loader: Loader,
|
||||
cache: Mutex<HashMap<ModelKey, Arc<three_d::Model<PhysicalMaterial>>>>,
|
||||
}
|
||||
|
||||
impl ModelBuilder {
|
||||
fn new(source: Model, loader: Loader) -> Self {
|
||||
ModelBuilder {
|
||||
source,
|
||||
loader,
|
||||
cache: Mutex::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn frame_count(&self, animation: usize) -> usize {
|
||||
self.source
|
||||
.animations()
|
||||
.nth(animation)
|
||||
.map(|animation| animation.frame_count)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get(&self, context: &Context, key: ModelKey) -> Arc<three_d::Model<PhysicalMaterial>> {
|
||||
self.cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(key)
|
||||
.or_insert_with(|| {
|
||||
let cpu_model = model_to_model(
|
||||
&self.source,
|
||||
&self.loader,
|
||||
key.skin,
|
||||
key.animation,
|
||||
key.frame,
|
||||
);
|
||||
Arc::new(
|
||||
three_d::Model::new(context, &cpu_model).expect("failed to build gpu model"),
|
||||
)
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,116 @@
|
|||
use crate::mdl::Mdl;
|
||||
use crate::mdl::{Bone, BoneId, Mdl};
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A handle represents a mdl structure in the mdl file and the mdl file containing it.
|
||||
///
|
||||
/// Keeping a reference of the mdl file with the mdl is required since a lot of mdl types
|
||||
/// reference parts from other structures in the mdl file
|
||||
#[derive(Debug)]
|
||||
pub struct Handle<'a, T> {
|
||||
_mdl: &'a Mdl,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Handle<'a, T, K> {
|
||||
mdl: &'a Mdl,
|
||||
data: &'a T,
|
||||
key: K,
|
||||
}
|
||||
|
||||
impl<T> Clone for Handle<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Handle { ..*self }
|
||||
impl<T, K: PartialEq> PartialEq for Handle<'_, T, K> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key == other.key
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> AsRef<T> for Handle<'a, T> {
|
||||
impl<'a, T, K> Handle<'a, T, K> {
|
||||
pub fn new(mdl: &'a Mdl, data: &'a T, key: K) -> Self {
|
||||
Self { mdl, data, key }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, K: Clone> Handle<'_, T, K> {
|
||||
pub fn key(&self) -> K {
|
||||
self.key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, K> AsRef<T> for Handle<'a, T, K> {
|
||||
fn as_ref(&self) -> &'a T {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Handle<'_, T> {
|
||||
impl<T, K> Deref for Handle<'_, T, K> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Handle<'a, Bone, BoneId> {
|
||||
pub fn parent(&self) -> Option<Self> {
|
||||
Some(Self::new(
|
||||
self.mdl,
|
||||
self.mdl.bones.get(usize::from(self.parent))?,
|
||||
self.parent,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn children(&self) -> impl Iterator<Item = Self> + 'a {
|
||||
let key = self.key();
|
||||
let mdl = self.mdl;
|
||||
self.mdl
|
||||
.bones
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(move |(_, bone)| bone.parent == key)
|
||||
.map(move |(i, bone)| Self::new(mdl, bone, i.into()))
|
||||
}
|
||||
|
||||
pub fn tree(&self) -> impl Iterator<Item = Self> {
|
||||
BoneTreeIter::new(self.clone())
|
||||
}
|
||||
|
||||
pub fn ancestors(&self) -> impl Iterator<Item = Self> {
|
||||
BoneAncestorsIter { bone: self.clone() }
|
||||
}
|
||||
|
||||
pub fn is_affected_by(&self, bone_id: BoneId) -> bool {
|
||||
self.key == bone_id || self.ancestors().any(|ancestor| ancestor.key == bone_id)
|
||||
}
|
||||
}
|
||||
|
||||
struct BoneTreeIter<'a> {
|
||||
queue: VecDeque<Handle<'a, Bone, BoneId>>,
|
||||
}
|
||||
|
||||
impl<'a> BoneTreeIter<'a> {
|
||||
pub fn new(root: Handle<'a, Bone, BoneId>) -> Self {
|
||||
let mut queue = VecDeque::with_capacity(16);
|
||||
queue.push_back(root);
|
||||
BoneTreeIter { queue }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BoneTreeIter<'a> {
|
||||
type Item = Handle<'a, Bone, BoneId>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next = self.queue.pop_front()?;
|
||||
self.queue.extend(next.children());
|
||||
|
||||
Some(next)
|
||||
}
|
||||
}
|
||||
|
||||
struct BoneAncestorsIter<'a> {
|
||||
bone: Handle<'a, Bone, BoneId>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BoneAncestorsIter<'a> {
|
||||
type Item = Handle<'a, Bone, BoneId>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next = self.bone.parent()?;
|
||||
self.bone = next.clone();
|
||||
Some(next)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
57
src/lib.rs
57
src/lib.rs
|
|
@ -7,7 +7,9 @@ pub mod vtx;
|
|||
pub mod vvd;
|
||||
|
||||
pub use crate::mdl::Mdl;
|
||||
use crate::mdl::{AnimationDescription, Bone, ModelFlags, PoseParameterDescription, TextureInfo};
|
||||
use crate::mdl::{
|
||||
AnimationDescription, Bone, BoneId, ModelFlags, PoseParameterDescription, TextureInfo,
|
||||
};
|
||||
pub use crate::vtx::Vtx;
|
||||
use crate::vvd::Vertex;
|
||||
pub use crate::vvd::Vvd;
|
||||
|
|
@ -134,8 +136,19 @@ impl Model {
|
|||
self.mdl.name.as_str()
|
||||
}
|
||||
|
||||
pub fn bones(&self) -> impl Iterator<Item = &Bone> {
|
||||
self.mdl.bones.iter()
|
||||
pub fn bones(&self) -> impl Iterator<Item = Handle<Bone, BoneId>> {
|
||||
self.mdl
|
||||
.bones
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, bone)| Handle::new(&self.mdl, bone, i.into()))
|
||||
}
|
||||
|
||||
pub fn bone(&self, id: BoneId) -> Option<Handle<Bone, BoneId>> {
|
||||
self.mdl
|
||||
.bones
|
||||
.get(usize::from(id))
|
||||
.map(|bone| Handle::new(&self.mdl, bone, id))
|
||||
}
|
||||
|
||||
pub fn root_transform(&self) -> Matrix4<f32> {
|
||||
|
|
@ -157,7 +170,11 @@ impl Model {
|
|||
self.mdl
|
||||
.local_animations
|
||||
.iter()
|
||||
.filter_map(|desc| desc.animations.iter().find(|animation| animation.bone == 0))
|
||||
.filter_map(|desc| {
|
||||
desc.animations
|
||||
.iter()
|
||||
.find(|animation| animation.bone == BoneId::default())
|
||||
})
|
||||
.next()
|
||||
.map(|animation| animation.rotation(0))
|
||||
.map(Matrix4::from)
|
||||
|
|
@ -176,6 +193,38 @@ impl Model {
|
|||
let transform = self.idle_transform() * self.root_transform();
|
||||
transform.transform_vector(Vector3::from(vec)).into()
|
||||
}
|
||||
|
||||
pub fn apply_animation(
|
||||
&self,
|
||||
animation: &AnimationDescription,
|
||||
vertex: &Vertex,
|
||||
frame: usize,
|
||||
) -> Vector {
|
||||
let mut position = vertex.position.into();
|
||||
for animation in animation.animations.iter() {
|
||||
if let Some(animated_bone) = self.bone(animation.bone) {
|
||||
let weight: f32 = vertex
|
||||
.bone_weights
|
||||
.weights()
|
||||
.flat_map(|weight| Some((self.bone(weight.bone_id)?, weight)))
|
||||
.filter(|(bone, _)| bone.is_affected_by(animated_bone.key()))
|
||||
.map(|(_, weight)| weight.weight)
|
||||
.sum();
|
||||
|
||||
let pose_to_bone = animated_bone.pos.into();
|
||||
|
||||
let bone_rotation = Matrix4::from(animated_bone.rot);
|
||||
if weight > 0.0 {
|
||||
position -= pose_to_bone;
|
||||
let transform = (animation.transform(frame)) * bone_rotation;
|
||||
position = transform.transform_vector(position);
|
||||
position += pose_to_bone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
position.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SkinTable<'a> {
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ impl Mdl {
|
|||
.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);
|
||||
if let Some(bone) = bones.get(usize::from(animation.bone)) {
|
||||
animation.apply_bone_data(bone);
|
||||
}
|
||||
});
|
||||
let animation_block_source: String = read_single(data, header.anim_blocks_name_index)?;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use crate::compressed_vector::{Quaternion48, Quaternion64, Vector48};
|
||||
use crate::mdl::Bone;
|
||||
use crate::mdl::{Bone, BoneId};
|
||||
use crate::{
|
||||
index_range, read_relative, read_single, ModelError, Quaternion, RadianEuler, ReadRelative,
|
||||
Readable, ReadableRelative, Vector,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use cgmath::{Matrix4, SquareMatrix};
|
||||
use cgmath::Matrix4;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
|
|
@ -89,16 +89,6 @@ pub struct AnimationDescription {
|
|||
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;
|
||||
|
||||
|
|
@ -139,7 +129,7 @@ impl ReadableRelative for AnimationBlock {}
|
|||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct AnimationHeader {
|
||||
bone: u8,
|
||||
bone: BoneId,
|
||||
flags: AnimationFlags,
|
||||
next_offset: u16,
|
||||
}
|
||||
|
|
@ -215,7 +205,7 @@ struct FrameValues<'a> {
|
|||
}
|
||||
|
||||
impl FrameValues<'_> {
|
||||
pub fn get(&self, index: u8) -> Result<u16, ModelError> {
|
||||
pub fn get(&self, index: u8) -> Result<i16, ModelError> {
|
||||
if self.header.total <= index {
|
||||
let offset_count = self.header.valid + 1;
|
||||
let offset = (offset_count as usize) * size_of::<u16>();
|
||||
|
|
@ -292,14 +282,25 @@ impl RotationData {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_scale(&mut self, scale: Vector) {
|
||||
fn set_scale(&mut self, scale: RadianEuler) {
|
||||
if let RotationData::Animated(values) = self {
|
||||
values.iter_mut().for_each(|value| {
|
||||
// scale and fixup the angles
|
||||
*value = RadianEuler {
|
||||
y: value.x * scale.x,
|
||||
z: value.y * scale.y,
|
||||
x: value.z * scale.z,
|
||||
x: value.x * scale.x,
|
||||
y: value.y * scale.y,
|
||||
z: value.z * scale.z,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn set_base_rotation(&mut self, base: RadianEuler) {
|
||||
if let RotationData::Animated(values) = self {
|
||||
values.iter_mut().for_each(|value| {
|
||||
*value = RadianEuler {
|
||||
x: value.x + base.x,
|
||||
y: value.y + base.y,
|
||||
z: value.z + base.z,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -338,7 +339,7 @@ impl PositionData {
|
|||
/// Per bone animation data
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Animation {
|
||||
pub bone: u8,
|
||||
pub bone: BoneId,
|
||||
pub flags: AnimationFlags,
|
||||
rotation_data: RotationData,
|
||||
position_data: PositionData,
|
||||
|
|
@ -353,8 +354,15 @@ impl Animation {
|
|||
self.position_data.position(frame)
|
||||
}
|
||||
|
||||
pub(crate) fn set_scales(&mut self, bone: &Bone) {
|
||||
pub fn transform(&self, frame: usize) -> Matrix4<f32> {
|
||||
Matrix4::from_translation(self.position(frame).into()) * Matrix4::from(self.rotation(frame))
|
||||
}
|
||||
|
||||
pub(crate) fn apply_bone_data(&mut self, bone: &Bone) {
|
||||
self.rotation_data.set_scale(bone.rot_scale);
|
||||
if self.flags.contains(AnimationFlags::STUDIO_ANIM_DELTA) {
|
||||
self.rotation_data.set_base_rotation(bone.rot);
|
||||
}
|
||||
self.position_data.set_scale(bone.pos_scale);
|
||||
}
|
||||
}
|
||||
|
|
@ -381,7 +389,7 @@ fn read_animation(
|
|||
let value_data = &data[offset..];
|
||||
let values: Vec<RadianEuler> = (0..frames)
|
||||
.map(|frame| read_animation_values(value_data, frame, pointers))
|
||||
.map(|r| r.map(|[x, y, z]| RadianEuler { x, z, y }))
|
||||
.map(|r| r.map(|[y, z, x]| RadianEuler { x, z, y }))
|
||||
.collect::<Result<_, ModelError>>()?;
|
||||
RotationData::from(values)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,43 @@ use crate::{
|
|||
use bitflags::bitflags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use std::fmt::Display;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Zeroable, Pod, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct BoneId(u8);
|
||||
|
||||
impl From<u8> for BoneId {
|
||||
fn from(val: u8) -> Self {
|
||||
BoneId(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for BoneId {
|
||||
fn from(val: i32) -> Self {
|
||||
BoneId(u8::try_from(val).unwrap_or(255))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for BoneId {
|
||||
fn from(val: usize) -> Self {
|
||||
BoneId(u8::try_from(val).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoneId> for usize {
|
||||
fn from(val: BoneId) -> Self {
|
||||
val.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BoneId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct BoneHeader {
|
||||
|
|
@ -17,7 +52,7 @@ pub struct BoneHeader {
|
|||
pub quaternion: Quaternion,
|
||||
pub rot: RadianEuler,
|
||||
pub pos_scale: Vector,
|
||||
pub rot_scale: Vector,
|
||||
pub rot_scale: [f32; 3],
|
||||
|
||||
pub pose_to_bone: Transform3x4,
|
||||
pub q_alignment: Quaternion,
|
||||
|
|
@ -38,14 +73,14 @@ static_assertions::const_assert_eq!(size_of::<BoneHeader>(), 216);
|
|||
#[repr(C)]
|
||||
pub struct Bone {
|
||||
pub name: String,
|
||||
pub parent: i32, // parent bone
|
||||
pub parent: BoneId,
|
||||
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 rot_scale: RadianEuler,
|
||||
|
||||
pub pose_to_bone: Transform3x4,
|
||||
pub q_alignment: Quaternion,
|
||||
|
|
@ -93,13 +128,21 @@ impl ReadRelative for Bone {
|
|||
|
||||
Ok(Bone {
|
||||
name: read_single(data, header.sz_name_index)?,
|
||||
parent: header.parent,
|
||||
parent: header.parent.into(),
|
||||
bone_controller: header.bone_controller,
|
||||
pos: header.pos,
|
||||
pos: Vector {
|
||||
x: header.pos.z,
|
||||
y: header.pos.x,
|
||||
z: header.pos.y,
|
||||
},
|
||||
quaternion: header.quaternion,
|
||||
rot: header.rot,
|
||||
pos_scale: header.pos_scale,
|
||||
rot_scale: header.rot_scale,
|
||||
rot_scale: RadianEuler {
|
||||
x: header.rot_scale[2],
|
||||
y: header.rot_scale[0],
|
||||
z: header.rot_scale[1],
|
||||
},
|
||||
pose_to_bone: header.pose_to_bone,
|
||||
q_alignment: header.q_alignment,
|
||||
flags: header.flags,
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ impl From<cgmath::Quaternion<f32>> for Quaternion {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Quaternion> for cgmath::Matrix4<f32> {
|
||||
impl From<Quaternion> for Matrix4<f32> {
|
||||
fn from(q: Quaternion) -> Self {
|
||||
// cgmath::Quaternion::from(Quaternion {
|
||||
// x: q.z,
|
||||
|
|
@ -240,6 +240,18 @@ impl From<RadianEuler> for Matrix4<f32> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for RadianEuler {
|
||||
type Output = RadianEuler;
|
||||
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
RadianEuler {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
z: self.z * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fixed length, null-terminated string
|
||||
#[derive(Debug, Clone, Default, Copy)]
|
||||
pub struct FixedString<const LEN: usize>(ArrayString<LEN>);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::mdl::BoneId;
|
||||
use crate::{index_range, ReadableRelative, Vector};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use std::cmp::min;
|
||||
|
|
@ -82,22 +83,33 @@ static_assertions::const_assert_eq!(size_of::<Vertex>(), 48);
|
|||
#[repr(C)]
|
||||
pub struct BoneWeights {
|
||||
weight: [f32; 3],
|
||||
bone: [u8; 3],
|
||||
bone: [BoneId; 3],
|
||||
bone_count: u8,
|
||||
}
|
||||
|
||||
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] / self.bone_count as f32,
|
||||
bone_id: self.bone[i],
|
||||
self.bone
|
||||
.into_iter()
|
||||
.zip(self.weight)
|
||||
.take(min(self.bone_count as usize, 3))
|
||||
.map(|(bone_id, weight)| BoneWeight {
|
||||
bone_id,
|
||||
weight: weight / self.bone_count as f32,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_weight(&self, bone_id: BoneId) -> f32 {
|
||||
self.weights()
|
||||
.find(|weight| weight.bone_id == bone_id)
|
||||
.map(|weight| weight.weight)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoneWeight {
|
||||
pub bone_id: u8,
|
||||
pub bone_id: BoneId,
|
||||
pub weight: f32,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue