mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
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:
@@ -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 {}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user