mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-19 07:53:25 +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 {}
|
||||
Reference in New Issue
Block a user