From c0df462b37f6342ee872c4aa15c99f760b98b2cc Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 11 Apr 2024 10:19:27 +0800 Subject: [PATCH 1/4] track sampling --- src/track_sampling_job.rs | 135 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/track_sampling_job.rs diff --git a/src/track_sampling_job.rs b/src/track_sampling_job.rs new file mode 100644 index 0000000..646ce4a --- /dev/null +++ b/src/track_sampling_job.rs @@ -0,0 +1,135 @@ +use std::fmt::Debug; +use std::rc::Rc; +use std::sync::Arc; + +use crate::base::{OzzError, OzzObj}; +use crate::math::f32_clamp_or_max; +use crate::track::{Track, TrackValue}; + +#[derive(Debug)] +pub struct TrackSamplingJob>> +where + V: TrackValue, + T: OzzObj>, +{ + track: Option, + ratio: f32, + result: V, +} + +pub type TrackSamplingJobRef<'t, V> = TrackSamplingJob>; +pub type TrackSamplingJobRc = TrackSamplingJob>>; +pub type TrackSamplingJobArc = TrackSamplingJob>>; + +impl Default for TrackSamplingJob +where + V: TrackValue, + T: OzzObj>, +{ + fn default() -> TrackSamplingJob { + return TrackSamplingJob { + track: None, + ratio: 0.0, + result: V::default(), + }; + } +} + +impl TrackSamplingJob +where + V: TrackValue, + T: OzzObj>, +{ + /// Gets track of `TrackSamplingJob`. + #[inline] + pub fn track(&self) -> Option<&T> { + return self.track.as_ref(); + } + + /// Sets track of `TrackSamplingJob`. + /// + /// Track to sample. + #[inline] + pub fn set_track(&mut self, track: T) { + self.track = Some(track); + } + + /// Clears track of `TrackSamplingJob`. + #[inline] + pub fn clear_track(&mut self) { + self.track = None; + } + + /// Gets ratio of `TrackSamplingJob`. + #[inline] + pub fn ratio(&self) -> f32 { + return self.ratio; + } + + /// Sets ratio of `TrackSamplingJob`. + /// + /// Ratio used to sample track, clamped in range 0.0-1.0 before job execution. + /// 0 is the beginning of the track, 1 is the end. + /// This is a ratio rather than a ratio because tracks have no duration. + #[inline] + pub fn set_ratio(&mut self, ratio: f32) { + self.ratio = f32_clamp_or_max(ratio, 0.0f32, 1.0f32); + } + + /// Gets **output** result of `TrackSamplingJob`. + #[inline] + pub fn result(&self) -> V { + return self.result; + } + + /// Clears result of `TrackSamplingJob`. + #[inline] + pub fn clear_result(&mut self) { + self.result = V::default(); + } + + /// Clears all outputs of `TrackSamplingJob`. + #[inline] + pub fn clear_outs(&mut self) { + self.clear_result(); + } + + /// Validates `TrackSamplingJob` parameters. + #[inline] + pub fn validate(&self) -> bool { + return self.track.is_some(); + } + + /// Runs track sampling job's task. + /// The validate job before any operation is performed. + pub fn run(&mut self) -> Result<(), OzzError> { + let track = self.track.as_ref().ok_or(OzzError::InvalidJob)?.obj(); + + if track.key_count() == 0 { + self.result = V::default(); + return Ok(()); + } + + let id1 = track + .ratios() + .iter() + .position(|&x| self.ratio < x) + .unwrap_or(track.key_count()); + let id0 = id1.saturating_sub(1); + + let id0_step = (track.steps()[id0 / 8] & (1 << (id0 & 7))) != 0; + if id0_step || id1 == track.key_count() { + self.result = track.values()[id0]; + } else { + let tk0 = track.ratios()[id0]; + let tk1 = track.ratios()[id1]; + let t = (self.ratio - tk0) / (tk1 - tk0); + let v0 = track.values()[id0]; + let v1 = track.values()[id1]; + self.result = V::lerp(v0, v1, t); + } + + return Ok(()); + } +} + From 08e35aef399c7f8a2ca7cb0a62110c4a99280a16 Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 11 Apr 2024 10:21:11 +0800 Subject: [PATCH 2/4] track triggering --- src/track_triggering_job.rs | 289 ++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 src/track_triggering_job.rs diff --git a/src/track_triggering_job.rs b/src/track_triggering_job.rs new file mode 100644 index 0000000..430a4bb --- /dev/null +++ b/src/track_triggering_job.rs @@ -0,0 +1,289 @@ +use std::fmt::Debug; +use std::rc::Rc; +use std::sync::Arc; + +use crate::base::{OzzError, OzzObj}; + +use crate::track::Track; + +/// Structure of an edge as detected by the job. +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct Edge { + /// Ratio at which track value crossed threshold. + ratio: f32, + /// true is edge is rising (getting higher than threshold). + rising: bool, +} + +impl Edge { + /// Creates a new `Edge`. + pub fn new(ratio: f32, rising: bool) -> Edge { + return Edge { + ratio: ratio, + rising: rising, + }; + } +} + +#[derive(Debug)] +pub struct TrackTriggeringJob>> +where + T: OzzObj>, +{ + track: Option, + from: f32, + to: f32, + threshold: f32, +} + +pub type TrackTriggeringJobRef<'t> = TrackTriggeringJob<&'t Track>; +pub type TrackTriggeringJobRc = TrackTriggeringJob>>; +pub type TrackTriggeringJobArc = TrackTriggeringJob>>; + +impl Default for TrackTriggeringJob +where + T: OzzObj>, +{ + fn default() -> TrackTriggeringJob { + return TrackTriggeringJob { + track: None, + from: 0.0, + to: 0.0, + threshold: 0.0, + }; + } +} + +impl TrackTriggeringJob +where + T: OzzObj>, +{ + /// Gets track of `TrackTriggeringJob`. + #[inline] + pub fn track(&self) -> Option<&T> { + return self.track.as_ref(); + } + + /// Sets track of `TrackTriggeringJob`. + /// + /// Track to sample. + #[inline] + pub fn set_track(&mut self, track: T) { + self.track = Some(track); + } + + /// Clears track of `TrackTriggeringJob`. + #[inline] + pub fn clear_track(&mut self) { + self.track = None; + } + + /// Gets from of `TrackTriggeringJob`. + #[inline] + pub fn from(&self) -> f32 { + return self.from; + } + + /// Sets from of `TrackTriggeringJob`. + /// + /// Input range. 0 is the beginning of the track, 1 is the end. + /// + /// `from` can be of any sign, any order, and any range. The job will perform accordingly: + /// + /// - If difference between `from` and `to` is greater than 1, the iterator will loop multiple + /// times on the track. + /// - If `from` is greater than `to`, then the track is processed backward (rising edges in + /// forward become falling ones). + #[inline] + pub fn set_from(&mut self, from: f32) { + self.from = from; + } + + /// Gets to of `TrackTriggeringJob`. + #[inline] + pub fn to(&self) -> f32 { + return self.to; + } + + /// Sets to of `TrackTriggeringJob`. + /// + /// Input range. 0 is the beginning of the track, 1 is the end. + /// + /// `to` can be of any sign, any order, and any range. The job will perform accordingly: + /// + /// - If difference between `from` and `to` is greater than 1, the iterator will loop multiple + /// times on the track. + /// - If `from` is greater than `to`, then the track is processed backward (rising edges in + /// forward become falling ones). + #[inline] + pub fn set_to(&mut self, to: f32) { + self.to = to; + } + + /// Gets threshold of `TrackTriggeringJob`. + #[inline] + pub fn threshold(&self) -> f32 { + return self.threshold; + } + + /// Sets threshold of `TrackTriggeringJob`. + /// + /// Edge detection threshold value. + /// + /// A rising edge is detected as soon as the track value becomes greater than the threshold. + /// + /// A falling edge is detected as soon as the track value becomes smaller or equal than the threshold. + #[inline] + pub fn set_threshold(&mut self, threshold: f32) { + self.threshold = threshold; + } + + /// Validates `TrackTriggeringJob` parameters. + #[inline] + pub fn validate(&self) -> bool { + return self.track.is_some(); + } + + /// Runs track triggering job's task. + /// The validate job before any operation is performed. + /// + /// Returns an iterator of `Edge` that represents the detected edges. + pub fn run<'t>(&'t mut self) -> Result, OzzError> { + if !self.track.is_some() { + return Err(OzzError::InvalidJob); + } + return Ok(TrackTriggeringIter::new(self)); + } +} + +#[derive(Debug)] +pub struct TrackTriggeringIter<'t, T> +where + T: OzzObj>, +{ + job: &'t TrackTriggeringJob, + track: Option<&'t Track>, // also used as end marker + outer: f32, + inner: isize, +} + +impl<'t, T> TrackTriggeringIter<'t, T> +where + T: OzzObj>, +{ + fn new(job: &'t TrackTriggeringJob) -> TrackTriggeringIter<'t, T> { + let track = job.track().unwrap().obj(); + let end = job.from == job.to; + return TrackTriggeringIter { + job: job, + track: if end { None } else { Some(track) }, + outer: job.from.floor(), + inner: if job.from < job.to { + 0 + } else { + track.key_count() as isize - 1 + }, + }; + } + + fn detect_edge(&self, it0: usize, it1: usize, forward: bool) -> Option { + let track = self.track.unwrap(); + let val0 = track.values()[it0]; + let val1 = track.values()[it1]; + + let mut detected = false; + let mut edge = Edge::default(); + if val0 <= self.job.threshold && val1 > self.job.threshold { + // rising edge + edge.rising = forward; + detected = true; + } else if val0 > self.job.threshold && val1 <= self.job.threshold { + // falling edge + edge.rising = !forward; + detected = true; + } + + if !detected { + return None; + } + + let step = (track.steps()[it0 / 8] & (1 << (it0 & 7))) != 0; + if step { + edge.ratio = track.ratios()[it1]; + } else { + if it1 == 0 { + edge.ratio = 0.0; + } else { + let alpha = (self.job.threshold - val0) / (val1 - val0); + let ratio0 = track.ratios()[it0]; + let ratio1 = track.ratios()[it1]; + edge.ratio = ratio0 + (ratio1 - ratio0) * alpha; + } + } + return Some(edge); + } +} + +impl<'t, T> Iterator for TrackTriggeringIter<'t, T> +where + T: OzzObj>, +{ + type Item = Edge; + + fn next(&mut self) -> Option { + let track = self.track?; + + let key_count = track.key_count() as isize; + if self.job.to > self.job.from { + // from => to + while self.outer < self.job.to { + while self.inner < key_count { + let it0 = if self.inner == 0 { key_count - 1 } else { self.inner - 1 }; + if let Some(mut edge) = self.detect_edge(it0 as usize, self.inner as usize, true) { + edge.ratio += self.outer; + if edge.ratio >= self.job.from && (edge.ratio < self.job.to || self.job.to >= 1.0 + self.outer) + { + self.inner += 1; + return Some(edge); + } + } + // no edge found + if track.ratios()[self.inner as usize] + self.outer >= self.job.to { + break; + } + self.inner += 1; + } + self.inner = 0; + self.outer += 1.0; + } + } else { + // to => from + while self.outer + 1.0 > self.job.to { + while self.inner >= 0 { + let it0 = if self.inner == 0 { key_count - 1 } else { self.inner - 1 }; + if let Some(mut edge) = self.detect_edge(it0 as usize, self.inner as usize, false) { + edge.ratio += self.outer; + if edge.ratio >= self.job.to + && (edge.ratio < self.job.from || self.job.from >= 1.0 + self.outer) + { + self.inner -= 1; + return Some(edge); + } + } + // no edge found + if track.ratios()[self.inner as usize] + self.outer <= self.job.to { + break; + } + self.inner -= 1; + } + self.inner = key_count - 1; + self.outer -= 1.0; + } + } + + // iterator end + self.track = None; + return None; + } +} + From fbd5a73864794f6fe975e9b10c6965b4d1e95208 Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 11 Apr 2024 10:23:07 +0800 Subject: [PATCH 3/4] skinning --- src/skinning_job.rs | 599 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 599 insertions(+) create mode 100644 src/skinning_job.rs diff --git a/src/skinning_job.rs b/src/skinning_job.rs new file mode 100644 index 0000000..98876e6 --- /dev/null +++ b/src/skinning_job.rs @@ -0,0 +1,599 @@ +use glam::{Mat4, Vec3, Vec4}; +use std::cell::RefCell; +use std::fmt::Debug; +use std::rc::Rc; +use std::sync::{Arc, RwLock}; + +use crate::base::{OzzBuf, OzzError, OzzMutBuf}; + +#[derive(Debug)] +pub struct SkinningJob< + JM = Rc>>, + JI = Rc>>, + JW = Rc>>, + I = Rc>>, + O = Rc>>, +> where + JM: OzzBuf, + JI: OzzBuf, + JW: OzzBuf, + I: OzzBuf, + O: OzzMutBuf, +{ + vertex_count: usize, + influences_count: usize, + + joint_matrices: Option, + joint_it_matrices: Option, + joint_indices: Option, + joint_weights: Option, + + in_positions: Option, + in_normals: Option, + in_tangents: Option, + + out_positions: Option, + out_normals: Option, + out_tangents: Option, +} + +pub type SkinningJobRef<'t> = SkinningJob<&'t [Mat4], &'t [u16], &'t [f32], &'t [Vec3], &'t mut [Vec3]>; +pub type SkinningJobRc = SkinningJob< + Rc>>, + Rc>>, + Rc>>, + Rc>>, + Rc>>, +>; +pub type SkinningJobArc = SkinningJob< + Arc>>, + Arc>>, + Arc>>, + Arc>>, + Arc>>, +>; + +impl Default for SkinningJob +where + JM: OzzBuf, + JI: OzzBuf, + JW: OzzBuf, + I: OzzBuf, + O: OzzMutBuf, +{ + fn default() -> Self { + return SkinningJob { + vertex_count: 0, + influences_count: 0, + + joint_matrices: None, + joint_it_matrices: None, + joint_indices: None, + joint_weights: None, + + in_positions: None, + in_normals: None, + in_tangents: None, + + out_positions: None, + out_normals: None, + out_tangents: None, + }; + } +} + +impl SkinningJob +where + JM: OzzBuf, + JI: OzzBuf, + JW: OzzBuf, + I: OzzBuf, + O: OzzMutBuf, +{ + /// Gets vertex count of `SkinningJob`. + #[inline] + pub fn vertex_count(&self) -> usize { + return self.vertex_count; + } + + /// Sets vertex count of `SkinningJob`. + /// + /// Number of vertices to transform. All input and output arrays must store at + /// least this number of vertices. + #[inline] + pub fn set_vertex_count(&mut self, vertex_count: usize) { + self.vertex_count = vertex_count; + } + + /// Gets influences count of `SkinningJob`. + #[inline] + pub fn influences_count(&self) -> usize { + return self.influences_count; + } + + /// Sets influences count of `SkinningJob`. + /// + /// Maximum number of matrices influencing each vertex. Must be greater than 0. + /// + /// The number of influences drives how joint_indices and joint_weights are sampled: + /// - influences_count joint indices are red from joint_indices for each vertex. + /// - influences_count - 1 joint weights are red from joint_weightrs for each vertex. + /// The weight of the last joint is restored (weights are normalized). + #[inline] + pub fn set_influences_count(&mut self, influences_count: usize) { + self.influences_count = influences_count; + } + + /// Gets joint matrices of `SkinningJob`. + #[inline] + pub fn joint_matrices(&self) -> Option<&JM> { + return self.joint_matrices.as_ref(); + } + + /// Sets joint matrices of `SkinningJob`. + /// + /// Array of matrices for each joint. Joint are indexed through indices array. + #[inline] + pub fn set_joint_matrices(&mut self, joint_matrices: JM) { + self.joint_matrices = Some(joint_matrices); + } + + /// Clears joint matrices of `SkinningJob`. + #[inline] + pub fn clear_joint_matrices(&mut self) { + self.joint_matrices = None; + } + + /// Gets joint inverse transpose matrices of `SkinningJob`. + #[inline] + pub fn joint_it_matrices(&self) -> Option<&JM> { + return self.joint_it_matrices.as_ref(); + } + + /// Sets joint inverse transpose matrices of `SkinningJob`. + /// + /// Optional array of inverse transposed matrices for each joint. If provided, this array is used to + /// transform vectors (normals and tangents), otherwise joint_matrices array is used. + #[inline] + pub fn set_joint_it_matrices(&mut self, joint_it_matrices: JM) { + self.joint_it_matrices = Some(joint_it_matrices); + } + + /// Clears joint inverse transpose matrices of `SkinningJob`. + #[inline] + pub fn clear_joint_it_matrices(&mut self) { + self.joint_it_matrices = None; + } + + /// Gets joint indices of `SkinningJob`. + #[inline] + pub fn joint_indices(&self) -> Option<&JI> { + return self.joint_indices.as_ref(); + } + + /// Sets joint indices of `SkinningJob`. + #[inline] + pub fn set_joint_indices(&mut self, joint_indices: JI) { + self.joint_indices = Some(joint_indices); + } + + /// Clears joint indices of `SkinningJob`. + #[inline] + pub fn clear_joint_indices(&mut self) { + self.joint_indices = None; + } + + /// Gets joint weights of `SkinningJob`. + #[inline] + pub fn joint_weights(&self) -> Option<&JW> { + return self.joint_weights.as_ref(); + } + + /// Sets joint weights of `SkinningJob`. + /// + /// Array of matrices weights. This array is used to associate a weight to every joint that influences + /// a vertex. The number of weights required per vertex is "influences_max - 1". The weight for the + /// last joint (for each vertex) is restored at runtime thanks to the fact that the sum of the weights + /// for each vertex is 1. + /// + /// Each vertex has (influences_max - 1) number of weights, meaning that the size of this array must + /// be at least (influences_max - 1)* `joint_matrices.len()`. + #[inline] + pub fn set_joint_weights(&mut self, joint_weights: JW) { + self.joint_weights = Some(joint_weights); + } + + /// Clears joint weights of `SkinningJob`. + #[inline] + pub fn clear_joint_weights(&mut self) { + self.joint_weights = None; + } + + /// Gets input positions of `SkinningJob`. + #[inline] + pub fn in_positions(&self) -> Option<&I> { + return self.in_positions.as_ref(); + } + + /// Sets input positions of `SkinningJob`. + /// + /// Array length must be at least `joint_matrices.len()`. + #[inline] + pub fn set_in_positions(&mut self, in_positions: I) { + self.in_positions = Some(in_positions); + } + + /// Clears input positions of `SkinningJob`. + #[inline] + pub fn clear_in_positions(&mut self) { + self.in_positions = None; + } + + /// Gets input normals of `SkinningJob`. + #[inline] + pub fn in_normals(&self) -> Option<&I> { + return self.in_normals.as_ref(); + } + + /// Sets input normals of `SkinningJob`. + /// + /// Array length must be at least `joint_matrices.len()`. + #[inline] + pub fn set_in_normals(&mut self, in_normals: I) { + self.in_normals = Some(in_normals); + } + + /// Clears input normals of `SkinningJob`. + #[inline] + pub fn clear_in_normals(&mut self) { + self.in_normals = None; + } + + /// Gets input tangents of `SkinningJob`. + #[inline] + pub fn in_tangents(&self) -> Option<&I> { + return self.in_tangents.as_ref(); + } + + /// Sets input tangents of `SkinningJob`. + /// + /// Array length must be at least `joint_matrices.len()`. + #[inline] + pub fn set_in_tangents(&mut self, in_tangents: I) { + self.in_tangents = Some(in_tangents); + } + + /// Clears input tangents of `SkinningJob`. + #[inline] + pub fn clear_in_tangents(&mut self) { + self.in_tangents = None; + } + + /// Gets output positions of `SkinningJob`. + #[inline] + pub fn out_positions(&self) -> Option<&O> { + return self.out_positions.as_ref(); + } + + /// Sets output positions of `SkinningJob`. + /// + /// Array length must be at least `joint_matrices.len()`. + #[inline] + pub fn set_out_positions(&mut self, out_positions: O) { + self.out_positions = Some(out_positions); + } + + /// Clears output positions of `SkinningJob`. + #[inline] + pub fn clear_out_positions(&mut self) { + self.out_positions = None; + } + + /// Gets output normals of `SkinningJob`. + #[inline] + pub fn out_normals(&self) -> Option<&O> { + return self.out_normals.as_ref(); + } + + /// Sets output normals of `SkinningJob`. + /// + /// Array length must be at least `joint_matrices.len()`. + #[inline] + pub fn set_out_normals(&mut self, out_normals: O) { + self.out_normals = Some(out_normals); + } + + /// Clears output normals of `SkinningJob`. + #[inline] + pub fn clear_out_normals(&mut self) { + self.out_normals = None; + } + + /// Gets output tangents of `SkinningJob`. + #[inline] + pub fn out_tangents(&self) -> Option<&O> { + return self.out_tangents.as_ref(); + } + + /// Sets output tangents of `SkinningJob`. + /// + /// Array length must be at least `joint_matrices.len()`. + #[inline] + pub fn set_out_tangents(&mut self, out_tangents: O) { + self.out_tangents = Some(out_tangents); + } + + /// Clears output tangents of `SkinningJob`. + #[inline] + pub fn clear_out_tangents(&mut self) { + self.out_tangents = None; + } + + /// Validates `SkinningJob` parameters. + pub fn validate(&self) -> bool { + return (|| { + let mut ok = self.influences_count > 0; + ok &= !self.joint_matrices.as_ref()?.buf().ok()?.is_empty(); + + let joint_indices = self.joint_indices.as_ref()?.buf().ok()?; + ok &= joint_indices.len() >= self.vertex_count * self.influences_count; + + if self.influences_count > 1 { + let joint_weights = self.joint_weights.as_ref()?.buf().ok()?; + ok &= joint_weights.len() >= self.vertex_count * (self.influences_count - 1); + } + + ok &= self.in_positions.as_ref()?.buf().ok()?.len() >= self.vertex_count; + ok &= self.out_positions.as_ref()?.buf().ok()?.len() >= self.vertex_count; + + if let Some(in_normals) = &self.in_normals { + ok &= in_normals.buf().ok()?.len() >= self.vertex_count; + ok &= self.out_normals.as_ref()?.buf().ok()?.len() >= self.vertex_count; + + if let Some(in_tangents) = &self.in_tangents { + ok &= in_tangents.buf().ok()?.len() >= self.vertex_count; + ok &= self.out_tangents.as_ref()?.buf().ok()?.len() >= self.vertex_count; + } + } else { + ok &= self.in_tangents.is_none(); + } + + return Some(ok); + })() + .unwrap_or(false); + } +} + +fn unpack_buf<'t, T, B>(ozz_buf: &'t Option, size: usize) -> Result, OzzError> +where + T: Debug + Clone, + B: OzzBuf, +{ + let buf = ozz_buf.as_ref().ok_or(OzzError::InvalidJob)?.buf()?; + if buf.len() < size { + return Err(OzzError::InvalidJob); + } + return Ok(buf); +} + +fn unpack_mut_buf<'t, T, B>(ozz_buf: &'t mut Option, size: usize) -> Result, OzzError> +where + T: Debug + Clone, + B: OzzMutBuf, +{ + let buf = ozz_buf.as_mut().ok_or(OzzError::InvalidJob)?.mut_buf()?; + if buf.len() < size { + return Err(OzzError::InvalidJob); + } + return Ok(buf); +} + +#[rustfmt::skip] +macro_rules! it { + (_, $s1:stmt) => {}; + (IT, $s1:stmt) => { $s1 }; + (_, $s1:expr, $s2:expr) => { $s2 }; + (IT, $s1:expr, $s2:expr) => { $s1 }; +} + +#[rustfmt::skip] +macro_rules! pnt { + (P, $s1:stmt; $s2:stmt; $s3:stmt;) => { $s1 }; + (PN, $s1:stmt; $s2:stmt; $s3:stmt;) => { $s1 $s2 }; + (PNT, $s1:stmt; $s2:stmt; $s3:stmt;) => { $s1 $s2 $s3 }; +} + +macro_rules! skinning_1 { + ($fn:ident, $it:tt, $pnt:tt) => { + fn $fn(&mut self) -> Result<(), OzzError> { + let matrices = self.joint_matrices.as_ref().ok_or(OzzError::InvalidJob)?.buf()?; + it!($it, let it_matrices = self.joint_it_matrices.as_ref().ok_or(OzzError::InvalidJob)?.buf()?); + let indices = unpack_buf(&self.joint_indices, self.vertex_count)?; + + pnt!($pnt, + let in_positions = unpack_buf(&self.in_positions, self.vertex_count)?; + let in_normals = unpack_buf(&self.in_normals, self.vertex_count)?; + let in_tangents = unpack_buf(&self.in_tangents, self.vertex_count)?; + ); + pnt!($pnt, + let mut out_positions = unpack_mut_buf(&mut self.out_positions, self.vertex_count)?; + let mut out_normals = unpack_mut_buf(&mut self.out_normals, self.vertex_count)?; + let mut out_tangents = unpack_mut_buf(&mut self.out_tangents, self.vertex_count)?; + ); + + for i in 0..self.vertex_count { + let joint_index = indices[i] as usize; + let transform = &matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?; + #[allow(unused_variables)] + let transform_it = it!($it, &it_matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?, transform); + + pnt!($pnt, + out_positions[i] = transform.transform_point3(in_positions[i]); + out_normals[i] = transform_it.transform_vector3(in_normals[i]); + out_tangents[i] = transform_it.transform_vector3(in_tangents[i]); + ); + } + return Ok(()); + } + }; +} + +macro_rules! skinning_impl { + ($self:expr, $n:expr, $it:tt, $pnt:tt) => { + let matrices = $self.joint_matrices.as_ref().unwrap().buf()?; + it!($it, let it_matrices = $self.joint_it_matrices.as_ref().ok_or(OzzError::InvalidJob)?.buf()?); + let indices = unpack_buf(&$self.joint_indices, $self.vertex_count * $n)?; + let weights = unpack_buf(&$self.joint_weights, $self.vertex_count * ($n - 1))?; + + pnt!($pnt, + let in_positions = unpack_buf(&$self.in_positions, $self.vertex_count)?; + let in_normals = unpack_buf(&$self.in_normals, $self.vertex_count)?; + let in_tangents = unpack_buf(&$self.in_tangents, $self.vertex_count)?; + ); + pnt!($pnt, + let mut out_positions = unpack_mut_buf(&mut $self.out_positions, $self.vertex_count)?; + let mut out_normals = unpack_mut_buf(&mut $self.out_normals, $self.vertex_count)?; + let mut out_tangents = unpack_mut_buf(&mut $self.out_tangents, $self.vertex_count)?; + ); + + for i in 0..$self.vertex_count { + let weight_offset = i * ($n - 1); + let index_offset = i * $n; + + let weight = Vec4::splat(weights[weight_offset]); + let mut weight_sum = weight; + let joint_index = indices[index_offset] as usize; + let mut transform = mat4_col_mul(matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?, weight); + it!($it, let mut transform_it = mat4_col_mul(it_matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?, weight)); + + for j in 1..($n - 1) { + let weight = Vec4::splat(weights[weight_offset + j]); + weight_sum += weight; + let joint_index = indices[index_offset + j] as usize; + transform += mat4_col_mul(matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?, weight); + it!($it, transform_it += mat4_col_mul(it_matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?, weight)); + } + + let weight = Vec4::ONE - weight_sum; + let joint_index = indices[index_offset + $n - 1] as usize; + transform += mat4_col_mul(matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?, weight); + it!($it, transform_it += mat4_col_mul(it_matrices.get(joint_index).ok_or(OzzError::InvalidIndex)?, weight)); + + pnt!($pnt, + out_positions[i] = transform.transform_point3(in_positions[i]); + out_normals[i] = it!($it, transform_it, transform).transform_vector3(in_normals[i]); + out_tangents[i] = it!($it, transform_it, transform).transform_vector3(in_tangents[i]); + ); + } + return Ok(()); + }; +} + +#[inline(always)] +fn mat4_col_mul(m: &Mat4, v: Vec4) -> Mat4 { + // println!("mat4_col_mul: {:?}, {:?}", m, v); + let res = Mat4::from_cols(m.x_axis * v, m.y_axis * v, m.z_axis * v, m.w_axis * v); + // println!("mat4_col_mul_res: {:?}", res); + return res; +} + +macro_rules! skinning_c { + ($fn:ident, $n:expr, $it:tt, $pnt:tt) => { + fn $fn(&mut self) -> Result<(), OzzError> { + skinning_impl!(self, $n, $it, $pnt); + } + }; +} + +macro_rules! skinning_n { + ($fn:ident, $it:tt, $pnt:tt) => { + fn $fn(&mut self) -> Result<(), OzzError> { + let n = self.influences_count as usize; + skinning_impl!(self, n, $it, $pnt); + } + }; +} + +impl SkinningJob +where + JM: OzzBuf, + JI: OzzBuf, + JW: OzzBuf, + I: OzzBuf, + O: OzzMutBuf, +{ + /// Runs skinning job's task. + /// The validate job before any operation is performed. + pub fn run(&mut self) -> Result<(), OzzError> { + if self.influences_count <= 0 { + return Err(OzzError::InvalidJob); + } + + let mut branch = (self.influences_count - 1).min(4) * 5; + if self.in_normals.is_some() { + branch += 1; + if self.in_tangents.is_some() { + branch += 1; + } + if self.joint_it_matrices.is_some() { + branch += 2; + } + } + + return match branch { + 0 => self.skinning_1_p(), + 1 => self.skinning_1_pn(), + 2 => self.skinning_1_pnt(), + 3 => self.skinning_1_pn_it(), + 4 => self.skinning_1_pnt_it(), + 5 => self.skinning_2_p(), + 6 => self.skinning_2_pn(), + 7 => self.skinning_2_pnt(), + 8 => self.skinning_2_pn_it(), + 9 => self.skinning_2_pnt_it(), + 10 => self.skinning_3_p(), + 11 => self.skinning_3_pn(), + 12 => self.skinning_3_pnt(), + 13 => self.skinning_3_pn_it(), + 14 => self.skinning_3_pnt_it(), + 15 => self.skinning_4_p(), + 16 => self.skinning_4_pn(), + 17 => self.skinning_4_pnt(), + 18 => self.skinning_4_pn_it(), + 19 => self.skinning_4_pnt_it(), + 20 => self.skinning_n_p(), + 21 => self.skinning_n_pn(), + 22 => self.skinning_n_pnt(), + 23 => self.skinning_n_pn_it(), + 24 => self.skinning_n_pnt_it(), + _ => unreachable!(), + }; + } + + skinning_1!(skinning_1_p, _, P); + skinning_1!(skinning_1_pn, _, PN); + skinning_1!(skinning_1_pnt, _, PNT); + skinning_1!(skinning_1_pn_it, IT, PN); + skinning_1!(skinning_1_pnt_it, IT, PNT); + skinning_c!(skinning_2_p, 2, _, P); + skinning_c!(skinning_2_pn, 2, _, PN); + skinning_c!(skinning_2_pnt, 2, _, PNT); + skinning_c!(skinning_2_pn_it, 2, IT, PN); + skinning_c!(skinning_2_pnt_it, 2, IT, PNT); + skinning_c!(skinning_3_p, 3, _, P); + skinning_c!(skinning_3_pn, 3, _, PN); + skinning_c!(skinning_3_pnt, 3, _, PNT); + skinning_c!(skinning_3_pn_it, 3, IT, PN); + skinning_c!(skinning_3_pnt_it, 3, IT, PNT); + skinning_c!(skinning_4_p, 4, _, P); + skinning_c!(skinning_4_pn, 4, _, PN); + skinning_c!(skinning_4_pnt, 4, _, PNT); + skinning_c!(skinning_4_pn_it, 4, IT, PN); + skinning_c!(skinning_4_pnt_it, 4, IT, PNT); + skinning_n!(skinning_n_p, _, P); + skinning_n!(skinning_n_pn, _, PN); + skinning_n!(skinning_n_pnt, _, PNT); + skinning_n!(skinning_n_pn_it, IT, PN); + skinning_n!(skinning_n_pnt_it, IT, PNT); +} + From 0e1b99427a2af343883197c67f5098aac5875061 Mon Sep 17 00:00:00 2001 From: Nan Date: Thu, 11 Apr 2024 10:27:48 +0800 Subject: [PATCH 4/4] version to 0.9.0 --- Cargo.toml | 2 +- README.md | 5 +++-- src/lib.rs | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae45810..36ac7c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ozz-animation-rs" -version = "0.8.1" +version = "0.9.0" authors = ["SlimeYummy "] edition = "2021" rust-version = "1.75" diff --git a/README.md b/README.md index 19f33f0..9a855bb 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,15 @@ The library supports almost all runtime features supported by C++ version ozz, i - Animation blending (partial/additive blending) - Two bone IK - Aim (Look-at) IK +- User channels +- Skinning - Multi-threading - SIMD (SSE2 + NEON) - WASM The following functions are not supported yet: -- User channels (developing) -- Skinning (developing) - Baked physic simulation (no plan) +- All offline features (no plan, use C++ library instead) Ozz-animation offline features are not supported, and no plans to support. Please use the original C++ library, which has a many tools and plug-ins. diff --git a/src/lib.rs b/src/lib.rs index d709c5b..2493e75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,10 +60,10 @@ mod ik_two_bone_job; mod local_to_model_job; mod sampling_job; mod skeleton; -// mod skinning_job; -// mod track; -// mod track_sampling_job; -// mod track_triggering_job; +mod skinning_job; +mod track; +mod track_sampling_job; +mod track_triggering_job; pub mod math; #[cfg(all(feature = "wasm", feature = "nodejs"))] @@ -80,9 +80,9 @@ pub use sampling_job::{ InterpSoaFloat3, InterpSoaQuaternion, SamplingContext, SamplingJob, SamplingJobArc, SamplingJobRc, SamplingJobRef, }; pub use skeleton::Skeleton; -// pub use skinning_job::{SkinningJob, SkinningJobArc, SkinningJobRc, SkinningJobRef}; -// pub use track::Track; -// pub use track_sampling_job::{TrackSamplingJob, TrackSamplingJobArc, TrackSamplingJobRc, TrackSamplingJobRef}; -// pub use track_triggering_job::{ -// TrackTriggeringJob, TrackTriggeringJobArc, TrackTriggeringJobRc, TrackTriggeringJobRef, -// }; +pub use skinning_job::{SkinningJob, SkinningJobArc, SkinningJobRc, SkinningJobRef}; +pub use track::Track; +pub use track_sampling_job::{TrackSamplingJob, TrackSamplingJobArc, TrackSamplingJobRc, TrackSamplingJobRef}; +pub use track_triggering_job::{ + TrackTriggeringJob, TrackTriggeringJobArc, TrackTriggeringJobRc, TrackTriggeringJobRef, +};