mirror of
https://github.com/storytold/bevy-mocap.git
synced 2026-10-29 08:57:48 +00:00
fmt + clippy
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
use bevy::{pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, prelude::*};
|
use bevy::{
|
||||||
|
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -11,13 +14,10 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
commands.spawn(
|
commands.spawn(Camera3dBundle {
|
||||||
Camera3dBundle {
|
transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
|
||||||
transform: Transform::from_xyz(0.7, 0.7, 1.0)
|
..default()
|
||||||
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
|
});
|
||||||
..default()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
commands.spawn(DirectionalLightBundle {
|
commands.spawn(DirectionalLightBundle {
|
||||||
directional_light: DirectionalLight {
|
directional_light: DirectionalLight {
|
||||||
@@ -37,7 +37,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
commands.spawn(SceneBundle {
|
commands.spawn(SceneBundle {
|
||||||
scene: asset_server.load("example.gltf#Scene0"),
|
scene: asset_server.load("example.gltf#Scene0"),
|
||||||
..default()
|
..default()
|
||||||
|
|||||||
+133
-42
@@ -1,6 +1,6 @@
|
|||||||
use std::{num::ParseFloatError, path::Display, str::FromStr, sync::Arc};
|
use std::{num::ParseFloatError, str::FromStr};
|
||||||
|
|
||||||
|
|
||||||
use bevy::{time::{self, Time}, utils::{default, HashMap}};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -46,27 +46,54 @@ impl FromStr for Timestamp {
|
|||||||
return Err(TimestampParseError::EmptyTimestamp);
|
return Err(TimestampParseError::EmptyTimestamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
let frame = frame.parse::<f32>().map(|v| v.floor() as u8).map_err(|e| TimestampParseError::NumberParseError(e, format!("frame failed for {s}")))?;
|
let frame = frame.parse::<f32>().map(|v| v.floor() as u8).map_err(|e| {
|
||||||
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}")))?;
|
TimestampParseError::NumberParseError(e, format!("frame 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}")))?;
|
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 })
|
Ok(Self {
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
second,
|
||||||
|
frame,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct CSVFrame {
|
pub struct CSVFrame {
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: Timestamp,
|
||||||
pub morphs: Vec<f32>
|
pub morphs: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct CSVAnimation {
|
pub struct CSVAnimation {
|
||||||
pub frames: Vec<CSVFrame>,
|
pub frames: Vec<CSVFrame>,
|
||||||
pub morph_names: Vec<String>,
|
pub morph_names: Vec<String>,
|
||||||
pub fps: u8
|
pub fps: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -88,31 +115,45 @@ impl std::fmt::Display for CSVParseError {
|
|||||||
|
|
||||||
impl std::error::Error for CSVParseError {}
|
impl std::error::Error for CSVParseError {}
|
||||||
|
|
||||||
const SKIP_COLUMNS_BEFORE_MORPH : usize = 1;
|
const SKIP_COLUMNS_BEFORE_MORPH: usize = 1;
|
||||||
|
|
||||||
impl CSVAnimation {
|
impl CSVAnimation {
|
||||||
pub fn parse(csv: &str) -> Result<CSVAnimation, CSVParseError> {
|
pub fn parse(csv: &str) -> Result<CSVAnimation, CSVParseError> {
|
||||||
if csv.is_empty() {
|
if csv.is_empty() {
|
||||||
return Err(CSVParseError::NoData);
|
return Err(CSVParseError::NoData);
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer = csv.as_bytes();
|
let buffer = csv.as_bytes();
|
||||||
|
|
||||||
let mut csv_content = csv::ReaderBuilder::new().has_headers(false).from_reader(buffer);
|
let mut csv_content = csv::ReaderBuilder::new()
|
||||||
|
.has_headers(false)
|
||||||
|
.from_reader(buffer);
|
||||||
|
|
||||||
let mut records = csv_content.records();
|
let mut records = csv_content.records();
|
||||||
|
|
||||||
let header_row = records.next().ok_or(CSVParseError::NoData)?.map_err(|e| CSVParseError::InvalidCSV(e))?;
|
let header_row = records
|
||||||
|
.next()
|
||||||
|
.ok_or(CSVParseError::NoData)?
|
||||||
|
.map_err(CSVParseError::InvalidCSV)?;
|
||||||
|
|
||||||
let morph_names : Vec<String> = header_row.iter().enumerate().filter_map(|(i, v)| if i > SKIP_COLUMNS_BEFORE_MORPH { Some(v.trim().to_string()) } else { None }).collect();
|
let morph_names: Vec<String> = header_row
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, v)| {
|
||||||
|
if i > SKIP_COLUMNS_BEFORE_MORPH {
|
||||||
|
Some(v.trim().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
if morph_names.is_empty() {
|
if morph_names.is_empty() {
|
||||||
return Err(CSVParseError::NoMorphs);
|
return Err(CSVParseError::NoMorphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut frames = Vec::new();
|
let mut frames = Vec::new();
|
||||||
let mut fps : u8 = 0;
|
let mut fps: u8 = 0;
|
||||||
|
|
||||||
|
|
||||||
for result in records {
|
for result in records {
|
||||||
let record = result.map_err(CSVParseError::InvalidCSV)?;
|
let record = result.map_err(CSVParseError::InvalidCSV)?;
|
||||||
@@ -120,12 +161,21 @@ impl CSVAnimation {
|
|||||||
let mut iter = record.iter();
|
let mut iter = record.iter();
|
||||||
|
|
||||||
let timestamp = iter.next().ok_or(CSVParseError::MisssingKnownHeaders)?; // This error should be impossible since the CSV parser validates that all rows have the same length, and we already confirmed there is more than 0 characters in the first row.
|
let timestamp = iter.next().ok_or(CSVParseError::MisssingKnownHeaders)?; // This error should be impossible since the CSV parser validates that all rows have the same length, and we already confirmed there is more than 0 characters in the first row.
|
||||||
let timestamp : Timestamp = timestamp.parse().map_err(CSVParseError::TimecodeParseError)?;
|
let timestamp: Timestamp = timestamp
|
||||||
|
.parse()
|
||||||
|
.map_err(CSVParseError::TimecodeParseError)?;
|
||||||
for _ in 0..SKIP_COLUMNS_BEFORE_MORPH {
|
for _ in 0..SKIP_COLUMNS_BEFORE_MORPH {
|
||||||
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 _ = 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.trim().parse::<f32>().map_err(CSVParseError::MorphParseError)).collect::<Result<Vec<_>,_>>()?;
|
let morphs = iter
|
||||||
|
.map(|v| {
|
||||||
|
v.trim()
|
||||||
|
.parse::<f32>()
|
||||||
|
.map_err(CSVParseError::MorphParseError)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
if fps < timestamp.frame {
|
if fps < timestamp.frame {
|
||||||
fps = timestamp.frame;
|
fps = timestamp.frame;
|
||||||
@@ -141,59 +191,91 @@ impl CSVAnimation {
|
|||||||
Ok(CSVAnimation {
|
Ok(CSVAnimation {
|
||||||
frames,
|
frames,
|
||||||
morph_names,
|
morph_names,
|
||||||
fps: fps + 1
|
fps: fps + 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use bevy::time::{self, Time};
|
|
||||||
|
|
||||||
use crate::{CSVAnimation, CSVParseError, Timestamp, TimestampParseError};
|
use crate::{CSVAnimation, CSVParseError, Timestamp, TimestampParseError};
|
||||||
#[test]
|
#[test]
|
||||||
fn given_an_empty_timestamp_it_returns_an_empty_timestamp_error() {
|
fn given_an_empty_timestamp_it_returns_an_empty_timestamp_error() {
|
||||||
let timestamp = "".parse::<Timestamp>().expect_err("succeeded against expectations");
|
let timestamp = ""
|
||||||
|
.parse::<Timestamp>()
|
||||||
|
.expect_err("succeeded against expectations");
|
||||||
assert!(matches!(timestamp, TimestampParseError::EmptyTimestamp))
|
assert!(matches!(timestamp, TimestampParseError::EmptyTimestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_a_timestamp_with_more_than_4_segments_returns_a_too_many_segments_error() {
|
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");
|
let timestamp = "00:00:00:00:00.1234"
|
||||||
|
.parse::<Timestamp>()
|
||||||
|
.expect_err("succeeded against expectations");
|
||||||
assert!(matches!(timestamp, TimestampParseError::TooManySegments))
|
assert!(matches!(timestamp, TimestampParseError::TooManySegments))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_a_zero_timestamp_it_is_parsed_correctly() {
|
fn given_a_zero_timestamp_it_is_parsed_correctly() {
|
||||||
let timestamp = "00:00:00:00".parse::<Timestamp>().expect("Timestamp failed to parse");
|
let timestamp = "00:00:00:00"
|
||||||
assert_eq!(timestamp, Timestamp { hour: 0, minute: 0, second: 0, frame: 0 })
|
.parse::<Timestamp>()
|
||||||
|
.expect("Timestamp failed to parse");
|
||||||
|
assert_eq!(
|
||||||
|
timestamp,
|
||||||
|
Timestamp {
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
second: 0,
|
||||||
|
frame: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_a_valid_timestamp_it_is_parsed_correctly() {
|
fn given_a_valid_timestamp_it_is_parsed_correctly() {
|
||||||
let timestamp = "01:02:03:04.123".parse::<Timestamp>().expect("Timestamp failed to parse");
|
let timestamp = "01:02:03:04.123"
|
||||||
assert_eq!(timestamp, Timestamp { hour: 1, minute: 2, second: 3, frame: 4 })
|
.parse::<Timestamp>()
|
||||||
|
.expect("Timestamp failed to parse");
|
||||||
|
assert_eq!(
|
||||||
|
timestamp,
|
||||||
|
Timestamp {
|
||||||
|
hour: 1,
|
||||||
|
minute: 2,
|
||||||
|
second: 3,
|
||||||
|
frame: 4
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_no_data_it_emits_a_no_data_error() {
|
fn given_no_data_it_emits_a_no_data_error() {
|
||||||
assert!(matches!(CSVAnimation::parse("").err().unwrap(), CSVParseError::NoData))
|
assert!(matches!(
|
||||||
|
CSVAnimation::parse("").err().unwrap(),
|
||||||
|
CSVParseError::NoData
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_an_invalid_csv_it_emits_an_invalid_csv_error() {
|
fn given_an_invalid_csv_it_emits_an_invalid_csv_error() {
|
||||||
let result = CSVAnimation::parse("an, invalid, csv, file
|
let result = CSVAnimation::parse(
|
||||||
|
"an, invalid, csv, file
|
||||||
seems to be here... right now?
|
seems to be here... right now?
|
||||||
I wonder, will it be caught");
|
I wonder, will it be caught",
|
||||||
|
);
|
||||||
if !matches!(result, Err(CSVParseError::InvalidCSV(_))) {
|
if !matches!(result, Err(CSVParseError::InvalidCSV(_))) {
|
||||||
panic!( "got {result:?} instead of a CSVParseError");
|
panic!("got {result:?} instead of a CSVParseError");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_a_header_row_it_sets_correct_headers() {
|
fn given_a_header_row_it_sets_correct_headers() {
|
||||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2
|
let result = CSVAnimation::parse(
|
||||||
00:00:00:00, 0, 0, 0").unwrap();
|
"timecode, blendshapecount, shape_1, shape_2
|
||||||
|
00:00:00:00, 0, 0, 0",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(result.morph_names.first().unwrap(), "shape_1");
|
assert_eq!(result.morph_names.first().unwrap(), "shape_1");
|
||||||
assert_eq!(result.morph_names.get(1).unwrap(), "shape_2");
|
assert_eq!(result.morph_names.get(1).unwrap(), "shape_2");
|
||||||
}
|
}
|
||||||
@@ -206,33 +288,42 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_no_frames_it_emits_a_no_frame_error() {
|
fn given_no_frames_it_emits_a_no_frame_error() {
|
||||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2").unwrap_err();
|
let result =
|
||||||
|
CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2").unwrap_err();
|
||||||
assert!(matches!(result, CSVParseError::NoFrames));
|
assert!(matches!(result, CSVParseError::NoFrames));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_a_single_frame_it_has_a_time_of_0() {
|
fn given_a_single_frame_it_has_a_time_of_0() {
|
||||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2
|
let result = CSVAnimation::parse(
|
||||||
00:00:00:00.00, 0, 0, 0").unwrap();
|
"timecode, blendshapecount, shape_1, shape_2
|
||||||
|
00:00:00:00.00, 0, 0, 0",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(result.frames.first().unwrap().timestamp.frame, 0);
|
assert_eq!(result.frames.first().unwrap().timestamp.frame, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_multiple_frames_can_deduce_fps() {
|
fn given_multiple_frames_can_deduce_fps() {
|
||||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2
|
let result = CSVAnimation::parse(
|
||||||
|
"timecode, blendshapecount, shape_1, shape_2
|
||||||
00:00:00:00.00, 0, 0, 0
|
00:00:00:00.00, 0, 0, 0
|
||||||
00:00:00:01.00, 0, 0, 0
|
00:00:00:01.00, 0, 0, 0
|
||||||
00:00:00:02.00, 0, 0, 0
|
00:00:00:02.00, 0, 0, 0
|
||||||
00:00:01:00.00, 0, 0, 0
|
00:00:01:00.00, 0, 0, 0
|
||||||
00:00:01:01.00, 0, 0, 0").unwrap();
|
00:00:01:01.00, 0, 0, 0",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(result.fps, 3);
|
assert_eq!(result.fps, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn given_a_single_frame_it_parses_morphs_correctly() {
|
fn given_a_single_frame_it_parses_morphs_correctly() {
|
||||||
let result = CSVAnimation::parse("timecode, blendshapecount, shape_1, shape_2
|
let result = CSVAnimation::parse(
|
||||||
00:00:00:00.00, 0, 0.1, 0.5").unwrap();
|
"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[0] - 0.1 < 0.0001);
|
||||||
assert!(&result.frames.first().unwrap().morphs[1] - 0.5 < 0.0001);
|
assert!(&result.frames.first().unwrap().morphs[1] - 0.5 < 0.0001);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user