BVH animation loading / retargeting (#25)

This PR adds support for BVH animation files, with a custom animation
player and retargeting solution for our Blender Rigify-based skeletal
meshes.

---------

Co-authored-by: Brooks Palin <brookspalin710@gmail.com>
This commit is contained in:
Danny McGee
2024-01-18 17:28:25 -05:00
committed by GitHub
parent cc00f197dc
commit 1687bfd6ed
36 changed files with 29129 additions and 37 deletions
+507
View File
@@ -0,0 +1,507 @@
//! Errors which may occur when manipulating `Bvh` files.
use crate::Axis;
use lexical::Error as LexicalError;
use std::{error::Error as StdError, fmt, io};
/// Errors which may arise when loading a `Bvh` file from
/// a `Reader`.
#[derive(Debug)]
pub struct LoadError {
/// The error kind.
kind: LoadErrorKind,
}
impl LoadError {
/// Get the line where the error occurred, or `None` if there is
/// no associated line number.
#[inline]
pub fn line(&self) -> Option<usize> {
match self.kind {
LoadErrorKind::Joints(ref e) => e.line(),
LoadErrorKind::Motion(ref e) => e.line(),
}
}
/// Returns the `LoadError` kind.
#[inline]
pub fn kind(&self) -> &LoadErrorKind {
&self.kind
}
/// Unwraps the `LoadErrorKind` from the `LoadError`.
#[inline]
pub fn into_kind(self) -> LoadErrorKind {
self.kind
}
}
impl<K: Into<LoadErrorKind>> From<K> for LoadError {
#[inline]
fn from(kind: K) -> Self {
LoadError { kind: kind.into() }
}
}
impl fmt::Display for LoadError {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = match self.kind {
LoadErrorKind::Joints(_) => "Could not load hierarchy",
LoadErrorKind::Motion(_) => "Could not load motion",
};
write!(fmtr, "{}: {}", desc, self.source().unwrap())
}
}
impl StdError for LoadError {
#[inline]
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self.kind {
LoadErrorKind::Joints(ref e) => Some(e),
LoadErrorKind::Motion(ref e) => Some(e),
}
}
}
/// The kind of the `LoadError`.
#[derive(Debug)]
pub enum LoadErrorKind {
/// An error occurred when loading the joints hierarchy.
Joints(LoadJointsError),
/// An error occurred when loading the motion values.
Motion(LoadMotionError),
}
impl From<LoadJointsError> for LoadErrorKind {
#[inline]
fn from(e: LoadJointsError) -> Self {
LoadErrorKind::Joints(e)
}
}
impl From<LoadMotionError> for LoadErrorKind {
#[inline]
fn from(e: LoadMotionError) -> Self {
LoadErrorKind::Motion(e)
}
}
/// Represents an error which occurred when loading the `Joints` of the
/// bvh file.
#[derive(Debug)]
pub enum LoadJointsError {
/// An I/O error occurred.
Io(io::Error),
/// The skeletal hierarchy is missing the `Root` joint.
MissingRoot,
/// A name could not be found for the `Joint`.
MissingJointName {
/// Line number in the source bvh where the error occurred.
line: usize,
},
/// A `CHANNELS` section was encountered in the wrong location.
UnexpectedChannelsSection {
/// Line number in the source bvh where the error occurred.
line: usize,
},
/// The number of channels could not be parsed in a `CHANNELS` section.
ParseNumChannelsError {
/// The parse error, if there was a malformed string to parse.
error: Option<LexicalError>,
/// Line number in the source bvh where the error occurred.
line: usize,
},
/// A channel type could not be parsed in the `CHANNELS` section.
ParseChannelError {
/// The parse error.
error: ParseChannelError,
/// Line number in the source bvh where the error occurred.
line: usize,
},
/// An `OFFSET` section was encountered in the wrong location.
UnexpectedOffsetSection {
/// Line number in the source bvh where the error occurred.
line: usize,
},
/// An axis in the `OFFSET` section could not be parsed into a value.
ParseOffsetError {
/// The parse error.
parse_float_error: LexicalError,
/// The axis of the offset which could not be parsed.
axis: Axis,
/// Line number in the source bvh where the error occurred.
line: usize,
},
/// An `OFFSET` section was missing an axis in the offset vector.
MissingOffsetAxis {
/// The smallest axis which was missing.
axis: Axis,
/// Line number in the source bvh where the error occurred.
line: usize,
},
}
impl LoadJointsError {
/// Get the line where the error occurred, or `None` if there is
/// no associated line number.
#[inline]
pub fn line(&self) -> Option<usize> {
match *self {
LoadJointsError::MissingJointName { line }
| LoadJointsError::UnexpectedChannelsSection { line }
| LoadJointsError::ParseNumChannelsError { line, .. }
| LoadJointsError::ParseChannelError { line, .. }
| LoadJointsError::UnexpectedOffsetSection { line }
| LoadJointsError::ParseOffsetError { line, .. }
| LoadJointsError::MissingOffsetAxis { line, .. } => Some(line),
_ => None,
}
}
}
impl From<io::Error> for LoadJointsError {
#[inline]
fn from(e: io::Error) -> Self {
LoadJointsError::Io(e)
}
}
impl fmt::Display for LoadJointsError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
LoadJointsError::Io(ref e) => fmt::Display::fmt(&e, f),
LoadJointsError::MissingRoot => f.write_str("The root heirarchy could not be found"),
LoadJointsError::MissingJointName { line } => {
write!(f, "{}: the name is missing from the joints section", line)
}
LoadJointsError::UnexpectedChannelsSection { line } => write!(
f,
"{}: unexpectedly encountered a \"CHANNELS\" section",
line
),
LoadJointsError::ParseNumChannelsError { line, .. } => {
write!(f, "{}: could not find the number of channels", line)
}
LoadJointsError::ParseChannelError { line, .. } => {
write!(f, "{}: could not parse channel", line)
}
LoadJointsError::UnexpectedOffsetSection { line } => write!(
f,
"{}: unexpectedly encountered an \"OFFSET\" section",
line
),
LoadJointsError::ParseOffsetError { axis, line, .. } => {
write!(f, "{}: could not parse the {}-axis offset", line, axis,)
}
LoadJointsError::MissingOffsetAxis { axis, line } => {
write!(f, "{}: the {}-axis offset value is missing", line, axis)
}
}
}
}
impl StdError for LoadJointsError {
#[inline]
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
LoadJointsError::Io(ref e) => Some(e),
/*
LoadJointsError::ParseNumChannelsError { ref error, .. } => {
error.as_ref().map(|e| e as &(dyn StdError + 'static))
}
*/
LoadJointsError::ParseChannelError { ref error, .. } => Some(error),
/*
LoadJointsError::ParseOffsetError {
ref parse_float_error,
..
} => Some(parse_float_error),
*/
_ => None,
}
}
}
/// Represents an error which occurred when loading the motion of the
/// bvh file.
#[derive(Debug)]
pub enum LoadMotionError {
/// An I/O error occurred.
Io(io::Error),
/// The `MOTION` section is missing in the bvh.
MissingMotionSection {
/// The line where the error occurred.
line: usize,
},
/// The "Number of Frames" section could not be parsed in the bvh.
MissingNumFrames {
/// The parse error, or `None` if there was no number to be parsed.
parse_error: Option<LexicalError>,
/// The line where the error occurred.
line: usize,
},
/// The "Frame Time" section could not be parsed in the bvh.
MissingFrameTime {
/// The parse error, or `None` if there was no number to be parsed.
parse_error: Option<LexicalError>,
/// The line where the error occurred.
line: usize,
},
/// The motion values section could not be parsed in the bvh.
ParseMotionSection {
/// The parse error, or `None` if there was no number to be parsed.
parse_error: LexicalError,
/// The index of the motion value where the error occurred.
channel_index: usize,
/// The line where the error occurred.
line: usize,
},
/// There was a discrepancy between the number of motion values promised
/// by the file and the actual amount.
MotionCountMismatch {
/// Actual number of motion values parsed.
actual_total_motion_values: usize,
/// Expected number of motion values.
expected_total_motion_values: usize,
/// Expected number of frames.
expected_num_frames: usize,
/// Expected number of clips.
expected_num_clips: usize,
},
}
impl LoadMotionError {
/// Get the line where the error occurred, or `None` if there is
/// no associated line number.
pub fn line(&self) -> Option<usize> {
match *self {
LoadMotionError::MissingMotionSection { line }
| LoadMotionError::MissingNumFrames { line, .. }
| LoadMotionError::MissingFrameTime { line, .. }
| LoadMotionError::ParseMotionSection { line, .. } => Some(line),
_ => None,
}
}
}
impl fmt::Display for LoadMotionError {
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
LoadMotionError::Io(ref e) => fmt::Display::fmt(e, fmtr),
LoadMotionError::MissingMotionSection { line } => {
write!(
fmtr,
"{}: The 'MOTION' section of the bvh file is missing",
line
)
}
LoadMotionError::MissingNumFrames {
ref parse_error,
line,
} => {
if parse_error.is_some() {
write!(fmtr, "{}: could not parse the num frames value", line)
} else {
write!(
fmtr,
"{}: The number of frames section is missing from the bvh file",
line
)
}
}
LoadMotionError::MissingFrameTime {
ref parse_error,
line,
} => {
if parse_error.is_some() {
write!(fmtr, "{}: could not parse the frame time", line)
} else {
write!(fmtr, "{}: Could not parse the frame time", line)
}
}
LoadMotionError::ParseMotionSection { line, .. } => {
write!(fmtr, "{}: Could not parse the motion value", line)
}
LoadMotionError::MotionCountMismatch {
actual_total_motion_values,
expected_total_motion_values,
expected_num_frames,
expected_num_clips,
} => {
write!(
fmtr,
"expected to find {} motion values, found {} values (num frames = {}, num clips = {})",
expected_total_motion_values,
actual_total_motion_values,
expected_num_frames,
expected_num_clips)
}
}
}
}
impl StdError for LoadMotionError {
#[inline]
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
LoadMotionError::Io(ref e) => Some(e),
/*
LoadMotionError::MissingFrameTime {
parse_error: Some(ref e),
..
} => Some(e),
LoadMotionError::ParseMotionSection {
ref parse_error, ..
} => Some(parse_error),
LoadMotionError::MissingNumFrames {
parse_error: Some(ref e),
..
} => Some(e),
*/
_ => None,
}
}
}
impl From<io::Error> for LoadMotionError {
#[inline]
fn from(e: io::Error) -> Self {
LoadMotionError::Io(e)
}
}
/// An error which may occurr when setting a motion which is out
/// of bounds.
#[derive(Clone, Debug, PartialEq)]
pub enum SetMotionError {
/// The frame was out of bounds.
BadFrame(usize),
/// The channel was out of bounds.
BadChannel(usize),
}
impl fmt::Display for SetMotionError {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SetMotionError::BadFrame(frame) => write!(fmtr, "Frame {} was out of bounds", frame),
SetMotionError::BadChannel(channel) => {
write!(fmtr, "Channel {} of the bvh was out of bounds", channel,)
}
}
}
}
impl StdError for SetMotionError {
#[inline]
fn description(&self) -> &'static str {
match *self {
SetMotionError::BadFrame(_) => "The frame was out of bounds",
SetMotionError::BadChannel(_) => "The channel was out of bounds",
}
}
}
/// Represents an error which may occur when attempting to parse a
/// `BString` into a `ChannelType`.
#[derive(Debug)]
pub struct ParseChannelError {
// @TODO(burtonageo): Borrow the erroneous string when hrts
// land.
bad_string: Vec<u8>,
}
impl ParseChannelError {
/// Get the `BString` which caused the parse error.
#[inline]
pub fn into_inner(self) -> Vec<u8> {
self.bad_string
}
}
impl<S: Into<Vec<u8>>> From<S> for ParseChannelError {
#[inline]
fn from(bad_string: S) -> Self {
ParseChannelError {
bad_string: bad_string.into(),
}
}
}
impl fmt::Display for ParseChannelError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"The channel could not be parsed from the given string: {:?}",
&self.bad_string
)
}
}
impl StdError for ParseChannelError {}
/// An error which may occur when attempting to insert a frame into a
/// `Bvh`.
#[derive(Debug)]
pub struct FrameInsertError {
kind: FrameInsertErrorKind,
}
impl FrameInsertError {
pub(crate) const fn incorrect_len(expected: usize, actual: usize) -> Self {
Self {
kind: FrameInsertErrorKind::IncorrectFrameLength { expected, actual },
}
}
}
impl fmt::Display for FrameInsertError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
FrameInsertErrorKind::IncorrectFrameLength { expected, actual } => {
write!(
f,
"Attempted to insert a frame with {} channels into a bvh containing {} channels",
actual,
expected,
)
}
}
}
}
impl StdError for FrameInsertError {}
#[derive(Debug)]
enum FrameInsertErrorKind {
IncorrectFrameLength { expected: usize, actual: usize },
}
/// An error which may occur when attempting to remove a frame from a
/// `Bvh`.
#[derive(Debug)]
pub struct FrameRemoveError {
index: usize,
}
impl FrameRemoveError {
pub(crate) const fn new(index: usize) -> Self {
Self { index }
}
}
impl fmt::Display for FrameRemoveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Attempted to remove a frame at index {} which does not exist",
self.index
)
}
}
impl StdError for FrameRemoveError {}
+607
View File
@@ -0,0 +1,607 @@
use crate::{
errors::{FrameInsertError, FrameRemoveError},
frames::{Frame, FrameMut, Frames, FramesMut},
Bvh,
};
use std::cmp::min;
/// A `FrameCursor` is used to insert frames into a [`Bvh`]. It operates
/// similarly to a [`linked_list::Cursor`]: methods are provided to move
/// through frames, as well as insert and delete frames.
///
/// You can create a `FrameCursor` using the [`Bvh::frame_cursor`] method.
///
/// [`Bvh`]: ../struct.Bvh.html
/// [`linked_list::Cursor`]: http://doc.rust-lang.org/stable/std/collections/linked_list/struct.Cursor.html
/// [`Bvh::frame_cursor`]: ../struct.Bvh.html#method.frame_cursor
#[derive(Debug)]
pub struct FrameCursor<'bvh> {
bvh: &'bvh mut Bvh,
index: usize,
}
impl<'bvh> FrameCursor<'bvh> {
/// Returns the current index of the `FrameCursor`.
#[inline]
pub const fn index(&self) -> usize {
self.index
}
/// Returns the number of frames in the [`Bvh`] the cursor is is currently
/// pointing to.
///
/// [`Bvh`]: ../struct.Bvh.html
#[inline]
pub fn len(&self) -> usize {
self.bvh.frames().len()
}
#[must_use]
#[inline(always)]
#[allow(missing_docs)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the number of channels in the [`Bvh`] the cursor is is currently
/// pointing to.
///
/// [`Bvh`]: ../struct.Bvh.html
#[inline]
pub const fn num_channels(&self) -> usize {
self.bvh.num_channels
}
/// Move the `FrameCursor` to the next frame of the `Bvh`'s motion values.
///
/// If the `FrameCursor` is currently at the last frame, then this method will
/// do nothing.
#[inline]
pub fn move_next(&mut self) -> &mut Self {
self.index = min(self.index + 1, self.bvh.frames().len());
self
}
/// Move the `FrameCursor` to the previous frame of the `Bvh`'s motion values.
///
/// If the `FrameCursor` is currently at the first frame, then this method will
/// do nothing.
#[inline]
pub fn move_prev(&mut self) -> &mut Self {
self.index = self.index.saturating_sub(1);
self
}
/// Move the `FrameCursor` to the first frame of the `Bvh`'s motion values.
///
/// If the `FrameCursor` is currently at the first frame, then this method will
/// do nothing.
#[inline]
pub fn move_first(&mut self) -> &mut Self {
self.index = 0;
self
}
/// Move the `FrameCursor` to the last frame of the `Bvh`'s motion values.
///
/// If the `FrameCursor` is currently at the last frame, then this method will
/// do nothing.
#[inline]
pub fn move_last(&mut self) -> &mut Self {
self.index = self.len();
self
}
/// Returns the frame next to the current index of the `FrameCursor`.
///
/// Returns `None` if there is no frame available.
#[inline]
pub fn peek_next(&self) -> Option<Frame<'_>> {
self.bvh.frames().nth(self.index)
}
/// Returns the frame before the current index of the `FrameCursor`.
///
/// Returns `None` if there is no frame available.
#[inline]
pub fn peek_prev(&self) -> Option<Frame<'_>> {
if self.index() > 0 {
self.bvh.frames().nth(self.index - 1)
} else {
None
}
}
/// Returns a mutable reference to the frame next to the current
/// index of the `FrameCursor`.
///
/// Returns `None` if there is no frame available.
#[inline]
pub fn peek_next_mut(&mut self) -> Option<FrameMut<'_>> {
self.bvh.frames_mut().nth(self.index)
}
/// Returns a mutable reference to the frame before the current index
/// of the `FrameCursor`.
///
/// Returns `None` if there is no frame available.
#[inline]
pub fn peek_prev_mut(&mut self) -> Option<FrameMut<'_>> {
if self.index() > 0 {
self.bvh.frames_mut().nth(self.index - 1)
} else {
None
}
}
/// Returns the frames surrounding the current index of the `FrameCursor`.
///
/// If either of the frames is not available, then `None` will be returned
/// for that frame.
#[inline]
pub fn surrounding_frames(&self) -> (Option<Frame<'_>>, Option<Frame<'_>>) {
(self.peek_prev(), self.peek_next())
}
/// Attempt to insert a new frame of animation after the current index.
/// The index of the cursor will be advanced to the end of the frames inserted.
///
/// # Errors
///
/// If the frame could not be inserted (because it was an incorrect size),
/// then an error will be returned.
///
/// # Examples
///
/// ```
/// # fn main() {
/// # use studio_bvh::bvh;
/// let mut simple_skeleton = bvh! {
/// HIERARCHY
/// ROOT Base
/// {
/// // Hierarchy omitted...
/// # OFFSET 0.0 0.0 0.0
/// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// # JOINT Middle1
/// # {
/// # OFFSET 0.0 0.0 15.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip1
/// # {
/// # OFFSET 0.0 0.0 30.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 0.0 45.0
/// # }
/// # }
/// # }
/// # JOINT Middle2
/// # {
/// # OFFSET 0.0 15.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip2
/// # {
/// # OFFSET 0.0 30.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 45.0 0.0
/// # }
/// # }
/// # }
/// }
///
/// MOTION
/// Frames: 3
/// Frame Time: 0.033333333333
/// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
/// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
/// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0
/// };
///
/// let mut frame_cursor = simple_skeleton.frame_cursor();
/// frame_cursor.move_last();
///
/// assert_eq!(frame_cursor.index(), 3);
///
/// frame_cursor
/// .try_insert_frame(&[3.0; 18])
/// .expect("Could not insert frame");
///
/// assert_eq!(frame_cursor.index(), 4);
/// # drop(frame_cursor);
/// assert_eq!(simple_skeleton.frames().len(), 4);
///
/// # for (i, frame) in simple_skeleton.frames().enumerate() {
/// # for motion in &frame.as_slice()[..] {
/// # assert_eq!(*motion, i as f32);
/// # }
/// # }
/// # } // fn main()
/// ```
pub fn try_insert_frame<F>(&mut self, frame: &F) -> Result<&mut Self, FrameInsertError>
where
F: AsRef<[f32]> + ?Sized,
{
let frame = frame.as_ref();
if frame.len() != self.num_channels() {
return Err(FrameInsertError::incorrect_len(
self.num_channels(),
frame.len(),
));
}
let idx = self.index * self.num_channels();
vec_insert_slice(&mut self.bvh.motion_values, idx, frame);
Ok(self.move_next())
}
/// Insert multiple frames contiguously at the current index. This is generally more
/// efficient than calling [`try_insert_frame`] multiple times because this method will
/// preallocate space for the multiple frames.
///
/// The index of the cursor will be advanced to the end of the frames inserted.
///
/// # Errors
///
/// If any of the frames could not be inserted (because they were an incorrect size),
/// then an error will be returned.
///
/// # Examples
///
/// ```
/// # fn main() {
/// # use studio_bvh::bvh;
/// let mut simple_skeleton = bvh! {
/// HIERARCHY
/// ROOT Base
/// {
/// // Hierarchy omitted...
/// # OFFSET 0.0 0.0 0.0
/// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// # JOINT Middle1
/// # {
/// # OFFSET 0.0 0.0 15.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip1
/// # {
/// # OFFSET 0.0 0.0 30.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 0.0 45.0
/// # }
/// # }
/// # }
/// # JOINT Middle2
/// # {
/// # OFFSET 0.0 15.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip2
/// # {
/// # OFFSET 0.0 30.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 45.0 0.0
/// # }
/// # }
/// # }
/// }
///
/// MOTION
/// Frames: 3
/// Frame Time: 0.033333333333
/// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
/// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
/// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0
/// };
///
/// simple_skeleton
/// .frame_cursor()
/// .move_last()
/// .try_insert_frames(&[[3.0; 18], [4.0; 18]])
/// .expect("Could not insert frames");
///
/// assert_eq!(simple_skeleton.frames().len(), 5);
///
/// # for (i, frame) in simple_skeleton.frames().enumerate() {
/// # for motion in &frame.as_slice()[..] {
/// # assert_eq!(*motion, i as f32);
/// # }
/// # }
/// # } // fn main()
/// ```
///
///[`try_insert_frame`]: ./struct.FrameInserter.html#method.try_insert_frame
pub fn try_insert_frames<'f, I, F>(&mut self, frames: I) -> Result<&mut Self, FrameInsertError>
where
I: IntoIterator<Item = &'f F>,
F: 'f + AsRef<[f32]> + ?Sized,
{
let frames = frames.into_iter();
let num_channels = self.num_channels();
let num_frames = {
let (low, up) = frames.size_hint();
up.unwrap_or(low)
};
self.bvh.motion_values.reserve(num_frames * num_channels);
for frame in frames.into_iter() {
let frame = frame.as_ref();
self.try_insert_frame(&frame)?;
}
Ok(self)
}
/// Attempt to remove a frame of animation at the current index.
///
/// # Errors
///
/// If the frame could not be removed, then an error will be returned.
///
/// # Examples
///
/// ```
/// # fn main() {
/// # use studio_bvh::bvh;
/// let mut simple_skeleton = bvh! {
/// HIERARCHY
/// ROOT Base
/// {
/// // Hierarchy omitted...
/// # OFFSET 0.0 0.0 0.0
/// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// # JOINT Middle1
/// # {
/// # OFFSET 0.0 0.0 15.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip1
/// # {
/// # OFFSET 0.0 0.0 30.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 0.0 45.0
/// # }
/// # }
/// # }
/// # JOINT Middle2
/// # {
/// # OFFSET 0.0 15.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip2
/// # {
/// # OFFSET 0.0 30.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 45.0 0.0
/// # }
/// # }
/// # }
/// }
///
/// MOTION
/// Frames: 3
/// Frame Time: 0.033333333333
/// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
/// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
/// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0
/// };
///
/// simple_skeleton
/// .frame_cursor()
/// .remove_frame()
/// .expect("Could not remove frame");
///
/// assert_eq!(simple_skeleton.frames().len(), 2);
///
/// # for (i, frame) in simple_skeleton.frames().enumerate() {
/// # for motion in &frame.as_slice()[..] {
/// # assert_eq!(*motion, (i + 1) as f32);
/// # }
/// # }
/// # } // fn main()
/// ```
pub fn remove_frame(&mut self) -> Result<&mut Self, FrameRemoveError> {
if self.is_empty() {
return Err(FrameRemoveError::new(self.index));
}
let bnd = |idx| idx * self.num_channels();
let frame_bound = bnd(self.index)..bnd(self.index + 1);
let mut i = 0;
self.bvh
.motion_values
.retain(|_| (!frame_bound.contains(&i), i += 1).0);
Ok(self)
}
/// Removes every frame in the [`Bvh`].
//
/// # Examples
///
/// ```
/// # fn main() {
/// # use studio_bvh::bvh;
/// let mut simple_skeleton = bvh! {
/// HIERARCHY
/// ROOT Base
/// {
/// // Hierarchy omitted...
/// # OFFSET 0.0 0.0 0.0
/// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// # JOINT Middle1
/// # {
/// # OFFSET 0.0 0.0 15.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip1
/// # {
/// # OFFSET 0.0 0.0 30.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 0.0 45.0
/// # }
/// # }
/// # }
/// # JOINT Middle2
/// # {
/// # OFFSET 0.0 15.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # JOINT Tip2
/// # {
/// # OFFSET 0.0 30.0 0.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 45.0 0.0
/// # }
/// # }
/// # }
/// }
///
/// MOTION
/// Frames: 3
/// Frame Time: 0.033333333333
/// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
/// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
/// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0
/// };
///
/// let mut frame_cursor = simple_skeleton.frame_cursor();
/// assert_eq!(frame_cursor.len(), 3);
/// frame_cursor.remove_all_frames();
/// assert_eq!(frame_cursor.len(), 0);
/// # } // fn main()
/// ```
///
/// [`Bvh`]: ../struct.Bvh.html
#[inline]
pub fn remove_all_frames(&mut self) -> &mut Self {
self.bvh.motion_values.clear();
self
}
/// Shrinks the capacity of the motion values in the [`Bvh`] as much as possible.
///
/// [`Bvh`]: ../struct.Bvh.html
#[inline]
pub fn shrink_to_fit(&mut self) {
self.bvh.motion_values.shrink_to_fit();
}
/// Create a new `Frames` iterator from the `FramesCursor`, starting at the current
/// index.
#[inline]
pub fn into_frames(self) -> Frames<'bvh> {
// @TODO: Replace with `Iterator::advance_by` when stable.
let mut frames = self.bvh.frames();
for _ in 0..self.index {
if frames.next().is_none() {
break;
}
}
frames
}
/// Create a new `FramesMut` iterator from the `FramesCursor`, starting at the current
/// index.
#[inline]
pub fn into_frames_mut(self) -> FramesMut<'bvh> {
// @TODO: Replace with `Iterator::advance_by` when stable.
let mut frames = self.bvh.frames_mut();
for _ in 0..self.index {
if frames.next().is_none() {
break;
}
}
frames
}
}
impl<'bvh> From<&'bvh mut Bvh> for FrameCursor<'bvh> {
#[inline]
fn from(bvh: &'bvh mut Bvh) -> Self {
Self { bvh, index: 0 }
}
}
impl<'bvh> From<FrameCursor<'bvh>> for Frames<'bvh> {
#[inline]
fn from(cursor: FrameCursor<'bvh>) -> Self {
cursor.into_frames()
}
}
impl<'bvh> From<FrameCursor<'bvh>> for FramesMut<'bvh> {
#[inline]
fn from(cursor: FrameCursor<'bvh>) -> Self {
cursor.into_frames_mut()
}
}
/// A utility function which inserts the `slice` into the `vec` at the given `index`.
///
/// # Panics
///
/// Panics if `index` is out of bounds.
fn vec_insert_slice<T: Clone + Default>(vec: &mut Vec<T>, index: usize, slice: &[T]) {
assert!(
index <= vec.len(),
"attempted to insert a slice at index {} into a vec of length {}",
index,
vec.len(),
);
let spare_capacity = vec.capacity().saturating_sub(vec.len());
if slice.len() > spare_capacity {
vec.reserve(slice.len() - spare_capacity);
}
let old_len = vec.len();
let new_len = vec.len() + slice.len();
vec.resize_with(new_len, T::default);
// Move the existing items to the end
for (to_move, end_index) in (index..old_len).zip(old_len + index..new_len) {
vec.swap(to_move, end_index);
}
// Clone the slice into the new area
for (i, item) in slice.iter().enumerate() {
vec[i + index] = item.clone();
}
}
#[test]
fn test_vec_insert_slice() {
let mut v;
let sl = &[100, 200, 300];
v = vec![1, 2, 3];
vec_insert_slice(&mut v, 0, sl);
assert_eq!(v, &[100, 200, 300, 1, 2, 3]);
v = vec![1, 2, 3];
vec_insert_slice(&mut v, 1, sl);
assert_eq!(v, &[1, 100, 200, 300, 2, 3]);
v = vec![1, 2, 3];
vec_insert_slice(&mut v, 2, sl);
assert_eq!(v, &[1, 2, 100, 200, 300, 3]);
v = vec![1, 2, 3];
vec_insert_slice(&mut v, 3, sl);
assert_eq!(v, &[1, 2, 3, 100, 200, 300]);
}
+324
View File
@@ -0,0 +1,324 @@
use crate::{errors::SetMotionError, Channel};
use std::{
borrow::{Borrow, BorrowMut},
iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator, Iterator},
mem,
ops::{Index, IndexMut},
slice::{ChunksExact, ChunksExactMut, SliceIndex},
};
/// An iterator over the frames of a `Bvh`.
///
/// This type is created using the [`Bvh::frames`] method.
///
/// [`Bvh::frames`]: ../struct.Bvh.html#method.frames
#[derive(Debug)]
pub struct Frames<'a> {
/// Note: `chunks` is wrapped in an option because having a `ChunksExact`
/// iterator over 0-length chunks panics, and empty `Bvh`s have empty
/// frames.
pub(crate) chunks: Option<ChunksExact<'a, f32>>,
}
impl<'a> Iterator for Frames<'a> {
type Item = Frame<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.chunks.as_mut().and_then(|c| c.next().map(Frame))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.chunks
.as_ref()
.map(|c| c.size_hint())
.unwrap_or((0, Some(0)))
}
}
impl<'a> DoubleEndedIterator for Frames<'a> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.chunks.as_mut().and_then(|c| c.next_back().map(Frame))
}
}
impl<'a> ExactSizeIterator for Frames<'a> {
#[inline]
fn len(&self) -> usize {
self.chunks.as_ref().map(|c| c.len()).unwrap_or(0)
}
}
impl<'a> FusedIterator for Frames<'a> {}
/// A mutable iterator over the frames of a [`Bvh`].
///
/// This type is created using the [`Bvh::frames_mut`] method.
///
/// [`Bvh`]: ../struct.Bvh.html
/// [`Bvh::frames_mut`]: ../struct.Bvh.html#method.frames_mut
#[derive(Debug)]
pub struct FramesMut<'a> {
/// Note: `chunks` is wrapped in an option for the same reason
/// that `Frames<'_>`'s `chunks` is wrapped.
pub(crate) chunks: Option<ChunksExactMut<'a, f32>>,
}
impl<'a> Iterator for FramesMut<'a> {
type Item = FrameMut<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.chunks.as_mut().and_then(|c| c.next().map(FrameMut))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.chunks
.as_ref()
.map(|c| c.size_hint())
.unwrap_or((0, Some(0)))
}
}
impl<'a> DoubleEndedIterator for FramesMut<'a> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.chunks
.as_mut()
.and_then(|c| c.next_back().map(FrameMut))
}
}
impl<'a> ExactSizeIterator for FramesMut<'a> {
#[inline]
fn len(&self) -> usize {
self.chunks.as_ref().map(|c| c.len()).unwrap_or(0)
}
}
impl<'a> FusedIterator for FramesMut<'a> {}
/// A wrapper for a slice of motion values, so that they can be indexed by [`Channel`].
///
/// [`Channel`]: ./struct.Channel.html
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Frame<'a>(&'a [f32]);
impl<'a> Frame<'a> {
/// Return the number of values in the `Frame`.
#[inline]
pub const fn len(&self) -> usize {
self.0.len()
}
/// Returns `true` if there are `0` values in the `FrameMut`. Otherwise,
/// returns `false`.
#[inline]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Convert the `Frame` into a `&[f32]`.
#[inline]
pub const fn as_slice(&self) -> &[f32] {
self.0
}
/// Attempts to get the motion value at `index`. Otherwise, returns `None`.
#[inline]
pub fn get<I: FrameIndex>(&self, index: I) -> Option<&Output<I>> {
self.0.get(index.to_slice_index())
}
}
impl<'a> AsRef<[f32]> for Frame<'a> {
#[inline]
fn as_ref(&self) -> &[f32] {
self.0
}
}
impl<'a> Borrow<[f32]> for Frame<'a> {
#[inline]
fn borrow(&self) -> &[f32] {
self.0
}
}
impl<'a, I: FrameIndex> Index<I> for Frame<'a> {
type Output = Output<I>;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0.index(index.to_slice_index())
}
}
/// A wrapper for a mutable slice of motion values, so that they can be indexed by [`Channel`].
///
/// [`Channel`]: ./struct.Channel.html
#[derive(Debug, PartialEq)]
pub struct FrameMut<'a>(&'a mut [f32]);
impl<'a> FrameMut<'a> {
/// Return the number of values in the `FrameMut`.
#[inline]
pub const fn len(&self) -> usize {
self.0.len()
}
/// Returns `true` if there are `0` values in the `FrameMut`. Otherwise,
/// returns `false`.
#[inline]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Convert the `FrameMut` into a `&[f32]`.
#[inline]
pub const fn as_slice(&self) -> &[f32] {
&*self.0
}
/// Convert the `FrameMut` into a `&mut [f32]`.
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [f32] {
self.0
}
/// Attempts to get the motion value at `index`. Otherwise, returns `None`.
#[inline]
pub fn get<I: FrameIndex>(&self, index: I) -> Option<&Output<I>> {
self.0.get(index.to_slice_index())
}
/// Attempts to return a mutable reference to the motion value at `channel`.
/// Otherwise, returns `None`.
#[inline]
pub fn get_mut<I: FrameIndex>(&'a mut self, index: I) -> Option<&mut Output<I>> {
self.0.get_mut(index.to_slice_index())
}
/// Updates the `motion` value at `channel` to `new_motion`.
///
/// # Notes
///
/// Returns the previous motion value if the operation was successful, and `Err(_)` if
/// the operation was out of bounds.
pub fn try_set_motion<I>(&mut self, index: I, new_motion: f32) -> Result<f32, SetMotionError>
where
I: FrameIndex<SliceIndex = usize>,
{
let index = index.to_slice_index();
let motion = self
.0
.get_mut(index)
.ok_or(SetMotionError::BadChannel(index))?;
Ok(mem::replace(motion, new_motion))
}
}
impl<'a> Borrow<[f32]> for FrameMut<'a> {
#[inline]
fn borrow(&self) -> &[f32] {
&*self.0
}
}
impl<'a> BorrowMut<[f32]> for FrameMut<'a> {
#[inline]
fn borrow_mut(&mut self) -> &mut [f32] {
self.0
}
}
impl<'a> AsRef<[f32]> for FrameMut<'a> {
#[inline]
fn as_ref(&self) -> &[f32] {
&*self.0
}
}
impl<'a> AsMut<[f32]> for FrameMut<'a> {
#[inline]
fn as_mut(&mut self) -> &mut [f32] {
self.0
}
}
impl<'a, I: FrameIndex> Index<I> for FrameMut<'a> {
type Output = Output<I>;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0.index(index.to_slice_index())
}
}
impl<'a, I: FrameIndex> IndexMut<I> for FrameMut<'a> {
#[inline]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.0.index_mut(index.to_slice_index())
}
}
impl<'a> From<&'a FrameMut<'a>> for Frame<'a> {
#[inline]
fn from(frame_mut: &'a FrameMut<'a>) -> Self {
Frame(&*frame_mut.0)
}
}
impl<'a> From<FrameMut<'a>> for Frame<'a> {
#[inline]
fn from(frame_mut: FrameMut<'a>) -> Self {
Frame(&*frame_mut.0)
}
}
mod private {
pub trait Sealed {}
}
/// A helper trait used for indexing into a frame.
pub trait FrameIndex: Sized + private::Sealed {
/// The type of the index generated.
type SliceIndex: SliceIndex<[f32]>;
/// Convert `Self` to a `SliceIndex`.
fn to_slice_index(self) -> Self::SliceIndex;
}
/// Type alias for the result of a `FrameIndex` operation.
pub type Output<I> = <<I as FrameIndex>::SliceIndex as SliceIndex<[f32]>>::Output;
impl<T: SliceIndex<[f32]>> FrameIndex for T {
type SliceIndex = Self;
#[inline]
fn to_slice_index(self) -> Self::SliceIndex {
self
}
}
// @TODO: Combine these into one impl for `impl Borrow<Channel>` when specialization lands.
macro_rules! impl_channel_frame_index {
($($chn:ty),* $(,)?) => {
$(
impl FrameIndex for $chn {
type SliceIndex = usize;
#[inline]
fn to_slice_index(self) -> Self::SliceIndex {
self.motion_index
}
}
)*
}
}
impl_channel_frame_index!(Channel, &'_ Channel);
impl private::Sealed for Channel {}
impl private::Sealed for &'_ Channel {}
impl<T: SliceIndex<[f32]>> private::Sealed for T {}
+870
View File
@@ -0,0 +1,870 @@
#![allow(dead_code, missing_docs)]
use crate::Channel;
use bstr::{BStr, BString, ByteSlice, Utf8Error};
use smallvec::SmallVec;
use std::{
cmp::{Ordering, PartialEq, PartialOrd},
collections::VecDeque,
ffi::{CStr, CString},
fmt, mem,
ops::{Deref, DerefMut},
str,
};
/// A 3-element point representing the location of a joint offset or
/// end site.
pub type Offset = [f32; 3];
/// Internal representation of a joint.
#[derive(Clone, Debug, PartialEq)]
pub enum JointData {
/// Root of the skeletal heirarchy.
Root {
/// Name of the root `Joint`.
name: JointName,
/// Positional offset of this `Joint` relative to the parent.
offset: Offset,
/// The channels applicable to this `Joint`.
channels: SmallVec<[Channel; 6]>,
},
/// A child joint in the skeleton.
Child {
/// Name of the `Joint`.
name: JointName,
/// Positional offset of this `Joint` relative to the parent.
offset: Offset,
/// The channels applicable to this `Joint`.
channels: SmallVec<[Channel; 3]>,
/// End site offset.
end_site_offset: Option<Offset>,
/// Private data.
private: JointPrivateData,
},
}
impl JointData {
#[inline]
pub const fn is_root(&self) -> bool {
matches!(*self, JointData::Root { .. })
}
#[inline]
pub const fn is_child(&self) -> bool {
matches!(*self, JointData::Child { .. })
}
#[inline]
pub fn name(&self) -> &[u8] {
let name = match *self {
JointData::Root { ref name, .. } | JointData::Child { ref name, .. } => name,
};
name.as_bytes()
}
#[inline]
pub fn name_str(&self) -> Result<&str, Utf8Error> {
let name = match *self {
JointData::Root { ref name, .. } => name,
JointData::Child { ref name, .. } => name,
};
name.as_str()
}
#[inline]
pub const fn offset(&self) -> &Offset {
match *self {
JointData::Child { ref offset, .. } | JointData::Root { ref offset, .. } => offset,
}
}
#[inline]
pub const fn end_site(&self) -> Option<&Offset> {
match *self {
JointData::Child {
ref end_site_offset,
..
} => end_site_offset.as_ref(),
_ => None,
}
}
#[inline]
pub fn channels(&self) -> &[Channel] {
match *self {
JointData::Child { ref channels, .. } => &channels[..],
JointData::Root { ref channels, .. } => &channels[..],
}
}
/// Returns a mutable reference to ordered array of `Channel`s of this `JointData`.
#[inline]
pub fn channels_mut(&mut self) -> &mut [Channel] {
match *self {
JointData::Child {
ref mut channels, ..
} => &mut channels[..],
JointData::Root {
ref mut channels, ..
} => &mut channels[..],
}
}
/// Return the index of this `Joint` in the array.
#[inline]
pub fn index(&self) -> usize {
self.private_data().map(|d| d.self_index).unwrap_or(0)
}
/// Returns the index of this joint's parent in the `Bvh`'s array of joints,
/// or `None` if this `Joint` is the root joint.
#[inline]
pub fn parent_index(&self) -> Option<usize> {
self.private_data().map(|d| d.parent_index)
}
/// Returns a reference to the `JointPrivateData` of the `JointData` if it
/// exists, or `None`.
#[inline]
pub(crate) const fn private_data(&self) -> Option<&JointPrivateData> {
match *self {
JointData::Child { ref private, .. } => Some(private),
_ => None,
}
}
/// Get the depth of the `JointData` in the heirarchy.
#[inline]
pub(crate) const fn depth(&self) -> usize {
match *self {
JointData::Child { ref private, .. } => private.depth,
_ => 0,
}
}
#[inline]
pub(crate) fn empty_root() -> Self {
JointData::Root {
name: Default::default(),
offset: [0.0, 0.0, 0.0],
channels: Default::default(),
}
}
#[inline]
pub(crate) fn empty_child() -> Self {
JointData::Child {
name: Default::default(),
offset: [0.0, 0.0, 0.0],
channels: Default::default(),
end_site_offset: Default::default(),
private: JointPrivateData::empty(),
}
}
#[inline]
pub(crate) fn set_name<S: Into<JointName>>(&mut self, new_name: S) {
match *self {
JointData::Root { ref mut name, .. } => *name = new_name.into(),
JointData::Child { ref mut name, .. } => *name = new_name.into(),
}
}
pub(crate) fn set_offset(&mut self, new_offset: impl Into<Offset>, is_site: bool) {
let new_offset = new_offset.into();
match *self {
JointData::Root { ref mut offset, .. } => *offset = new_offset,
JointData::Child {
ref mut offset,
ref mut end_site_offset,
..
} => {
if is_site {
*end_site_offset = Some(new_offset);
} else {
*offset = new_offset;
}
}
}
}
#[inline]
pub(crate) fn set_channels(&mut self, new_channels: SmallVec<[Channel; 6]>) {
match *self {
JointData::Root {
ref mut channels, ..
} => *channels = new_channels,
JointData::Child {
ref mut channels, ..
} => *channels = new_channels.iter().copied().collect(),
}
}
/// Returns a mutable reference to the `JointPrivateData` of the `JointData` if it
/// exists, or `None`.
#[inline]
pub(crate) fn private_data_mut(&mut self) -> Option<&mut JointPrivateData> {
match *self {
JointData::Child {
ref mut private, ..
} => Some(private),
_ => None,
}
}
}
/// A string type for the `Joint` name. A `SmallVec` is used for
/// better data locality.
pub(crate) type JointNameInner = SmallVec<[u8; mem::size_of::<String>()]>;
/// Wrapper struct for the `Joint` name type.
#[derive(Clone, Default, Eq, Hash, Ord)]
pub struct JointName(pub(crate) JointNameInner);
impl JointName {
#[inline]
pub(crate) fn as_bytes(&self) -> &[u8] {
self.as_ref()
}
#[inline]
pub(crate) fn as_str(&self) -> Result<&str, Utf8Error> {
self.as_bytes().to_str()
}
}
impl Deref for JointName {
type Target = JointNameInner;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for JointName {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<B: ?Sized + AsRef<[u8]>> PartialEq<B> for JointName {
#[inline]
fn eq(&self, rhs: &B) -> bool {
AsRef::<[u8]>::as_ref(self) == rhs.as_ref()
}
}
impl<B: ?Sized + AsRef<[u8]>> PartialOrd<B> for JointName {
#[inline]
fn partial_cmp(&self, rhs: &B) -> Option<Ordering> {
AsRef::<[u8]>::as_ref(self).partial_cmp(rhs.as_ref())
}
}
macro_rules! impl_from {
() => {};
(;) => {};
($t:ty; $($rest:tt)*) => {
impl From<$t> for JointName {
#[inline]
fn from(b: $t) -> Self {
JointName(b.bytes().collect())
}
}
impl_from!($($rest)*);
}
}
impl From<CString> for JointName {
#[inline]
fn from(s: CString) -> Self {
From::from(s.into_bytes())
}
}
impl From<&'_ CStr> for JointName {
#[inline]
fn from(s: &'_ CStr) -> Self {
From::from(s.to_bytes())
}
}
impl From<Vec<u8>> for JointName {
#[inline]
fn from(s: Vec<u8>) -> Self {
JointName(JointNameInner::from(s))
}
}
impl From<&'_ [u8]> for JointName {
#[inline]
fn from(s: &'_ [u8]) -> Self {
JointName(s.iter().copied().collect::<JointNameInner>())
}
}
impl_from! {
String; &'_ str;
BString; &'_ BStr;
}
macro_rules! impl_as_ref {
($( $t:ty { ref => $method:path, mut => $mut_method:path } )*) => {
$(
impl AsRef<$t> for JointName {
#[inline]
fn as_ref(&self) -> &$t {
$method(&self.0[..])
}
}
impl AsMut<$t> for JointName {
#[inline]
fn as_mut(&mut self) -> &mut $t {
$mut_method(&mut self.0[..])
}
}
)*
}
}
impl_as_ref! {
BStr { ref => AsRef::<BStr>::as_ref, mut => AsMut::<BStr>::as_mut }
[u8] { ref => AsRef::<[u8]>::as_ref, mut => AsMut::<[u8]>::as_mut }
}
impl From<JointNameInner> for JointName {
#[inline]
fn from(s: JointNameInner) -> Self {
JointName(s)
}
}
impl From<JointName> for JointNameInner {
#[inline]
fn from(j: JointName) -> Self {
j.0
}
}
impl fmt::Debug for JointName {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(AsRef::<BStr>::as_ref(self), fmtr)
}
}
impl fmt::Display for JointName {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(AsRef::<BStr>::as_ref(self), fmtr)
}
}
/// Data private to joints.
#[doc(hidden)]
#[derive(Clone, Eq, PartialEq)]
pub struct JointPrivateData {
/// Index of this `Joint` in the array.
pub(crate) self_index: usize,
/// The parent index in the array of `JointPrivateData`s in the `Bvh`.
pub(crate) parent_index: usize,
/// Depth of the `Joint`. A depth of `1` signifies a `Joint` attached to
/// the root.
pub(crate) depth: usize,
}
impl JointPrivateData {
#[inline]
pub(crate) const fn new(self_index: usize, parent_index: usize, depth: usize) -> Self {
JointPrivateData {
self_index,
parent_index,
depth,
}
}
#[inline]
pub(crate) const fn empty() -> Self {
Self::new(0, 0, 0)
}
}
impl fmt::Debug for JointPrivateData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("JointPrivateData { .. }")
}
}
pub(crate) enum JointIterType {
All(usize),
Sequence(VecDeque<usize>),
}
/// An iterator over the `Joint`s of a `Bvh` skeleton.
pub struct Joints<'a> {
pub(crate) joints: &'a [JointData],
// pub(crate) motion_values: &'a [f32],
pub(crate) iter_type: JointIterType,
}
impl fmt::Debug for Joints<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Joints { .. }")
}
}
impl<'a> Joints<'a> {
/// Create a `Joints` iterator over all the `joints` in a `Bvh` file.
pub(crate) const fn iter_root(joints: &'a [JointData]) -> Self {
Joints {
joints,
// clips,
iter_type: JointIterType::All(0),
}
}
pub(crate) const fn iter_from(joints: &'a [JointData], start: usize) -> Self {
Joints {
joints,
// clips,
iter_type: JointIterType::All(start),
}
}
/// Create a `Joints` iterator over all the child joints of `joint`.
///
/// # Notes
///
/// This function only iterates over the direct children of `joint`. If you
/// need to iterate through to the end sites of all children, you will
/// need to continually call `iter_children` on each `Joint` in the iterator.
pub(crate) fn iter_children(joint: &Joint<'a>) -> Self {
let first_child: VecDeque<usize> = joint
.joints
.iter()
.filter(|jd| {
if let Some(p) = jd.private_data() {
p.parent_index == joint.index
} else {
false
}
})
.map(|value| value.index())
.collect();
Joints {
joints: joint.joints,
iter_type: JointIterType::Sequence(first_child),
}
}
/// Finds the `Joint` named `joint_name`, or `None` if it doesn't exist.
#[inline]
pub fn find_by_name<B>(&mut self, joint_name: &B) -> Option<Joint<'a>>
where
B: ?Sized + AsRef<[u8]>,
{
self.find(|b| b.data().name() == joint_name.as_ref())
}
#[allow(unused)]
pub(crate) fn nth_child(joint: &Joint<'a>, child: usize) -> Option<usize> {
Joints::iter_children(joint)
.nth(child)
.map(|joint| joint.index)
}
}
impl<'a> Iterator for Joints<'a> {
type Item = Joint<'a>;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.iter_type {
JointIterType::All(current) => {
if *current >= self.joints.len() {
return None;
}
Some(Joint {
index: *current,
joints: self.joints,
})
}
JointIterType::Sequence(ref mut joints) => {
let Some(next) = joints.pop_front() else {
return None;
};
if next >= self.joints.len() {
return None;
}
Some(Joint {
index: next,
joints: self.joints,
})
}
}
}
}
/// A mutable iterator over the `Joint`s of a `Bvh` skeleton.
#[allow(unused)]
pub struct JointsMut<'a> {
pub(crate) joints: &'a mut [JointData],
pub(crate) current_joint: usize,
pub(crate) from_child: Option<usize>,
}
impl<'a> JointsMut<'a> {
pub(crate) fn iter_root(joints: &'a mut [JointData]) -> Self {
JointsMut {
joints,
// clips,
current_joint: 0,
from_child: None,
}
}
}
impl<'a> Iterator for JointsMut<'a> {
type Item = JointMut<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
None
}
}
impl fmt::Debug for JointsMut<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("JointsMut { .. }")
}
}
/// A view of a joint which provides access to various relevant data.
pub struct Joint<'a> {
/// Index of the `Joint` in the skeleton.
pub(crate) index: usize,
/// `Joints` array which the joint is part of.
pub(crate) joints: &'a [JointData],
}
impl PartialEq for Joint<'_> {
#[inline]
fn eq(&self, rhs: &Self) -> bool {
let (d0, d1) = (self.data(), rhs.data());
PartialEq::eq(d0, d1)
}
}
impl Joint<'_> {
/// Returns `true` if the `Joint` is the root `Joint`, or `false` if it isn't.
///
/// # Examples
///
/// ```no_run
/// # use studio_bvh::bvh;
/// let bvh = bvh! {
/// HIERARCHY
/// ROOT Hips
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// End Site {
/// OFFSET 0.0 0.0 30.0
/// }
/// }
/// MOTION
/// Frames: 0
/// Frame Time: 0.0333333
/// };
///
/// let root = bvh.root_joint().unwrap();
/// assert!(root.is_root());
/// ```
#[inline]
pub const fn is_root(&self) -> bool {
self.data().is_root()
}
/// Returns `true` if the `Joint` is a child `Joint`, or `false` if it isn't.
///
/// # Examples
///
/// ```no_run
/// # use studio_bvh::bvh;
/// let bvh = bvh! {
/// # HIERARCHY
/// # ROOT Base
/// # {
/// # OFFSET 0.0 0.0 0.0
/// # CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// # JOINT End
/// # {
/// # OFFSET 0.0 0.0 15.0
/// # CHANNELS 3 Zrotation Xrotation Yrotation
/// # End Site
/// # {
/// # OFFSET 0.0 0.0 30.0
/// # }
/// # }
/// # }
/// # MOTION
/// # Frames: 0
/// # Frame Time: 0.0333333
/// // bvh hierarchy unspecified ..
/// };
///
/// for joint in bvh.joints().skip(1) {
/// assert!(joint.is_child());
/// }
/// ```
#[inline]
pub const fn is_child(&self) -> bool {
self.data().is_child()
}
/// Returns the name of the `JointData`.
///
/// # Examples
///
/// ```no_run
/// # use studio_bvh::bvh;
/// let bvh = bvh! {
/// HIERARCHY
/// ROOT Hips
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// // ...
/// # End Site {
/// # OFFSET 0.0 0.0 30.0
/// # }
/// }
/// MOTION
/// // ...
/// # Frames: 0
/// # Frame Time: 0.0333333
/// };
///
/// let root = bvh.root_joint().unwrap();
/// assert_eq!(root.name(), b"Hips");
/// ```
#[inline]
pub fn name(&self) -> &[u8] {
self.data().name()
}
/// Returns the offset of the `JointData` if it exists, or `None`.
///
/// # Examples
///
/// ```no_run
/// # use studio_bvh::bvh;
/// let bvh = bvh! {
/// HIERARCHY
/// ROOT Hips
/// {
/// OFFSET 1.2 3.4 5.6
/// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// // ...
/// # End Site {
/// # OFFSET 0.0 0.0 30.0
/// # }
/// }
/// MOTION
/// // ...
/// # Frames: 0
/// # Frame Time: 0.0333333
/// };
///
/// let root = bvh.root_joint().unwrap();
/// assert_eq!(root.offset(), &[1.2, 3.4, 5.6]);
/// ```
#[inline]
pub const fn offset(&self) -> &Offset {
self.data().offset()
}
/// Returns the `end_site` position if this `Joint` has an end site, or `None` if
/// it doesn't.
///
/// # Examples
///
/// ```no_run
/// # use studio_bvh::bvh;
/// let bvh = bvh! {
/// HIERARCHY
/// ROOT Base
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// JOINT Tip
/// {
/// OFFSET 0.0 0.0 5.0
/// CHANNELS 3 Zrotation Xrotation Yrotation
/// End Site
/// {
/// OFFSET 5.4 3.2 1.0
/// }
/// }
/// }
/// MOTION
/// // ...
/// # Frames: 0
/// # Frame Time: 0.0333333
/// };
/// let mut joints = bvh.joints();
/// let base = joints.next().unwrap();
/// assert!(base.end_site().is_none());
///
/// let tip = joints.next().unwrap();
/// assert_eq!(tip.end_site(), Some([5.4, 3.2, 1.0].into()).as_ref());
/// ```
#[inline]
pub const fn end_site(&self) -> Option<&Offset> {
self.data().end_site()
}
/// Returns `true` if the `Joint` has an `end_site_offset`, or `false` if it doesn't.
#[inline]
pub const fn has_end_site(&self) -> bool {
self.end_site().is_some()
}
/// Returns the ordered array of `Channel`s of this `JointData`.
///
/// # Examples
///
/// ```no_run
/// # use studio_bvh::{bvh, ChannelType};
/// let bvh = bvh! {
/// HIERARCHY
/// ROOT Hips
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// // ...
/// # End Site {
/// # OFFSET 0.0 0.0 30.0
/// # }
/// }
/// MOTION
/// // ...
/// # Frames: 0
/// # Frame Time: 0.0333333
/// };
///
/// let root = bvh.root_joint().unwrap();
/// let expected_channels = &[
/// ChannelType::PositionX,
/// ChannelType::PositionY,
/// ChannelType::PositionZ,
/// ChannelType::RotationZ,
/// ChannelType::RotationX,
/// ChannelType::RotationY,
/// ];
///
/// for (channel, &expected) in root
/// .channels()
/// .iter()
/// .map(|c| c.channel_type())
/// .zip(expected_channels.iter())
/// {
/// assert_eq!(channel, expected);
/// }
/// ```
#[inline]
pub fn channels(&self) -> &[Channel] {
self.data().channels()
}
/// Returns the index of this joint in the `Bvh`'s array of joints.
#[inline]
pub const fn index(&self) -> usize {
self.index
}
/// Returns the index of this joint's parent in the `Bvh`'s array of joints,
/// or `None` if this `Joint` is the root joint.
#[inline]
pub fn parent_index(&self) -> Option<usize> {
self.data().parent_index()
}
/// Return the parent `Joint` if it exists, or `None` if it doesn't.
#[inline]
pub fn parent(&self) -> Option<Joint<'_>> {
self.data().parent_index().map(|idx| Joint {
index: idx,
joints: self.joints,
})
}
/// Returns an iterator over the children of `self`.
#[inline]
pub fn children(&self) -> Joints<'_> {
Joints::iter_children(self)
}
/// Access a read-only view of the internal data of the `Joint`.
#[inline]
pub const fn data(&self) -> &JointData {
&self.joints[self.index]
}
}
impl fmt::Debug for Joint<'_> {
#[inline]
fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result {
fmtr.debug_struct("Joint")
.field("index", &self.index)
.field("data", &self.data())
.finish()
}
}
/// A view of a joint which provides mutable access.
pub struct JointMut<'a> {
/// Index of the `Joint` in the skeleton.
pub(crate) index: usize,
/// `Joints` array which the joint is part of.
pub(crate) joints: &'a mut [JointData],
}
impl<'a> JointMut<'a> {
/*
/// Return the parent `Joint` if it exists, or `None` if it doesn't.
#[inline]
pub fn parent(&self) -> Option<Joint<'_>> {
self.data().parent_index().map(|idx| Joint {
self_index: idx,
skeleton: self.skeleton,
clips: self.clips,
})
}
pub fn
/// Returns an iterator over the children of `self`.
#[inline]
pub fn children(&self) -> Joints<'_> {
Joints::iter_children(&self)
}
/// Access a read-only view of the internal data of the `Joint`.
#[inline]
pub fn data(&self) -> &JointData {
&self.joints[self_index]
}
*/
/// Mutable access to the internal data of the `JointMut`.
#[inline]
pub(crate) fn data_mut(&mut self) -> &mut JointData {
&mut self.joints[self.index]
}
}
+42
View File
@@ -0,0 +1,42 @@
use crate::Bvh;
use mint::Vector3;
#[derive(Debug)]
pub struct JointCursor<'a> {
bvh: &'a mut Bvh,
index: usize,
}
impl<'a> JointCursor<'a> {
#[inline]
pub const fn index(&self) -> usize {
self.index
}
#[inline]
pub fn push_joints(&mut self) -> PushJointsChain<'a> {
PushJointsChain { cursor: self }
}
}
#[derive(Debug)]
pub struct PushJointsChain<'a> {
cursor: &'a mut JointCursor<'a>,
}
impl<'a> PushJointsChain<'a> {
pub fn push_joint<B: AsRef<[u8]>, V: Into<Vector3<f32>>>(
&mut self,
name: B,
offset: V,
) -> Self {
todo!()
}
}
impl<'a> From<&'a mut Bvh> for JointCursor<'a> {
#[inline]
fn from(bvh: &'a mut Bvh) -> Self {
Self { bvh, index: 0 }
}
}
+956
View File
@@ -0,0 +1,956 @@
#![warn(missing_docs)]
#![deny(bare_trait_objects, unsafe_code)]
//! # About this library
//!
//! A small library for loading and manipulating BioVision motion files.
//!
//! ## The bvh file format
//!
//! The `Bvh` file format is comprised of two main sections: the 'Heirarchy' section,
//! which defines the joints of the skeleton, and the 'Motion' section, which defines
//! the motion values for each channel.
//!
//! This project contains some samples in the [`data` directory][`data` directory].
//!
//! ### Heierarchy
//!
//! The 'Heirarchy' section defines the skeleton as a tree of joints, where there is
//! a single root joint, and a chain of child joints extending out from each joint,
//! terminated by an 'End Site' section.
//!
//! Each joint has:
//!
//! * A list of channels, which are the degrees of freedom in which the joint may move.
//! Channels are listed in the order in which the transformation should be applied
//! to the global transform for the root.
//! * An offset, which is the vector distance from the parent joint.
//! * An optional end site, which is used to cap off a chain of joints. This is only used
//! to calculate the length of the final bone in the chain.
//!
//! ```text
//! HEIRARCHY
//! ROOT <Root-name>
//! {
//! OFFSET <Root-offset-x> <Root-offset-y> <Root-offset-z>
//! CHANNELS <Num-root-joint-channels> Xposition Yposition <other-root-channels ...>
//! JOINT <Joint-1-name>
//! {
//! OFFSET <Joint-1-offset-x> <Joint-1-offset-y> <Joint-1-offset-z>
//! CHANNELS <Num-joint-1-channels> <Joint-1-channels ...>
//! JOINT <Joint-2-name>
//! {
//! OFFSET <Joint-2-offset-x> <Joint-2-offset-y> <Joint-2-offset-z>
//! CHANNELS <Num-joint-2-channels> <Joint-2-channels ...>
//! JOINT <Joint-with-end-site>
//! {
//! OFFSET ...
//! CHANNELS ...
//! End Site
//! {
//! OFFSET <end-site-offset-x> <end-site-offset-y> <end-site-offset-z>
//! }
//! }
//! ... More child joints
//! }
//! JOINT <Joint-3-name>
//! {
//! OFFSET <Joint-3-offset-x> <Joint-3-offset-y> <Joint-3-offset-z>
//! CHANNELS <Num-joint-3-channels> <Joint-3-channels ...>
//! ... More child joints
//! }
//! ... More child joints
//! }
//! ... More child joints
//! }
//! ```
//!
//! Note that the bvh data is defined in terms of a right-handed coordinate system, where
//! the positive y-axis is the up vector.
//!
//! ### Motion
//!
//! The `MOTION` section of the bvh file records the number of frames, the frame time, and
//! defines the full range of motions for each channel, frame by frame.
//!
//! ```text
//! MOTION
//! Frames: <num-frames>
//! Frame Time: <frame-time>
//! <frame-0-channel-0-value> <frame-0-channel-1-value> <frame-0-channel-2-value> ...
//! <frame-1-channel-0-value> <frame-1-channel-1-value> <frame-1-channel-2-value> ...
//! <frame-2-channel-0-value> <frame-2-channel-1-value> <frame-2-channel-2-value> ...
//! ⋮
//! ```
//!
//! The frame time is recorded in seconds, and tells the animation system how long each frame
//! of the animation should last for. This value is usually around 0.033333333, which is close
//! to 30 frames per second.
//!
//! The list of motion values is a matrix, where each row represents a frame. Each column of
//! the row represents a transformation around the channel axis - for example a motion value
//! of 130.0 for an `Xposition` channel would correspond to a rotation of 130.0 degrees around
//! the x-axis.
//!
//! Note that rotations are conventionally in degrees, although it will be up to your application
//! how to interpret each motion's value.
//!
//! ## Using this library.
//!
//! ### Creating a [`Bvh`][`Bvh`] struct:
//!
//! There are a few ways to create a [`Bvh`][`Bvh`] struct:
//!
//! * You can use the [`from_reader`][`from_reader`] function, which will parse a `BufRead`
//! as a bvh file. The [`from_bytes`][`from_bytes`] function is a convenient wrapper function
//! to parse an in-memory slice of bytes as a `bvh` file. Note that the file does not need to
//! be strictly UTF-8, although it should be an ascii-compatible encoding. These functions are
//! also available as associated methods on the `Bvh` type directly as [`Bvh::from_reader`]
//! [`Bvh::from_reader`] and [`Bvh::from_bytes`][`Bvh::from_bytes`]
//!
//! * You can use the [`bvh!`][`bvh!`] macro to construct a [`Bvh`][`Bvh`] instance in your source files
//! using the same syntax as you would use for a standard bvh file.
//!
//! * You can use the [`builder`][`builder`] module to dynamically construct a bvh. This is useful
//! for converting data from other formats into a [`Bvh`][`Bvh`] struct.
//!
//! * You can create an empty [`Bvh`][`Bvh`] using the [`Bvh::new`][`Bvh::new`] or [`Default::default`]
//! [`Default::default`] methods.
//!
//! ### Other operations:
//!
//! Once you have a valid [`Bvh`][`Bvh`] struct, there are a number of ways you can inspect and
//! manipulate it:
//!
//! * The [`Bvh::joints`][`Bvh::joints`] method can be used to iterate through each [`Joint`][`Joint`]
//! of the [`Bvh`][`Bvh`]. Each [`Joint`][`Joint`] can be inspected through its [`JointData`]
//! [`JointData`], which can be obtained with the [`Joint::data`][`Joint::data`] method.
//!
//! * The [`Bvh::frames`][`Bvh::frames`] method returns a [`Frames`][`Frames`] iterator over each
//! frame of the animation. A [`Frame`][`Frame`] can only be indexed by a [`Channel`][`Channel`]
//! belonging to an associated [`Joint`][`Joint`] of the [`Bvh`][`Bvh`], although you can convert
//! it into an [`&[`][`slice`][`f32`][`f32`][`]`][`slice`] using the [`Frame::as_slice`][`Frame::as_slice`] method.
//!
//! * You can serialise the [`Bvh`][`Bvh`] into a [`Write`][`Write`] type using the [`Bvh::write_to`]
//! [`Bvh::write_to`] method. There is also the [`Bvh::to_bstring`][`Bvh::to_bstring`] method, which
//! converts the [`Bvh`][`Bvh`] into a [`BString`][`BString`]. Various aspects of the formatting
//! can be customised using the [`WriteOptions`][`WriteOptions`] type, such as the line termination
//! style, indentation method, and floating point accuracy.
//!
//! ## Examples
//!
//! This library comes with some example applications, which can be viewed on [Github][Github].
//!
//! ## Other resources
//!
//! * More information on this file format can be found [here][bvh_html].
//! * A large library of bvh files is freely available from [CMU's motion capture database]
//! [CMU's motion capture database].
//!
//! [`data` directory]: https://github.com/burtonageo/bvh_anim/tree/master/data
//! [`bvh`]: struct.Bvh.html
//! [`from_reader`]: fn.from_reader.html
//! [`from_bytes`]: fn.from_bytes.html
//! [`Bvh::from_reader`]: struct.Bvh.html#method.from_reader
//! [`Bvh::from_bytes`]: struct.Bvh.html#method.from_bytes
//! [`bvh!`]: macro.bvh.html
//! [`builder`]: builder/index.html
//! [`Bvh::new`]: struct.Bvh.html#method.new
//! [`Default::default`]: https://doc.rust-lang.org/stable/std/default/trait.Default.html#tymethod.default
//! [`Bvh::joints`]: struct.Bvh.html#method.joints
//! [`Joint`]: struct.Joint.html
//! [`JointData`]: enum.JointData.html
//! [`Joint::data`]: struct.Joint.html#method.data
//! [`Bvh::frames`]: struct.Bvh.html#method.frames
//! [`Frames`]: struct.Frames.html
//! [`Frame`]: struct.Frame.html
//! [`slice`]: https://doc.rust-lang.org/std/primitive.slice.html
//! [`f32`]: https://doc.rust-lang.org/stable/std/primitive.f32.html
//! [`Channel`]: struct.Channel.html
//! [`Frame::as_slice`]: struct.Frame.html#method.as_slice
//! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
//! [`Bvh::write_to`]: struct.Bvh.html#method.write_to
//! [`Bvh::to_bstring`]: struct.Bvh.html#method.to_bstring
//! [`BString`]: https://docs.rs/bstr/0.1.2/bstr/struct.BString.html
//! [`WriteOptions`]: write/struct.WriteOptions.html
//! [Github]: https://github.com/burtonageo/bvh_anim/tree/master/examples
//! [bvh_html]: https://research.cs.wisc.edu/graphics/Courses/cs-838-1999/Jeff/BVH.html
//! [CMU's motion capture database]: https://sites.google.com/a/cgspeed.com/cgspeed/motion-capture/daz-friendly-release
#[macro_use]
mod macros;
pub mod errors;
pub mod write;
mod frame_cursor;
mod frame_iter;
pub mod joint;
mod parse;
use crate::{
errors::{LoadError, ParseChannelError},
frames::{FrameCursor, Frames, FramesMut},
joint::Offset,
};
use bstr::{
io::{BufReadExt, ByteLines},
BStr, ByteSlice,
};
use std::{
convert::TryFrom,
fmt,
io::{self, Cursor, Write},
iter::Enumerate,
mem,
num::NonZeroUsize,
str::{self, FromStr},
time::Duration,
};
pub mod frames {
//! This module contains types and functions used for accessing and modifying
//! frame data.
pub use crate::frame_cursor::FrameCursor;
pub use crate::frame_iter::{Frame, FrameIndex, FrameMut, Frames, FramesMut};
}
pub use joint::{Joint, JointData, JointMut, Joints, JointsMut};
#[doc(hidden)]
pub use macros::BvhLiteralBuilder;
struct CachedEnumerate<I> {
iter: Enumerate<I>,
last_enumerator: Option<usize>,
}
impl<I> CachedEnumerate<I> {
#[inline]
fn new(iter: Enumerate<I>) -> Self {
CachedEnumerate {
iter,
last_enumerator: None,
}
}
#[inline]
fn last_enumerator(&self) -> Option<usize> {
self.last_enumerator
}
}
impl<I: Iterator> Iterator for CachedEnumerate<I> {
type Item = <Enumerate<I> as Iterator>::Item;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (curr, item) = self.iter.next()?;
self.last_enumerator = Some(curr);
Some((curr, item))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
type EnumeratedLines<'a> = CachedEnumerate<ByteLines<&'a mut dyn BufReadExt>>;
impl EnumeratedLines<'_> {
pub(crate) fn next_non_empty_line(&mut self) -> Option<<Self as Iterator>::Item> {
let mut next = self.next();
loop {
match next {
None => return None,
Some((idx, result)) => {
let string = match result {
Ok(s) => s,
Err(e) => return Some((idx, Err(e))),
};
if string.trim().is_empty() {
next = self.next()
} else {
return Some((idx, Ok(string)));
}
}
}
}
}
}
/// Loads the `Bvh` from the `reader`.
#[inline]
pub fn from_reader<R: BufReadExt>(data: R) -> Result<Bvh, LoadError> {
Bvh::from_reader(data)
}
/// Parse a sequence of bytes as if it were an in-memory `Bvh` file.
///
/// # Examples
///
/// ```
/// # use studio_bvh::{self, from_bytes};
/// let bvh_string = br#"
/// HIERARCHY
/// ROOT Hips
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 3 Xposition Yposition Zposition
/// End Site
/// {
/// OFFSET 0.0 0.0 0.0
/// }
/// }
/// MOTION
/// Frames: 1
/// Frame Time: 0.033333333
/// 0.0 0.0 0.0
/// "#;
///
/// let bvh = from_bytes(&bvh_string[..])?;
/// # let _ = bvh;
/// # Result::<(), studio_bvh::errors::LoadError>::Ok(())
/// ```
#[inline]
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Bvh, LoadError> {
Bvh::from_bytes(bytes)
}
/// Parse a `str` as if it were an in-memory `Bvh` file.
///
/// # Examples
///
/// ```
/// # use studio_bvh::{self, from_str};
/// let bvh_string = "
/// HIERARCHY
/// ROOT Hips
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 3 Xposition Yposition Zposition
/// End Site
/// {
/// OFFSET 0.0 0.0 0.0
/// }
/// }
/// MOTION
/// Frames: 1
/// Frame Time: 0.033333333
/// 0.0 0.0 0.0
/// ";
///
/// let bvh = from_str(bvh_string)?;
/// # let _ = bvh;
/// # Result::<(), studio_bvh::errors::LoadError>::Ok(())
/// ```
#[inline]
pub fn from_str(string: &str) -> Result<Bvh, LoadError> {
Bvh::from_str(string)
}
/// A complete `bvh` file.
///
/// See the [module documentation](index.html#using-this-library)
/// for more information.
#[derive(Clone, Debug, PartialEq)]
pub struct Bvh {
/// The list of joints. If the root joint exists, it is always at
/// index `0`.
joints: Vec<JointData>,
/// The motion values of the `Frame`.
motion_values: Vec<f32>,
/// The number of `Channel`s in the bvh.
num_channels: usize,
/// The total time it takes to play one frame.
frame_time: Duration,
}
impl Bvh {
/// Create an empty `Bvh`.
#[inline]
pub const fn new() -> Self {
Self {
joints: Vec::new(),
motion_values: Vec::new(),
num_channels: 0,
frame_time: Duration::from_secs(0),
}
}
/// Parse a sequence of bytes as if it were an in-memory `Bvh` file.
///
/// # Examples
///
/// ```
/// # use studio_bvh::{self, Bvh};
/// let bvh_string = br#"
/// HIERARCHY
/// ROOT Hips
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 3 Xposition Yposition Zposition
/// End Site
/// {
/// OFFSET 0.0 0.0 0.0
/// }
/// }
/// MOTION
/// Frames: 1
/// Frame Time: 0.033333333
/// 0.0 0.0 0.0
/// "#;
///
/// let bvh = Bvh::from_bytes(&bvh_string[..])?;
/// # let _ = bvh;
/// # Result::<(), studio_bvh::errors::LoadError>::Ok(())
/// ```
#[inline]
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self, LoadError> {
Bvh::from_reader(Cursor::new(bytes))
}
/// Loads the `Bvh` from the `reader`.
#[inline]
pub fn from_reader<R: BufReadExt>(mut reader: R) -> Result<Self, LoadError> {
#[inline(never)]
fn from_reader_(reader: &mut dyn BufReadExt) -> Result<Bvh, LoadError> {
let mut lines = CachedEnumerate::new(reader.byte_lines().enumerate());
let mut bvh = Bvh::default();
bvh.read_joints(&mut lines)?;
bvh.read_motion(&mut lines)?;
Ok(bvh)
}
from_reader_(reader.by_ref())
}
/// Writes the `Bvh` using the `bvh` file format to the `writer`, with
/// the default formatting options.
///
/// # Notes
///
/// To customise the formatting, see the [`WriteOptions`][`WriteOptions`] type.
///
/// # Examples
///
/// ```no_run
/// # use studio_bvh::bvh;
/// # use std::io;
/// # use std::fs::File;
/// let bvh = bvh! {
/// // fields unspecified
/// };
///
/// let mut out_file = File::create("./out_file.bvh")?;
/// bvh.write_to(&mut out_file)?;
/// # Result::<(), io::Error>::Ok(())
/// ```
///
/// [`WriteOptions`]: write/struct.WriteOptions.html
#[inline]
pub fn write_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
write::WriteOptions::default().write(self, writer)
}
/// Writes the `Bvh` using the `bvh` file format into a `Vec<u8>` with
/// the default formatting options.
///
/// The data is guaranteed to be `ASCII`, except for the joint names, which
/// may contain arbitrary bytes.
///
/// # Notes
///
/// To customise the formatting, see the [`WriteOptions`][`WriteOptions`] type.
///
/// [`WriteOptions`]: write/struct.WriteOptions.html
#[inline]
pub fn to_string(&self) -> Vec<u8> {
write::WriteOptions::default().write_to_string(self)
}
/// Returns the root joint if it exists, or `None` if the skeleton is empty.
///
/// # Examples
///
/// ```
/// # use studio_bvh::{Bvh, bvh};
/// let bvh = Bvh::new();
/// assert!(bvh.root_joint().is_none());
///
/// let bvh = bvh! {
/// HIERARCHY
/// ROOT Hips
/// {
/// # OFFSET 0.0 0.0 0.0
/// # CHANNELS 0
/// # End Site
/// # {
/// # OFFSET 0.0 0.0 0.0
/// # }
/// // Joints...
/// }
/// MOTION
/// # Frames: 0
/// # Frame Time: 0.033333333
/// // Frames...
/// };
///
/// assert!(bvh.root_joint().is_some());
/// ```
#[inline]
pub fn root_joint(&self) -> Option<Joint<'_>> {
if self.joints.is_empty() {
None
} else {
Some(Joint {
index: 0,
joints: &self.joints[..],
})
}
}
/// Returns an iterator over all the `Joint`s in the `Bvh`.
#[inline]
pub fn joints(&self) -> Joints<'_> {
Joints::iter_root(&self.joints[..])
}
/// Returns a mutable iterator over all the joints in the `Bvh`.
#[inline]
pub fn joints_mut(&mut self) -> JointsMut<'_> {
JointsMut::iter_root(&mut self.joints[..])
}
/// Returns a `Frames` iterator over the frames of the bvh.
///
/// # Example
///
/// ```
/// # use studio_bvh::Bvh;
/// # fn use_bvh(bvh: Bvh) {
/// let bvh = // ...
/// # bvh;
/// for frame in bvh.frames() {
/// // use frame...
/// # let _ = frame;
/// }
/// # } // fn use_bvh()
/// ```
#[inline]
pub fn frames(&self) -> Frames<'_> {
Frames {
chunks: NonZeroUsize::new(self.num_channels).map(|_| {
self.motion_values
.as_slice()
.chunks_exact(self.num_channels)
}),
}
}
/// Returns a mutable iterator over the frames of the bvh.
///
/// # Example
///
/// ```
/// # use studio_bvh::Bvh;
/// # fn use_bvh(bvh: Bvh) {
/// let mut bvh = // ...
/// # bvh;
/// for frame in bvh.frames_mut() {
/// // use frame...
/// # let _ = frame;
/// }
/// # } // fn use_bvh()
/// ```
#[inline]
pub fn frames_mut(&mut self) -> FramesMut<'_> {
FramesMut {
chunks: NonZeroUsize::new(self.num_channels).map(move |_| {
self.motion_values
.as_mut_slice()
.chunks_exact_mut(self.num_channels)
}),
}
}
/// Removes all frame data from the `Bvh`, returning the previous frames.
///
/// # Example
///
/// ```
/// # use studio_bvh::bvh;
/// let mut bvh = bvh! {
/// HIERARCHY
/// // hierarchy omitted...
/// # ROOT Hips
/// # {
/// # OFFSET 0.0 0.0 0.0
/// # CHANNELS 3 Xposition Yposition Zposition
/// # End Site
/// # {
/// # OFFSET 0.0 0.0 0.0
/// # }
/// # }
/// MOTION
/// Frames: 1
/// Frame Time: 0.033333333
/// 0.0 0.0 0.0
/// };
///
/// let frames = bvh.extract_frames();
/// assert_eq!(bvh.num_frames(), 0);
/// assert_eq!(bvh.frames().len(), 0);
/// assert_eq!(frames, &[0.0, 0.0, 0.0]);
/// ```
#[inline]
pub fn extract_frames(&mut self) -> Vec<f32> {
mem::take(&mut self.motion_values)
}
/// Get the number of frames in the `Bvh`.
#[inline]
#[deprecated(note = "Please use `frames().len()` instead,")]
pub fn num_frames(&self) -> usize {
self.frames().len()
}
/// Get the number of channels in the `Bvh`.
#[inline]
pub const fn num_channels(&self) -> usize {
self.num_channels
}
/// Get the duration each frame should play for in the `Bvh`.
///
/// # Example
///
/// ```
/// # fn main() {
/// # use studio_bvh::bvh;
/// # use std::time::Duration;
/// let bvh = bvh! {
/// # HIERARCHY
/// # MOTION
/// # Frames: 0
/// // ...
/// Frame Time: 1
/// // ...
/// };
///
/// assert_eq!(*bvh.frame_time(), Duration::from_secs(1));
/// # } // fn main()
/// ```
#[inline]
pub const fn frame_time(&self) -> &Duration {
&self.frame_time
}
/// Set the duration each frame should play for in the `Bvh` to `new_frame_time`.
///
/// # Example
///
/// ```
/// # fn main() {
/// # use studio_bvh::bvh;
/// # use std::time::Duration;
/// let mut bvh = bvh! {
/// // ...
/// };
///
/// let new_frame_time = Duration::from_secs(21);
/// bvh.set_frame_time(new_frame_time);
///
/// assert_eq!(*bvh.frame_time(), new_frame_time);
/// # } // fn main()
/// ```
#[inline]
pub fn set_frame_time(&mut self, new_frame_time: Duration) {
self.frame_time = new_frame_time;
}
/// Create a new `FrameCursor` for inserting and removing frames.
#[inline]
pub fn frame_cursor(&mut self) -> FrameCursor<'_> {
From::from(self)
}
#[allow(missing_docs)]
pub fn get_joint(&self, index: usize) -> Option<Joint<'_>> {
if index < self.joints.len() {
Some(Joint {
index,
joints: &self.joints,
})
} else {
None
}
}
}
impl Default for Bvh {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl FromStr for Bvh {
type Err = LoadError;
#[inline]
fn from_str(string: &str) -> Result<Self, Self::Err> {
Bvh::from_bytes(string.as_bytes())
}
}
impl TryFrom<&'_ str> for Bvh {
type Error = LoadError;
#[inline]
fn try_from(string: &'_ str) -> Result<Self, Self::Error> {
FromStr::from_str(string)
}
}
impl TryFrom<&'_ BStr> for Bvh {
type Error = LoadError;
#[inline]
fn try_from(string: &'_ BStr) -> Result<Self, Self::Error> {
Bvh::from_bytes(string.as_bytes())
}
}
impl TryFrom<&'_ [u8]> for Bvh {
type Error = LoadError;
#[inline]
fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
Bvh::from_bytes(bytes)
}
}
/// A `Channel` composed of a `ChannelType` and an index into the
/// corresponding motion data.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Channel {
/// The type of the `Channel`.
channel_type: ChannelType,
/// The index into the `Frame` which corresponds to this `Channel`.
motion_index: usize,
}
impl Channel {
#[inline]
const fn new(channel_type: ChannelType, motion_index: usize) -> Self {
Channel {
channel_type,
motion_index,
}
}
/// Returns the `ChannelType` to which this `Channel` corresponds.
#[inline]
pub const fn channel_type(&self) -> ChannelType {
self.channel_type
}
/// Returns the index of the motion value to which this `Channel` corresponds.
#[inline]
pub const fn motion_index(&self) -> usize {
self.motion_index
}
}
/// The available degrees of freedom along which a `Joint` may be manipulated.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ChannelType {
/// Can be translated along the `x` axis.
PositionX,
/// Can be translated along the `y` axis.
PositionY,
/// Can be translated along the `z` axis.
PositionZ,
/// Can be rotated along the `x` axis.
RotationX,
/// Can be rotated along the `y` axis.
RotationY,
/// Can be rotated along the `z` axis.
RotationZ,
}
impl ChannelType {
/// Attempt to parse a bvh channel byte string into a `ChannelType`.
/// Returns `Err` if the string cannot be parsed.
///
/// # Examples
///
/// ```
/// # use studio_bvh::ChannelType;
/// assert_eq!(
/// ChannelType::from_bytes("Xrotation").unwrap(),
/// ChannelType::RotationX);
///
/// let err = ChannelType::from_bytes("Hello").unwrap_err();
/// assert_eq!(&err.into_inner(), b"Hello");
/// ```
#[inline]
pub fn from_bytes<B>(s: &B) -> Result<Self, ParseChannelError>
where
B: AsRef<[u8]> + ?Sized,
{
let s = s.as_ref();
match s {
b"Xrotation" => Ok(ChannelType::RotationX),
b"Yrotation" => Ok(ChannelType::RotationY),
b"Zrotation" => Ok(ChannelType::RotationZ),
b"Xposition" => Ok(ChannelType::PositionX),
b"Yposition" => Ok(ChannelType::PositionY),
b"Zposition" => Ok(ChannelType::PositionZ),
_ => Err(ParseChannelError::from(s)),
}
}
/// Returns `true` if this channel corresponds to a rotational
/// transform, otherwise `false`.
///
/// # Example
///
/// ```
/// # use studio_bvh::ChannelType;
/// let channel_type = ChannelType::RotationX;
/// assert!(channel_type.is_rotation());
/// ```
#[inline]
pub const fn is_rotation(&self) -> bool {
matches!(
*self,
ChannelType::RotationX | ChannelType::RotationY | ChannelType::RotationZ
)
}
/// Returns `true` if this channel corresponds to a positional
/// transform, otherwise `false`.
///
/// # Example
///
/// ```
/// # use studio_bvh::ChannelType;
/// let channel_type = ChannelType::PositionZ;
/// assert!(channel_type.is_position());
/// ```
#[inline]
pub const fn is_position(&self) -> bool {
!self.is_rotation()
}
/// Get the `Axis` about which this `Channel` transforms.
///
/// # Example
///
/// ```
/// # use studio_bvh::{Axis, ChannelType};
/// let channel_type = ChannelType::PositionX;
/// assert_eq!(channel_type.axis(), Axis::X);
/// ```
#[inline]
pub const fn axis(&self) -> Axis {
match *self {
ChannelType::RotationX | ChannelType::PositionX => Axis::X,
ChannelType::RotationY | ChannelType::PositionY => Axis::Y,
ChannelType::RotationZ | ChannelType::PositionZ => Axis::Z,
}
}
/// Returns the string representation of the `ChannelType`.
#[inline]
pub const fn as_str(&self) -> &'static str {
match *self {
ChannelType::RotationX => "Xrotation",
ChannelType::RotationY => "Yrotation",
ChannelType::RotationZ => "Zrotation",
ChannelType::PositionX => "Xposition",
ChannelType::PositionY => "Yposition",
ChannelType::PositionZ => "Zposition",
}
}
}
impl TryFrom<&'_ [u8]> for ChannelType {
type Error = ParseChannelError;
#[inline]
fn try_from(string: &[u8]) -> Result<Self, Self::Error> {
ChannelType::from_bytes(string)
}
}
impl TryFrom<&'_ str> for ChannelType {
type Error = ParseChannelError;
#[inline]
fn try_from(string: &str) -> Result<Self, Self::Error> {
ChannelType::from_bytes(string.as_bytes())
}
}
impl FromStr for ChannelType {
type Err = ParseChannelError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
ChannelType::from_bytes(s)
}
}
impl fmt::Display for ChannelType {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// An enum which represents an axis along a direction in 3D space.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Axis {
/// `x` axis.
X,
/// `y` axis.
Y,
/// `z` axis.
Z,
}
impl Axis {
/// Returns the `Offset` which represents the axis.
///
/// # Examples
///
/// ```
/// # use studio_bvh::Axis;
/// assert_eq!(Axis::X.vector(), [1.0, 0.0, 0.0]);
/// assert_eq!(Axis::Y.vector(), [0.0, 1.0, 0.0]);
/// assert_eq!(Axis::Z.vector(), [0.0, 0.0, 1.0]);
/// ```
#[inline]
pub fn vector(&self) -> Offset {
match *self {
Axis::X => [1.0, 0.0, 0.0],
Axis::Y => [0.0, 1.0, 0.0],
Axis::Z => [0.0, 0.0, 1.0],
}
}
}
impl fmt::Display for Axis {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match *self {
Axis::X => "x",
Axis::Y => "y",
Axis::Z => "z",
};
f.write_str(s)
}
}
+617
View File
@@ -0,0 +1,617 @@
use crate::{
joint::{JointData, Offset},
Bvh, Channel, ChannelType,
};
#[doc(hidden)]
#[macro_export]
macro_rules! match_channels {
($builder:ident; ) => {
};
($builder:ident; Xposition $($rest:ident)*) => {
$builder.push_channel($crate::ChannelType::PositionX);
$crate::match_channels!($builder; $($rest)*);
};
($builder:ident; Yposition $($rest:ident)*) => {
$builder.push_channel($crate::ChannelType::PositionY);
$crate::match_channels!($builder; $($rest)*);
};
($builder:ident; Zposition $($rest:ident)*) => {
$builder.push_channel($crate::ChannelType::PositionZ);
$crate::match_channels!($builder; $($rest)*);
};
($builder:ident; Xrotation $($rest:ident)*) => {
$builder.push_channel($crate::ChannelType::RotationX);
$crate::match_channels!($builder; $($rest)*);
};
($builder:ident; Yrotation $($rest:ident)*) => {
$builder.push_channel($crate::ChannelType::RotationY);
$crate::match_channels!($builder; $($rest)*);
};
($builder:ident; Zrotation $($rest:ident)*) => {
$builder.push_channel($crate::ChannelType::RotationZ);
$crate::match_channels!($builder; $($rest)*);
};
($builder:expr; $($other:tt)*) => {
compile_error!("Unknown tokens");
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! parse_offset {
($builder:ident ($ofst_x:literal $ofst_y:literal $ofst_z:literal)) => {
let offset = [f32::from($ofst_x), f32::from($ofst_y), f32::from($ofst_z)];
$builder.push_joint_offset(offset.into(), false);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! parse_joints_internal {
($builder:ident (
$(
JOINT $joint_nm:ident
{
$( $children:tt )*
}
)*
)) => {
$(
$builder.push_joint(stringify!($joint_nm));
$builder.current_depth += 1;
$crate::parse_joints_internal!($builder ( $( $children )* ));
$builder.current_depth -= 1;
)*
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 0
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 1 $ch0:ident
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::match_channels!($builder ; $ch0);
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 2 $ch0:ident $ch1:ident
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::match_channels!($builder ; $ch0 $ch1);
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 3 $ch0:ident $ch1:ident $ch2:ident
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::match_channels!($builder ; $ch0 $ch1 $ch2);
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 4 $ch0:ident $ch1:ident $ch2:ident $ch3:ident
$($rest:tt)*
)) => {
bvh_anim$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
bvh_anim$crate::match_channels!($builder ; $ch0 $ch1 $ch2 $ch3);
bvh_anim$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 5
$ch0:ident
$ch1:ident
$ch2:ident
$ch3:ident
$ch4:ident
$($rest:tt)*
)) => {
bvh_anim::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
bvh_anim::match_channels!($builder ; $ch0 $ch1 $ch2 $ch3 $ch4);
bvh_anim::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 6
$ch0:ident
$ch1:ident
$ch2:ident
$ch3:ident
$ch4:ident
$ch5:ident
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::match_channels!($builder ;
$ch0 $ch1 $ch2 $ch3 $ch4 $ch5);
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
CHANNELS 7
$ch0:ident
$ch1:ident
$ch2:ident
$ch3:ident
$ch4:ident
$ch5:ident
$ch6:ident
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::match_channels!($builder ;
$ch0 $ch1 $ch2 $ch3 $ch4 $ch5 $ch6);
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 8
$ch0:ident
$ch1:ident
$ch2:ident
$ch3:ident
$ch4:ident
$ch5:ident
$ch6:ident
$ch7:ident
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::match_channels!($builder ;
$ch0 $ch1 $ch2 $ch3 $ch4 $ch5 $ch6 $ch7);
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS 9
$ch0:ident
$ch1:ident
$ch2:ident
$ch3:ident
$ch4:ident
$ch5:ident
$ch6:ident
$ch7:ident
$ch8:ident
$($rest:tt)*
)) => {
$crate::parse_offset!($builder ($ofst_x $ofst_y $ofst_z));
$crate::match_channels!($builder ;
$ch0 $ch1 $ch2 $ch3 $ch4 $ch5 $ch6 $ch7 $ch8);
$crate::parse_joints_internal!($builder ( $($rest)* ));
};
($builder:ident (
OFFSET $ofst_x:literal $ofst_y:literal $ofst_z:literal
CHANNELS $unsupported:literal $($rest:tt)*
)) => {
compile_error!("No more than 9 channels supported in CHANNELS statement");
};
($builder:ident (
End Site
{
OFFSET $end_x:literal $end_y:literal $end_z:literal
}
)) => {
let offset = [
f32::from($end_x),
f32::from($end_y),
f32::from($end_z),
];
$builder.push_joint_offset(offset.into() , true);
};
}
/// Create a new [`Bvh`][`Bvh`] object using a macro literal. Useful for
/// testing.
///
/// # Notes
///
/// If you have a very complex `Bvh` file with a large number of joints and frames, then
/// this macro will scale badly to it, and compilation time will suffer.
///
/// # Example
///
/// ```
/// # use studio_bvh::bvh;
/// let simple_skeleton = bvh! {
/// HIERARCHY
/// ROOT Base
/// {
/// OFFSET 0.0 0.0 0.0
/// CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
/// JOINT Middle1
/// {
/// OFFSET 0.0 0.0 15.0
/// CHANNELS 3 Zrotation Xrotation Yrotation
/// JOINT Tip1
/// {
/// OFFSET 0.0 0.0 30.0
/// CHANNELS 3 Zrotation Xrotation Yrotation
/// End Site
/// {
/// OFFSET 0.0 0.0 45.0
/// }
/// }
/// }
/// JOINT Middle2
/// {
/// OFFSET 0.0 15.0 0.0
/// CHANNELS 3 Zrotation Xrotation Yrotation
/// JOINT Tip2
/// {
/// OFFSET 0.0 30.0 0.0
/// CHANNELS 3 Zrotation Xrotation Yrotation
/// End Site
/// {
/// OFFSET 0.0 45.0 0.0
/// }
/// }
/// }
/// }
///
/// MOTION
/// Frames: 3
/// // Time in seconds.
/// Frame Time: 0.033333333333
/// 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
/// 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
/// 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0
/// };
/// ```
///
/// You can use the `bvh` macro to create empty `Bvh` instances:
///
/// ```
/// # use studio_bvh::bvh;
/// let empty = bvh!{};
///
/// let empty = bvh! {
/// HIERARCHY
/// MOTION
/// };
///
/// let empty = bvh! {
/// HIERARCHY
/// MOTION
/// Frames: 0
/// };
/// ```
///
/// [`bvh`]: struct.Bvh.html
#[macro_export]
macro_rules! bvh {
() => {
$crate::Bvh::default()
};
(
HIERARCHY
MOTION
) => {
$crate::Bvh::default()
};
(
HIERARCHY
MOTION
Frames: 0
) => {
$crate::Bvh::default()
};
(
HIERARCHY
MOTION
Frames: 0
Frame Time: $frame_time:literal
) => {
{
use std::time::Duration;
let mut bvh = $crate::Bvh::default();
bvh.set_frame_time(Duration::from_secs_f64(f64::from($frame_time)));
bvh
}
};
(
HIERARCHY
ROOT $root_name:ident
{
$( $joints:tt )*
}
MOTION
Frames: 0
Frame Time: $frame_time:literal
) => {
{
use std::time::Duration;
use $crate::parse_joints_internal;
let mut builder = $crate::BvhLiteralBuilder::default();
builder.push_root(stringify!($root_name));
builder.current_depth += 1;
parse_joints_internal!(builder ($($joints)*));
builder.current_depth -= 1;
builder.set_num_channels();
builder.bvh.set_frame_time(Duration::from_secs_f64(f64::from($frame_time)));
builder.bvh
}
};
(
HIERARCHY
ROOT $root_name:ident
{
$( $joints:tt )*
}
MOTION
Frames: $num_frames:literal
Frame Time: $frame_time:literal
$(
$motion:literal
)+
) => {
{
use $crate::bvh;
let mut new_bvh = bvh! {
HIERARCHY
ROOT $root_name
{
$( $joints )*
}
MOTION
Frames: 0
Frame Time: $frame_time
};
let num_channels = new_bvh.num_channels();
new_bvh
.frame_cursor()
.try_insert_frames([ $( f32::from($motion) ),+ ].chunks(num_channels))
.expect("Could not create bvh type from macro literal");
assert_eq!(new_bvh.frames().len(), $num_frames as usize);
new_bvh
}
};
}
// @TODO: refactor this into a general `Builder` so that we have an
// easy interface to get other animation/skeleton data into a `bvh`
// file.
/// Helper struct to build a `Bvh` from the macro without exposing
/// too many internals.
#[doc(hidden)]
#[derive(Default)]
pub struct BvhLiteralBuilder {
pub bvh: Bvh,
pub current_channel_index: usize,
pub current_depth: usize,
pub current_index: usize,
}
#[doc(hidden)]
impl BvhLiteralBuilder {
pub fn push_root(&mut self, name: &str) {
let mut root = JointData::empty_root();
root.set_name(name);
self.bvh.joints.push(root);
self.current_index += 1;
}
pub fn push_joint(&mut self, name: &str) {
// @TODO: make this shared
#[inline]
fn get_parent_index(joints: &[JointData], for_depth: usize) -> usize {
joints
.iter()
.rev()
.find(|jd| jd.depth() == for_depth.saturating_sub(1))
.and_then(|jd| jd.private_data().map(|p| p.self_index))
.unwrap_or(0)
}
let idx = self.current_index;
let dpth = self.current_depth;
let parent = get_parent_index(&self.bvh.joints[..], dpth);
let mut joint = JointData::empty_child();
joint.set_name(name);
if let Some(ref mut private) = joint.private_data_mut() {
private.self_index = idx;
private.depth = dpth;
private.parent_index = parent;
};
self.bvh.joints.push(joint);
self.current_index += 1;
}
pub fn push_channel(&mut self, channel: ChannelType) {
let channel = Channel::new(channel, self.current_channel_index);
if let Some(joint) = self.last_joint() {
match *joint {
JointData::Root {
ref mut channels, ..
} => {
channels.push(channel);
}
JointData::Child {
ref mut channels, ..
} => {
channels.push(channel);
}
}
}
self.current_channel_index += 1;
}
pub fn push_joint_offset(&mut self, offset: Offset, is_end_site: bool) {
if let Some(joint) = self.last_joint() {
joint.set_offset(offset, is_end_site);
}
}
#[inline]
pub fn set_num_channels(&mut self) {
self.bvh.num_channels = self.current_channel_index;
}
#[inline]
fn last_joint(&mut self) -> Option<&mut JointData> {
self.bvh.joints.last_mut()
}
}
#[cfg(test)]
mod tests {
// Needed for macros
use std::time::Duration;
#[test]
fn macro_create() {
let bvh = bvh! {
HIERARCHY
ROOT Base
{
OFFSET 0.0 0.0 0.0
CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation
JOINT End
{
OFFSET 0.0 0.0 15.0
CHANNELS 3 Zrotation Xrotation Yrotation
End Site
{
OFFSET 0.0 0.0 30.0
}
}
}
MOTION
Frames: 1
Frame Time: 0.033333333333
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
};
{
use super::{ChannelType, JointData, Offset};
fn check_joint<O: Into<Option<Offset>>>(
joint: &JointData,
expected_name: &str,
expected_offset: Offset,
channels: &[ChannelType],
end_site: O,
) {
let name = match joint.name_str() {
Ok(name) => name,
Err(err) => panic!("{err}"),
};
assert_eq!(name, expected_name);
assert_eq!(*joint.offset(), expected_offset);
for (chan, expected_chan) in joint
.channels()
.iter()
.map(|c| c.channel_type())
.zip(channels.iter())
{
assert_eq!(chan, *expected_chan);
}
let end_site = end_site.into().map(Into::into);
assert_eq!(joint.end_site(), end_site.as_ref());
}
let mut joints = bvh.joints();
check_joint(
joints.next().unwrap().data(),
"Base",
[0.0, 0.0, 0.0],
&[
ChannelType::PositionX,
ChannelType::PositionY,
ChannelType::PositionZ,
ChannelType::RotationZ,
ChannelType::RotationX,
ChannelType::RotationY,
],
None,
);
check_joint(
joints.next().unwrap().data(),
"End",
[0.0, 0.0, 15.0],
&[
ChannelType::RotationZ,
ChannelType::RotationX,
ChannelType::RotationY,
],
[0.0, 0.0, 30.0],
);
}
assert_eq!(*bvh.frame_time(), Duration::from_nanos(33333333));
let frames = bvh.frames();
assert_eq!(frames.len(), 1);
for frame in frames {
assert_eq!(frame.len(), 9);
for channel in frame.as_slice().iter() {
assert_eq!(*channel, 0.0);
}
}
}
#[test]
fn test_empty_create() {
macro_rules! assert_empty {
($bvh:expr) => {
assert!($bvh.joints().next().is_none());
assert_eq!(*$bvh.frame_time(), Duration::default());
assert!($bvh.frames().next().is_none());
};
}
let empty = bvh! {};
assert_empty!(empty);
let empty_2 = bvh! {
HIERARCHY
MOTION
};
assert_empty!(empty_2);
}
}
+626
View File
@@ -0,0 +1,626 @@
#![allow(unused)]
use crate::{
errors::{LoadJointsError, LoadMotionError},
joint::{JointData, JointName},
Axis, Bvh, Channel, ChannelType, EnumeratedLines,
};
use bstr::ByteSlice;
use lexical::parse;
use std::time::Duration;
/*
use nom::{
alt, char, delimited, digit, do_parse, map, map_res, named, opt, pair, recognize, space, tag,
take_while, parse, ws, Err as NomErr, IResult,
};
*/
use smallvec::{smallvec, SmallVec};
use std::{convert::TryFrom, mem, str};
/*
named! {
unsigned_float(&[u8]) -> f64,
map!(
recognize!(alt!(
delimited!(digit, tag!("."), opt!(digit))
| delimited!(opt!(digit), tag!("."), digit)
)),
lexical::parse
)
}
named! {
float(&[u8]) -> f64,
map!(
pair!(
opt!(alt!(tag!("+") | tag!("-"))),
unsigned_float
),
|(sign, value): (Option<&[u8]>, f64)| {
sign.and_then(|s|
if s[0] == (b'-') {
Some(-1f64)
} else {
None
})
.unwrap_or(1f64) * value
}
)
}
named! {
unsigned_int(&[u8]) -> u64,
map!(
recognize!(
delimited!(space, digit, space)
),
lexical::parse
)
}
named! {
token(&[u8]) -> &[u8],
delimited!(
space,
take_while!(|ch| char::from(ch).is_alphanumeric()),
space
)
}
named!(single_channel(&[u8]) -> ChannelType,
map_res!(
alt!(
tag!("Xposition") |
tag!("Yposition") |
tag!("Zposition") |
tag!("Xrotation") |
tag!("Yrotation") |
tag!("Zrotation")
),
ChannelType::from_bytes
)
);
named! {
offset(&[u8]) -> Vector3<f32>,
ws!(
do_parse!(
tag!(b"OFFSET") >>
x: float >>
y: float >>
z: float >>
(Vector3 { x: x as f32, y: y as f32, z: z as f32 })
)
)
}
named! {
end_site(&[u8]) -> Vector3<f32>,
ws!(
do_parse!(
tag!(b"End") >>
tag!(b"Site") >>
end_site: delimited!(char!('{'), offset, char!('}')) >>
(end_site)
)
)
}
*/
/*
fn heirarchy<'a>(data: &'a [u8]) -> IResult<&'a [u8], Vec<JointData>> {
let mut joints = Vec::new();
let mut num_channels = 0usize;
let mut joint_index = 0usize;
let mut channels = |data: &'a [u8]| -> IResult<&'a [u8], SmallVec<[Channel; 6]>> {
let (mut data, expected_num_channels) = do_parse!(
data,
tag!(b"CHANNELS") >> num_channels: unsigned_int >> (num_channels)
)?;
let mut out_channels = smallvec![];
for i in 0..expected_num_channels {
let (new_data, channel_ty) = single_channel(data)?;
let channel = Channel::new(channel_ty, num_channels);
num_channels += 1;
data = new_data;
}
Ok((&*data, out_channels))
};
struct JointMembers {
name: JointName,
offset: Vector3<f32>,
channels: SmallVec<[Channel; 6]>,
end_site: Option<Vector3<f32>>,
}
let joint_members = |mut data: &'a [u8]| -> IResult<&'a [u8], JointMembers> {
let (new_data, name) =
do_parse!(data, tag!(b"Joint") >> joint_name: token >> (joint_name))?;
let mut offset_and_channels =
|data: &'a [u8]| -> IResult<&'a [u8], (Vector3<f32>, SmallVec<[Channel; 6]>)> {
do_parse!(data, offst: offset >> chans: channels >> (offst, chans))
};
let opt_end_site = |data: &'a [u8]| -> IResult<&'a [u8], Option<Vector3<f32>>> {
match end_site(data) {
Ok((data, site)) => Ok((data, Some(site))),
Err(NomErr::Error(_)) => Ok((data, None)),
// @TODO: is this right?
Err(e) => Err(e),
}
};
let (data, members) = do_parse!(
data,
name: token
>> char!('{')
>> offst_and_chans: offset_and_channels
>> end_site: opt_end_site
>> (name, offst_and_chans.0, offst_and_chans.1, end_site)
)?;
let members = JointMembers {
name: JointName::from(members.0),
offset: members.1,
channels: members.2,
end_site: members.3,
};
Ok((data, members))
};
/*
parse!(data,
do_parse!(
tag!("HIERARCHY") >>
opt!(
tag!("Root") >>
root_joint_name: token >>
char!('{') >>
root_offset: offset >>
root_channels: channels >>
char!('}') >>
()
) >>
()
)
);
*/
Ok((data, joints))
}
named! {
frame_metadata(&[u8]) -> (std::time::Duration, usize),
ws!(do_parse!(
tag!("Frames") >>
char!(':') >>
num_frames: unsigned_int >>
tag!("Frame") >>
tag!("Time") >>
char!(':') >>
frame_time: unsigned_float >>
(Duration::from_secs_f64(frame_time), num_frames as usize)
))
}
fn motion_section(
data: &[u8],
num_channels: usize,
) -> IResult<&[u8], (std::time::Duration, usize, Vec<f32>)> {
let (frame_time, num_frames) = frame_metadata(data)?;
let motion_data = Vec::with_capacity(num_channels * num_frames);
for f in 0..num_frames {
let chomped = do_parse!(data, take_while!(|ch| char::from(ch).is_ascii_whitespace()));
let (output, motion_val) = float(chomped);
motion_data.push(motion_val);
data = output;
}
Ok((data, (frame_time, num_frames, motion_data)))
}
*/
impl Bvh {
// @TODO: Remove panics
/// Logic for parsing the data from a `BufRead`.
pub(crate) fn read_joints(
&mut self,
lines: &mut EnumeratedLines<'_>,
) -> Result<(), LoadJointsError> {
const HEIRARCHY_KEYWORD: &[u8] = b"HIERARCHY";
const ROOT_KEYWORD: &[u8] = b"ROOT";
const JOINT_KEYWORD: &[u8] = b"JOINT";
const ENDSITE_KEYWORDS: &[&[u8]] = &[b"End", b"Site"];
const OPEN_BRACE: &[u8] = b"{";
const CLOSE_BRACE: &[u8] = b"}";
const OFFSET_KEYWORD: &[u8] = b"OFFSET";
const CHANNELS_KEYWORD: &[u8] = b"CHANNELS";
#[derive(Debug, Eq, PartialEq)]
enum ParseMode {
NotStarted,
InHeirarchy,
Finished,
}
#[allow(unused)]
#[derive(Eq, PartialEq)]
enum NextExpectedLine {
Hierarchy,
Channels,
Offset,
OpeningBrace,
ClosingBrace,
JointName,
RootName,
}
let mut joints = vec![];
let mut curr_mode = ParseMode::NotStarted;
let mut curr_channel = 0usize;
let (mut curr_index, mut curr_depth) = (0usize, 0usize);
let mut next_expected_line = NextExpectedLine::Hierarchy;
let mut curr_joint = JointData::empty_root();
let mut in_end_site = false;
let mut pushed_end_site_joint = false;
#[inline]
fn get_parent_index(joints: &[JointData], for_depth: usize) -> usize {
joints
.iter()
.rev()
.find(|jd| jd.depth() == for_depth.saturating_sub(2))
.and_then(|jd| jd.private_data().map(|p| p.self_index))
.unwrap_or(0)
}
for (line_num, line) in lines {
let line = line?;
let line = line.trim();
let mut tokens = line.fields_with(|c: char| c.is_ascii_whitespace() || c == ':');
let first_token = match tokens.next() {
Some(tok) => tok,
None => continue,
};
match first_token.as_bytes() {
HEIRARCHY_KEYWORD => {
if curr_mode != ParseMode::NotStarted {
panic!("Unexpected hierarchy");
}
curr_mode = ParseMode::InHeirarchy;
next_expected_line = NextExpectedLine::RootName;
}
ROOT_KEYWORD => {
if curr_mode != ParseMode::InHeirarchy
|| next_expected_line != NextExpectedLine::RootName
{
panic!("Unexpected root: {:?}", curr_mode);
}
if let Some(name) = tokens.next() {
curr_joint.set_name(name);
} else {
panic!("Missing root name!");
}
}
OPEN_BRACE => {
curr_depth += 1;
}
CLOSE_BRACE => {
curr_depth -= 1;
if curr_depth == 0 {
// We have closed the brace of the root joint.
curr_mode = ParseMode::Finished;
}
if in_end_site {
if let JointData::Child {
ref mut private, ..
} = curr_joint
{
private.self_index = curr_index;
private.parent_index = get_parent_index(&joints, curr_depth);
private.depth = curr_depth - 1;
}
let new_joint = mem::replace(&mut curr_joint, JointData::empty_child());
joints.push(new_joint);
curr_index += 1;
in_end_site = false;
pushed_end_site_joint = true;
}
}
kw if kw == ENDSITE_KEYWORDS[0] => {
if tokens.next() == Some(ENDSITE_KEYWORDS[1]) {
in_end_site = true;
} else {
panic!("Unexpected end keyword");
}
}
JOINT_KEYWORD => {
if curr_mode != ParseMode::InHeirarchy {
panic!("Unexpected Joint");
}
if !pushed_end_site_joint {
if let JointData::Child {
ref mut private, ..
} = curr_joint
{
private.self_index = curr_index;
private.parent_index = get_parent_index(&joints, curr_depth);
private.depth = curr_depth - 1;
}
let new_joint = mem::replace(&mut curr_joint, JointData::empty_child());
joints.push(new_joint);
curr_index += 1;
} else {
pushed_end_site_joint = false;
}
if let Some(mut name) = tokens.next() {
curr_joint.set_name(name);
} else {
panic!("Missing joint name!");
}
}
OFFSET_KEYWORD => {
if curr_mode != ParseMode::InHeirarchy {
return Err(LoadJointsError::UnexpectedOffsetSection { line: line_num });
}
let mut offset = [0.0, 0.0, 0.0];
macro_rules! parse_axis {
($axis_field:literal, $axis_enum:ident) => {
if let Some(tok) = tokens.next() {
offset[$axis_field] =
parse(tok).map_err(|e| LoadJointsError::ParseOffsetError {
parse_float_error: e,
axis: Axis::$axis_enum,
line: line_num,
})?;
} else {
return Err(LoadJointsError::MissingOffsetAxis {
axis: Axis::$axis_enum,
line: line_num,
});
}
};
}
parse_axis!(0, X);
parse_axis!(1, Y);
parse_axis!(2, Z);
curr_joint.set_offset(offset, in_end_site);
}
CHANNELS_KEYWORD => {
if curr_mode != ParseMode::InHeirarchy {
return Err(LoadJointsError::UnexpectedChannelsSection { line: line_num });
}
let num_channels: usize = tokens
.next()
.ok_or(LoadJointsError::ParseNumChannelsError {
error: None,
line: line_num,
})
.and_then(|tok| match parse(tok) {
Ok(c) => Ok(c),
Err(e) => Err(LoadJointsError::ParseNumChannelsError {
error: Some(e),
line: line_num,
}),
})?;
let mut channels: SmallVec<[Channel; 6]> = Default::default();
channels.reserve(num_channels);
for tok in tokens {
let channel_ty = ChannelType::try_from(tok).map_err(|e| {
LoadJointsError::ParseChannelError {
error: e,
line: line_num,
}
})?;
let channel = Channel::new(channel_ty, curr_channel);
curr_channel += 1;
channels.push(channel);
}
curr_joint.set_channels(channels);
}
_ => {}
}
if curr_mode == ParseMode::Finished {
break;
}
}
if curr_mode != ParseMode::Finished {
return Err(LoadJointsError::MissingRoot);
}
self.joints = joints;
self.num_channels = curr_channel;
Ok(())
}
pub(crate) fn read_motion(
&mut self,
lines: &mut EnumeratedLines<'_>,
) -> Result<(), LoadMotionError> {
const MOTION_KEYWORD: &[u8] = b"MOTION";
const FRAMES_KEYWORD: &[u8] = b"Frames";
const FRAME_TIME_KEYWORDS: &[&[u8]] = &[b"Frame", b"Time:"];
macro_rules! last_line_num {
() => {
lines.last_enumerator().unwrap_or(0)
};
}
lines
.next_non_empty_line()
.ok_or(LoadMotionError::MissingMotionSection {
line: last_line_num!(),
})
.and_then(|(line_num, line)| {
let line = line?;
let line = line.trim();
if line == MOTION_KEYWORD {
Ok(())
} else {
Err(LoadMotionError::MissingMotionSection { line: line_num })
}
})?;
let num_frames = lines
.next_non_empty_line()
.ok_or(LoadMotionError::MissingNumFrames {
parse_error: None,
line: last_line_num!(),
})
.and_then(|(line_num, line)| {
let line = line?;
let line = line.trim();
let mut tokens = line.fields_with(|c: char| c.is_ascii_whitespace() || c == ':');
if tokens.next() != Some(FRAMES_KEYWORD) {
return Err(LoadMotionError::MissingNumFrames {
parse_error: None,
line: line_num,
});
}
let parse_num_frames = |token: Option<&[u8]>| {
if let Some(num_frames) = token.and_then(|b| str::from_utf8(b).ok()) {
parse::<usize, _>(num_frames)
.map_err(|e| LoadMotionError::MissingNumFrames {
parse_error: Some(e),
line: line_num,
})
.map_err(Into::into)
} else {
Err(LoadMotionError::MissingNumFrames {
parse_error: None,
line: line_num,
})
}
};
match tokens.next() {
Some(tok) if tok == b":" => parse_num_frames(tokens.next()),
Some(tok) => parse_num_frames(Some(tok)),
None => Err(LoadMotionError::MissingNumFrames {
parse_error: None,
line: line_num,
}),
}
})?;
self.frame_time = lines
.next_non_empty_line()
.ok_or(LoadMotionError::MissingFrameTime {
parse_error: None,
line: last_line_num!(),
})
.and_then(|(line_num, line)| {
let line = line?;
let mut tokens = line.fields();
let frame_time_kw = tokens.next();
if frame_time_kw == FRAME_TIME_KEYWORDS.get(0).copied() {
// do nothing
} else {
return Err(LoadMotionError::MissingFrameTime {
parse_error: None,
line: line_num,
});
}
let frame_time_kw = tokens.next();
if frame_time_kw == FRAME_TIME_KEYWORDS.get(1).copied() {
// do nothing
} else {
return Err(LoadMotionError::MissingFrameTime {
parse_error: None,
line: line_num,
});
}
let parse_frame_time = |token: Option<&[u8]>| {
if let Some(frame_time) = token {
let frame_time_secs = parse::<f64, _>(frame_time).map_err(|e| {
LoadMotionError::MissingFrameTime {
parse_error: Some(e),
line: line_num,
}
})?;
Ok(Duration::from_secs_f64(frame_time_secs))
} else {
Err(LoadMotionError::MissingFrameTime {
parse_error: None,
line: line_num,
})
}
};
match tokens.next() {
Some(tok) if tok == b":" => parse_frame_time(tokens.next()),
Some(tok) => parse_frame_time(Some(tok)),
None => Err(LoadMotionError::MissingNumFrames {
parse_error: None,
line: line_num,
}),
}
})?;
let expected_total_motion_values = self.num_channels * num_frames;
self.motion_values.reserve(expected_total_motion_values);
for (line_num, line) in lines {
let line = line?;
let tokens = line.fields();
for (channel_index, token) in tokens.enumerate() {
let motion =
parse::<f32, _>(token).map_err(|e| LoadMotionError::ParseMotionSection {
parse_error: e,
channel_index,
line: line_num,
})?;
self.motion_values.push(motion);
}
}
if self.motion_values.len() != self.num_channels * num_frames {
return Err(LoadMotionError::MotionCountMismatch {
actual_total_motion_values: self.motion_values.len(),
expected_total_motion_values,
expected_num_frames: num_frames,
expected_num_clips: self.num_channels,
});
}
Ok(())
}
}
+508
View File
@@ -0,0 +1,508 @@
//! Contains options for `bvh` file formatting.
use crate::{frames::Frames, Bvh, Joint, Joints};
use smallvec::SmallVec;
use std::{
fmt,
io::{self, Write},
iter, mem,
num::NonZeroUsize,
};
/// Specify formatting options for writing a `Bvh`.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub struct WriteOptions {
/// Which indentation style to use for nested bones.
pub indent: IndentStyle,
/// Which style new line terminator to use when writing the `bvh`.
pub line_terminator: LineTerminator,
/// Number of significant figures to use when writing `OFFSET` values.
///
/// If this is `None`, then the minimum precision required will be used
pub offset_significant_figures: Option<usize>,
/// Number of significant figures to use when writing the `Frame Time` value.
///
/// If this is `None`, then the minimum precision required will be used
pub frame_time_significant_figures: Option<usize>,
/// Number of significant figures to use when writing `MOTION` values.
///
/// If this is `None`, then the minimum precision required will be used
pub motion_values_significant_figures: Option<usize>,
}
impl Default for WriteOptions {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl WriteOptions {
/// Create a new `WriteOptions` with default values.
#[inline]
pub const fn new() -> Self {
WriteOptions {
indent: IndentStyle::Tabs,
line_terminator: LineTerminator::native(),
offset_significant_figures: None,
frame_time_significant_figures: None,
motion_values_significant_figures: None,
}
}
/// Output the `Bvh` file to the `writer` with the given options.
pub fn write<W: Write>(&self, bvh: &Bvh, writer: &mut W) -> io::Result<()> {
let mut curr_chunk = vec![];
let mut curr_bytes_written = 0usize;
let mut curr_string_len = 0usize;
let mut iter_state = WriteOptionsIterState::new();
while self.next_chunk(bvh, &mut curr_chunk, &mut iter_state) {
let bytes: &[u8] = curr_chunk.as_ref();
curr_string_len += bytes.len();
curr_bytes_written += writer.write(bytes)?;
if curr_bytes_written != curr_string_len {
return Err(io::Error::new(
io::ErrorKind::Other,
"Data has been dropped while writing to file",
));
}
}
writer.flush()
}
/// Output the `Bvh` file to the `string` with the given options.
pub fn write_to_string(&self, bvh: &Bvh) -> Vec<u8> {
let mut curr_chunk = vec![];
let mut out_string = vec![];
let mut iter_state = WriteOptionsIterState::new();
while self.next_chunk(bvh, &mut curr_chunk, &mut iter_state) {
out_string.append(&mut curr_chunk);
}
out_string
}
/// Sets `indent` on `self` to the new `IndentStyle`.
#[inline]
pub const fn with_indent(self, indent: IndentStyle) -> Self {
Self { indent, ..self }
}
/// Sets `line_terminator` on `self` to the new `LineTerminator`.
#[inline]
pub const fn with_line_terminator(self, line_terminator: LineTerminator) -> Self {
Self {
line_terminator,
..self
}
}
/// Sets `offset_significant_figures` on `self` to the new `offset_significant_figures`.
#[inline]
pub fn with_offset_significant_figures<N>(self, offset_significant_figures: N) -> Self
where
N: Into<Option<usize>>,
{
Self {
offset_significant_figures: offset_significant_figures.into(),
..self
}
}
/// Sets `motion_values_significant_figures` on `self` to the new `motion_values_significant_figures`.
#[inline]
pub fn with_frame_time_significant_figures<N>(self, frame_time_significant_figures: N) -> Self
where
N: Into<Option<usize>>,
{
Self {
frame_time_significant_figures: frame_time_significant_figures.into(),
..self
}
}
/// Sets `motion_values_significant_figures` on `self` to the new `motion_values_significant_figures`.
#[inline]
pub fn with_motion_values_significant_figures<N>(
self,
motion_values_significant_figures: N,
) -> Self
where
N: Into<Option<usize>>,
{
Self {
motion_values_significant_figures: motion_values_significant_figures.into(),
..self
}
}
// @TODO: Refactor all of this
/// Get the next text chunk of the written bvh file. This function is
/// structured so that the `chunk` string can be continually
/// re-used without allocating and de-allocating memory.
///
/// # Returns
///
/// Returns `true` when there are still more lines available,
/// `false` when all lines have been extracted.
fn next_chunk<'a, 'b: 'a>(
&self,
bvh: &'b Bvh,
chunk: &mut Vec<u8>,
iter_state: &'a mut WriteOptionsIterState<'b>,
) -> bool {
chunk.clear();
let terminator = self.line_terminator.as_bytes();
match *iter_state {
WriteOptionsIterState::Hierarchy { ref mut written } => {
if !*written {
*chunk = b"HIERARCHY".to_vec();
chunk.extend_from_slice(self.line_terminator.as_bytes());
*written = true;
} else {
let mut joints = bvh.joints();
*iter_state = WriteOptionsIterState::Joints {
current_joint: joints.next(),
joints,
wrote_name: false,
wrote_offset: false,
wrote_channels: false,
};
}
}
WriteOptionsIterState::Joints {
current_joint: None,
..
} => {
*iter_state = WriteOptionsIterState::Motion { written: false };
}
WriteOptionsIterState::Joints {
ref mut joints,
ref mut current_joint,
ref mut wrote_name,
ref mut wrote_offset,
ref mut wrote_channels,
} => {
if let Some(ref joint) = current_joint {
let joint_data = joint.data();
let mut depth = joint_data.depth();
if *wrote_name {
depth += 1
}
match (&mut *wrote_name, &mut *wrote_offset, &mut *wrote_channels) {
(&mut false, _, _) => {
chunk.extend(self.indent.prefix_chars(depth));
if joint_data.is_root() {
chunk.extend_from_slice(b"ROOT ");
} else {
chunk.extend_from_slice(b"JOINT ");
}
chunk.extend_from_slice(joint_data.name().as_ref());
chunk.extend_from_slice(terminator);
chunk.extend(self.indent.prefix_chars(depth));
chunk.push(b'{');
chunk.extend_from_slice(terminator);
*wrote_name = true;
}
(&mut true, &mut false, _) => {
chunk.extend(self.indent.prefix_chars(depth));
let [x, y, z] = joint_data.offset();
let offset_str = match self.offset_significant_figures {
Some(sf) => {
format!("OFFSET {:.*} {:.*} {:.*}", sf, x, sf, y, sf, z,)
}
None => format!("OFFSET {:.} {:.} {:.}", x, y, z),
};
chunk.extend_from_slice(offset_str.as_bytes());
chunk.extend_from_slice(terminator);
*wrote_offset = true;
}
(&mut true, &mut true, &mut false) => {
chunk.extend(self.indent.prefix_chars(depth));
let channels = joint_data.channels();
let channels_str = channels
.iter()
.map(|ch| ch.channel_type().as_str())
.collect::<SmallVec<[_; 6]>>()
.join(" ");
let channels_str =
format!("CHANNELS {} {}", channels.len(), channels_str);
chunk.extend_from_slice(channels_str.as_bytes());
chunk.extend_from_slice(terminator);
*wrote_channels = true;
}
(&mut true, &mut true, &mut true) => {
if let Some([x, y, z]) = joint_data.end_site() {
chunk.extend(self.indent.prefix_chars(depth));
chunk.extend_from_slice(b"End Site");
chunk.extend_from_slice(terminator);
chunk.extend(self.indent.prefix_chars(depth));
chunk.push(b'{');
chunk.extend_from_slice(terminator);
chunk.extend(self.indent.prefix_chars(depth + 1));
let offset_str = match self.offset_significant_figures {
Some(sf) => {
format!("OFFSET {:.*} {:.*} {:.*}", sf, x, sf, y, sf, z)
}
None => format!("OFFSET {:.} {:.} {:.}", x, y, z),
};
chunk.extend_from_slice(offset_str.as_bytes());
chunk.extend_from_slice(terminator);
chunk.extend(self.indent.prefix_chars(depth));
chunk.push(b'}');
chunk.extend_from_slice(terminator);
let next_joint = joints.next();
let prev_joint = mem::replace(current_joint, next_joint).unwrap();
let (curr_depth, mut depth_difference) =
if let Some(ref curr_j) = *current_joint {
let curr_depth = curr_j.data().depth();
(curr_depth, Some(prev_joint.data().depth() - curr_depth))
} else {
(0, Some(prev_joint.data().depth()))
};
while let Some(d) = depth_difference {
chunk.extend(self.indent.prefix_chars(curr_depth + d));
chunk.push(b'}');
chunk.extend_from_slice(terminator);
depth_difference =
depth_difference.and_then(|d| d.checked_sub(1));
}
} else {
*current_joint = joints.next();
}
*wrote_name = false;
*wrote_offset = false;
*wrote_channels = false;
}
}
}
}
WriteOptionsIterState::Motion { ref mut written } => {
if !*written {
*chunk = b"MOTION".to_vec();
chunk.extend_from_slice(terminator);
*written = true;
} else {
*iter_state = WriteOptionsIterState::NumFrames { written: false };
}
}
WriteOptionsIterState::NumFrames { ref mut written } => {
if !*written {
*chunk = format!("Frames: {}", bvh.frames().len()).into_bytes();
chunk.extend_from_slice(terminator);
*written = true;
} else {
*iter_state = WriteOptionsIterState::FrameTime { written: false };
}
}
WriteOptionsIterState::FrameTime { ref mut written } => {
if !*written {
*chunk = match self.frame_time_significant_figures {
Some(sf) => {
format!("Frame Time: {:.*}", sf, bvh.frame_time().as_secs_f64())
.into_bytes()
}
None => {
format!("Frame Time: {:.}", bvh.frame_time().as_secs_f64()).into_bytes()
}
};
chunk.extend_from_slice(terminator);
*written = true;
} else {
let frames = bvh.frames();
*iter_state = WriteOptionsIterState::Frames { frames };
}
}
WriteOptionsIterState::Frames { ref mut frames } => {
return frames
.next()
.map(|frame| {
let motion_values = frame
.as_slice()
.iter()
.map(|motion| match self.motion_values_significant_figures {
Some(sf) => format!("{:.*}", sf, motion),
None => format!("{:.}", motion),
})
.collect::<Vec<_>>()
.join(" ");
*chunk = motion_values.into_bytes();
chunk.extend_from_slice(terminator);
true
})
.unwrap_or_default();
}
}
true
}
}
enum WriteOptionsIterState<'a> {
Hierarchy {
written: bool,
},
Joints {
joints: Joints<'a>,
current_joint: Option<Joint<'a>>,
wrote_name: bool,
wrote_offset: bool,
wrote_channels: bool,
},
Motion {
written: bool,
},
NumFrames {
written: bool,
},
FrameTime {
written: bool,
},
Frames {
frames: Frames<'a>,
},
}
impl WriteOptionsIterState<'_> {
#[inline]
const fn new() -> Self {
WriteOptionsIterState::Hierarchy { written: false }
}
}
impl Default for WriteOptionsIterState<'_> {
#[inline]
fn default() -> Self {
Self::new()
}
}
/// Specify indentation style to use when writing the `Bvh` joints.
///
/// By default, this value is set to 1 tab.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum IndentStyle {
/// Do not indent nested joints.
NoIndentation,
/// Use a single tab (`'\t'`) for indentation.
Tabs,
/// Use `n` spaces for indentation.
Spaces(NonZeroUsize),
}
impl IndentStyle {
/// Create a new `IndentStyle` with `n` preceeding spaces.
///
/// If `n` is `0`, then `IndentStyle::NoIndentation` is returned.
#[inline]
pub fn with_spaces(n: usize) -> Self {
NonZeroUsize::new(n)
.map(IndentStyle::Spaces)
.unwrap_or(IndentStyle::NoIndentation)
}
/// Return an `Iterator` which yields bytes corresponding to the ascii
/// chars which form the `String` this indentation style would take.
#[inline]
fn prefix_chars(&self, depth: usize) -> impl Iterator<Item = u8> {
match *self {
IndentStyle::NoIndentation => iter::repeat(b'\0').take(0),
IndentStyle::Tabs => iter::repeat(b'\t').take(depth),
IndentStyle::Spaces(n) => iter::repeat(b' ').take(n.get() * depth),
}
}
}
/// Create a new `IndentStyle` using a single tab.
impl Default for IndentStyle {
#[inline]
fn default() -> Self {
IndentStyle::Tabs
}
}
/// Represents which line terminator style to use when writing a `Bvh` file.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LineTerminator {
/// Use Unix-style line endings (`'\n'`).
Unix,
/// Use Windows-style line endings (`'\r\n'`).
Windows,
}
impl LineTerminator {
/// Get the line terminator style native to the current OS:
///
/// * On Windows, this returns `LineTerminator::Windows`.
/// * Otherwise, this returns `LineTerminator::Unix`.
#[inline]
pub const fn native() -> Self {
if cfg!(windows) {
LineTerminator::Windows
} else {
LineTerminator::Unix
}
}
/// Return the characters of the `LineTerminator` as a `&str`.
#[inline]
pub const fn as_str(&self) -> &str {
match *self {
LineTerminator::Unix => "\n",
LineTerminator::Windows => "\r\n",
}
}
/// Returns the escaped characters of the `LineTerminator` as a `&str`.
#[inline]
pub const fn as_escaped_str(&self) -> &str {
match *self {
LineTerminator::Unix => r"\n",
LineTerminator::Windows => r"\r\n",
}
}
/// Return the characters of the `LineTerminator` as a `&[u8]`.
#[inline]
pub const fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
/// Returns the escaped characters of the `LineTerminator` as a `&[u8]`.
#[inline]
pub const fn as_escaped_bytes(&self) -> &[u8] {
self.as_escaped_str().as_bytes()
}
}
/// Returns the native line terminator for the current OS.
impl Default for LineTerminator {
#[inline]
fn default() -> Self {
LineTerminator::native()
}
}
impl fmt::Display for LineTerminator {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}