Skip to content

Commit

Permalink
Use bimap for skeleton names
Browse files Browse the repository at this point in the history
  • Loading branch information
Nan committed Apr 21, 2024
1 parent ff0c8c0 commit f77edf5
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 27 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ wasm = []
nodejs = ["wasm", "dep:js-sys", "dep:wasm-bindgen"]

[dependencies]
bimap = { version = "0.6" }
bytecheck = { version = "0.6", optional = true, default-features = false }
glam = { version = "0.25", features = [ "core-simd", "libm" ] }
js-sys = { version = "0.3", optional = true }
Expand Down
2 changes: 1 addition & 1 deletion src/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ impl ArchiveRead<QuaternionKey> for QuaternionKey {
/// coherency when sampling the animation, Keyframes in this array are sorted by
/// time, then by track number.
///
#[derive(Debug)]
#[derive(Debug, Default)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Animation {
duration: f32,
Expand Down
2 changes: 1 addition & 1 deletion src/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ impl Archive<Cursor<Vec<u8>>> {
pub fn from_path(path: &str) -> Result<Archive<Cursor<Vec<u8>>>, OzzError> {
match crate::nodejs::read_file(path) {
Ok(buf) => return Archive::from_vec(buf),
Err(err) => return Err(OzzError::Custom(err.as_string().unwrap_or("".into()).into())),
Err(err) => return Err(OzzError::Custom(err.as_string().unwrap_or("".into()))),
};
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
use std::cell::{Ref, RefCell, RefMut};
use std::collections::hash_map::DefaultHasher;
use std::error::Error;
use std::fmt::Debug;
use std::hash::BuildHasher;
use std::ops::{Deref, DerefMut};
Expand Down Expand Up @@ -40,9 +39,9 @@ pub enum OzzError {
InvalidVersion,

/// Custom errors.
/// Ozz-animation-rs does not generate this error, but you can use it in your own code.
/// Ozz-animation-rs does not generate this error (except test & nodejs), but you can use it in your own code.
#[error("Custom error: {0}")]
Custom(Box<dyn Error>),
Custom(String),
}

impl OzzError {
Expand Down Expand Up @@ -111,7 +110,7 @@ pub const SKELETON_MAX_SOA_JOINTS: i32 = (SKELETON_MAX_JOINTS + 3) / 4;
pub const SKELETON_NO_PARENT: i32 = -1;

/// A hasher builder that creates `DefaultHasher` with default keys.
#[derive(Default)]
#[derive(Debug, Default, Clone, Copy)]
pub struct DeterministicState;

impl DeterministicState {
Expand Down
14 changes: 7 additions & 7 deletions src/blending_job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,12 +528,12 @@ where

#[cfg(test)]
mod blending_tests {
use std::collections::HashMap;
use std::mem;
use wasm_bindgen_test::*;

use super::*;
use crate::base::DeterministicState;
use crate::skeleton::JointHashMap;

const IDENTITY: SoaTransform = SoaTransform {
translation: SoaVec3::splat_col([0.0; 3]),
Expand Down Expand Up @@ -787,7 +787,7 @@ mod blending_tests {
let skeleton = Rc::new(Skeleton::from_raw(
joint_rest_poses,
vec![0; 8],
HashMap::with_hasher(DeterministicState::new()),
JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new()),
));

execute_test(
Expand Down Expand Up @@ -849,7 +849,7 @@ mod blending_tests {
let skeleton = Rc::new(Skeleton::from_raw(
rest_poses,
vec![0; 8],
HashMap::with_hasher(DeterministicState::new()),
JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new()),
));

{
Expand Down Expand Up @@ -949,7 +949,7 @@ mod blending_tests {
let skeleton = Rc::new(Skeleton::from_raw(
rest_poses,
vec![0; 8],
HashMap::with_hasher(DeterministicState::new()),
JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new()),
));

{
Expand Down Expand Up @@ -1012,7 +1012,7 @@ mod blending_tests {
return Rc::new(Skeleton::from_raw(
joint_rest_poses,
vec![0; 4],
HashMap::with_hasher(DeterministicState::new()),
JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new()),
));
}
#[test]
Expand Down Expand Up @@ -1232,7 +1232,7 @@ mod blending_tests {
let skeleton = Rc::new(Skeleton::from_raw(
vec![IDENTITY; 1],
vec![0; 4],
HashMap::with_hasher(DeterministicState::new()),
JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new()),
));

let mut input1 = vec![IDENTITY; 1];
Expand Down Expand Up @@ -1403,7 +1403,7 @@ mod blending_tests {
let skeleton = Rc::new(Skeleton::from_raw(
vec![IDENTITY; 1],
vec![0; 4],
HashMap::with_hasher(DeterministicState::new()),
JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new()),
));

let mut input1 = vec![IDENTITY; 1];
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub use local_to_model_job::{LocalToModelJob, LocalToModelJobArc, LocalToModelJo
pub use sampling_job::{
InterpSoaFloat3, InterpSoaQuaternion, SamplingContext, SamplingJob, SamplingJobArc, SamplingJobRc, SamplingJobRef,
};
pub use skeleton::Skeleton;
pub use skeleton::{JointHashMap, Skeleton};
pub use skinning_job::{SkinningJob, SkinningJobArc, SkinningJobRc, SkinningJobRef};
pub use track::Track;
pub use track_sampling_job::{TrackSamplingJob, TrackSamplingJobArc, TrackSamplingJobRc, TrackSamplingJobRef};
Expand Down
6 changes: 3 additions & 3 deletions src/local_to_model_job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,12 @@ where
#[cfg(test)]
mod local_to_model_tests {
use glam::Vec3;
use std::collections::HashMap;
use wasm_bindgen_test::*;

use super::*;
use crate::base::DeterministicState;
use crate::math::{SoaQuat, SoaVec3};
use crate::skeleton::JointHashMap;

#[test]
#[wasm_bindgen_test]
Expand Down Expand Up @@ -339,7 +339,7 @@ mod local_to_model_tests {
],
vec![-1, 0, 1, 0, 3, 3],
(|| {
let mut map = HashMap::with_hasher(DeterministicState::new());
let mut map = JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new());
map.insert("j0".into(), 0);
map.insert("j1".into(), 1);
map.insert("j2".into(), 2);
Expand Down Expand Up @@ -398,7 +398,7 @@ mod local_to_model_tests {
],
vec![-1, 0, 1, 0, 3, 4, 3, -1],
(|| {
let mut map = HashMap::with_hasher(DeterministicState::new());
let mut map = JointHashMap::with_hashers(DeterministicState::new(), DeterministicState::new());
map.insert("j0".into(), 0);
map.insert("j1".into(), 1);
map.insert("j2".into(), 2);
Expand Down
107 changes: 97 additions & 10 deletions src/skeleton.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,66 @@
use std::collections::HashMap;
use bimap::BiHashMap;
use std::io::Read;

use crate::archive::Archive;
use crate::base::{DeterministicState, OzzError, OzzIndex};
use crate::math::SoaTransform;

/// Rexported `BiHashMap` in bimap crate.
pub type JointHashMap = BiHashMap<String, i16, DeterministicState, DeterministicState>;

struct JointHashMapWrapper;

#[cfg(feature = "rkyv")]
const _: () = {
use rkyv::collections::util::Entry;
use rkyv::ser::{ScratchSpace, Serializer};
use rkyv::string::ArchivedString;
use rkyv::vec::{ArchivedVec, VecResolver};
use rkyv::with::{ArchiveWith, DeserializeWith, SerializeWith};
use rkyv::{Deserialize, Fallible};

impl ArchiveWith<JointHashMap> for JointHashMapWrapper {
type Archived = ArchivedVec<Entry<ArchivedString, i16>>;
type Resolver = VecResolver;

unsafe fn resolve_with(field: &JointHashMap, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
ArchivedVec::resolve_from_len(field.len(), pos, resolver, out);
}
}

impl<S> SerializeWith<JointHashMap, S> for JointHashMapWrapper
where
S: ScratchSpace + Serializer + ?Sized,
{
fn serialize_with(field: &JointHashMap, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
return ArchivedVec::serialize_from_iter(field.iter().map(|(key, value)| Entry { key, value }), serializer);
}
}

impl<D> DeserializeWith<ArchivedVec<Entry<ArchivedString, i16>>, JointHashMap, D> for JointHashMapWrapper
where
D: Fallible + ?Sized,
{
fn deserialize_with(
field: &ArchivedVec<Entry<ArchivedString, i16>>,
deserializer: &mut D,
) -> Result<JointHashMap, D::Error> {
let mut result = JointHashMap::with_capacity_and_hashers(
field.len() as usize,
DeterministicState::new(),
DeterministicState::new(),
);
for entry in field.iter() {
result.insert(
entry.key.deserialize(deserializer)?,
entry.value.deserialize(deserializer)?,
);
}
return Ok(result);
}
}
};

///
/// This runtime skeleton data structure provides a const-only access to joint
/// hierarchy, joint names and rest-pose.
Expand All @@ -16,12 +72,13 @@ use crate::math::SoaTransform;
/// order. This is enough to traverse the whole joint hierarchy. Use
/// iter_depth_first() to implement a depth-first traversal utility.
///
#[derive(Debug)]
#[derive(Debug, Default)]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
pub struct Skeleton {
joint_rest_poses: Vec<SoaTransform>,
joint_parents: Vec<i16>,
joint_names: HashMap<String, i16, DeterministicState>,
#[cfg_attr(feature = "rkyv", with(JointHashMapWrapper))]
joint_names: JointHashMap,
}

impl Skeleton {
Expand All @@ -41,7 +98,7 @@ impl Skeleton {
pub(crate) fn from_raw(
joint_rest_poses: Vec<SoaTransform>,
joint_parents: Vec<i16>,
joint_names: HashMap<String, i16, DeterministicState>,
joint_names: JointHashMap,
) -> Skeleton {
return Skeleton {
joint_rest_poses,
Expand All @@ -64,12 +121,16 @@ impl Skeleton {
return Ok(Skeleton {
joint_rest_poses: Vec::new(),
joint_parents: Vec::new(),
joint_names: HashMap::with_hasher(DeterministicState::new()),
joint_names: BiHashMap::with_hashers(DeterministicState::new(), DeterministicState::new()),
});
}

let _char_count: i32 = archive.read()?;
let mut joint_names = HashMap::with_capacity_and_hasher(num_joints as usize, DeterministicState::new());
let mut joint_names = BiHashMap::with_capacity_and_hashers(
num_joints as usize,
DeterministicState::new(),
DeterministicState::new(),
);
for idx in 0..num_joints {
joint_names.insert(archive.read::<String>()?, idx as i16);
}
Expand Down Expand Up @@ -144,14 +205,20 @@ impl Skeleton {

/// Gets joint's name map.
#[inline]
pub fn joint_names(&self) -> &HashMap<String, i16, DeterministicState> {
pub fn joint_names(&self) -> &JointHashMap {
return &self.joint_names;
}

/// Gets joint's index by name.
#[inline]
pub fn joint_by_name(&self, name: &str) -> Option<i16> {
return self.joint_names.get(name).map(|idx| *idx);
return self.joint_names.get_by_left(name).map(|idx| *idx);
}

/// Gets joint's name by index.
#[inline]
pub fn name_by_joint(&self, index: i16) -> Option<&str> {
return self.joint_names.get_by_right(&index).map(|s| s.as_str());
}

/// Test if a joint is a leaf.
Expand Down Expand Up @@ -267,7 +334,27 @@ mod tests {
assert_eq!(skeleton.joint_parents()[66], 65);

assert_eq!(skeleton.joint_names().len(), 67);
assert_eq!(skeleton.joint_names()["Hips"], 0);
assert_eq!(skeleton.joint_names()["Bip01 R Toe0Nub"], 66);
assert_eq!(skeleton.joint_by_name("Hips"), Some(0));
assert_eq!(skeleton.joint_by_name("Bip01 R Toe0Nub"), Some(66));
}

#[cfg(feature = "rkyv")]
#[test]
#[wasm_bindgen_test]
fn test_rkyv_skeleton() {
use rkyv::ser::Serializer;
use rkyv::Deserialize;

let skeleton = Skeleton::from_path("./resource/playback/skeleton.ozz").unwrap();
let mut serializer = rkyv::ser::serializers::AllocSerializer::<30720>::default();
serializer.serialize_value(&skeleton).unwrap();
let buf = serializer.into_serializer().into_inner();
let archived = unsafe { rkyv::archived_root::<Skeleton>(&buf) };
let mut deserializer = rkyv::Infallible::default();
let skeleton2: Skeleton = archived.deserialize(&mut deserializer).unwrap();

assert_eq!(skeleton.joint_rest_poses(), skeleton2.joint_rest_poses());
assert_eq!(skeleton.joint_parents(), skeleton2.joint_parents());
assert_eq!(skeleton.joint_names(), skeleton2.joint_names());
}
}

0 comments on commit f77edf5

Please sign in to comment.