Initial experimental WIP

This commit is contained in:
Dietrich 2022-08-06 08:42:08 +02:00
commit ca690c2864
8 changed files with 4157 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

3690
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

21
Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "turtlers"
version = "0.1.0"
edition = "2021"
default-run = "turtlers"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = { version = "0.8", features = ["dynamic"] }
bevy-inspector-egui = "0.12.1"
bevy_prototype_lyon = "0.6"
bevy_tweening = "0.5.0"
# Enable a small amount of optimization in debug mode
[profile.dev]
opt-level = 1
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
[profile.dev.package."*"]
opt-level = 3

143
src/bin/animation.rs Normal file
View File

@ -0,0 +1,143 @@
//! Create and play an animation defined by code that operates on the `Transform` component.
use std::f32::consts::{FRAC_PI_2, PI};
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 1.0,
})
.add_startup_system(setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut animations: ResMut<Assets<AnimationClip>>,
) {
// Camera
commands.spawn_bundle(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
// The animation API uses the `Name` component to target entities
let planet = Name::new("planet");
let orbit_controller = Name::new("orbit_controller");
let satellite = Name::new("satellite");
// Creating the animation
let mut animation = AnimationClip::default();
// A curve can modify a single part of a transform, here the translation
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0],
keyframes: Keyframes::Translation(vec![
Vec3::new(1.0, 0.0, 1.0),
Vec3::new(-1.0, 0.0, 1.0),
Vec3::new(-1.0, 0.0, -1.0),
Vec3::new(1.0, 0.0, -1.0),
// in case seamless looping is wanted, the last keyframe should
// be the same as the first one
Vec3::new(1.0, 0.0, 1.0),
]),
},
);
// Or it can modify the rotation of the transform.
// To find the entity to modify, the hierarchy will be traversed looking for
// an entity with the right name at each level
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone(), orbit_controller.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0],
keyframes: Keyframes::Rotation(vec![
Quat::from_axis_angle(Vec3::Y, 0.0),
Quat::from_axis_angle(Vec3::Y, FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, PI),
Quat::from_axis_angle(Vec3::Y, 3.0 * FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, 0.0),
]),
},
);
// If a curve in an animation is shorter than the other, it will not repeat
// until all other curves are finished. In that case, another animation should
// be created for each part that would have a different duration / period
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone(), orbit_controller.clone(), satellite.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0],
keyframes: Keyframes::Scale(vec![
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
Vec3::splat(1.2),
Vec3::splat(0.8),
]),
},
);
// There can be more than one curve targeting the same entity path
animation.add_curve_to_path(
EntityPath {
parts: vec![planet.clone(), orbit_controller.clone(), satellite.clone()],
},
VariableCurve {
keyframe_timestamps: vec![0.0, 1.0, 2.0, 3.0, 4.0],
keyframes: Keyframes::Rotation(vec![
Quat::from_axis_angle(Vec3::Y, 0.0),
Quat::from_axis_angle(Vec3::Y, FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, PI),
Quat::from_axis_angle(Vec3::Y, 3.0 * FRAC_PI_2),
Quat::from_axis_angle(Vec3::Y, 0.0),
]),
},
);
// Create the animation player, and set it to repeat
let mut player = AnimationPlayer::default();
player.play(animations.add(animation)).repeat();
// Create the scene that will be animated
// First entity is the planet
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Icosphere::default())),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
..default()
})
// Add the Name component, and the animation player
.insert_bundle((planet, player))
.with_children(|p| {
// This entity is just used for animation, but doesn't display anything
p.spawn_bundle(SpatialBundle::default())
// Add the Name component
.insert(orbit_controller)
.with_children(|p| {
// The satellite, placed at a distance of the planet
p.spawn_bundle(PbrBundle {
transform: Transform::from_xyz(1.5, 0.0, 0.0),
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()),
..default()
})
// Add the Name component
.insert(satellite);
});
});
}

12
src/debug.rs Normal file
View File

@ -0,0 +1,12 @@
use bevy::prelude::Plugin;
use bevy_inspector_egui::WorldInspectorPlugin;
pub struct DebugPlugin;
impl Plugin for DebugPlugin {
fn build(&self, app: &mut bevy::prelude::App) {
if cfg!(debug_assertions) {
app.add_plugin(WorldInspectorPlugin::new());
}
}
}

26
src/main.rs Normal file
View File

