mirror of
https://github.com/storytold/bevy-mocap.git
synced 2026-10-13 23:52:47 +00:00
frames parse correctly
This commit is contained in:
+50
-34
@@ -3,19 +3,6 @@ use std::{num::ParseFloatError, path::Display, str::FromStr, sync::Arc};
|
||||
use bevy::{time::{self, Time}, utils::{default, HashMap}};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct MorphID {
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
impl MorphID {
|
||||
pub fn new(id: usize) -> Self {
|
||||
Self {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Timestamp {
|
||||
hour: u8,
|
||||
@@ -28,7 +15,7 @@ pub struct Timestamp {
|
||||
pub enum TimestampParseError {
|
||||
EmptyTimestamp,
|
||||
TooManySegments,
|
||||
NumberParseError(ParseFloatError),
|
||||
NumberParseError(ParseFloatError, String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TimestampParseError {
|
||||
@@ -43,6 +30,7 @@ impl FromStr for Timestamp {
|
||||
type Err = TimestampParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
return Err(TimestampParseError::EmptyTimestamp);
|
||||
}
|
||||
@@ -58,10 +46,10 @@ impl FromStr for Timestamp {
|
||||
return Err(TimestampParseError::EmptyTimestamp);
|
||||
};
|
||||
|
||||
let frame = frame.parse::<f32>().map(|v| v.floor() as u8).map_err(TimestampParseError::NumberParseError)?;
|
||||
let second = segments.get(1).unwrap_or(&"0").parse::<f32>().map(|v| v.floor() as u8).map_err(TimestampParseError::NumberParseError)?;
|
||||
let minute = segments.get(2).unwrap_or(&"0").parse::<f32>().map(|v| v.floor() as u8).map_err(TimestampParseError::NumberParseError)?;
|
||||
let hour = segments.get(3).unwrap_or(&"0").parse::<f32>().map(|v| v.floor() as u8).map_err(TimestampParseError::NumberParseError)?;
|
||||
let frame = frame.parse::<f32>().map(|v| v.floor() as u8).map_err(|e| TimestampParseError::NumberParseError(e, format!("frame failed for {s}")))?;
|
||||
let second = segments.get(1).unwrap_or(&"0").parse::<f32>().map(|v| v.floor() as u8).map_err(|e| TimestampParseError::NumberParseError(e, format!("second failed for {s}")))?;
|
||||
let minute = segments.get(2).unwrap_or(&"0").parse::<f32>().map(|v| v.floor() as u8).map_err(|e| TimestampParseError::NumberParseError(e, format!("minute failed for {s}")))?;
|
||||
let hour = segments.get(3).unwrap_or(&"0").parse::<f32>().map(|v| v.floor() as u8).map_err(|e| TimestampParseError::NumberParseError(e, format!("hour failed for {s}")))?;
|
||||
|
||||
Ok(Self { hour, minute, second, frame })
|
||||
}
|
||||
@@ -70,7 +58,7 @@ impl FromStr for Timestamp {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CSVFrame {
|
||||
pub timestamp: Timestamp,
|
||||
pub morphs: HashMap<MorphID, f32>,
|
||||
pub morphs: Vec<f32>
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +125,7 @@ impl CSVAnimation {
|
||||
let _ = iter.next().ok_or(CSVParseError::MisssingKnownHeaders)?; // similarly, we already validate that there are a single morph in the CSV, so the skipped columns shouldn't be valid CSVs.
|
||||
}
|
||||
|
||||
let morphs = iter.map(|v| v.parse::<f32>().map_err(CSVParseError::MorphParseError)).enumerate().map(|(i, v)| v.map(|v| (MorphID::new(i), v))).collect::<Result<HashMap<_, _>,_>>()?;
|
||||
let morphs = iter.map(|v| v.trim().parse::<f32>().map_err(CSVParseError::MorphParseError)).collect::<Result<Vec<_>,_>>()?;
|
||||
|
||||
if fps < timestamp.frame {
|
||||
fps = timestamp.frame;
|
||||
@@ -146,10 +134,14 @@ impl CSVAnimation {
|
||||
frames.push(CSVFrame { timestamp, morphs });
|
||||
}
|
||||
|
||||
if frames.is_empty() {
|
||||
return Err(CSVParseError::NoFrames);
|
||||
}
|
||||
|
||||
Ok(CSVAnimation {
|
||||
frames,
|
||||
morph_names,
|
||||
fps
|
||||
fps: fps + 1
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -159,6 +151,29 @@ mod tests {
|
||||
use bevy::time::{self, Time};
|
||||
|
||||
use crate::{CSVAnimation, CSVParseError, Timestamp, TimestampParseError};
|
||||
#[test]
|
||||
fn given_an_empty_timestamp_it_returns_an_empty_timestamp_error() {
|
||||
let timestamp = "".parse::<Timestamp>().expect_err("succeeded against expectations");
|
||||
assert!(matches!(timestamp, TimestampParseError::EmptyTimestamp))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_a_timestamp_with_more_than_4_segments_returns_a_too_many_segments_error() {
|
||||
let timestamp = "00:00:00:00:00.1234".parse::<Timestamp>().expect_err("succeeded against expectations");
|
||||
assert!(matches!(timestamp, TimestampParseError::TooManySegments))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_a_zero_timestamp_it_is_parsed_correctly() {
|
||||
let timestamp = "00:00:00:00".parse::<Timestamp>().expect("Timestamp failed to parse");
|
||||
assert_eq!(timestamp, Timestamp { hour: 0, minute: 0, second: 0, frame: 0 })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_a_valid_timestamp_it_is_parsed_correctly() {
|
||||
let timestamp = "01:02:03:04.123".parse::<Timestamp>().expect("Timestamp failed to parse");
|
||||
assert_eq!(timestamp, Timestamp { hour: 1, minute: 2, second: 3, frame: 4 })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_no_data_it_emits_a_no_data_error() {
|
||||
@@ -178,7 +193,7 @@ mod tests {
|
||||
#[test]
|
||||
fn given_a_header_row_it_sets_correct_headers() {
|
||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2
|
||||
0, 0, 0, 0").unwrap();
|
||||
00:00:00:00, 0, 0, 0").unwrap();
|
||||
assert_eq!(result.morph_names.first().unwrap(), "shape_1");
|
||||
assert_eq!(result.morph_names.get(1).unwrap(), "shape_2");
|
||||
}
|
||||
@@ -204,20 +219,21 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_an_empty_timestamp_it_returns_an_empty_timestamp_error() {
|
||||
let timestamp = "".parse::<Timestamp>().expect_err("succeeded against expectations");
|
||||
assert!(matches!(timestamp, TimestampParseError::EmptyTimestamp))
|
||||
fn given_multiple_frames_can_deduce_fps() {
|
||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2
|
||||
00:00:00:00.00, 0, 0, 0
|
||||
00:00:00:01.00, 0, 0, 0
|
||||
00:00:00:02.00, 0, 0, 0
|
||||
00:00:01:00.00, 0, 0, 0
|
||||
00:00:01:01.00, 0, 0, 0").unwrap();
|
||||
assert_eq!(result.fps, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_a_timestamp_with_more_than_4_segments_returns_a_too_many_segments_error() {
|
||||
let timestamp = "00:00:00:00:00.1234".parse::<Timestamp>().expect_err("succeeded against expectations");
|
||||
assert!(matches!(timestamp, TimestampParseError::TooManySegments))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn given_a_valid_timestamp_it_is_parsed_correctly() {
|
||||
let timestamp = "01:02:03:04.123".parse::<Timestamp>().expect("Timestamp failed to parse");
|
||||
assert_eq!(timestamp, Timestamp { hour: 1, minute: 2, second: 3, frame: 4 })
|
||||
fn given_a_single_frame_it_parses_morphs_correctly() {
|
||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2
|
||||
00:00:00:00.00, 0, 0.1, 0.5").unwrap();
|
||||
assert!(&result.frames.first().unwrap().morphs[0] - 0.1 < 0.0001);
|
||||
assert!(&result.frames.first().unwrap().morphs[1] - 0.5 < 0.0001);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user