Skip to content

Commit

Permalink
wip: procedural animation
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchantey committed Oct 21, 2024
1 parent 129e4bd commit 975f9f9
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 103 deletions.
2 changes: 2 additions & 0 deletions crates/beet_flow/src/events/on_run_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ pub mod child_expect {
pub struct OnRunResult(RunResult);
impl OnRunResult {
pub fn new(result: RunResult) -> Self { Self(result) }
/// Populate with [`RunResult::Success`]
pub fn success() -> Self { Self(RunResult::Success) }
/// Populate with [`RunResult::Failure`]
pub fn failure() -> Self { Self(RunResult::Failure) }
pub fn result(&self) -> RunResult { **self }
}
Expand Down
33 changes: 33 additions & 0 deletions crates/beet_spatial/src/procedural_animation/circle_animation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::prelude::*;
use bevy::prelude::*;

#[derive(Debug, Clone, PartialEq, Component, Reflect)]
#[reflect(Debug, Default, PartialEq)]
pub struct CircleAnimation {
pub radius: f32,
pub position: Vec3,
pub rotation: Quat,
}

impl Default for CircleAnimation {
fn default() -> Self {
Self {
radius: 0.5,
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
}
}
}


impl ProceduralAnimationStrategy for CircleAnimation {
/// C = 2πr
fn total_length(&self) -> f32 { std::f32::consts::PI * 2.0 * self.radius }

fn fraction_to_pos(&self, t: f32) -> Vec3 {
let angle = t * std::f32::consts::PI * 2.0;
let x = self.rotation * Vec3::X * angle.cos() * self.radius;
let y = self.rotation * Vec3::Y * angle.sin() * self.radius;
self.position + x + y
}
}
6 changes: 6 additions & 0 deletions crates/beet_spatial/src/procedural_animation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
pub mod circle_animation;
#[allow(unused_imports)]
pub use self::circle_animation::*;
pub mod play_procedural_animation;
#[allow(unused_imports)]
pub use self::play_procedural_animation::*;
pub mod points_animation;
#[allow(unused_imports)]
pub use self::points_animation::*;
pub mod procedural_animation_plugin;
#[allow(unused_imports)]
pub use self::procedural_animation_plugin::*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,84 @@ pub struct PlayProceduralAnimation {
pub shape: ProceduralAnimationShape,
pub speed: ProceduralAnimationSpeed,
pub repeat: RepeatAnimation,
pub num_animations: u32,
pub last_t: f32,
pub completions: u32,
pub elapsed_t: f32,
}

impl Default for PlayProceduralAnimation {
fn default() -> Self {
Self {
shape:default(),
shape: default(),
repeat: default(),
speed: default(),
num_animations: 0,
last_t: 0.0,
completions: 0,
elapsed_t: 0.0,
}
}
}

impl PlayProceduralAnimation {
pub fn get_fraction(&self,time:Res<Time>) -> f32 {
match self.speed{
ProceduralAnimationSpeed::MetersPerSecond(mps) => mps * time.delta_seconds(),
ProceduralAnimationSpeed::FractionPerSecond(fps) => fps,
pub fn is_finished(&self) -> bool {
match self.repeat {
RepeatAnimation::Forever => false,
RepeatAnimation::Never => self.completions >= 1,
RepeatAnimation::Count(n) => self.completions >= n,
}
}
pub fn get_fraction(&self, time: Res<Time>) -> f32 {
match self.speed {
ProceduralAnimationSpeed::FractionPerSecond(d_t) => {
d_t * time.delta_seconds()
}
ProceduralAnimationSpeed::MetersPerSecond(d_m) => {
(d_m * time.delta_seconds()) / self.shape.total_length()
}
}
}

}

fn play_procedural_animation(
trigger: Trigger<OnRun>,
time:Res<Time>,
mut commands: Commands,
time: Res<Time>,
mut transforms: Query<&mut Transform>,
mut query: Query<(&mut PlayProceduralAnimation, &TargetAgent)>,
) {
let (mut play_procedural_animation, target_agent) = query
let (mut action, target_agent) = query
.get_mut(trigger.entity())
.expect(expect_action::ACTION_QUERY_MISSING);

let mut transform = transforms
.get_mut(target_agent.0)
.expect(expect_action::TARGET_MISSING);

// let t =
let t = action.elapsed_t + action.get_fraction(time);
action.elapsed_t = t;

if t >= 1.0 {
action.completions += 1;
if action.is_finished() {
commands
.entity(trigger.entity())
.insert(OnRunResult::success());
}
}

transform.translation = action.shape.fraction_to_pos(t);
}


#[cfg(test)]
mod test {
use crate::prelude::*;
use anyhow::Result;
use sweet::*;

#[test]
fn works() -> Result<()> {
expect(true).to_be_false()?;

Ok(())
}

}
68 changes: 68 additions & 0 deletions crates/beet_spatial/src/procedural_animation/points_animation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::prelude::*;
use bevy::prelude::*;



#[derive(Debug, Clone, PartialEq, Component, Reflect)]
#[reflect(Debug, Default, PartialEq)]
pub struct PointsAnimation {
pub wrap: bool,
pub points: Vec<Vec3>,
}

impl Default for PointsAnimation {
fn default() -> Self {
Self {
points: vec![-Vec3::X, Vec3::X],
wrap: false,
}
}
}

impl PointsAnimation {
/// create a new path that is open (i.e. not a loop)
pub fn new_open(points: Vec<Vec3>) -> Self {
Self {
points,
wrap: false,
}
}

/// create a new path that is closed (i.e. a loop)
pub fn new_closed(points: Vec<Vec3>) -> Self {
Self {
points,
wrap: true,
}
}

pub fn num_segments(&self) -> usize {
if self.wrap {
self.points.len()
} else {
self.points.len() - 1
}
}
}

impl ProceduralAnimationStrategy for PointsAnimation {
fn fraction_to_pos(&self, t: f32) -> Vec3 {
let t = t.clamp(0.0, 1.0);
let t = t * self.num_segments() as f32;
let segment = t.floor() as usize;
let t = t - segment as f32;
let i1 = segment;
let i2 = (segment + 1) % self.points.len();
self.points[i1].lerp(self.points[i2], t)
}

fn total_length(&self) -> f32 {
let mut length = 0.0;
for i in 0..self.num_segments() {
let i1 = i;
let i2 = (i + 1) % self.points.len();
length += self.points[i1].distance(self.points[i2]);
}
length
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::prelude::*;
use bevy::prelude::*;

pub trait ProceduralAnimationStrategy {
Expand All @@ -7,12 +8,10 @@ pub trait ProceduralAnimationStrategy {
/// May panic if `t` is:
/// - less than zero
/// - equal to or greater than 1
fn get_position(&self, t: f32) -> Vec3;
fn fraction_to_pos(&self, t: f32) -> Vec3;
/// Sum of the lengths of all segments.
/// For some shapes this may be an approximation.
fn total_length(&self) -> f32;


}

#[derive(Debug, Clone, PartialEq, Component, Reflect)]
Expand All @@ -23,18 +22,20 @@ pub enum ProceduralAnimationShape {
}

impl Default for ProceduralAnimationShape {
fn default() -> Self {
Self::Circle(CircleAnimation::default())
}
fn default() -> Self { Self::Circle(CircleAnimation::default()) }
}



impl ProceduralAnimationStrategy for ProceduralAnimationShape {
fn get_position(&self, t: f32) -> Vec3 {
fn fraction_to_pos(&self, t: f32) -> Vec3 {
match self {
ProceduralAnimationShape::Circle(circle) => circle.get_position(t),
ProceduralAnimationShape::Points(points) => points.get_position(t),
ProceduralAnimationShape::Circle(circle) => {
circle.fraction_to_pos(t)
}
ProceduralAnimationShape::Points(points) => {
points.fraction_to_pos(t)
}
}
}
fn total_length(&self) -> f32 {
Expand All @@ -44,84 +45,3 @@ impl ProceduralAnimationStrategy for ProceduralAnimationShape {
}
}
}


#[derive(Debug, Clone, PartialEq, Component, Reflect)]
#[reflect(Debug, Default, PartialEq)]
pub struct CircleAnimation {
pub radius: f32,
pub position: Vec3,
pub rotation: Quat,
}

impl Default for CircleAnimation {
fn default() -> Self {
Self {
radius: 0.5,
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
}
}
}


impl ProceduralAnimationStrategy for CircleAnimation {
/// C = 2πr
fn total_length(&self) -> f32 { std::f32::consts::PI * 2.0 * self.radius }

fn get_position(&self, t: f32) -> Vec3 {
let angle = t * std::f32::consts::PI * 2.0;
let x = self.rotation * Vec3::X * angle.cos() * self.radius;
let y = self.rotation * Vec3::Y * angle.sin() * self.radius;
self.position + x + y
}
}


#[derive(Debug, Clone, PartialEq, Component, Reflect)]
#[reflect(Debug, Default, PartialEq)]
pub struct PointsAnimation {
pub wrap: bool,
pub points: Vec<Vec3>,
}

impl Default for PointsAnimation {
fn default() -> Self {
Self {
points: vec![-Vec3::X, Vec3::X],
wrap: false,
}
}
}

impl PointsAnimation {
pub fn num_segments(&self) -> usize {
if self.wrap {
self.points.len()
} else {
self.points.len() - 1
}
}
}

impl ProceduralAnimationStrategy for PointsAnimation {
fn get_position(&self, t: f32) -> Vec3 {
let t = t.clamp(0.0, 1.0);
let t = t * self.num_segments() as f32;
let segment = t.floor() as usize;
let t = t - segment as f32;
let i1 = segment;
let i2 = (segment + 1) % self.points.len();
self.points[i1].lerp(self.points[i2], t)
}

fn total_length(&self) -> f32 {
let mut length = 0.0;
for i in 0..self.num_segments() {
let i1 = i;
let i2 = (i + 1) % self.points.len();
length += self.points[i1].distance(self.points[i2]);
}
length
}
}

0 comments on commit 975f9f9

Please sign in to comment.