@ -0,0 +1,26 @@
mod debug;
mod turtle;
mod turtle_shapes;
use bevy::{prelude::*, window::close_on_esc};
use bevy_prototype_lyon::prelude::*;
use turtle::TurtlePlugin;
fn main() {
App::new()
.insert_resource(Msaa { samples: 4 })
.insert_resource(ClearColor(Color::BEIGE))
.insert_resource(WindowDescriptor {
width: 400.0,
height: 400.0,
title: "Turtle Window".to_string(),
present_mode: bevy::window::PresentMode::AutoVsync,
..default()
})
.add_plugins(DefaultPlugins)
.add_plugin(ShapePlugin)
.add_plugin(debug::DebugPlugin)
.add_plugin(TurtlePlugin)
.add_system(close_on_esc)
.run();
}

222
src/turtle.rs Normal file
View File

@ -0,0 +1,222 @@
use std::{f32::consts::PI, time::Duration};
use bevy::prelude::*;
use bevy_prototype_lyon::{entity::ShapeBundle, prelude::*};
use bevy_tweening::{
lens::{TransformPositionLens, TransformRotateXLens, TransformRotateZLens, TransformScaleLens},
Animator, EaseFunction, Lens, Sequence, Tween, Tweenable, TweeningPlugin, TweeningType,
};
use crate::turtle_shapes;
pub struct TurtlePlugin;
impl Plugin for TurtlePlugin {
fn build(&self, app: &mut bevy::prelude::App) {
app.add_plugin(TweeningPlugin)
.add_startup_system(setup)
.add_system(keypresses);
}
}
#[derive(Bundle)]
pub struct Turtle {
colors: Colors,
commands: TurtleCommands,
}
impl Default for Turtle {
fn default() -> Self {
Self {
colors: Colors {
color: Color::DARK_GRAY,
fill_color: Color::BLACK,
},
commands: TurtleCommands(vec![
TurtleCommand::Forward(Length(100.)),
TurtleCommand::Left(Angle(90.)),
]), /*
shape: TurtleShape(GeometryBuilder::build_as(
&turtle_shapes::turtle(),
DrawMode::Outlined {
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Default::default(),
)), */
}
}
}
impl Turtle {
pub fn set_color(&mut self, color: Color) {
self.colors.color = color;
}
pub fn set_fill_color(&mut self, color: Color) {
self.colors.fill_color = color;
}
pub fn get_colors(&self) -> &Colors {
&self.colors
}
pub fn forward(&mut self) -> &mut Self {
self.commands.0.push(TurtleCommand::Forward(Length(100.0)));
self
}
}
#[derive(Component)]
pub struct TurtleCommands(Vec<TurtleCommand>);
impl TurtleCommands {
fn generate_tweenable(&self) -> Sequence<Transform> {
let mut seq = Sequence::with_capacity(self.0.len());
for op in &self.0 {
match op {
TurtleCommand::Forward(Length(x)) => {
println!("Adding Forward");
seq = seq.then(Tween::new(
// Use a quadratic easing on both endpoints.
EaseFunction::QuadraticInOut,
// Loop animation back and forth.
TweeningType::Once,
// Animation time (one way only; for ping-pong it takes 2 seconds
// to come back to start).
Duration::from_secs(1),
// The lens gives access to the Transform component of the Entity,
// for the Animator to animate it. It also contains the start and
// end values respectively associated with the progress ratios 0. and 1.
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::new(*x as f32, 40., 0.),
},
));
}
TurtleCommand::Backward(_) => todo!(),
TurtleCommand::Left(Angle(x)) => {
println!("Adding Left");
seq = seq.then(Tween::new(
// Use a quadratic easing on both endpoints.
EaseFunction::QuadraticInOut,
// Loop animation back and forth.
TweeningType::Once,
// Animation time (one way only; for ping-pong it takes 2 seconds
// to come back to start).
Duration::from_secs(1),
// The lens gives access to the Transform component of the Entity,
// for the Animator to animate it. It also contains the start and
// end values respectively associated with the progress ratios 0. and 1.
TransformRotateZLens {
start: *x as f32 * (PI / 180.),
end: -*x as f32 * (PI / 180.),
},
));
}
TurtleCommand::Right(_) => todo!(),
TurtleCommand::PenUp => todo!(),
TurtleCommand::PenDown => todo!(),
TurtleCommand::Circle => todo!(),
}
}
seq
}
}
#[derive(Clone, Component)]
pub struct TurtleShape;
#[derive(Clone, Component)]
pub struct Colors {
color: Color,
fill_color: Color,
}
pub struct Length(f64);
pub struct Angle(f64);
#[derive(Component)]
enum TurtleCommand {
Forward(Length),
Backward(Length),
Left(Angle),
Right(Angle),
PenUp,
PenDown,
Circle,
}
struct TurtleMoveLens {
start: Vec3,
end: Vec3,
}
fn setup(mut commands: Commands) {
let animator = Animator::new(Tween::new(
// Use a quadratic easing on both endpoints.
EaseFunction::QuadraticInOut,
// Loop animation back and forth.
TweeningType::PingPong,
// Animation time (one way only; for ping-pong it takes 2 seconds
// to come back to start).
Duration::from_secs(1),
// The lens gives access to the Transform component of the Entity,
// for the Animator to animate it. It also contains the start and
// end values respectively associated with the progress ratios 0. and 1.
TransformPositionLens {
start: Vec3::ZERO,
end: Vec3::new(40., 40., 0.),
},
));
commands.spawn_bundle(Camera2dBundle::default());
commands
.spawn_bundle(Turtle::default())
.insert_bundle(GeometryBuilder::build_as(
&turtle_shapes::turtle(),
DrawMode::Outlined {
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Default::default(),
))
.insert(animator);
}
/// The sprite is animated by changing its translation depending on the time that has passed since
/// the last frame.
fn keypresses(
//time: Res<Time>,
keys: Res<Input<KeyCode>>,
mut commands: Commands,
mut qry: Query<&mut Animator<Transform>>,
tcmd: Query<&TurtleCommands>,
) {
if keys.just_pressed(KeyCode::W) {
let tcmd = tcmd.single();
let c = tcmd.generate_tweenable();
let mut shap = qry.single_mut();
shap.set_tweenable(c);
/* commands
.spawn_bundle(Turtle::default())
.insert_bundle(GeometryBuilder::build_as(
&turtle_shapes::turtle(),
DrawMode::Outlined {
fill_mode: FillMode::color(Color::RED),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Transform::from_translation(Vec3::new(-100., 0., 0.)),
))
.insert(Animator::new(Tween::new(
// Use a quadratic easing on both endpoints.
EaseFunction::QuadraticInOut,
// Loop animation back and forth.
TweeningType::PingPong,
// Animation time (one way only; for ping-pong it takes 2 seconds
// to come back to start).
Duration::from_secs(1),
// The lens gives access to the Transform component of the Entity,
// for the Animator to animate it. It also contains the start and
// end values respectively associated with the progress ratios 0. and 1.
TransformPositionLens {
start: Vec3::new(-100., 0., 0.),
end: Vec3::new(-140., 40., 0.),
},
))); */
}
}

42
src/turtle_shapes.rs Normal file
View File

@ -0,0 +1,42 @@
use bevy::prelude::Vec2;
use bevy_prototype_lyon::prelude::{Path, PathBuilder};
pub fn turtle() -> Path {
let polygon = &[
[-2.5f32, 14.0f32],
[-1.25f32, 10.0f32],
[-4.0f32, 7.0f32],
[-7.0f32, 9.0f32],
[-9.0f32, 8.0f32],
[-6.0f32, 5.0f32],
[-7.0f32, 1.0f32],
[-5.0f32, -3.0f32],
[-8.0f32, -6.0f32],
[-6.0f32, -8.0f32],
[-4.0f32, -5.0f32],
[0.0f32, -7.0f32],
[4.0f32, -5.0f32],
[6.0f32, -8.0f32],
[8.0f32, -6.0f32],
[5.0f32, -3.0f32],
[7.0f32, 1.0f32],
[6.0f32, 5.0f32],
[9.0f32, 8.0f32],
[7.0f32, 9.0f32],
[4.0f32, 7.0f32],
[1.25f32, 10.0f32],
[2.5f32, 14.0f32],
];
let mut turtle_path = PathBuilder::new();
turtle_path.line_to(Vec2::new(1.0, 1.0));
turtle_path.line_to(Vec2::new(-1.0, 1.0));
turtle_path.line_to(Vec2::new(-1.0, -1.0));
turtle_path.line_to(Vec2::new(1.0, -1.0));
turtle_path.close();
turtle_path.move_to(Vec2::new(0.0, 16.0));
for coord in polygon {
turtle_path.line_to(Vec2::from_array(*coord));
}
turtle_path.close();
turtle_path.build()
}