first port to modern bevy

This commit is contained in:
Franz Dietrich 2025-10-01 17:10:27 +02:00
parent da7b9493ec
commit 8a56176ceb
18 changed files with 635 additions and 4261 deletions

4084
Cargo.lock generated

File diff suppressed because it is too large Load Diff

32
Cargo.toml Normal file
View File

@ -0,0 +1,32 @@
[workspace]
resolver = "2"
members = ["turtle-lib", "turtle-example"]
[workspace.dependencies]
# Pin Bevy across the workspace
bevy = { version = "0.17.1" }
# Bevy ecosystem crates compatible with Bevy 0.17
# Lyon: main branch targets latest Bevy (0.17 at time of writing)
bevy_prototype_lyon = { git = "https://github.com/pindash-io/bevy_prototype_lyon.git", branch = "bevy-0.17" }
# Tweening: pin to a recent commit on main that states compatibility with latest Bevy
# If this still pulls Bevy 0.16, we'll revisit and temporarily gate tweening.
bevy_tweening = { git = "https://github.com/djeedai/bevy_tweening.git", rev = "8b3cad18a090078d9055d77a632be44e701aecc7" }
# Inspector: main branch adds support for newer Bevy, use git until 0.17 release is published
bevy-inspector-egui = { git = "https://github.com/jakobhellermann/bevy-inspector-egui.git" }
# Shared utility crates
num-traits = "0.2"
rand = "0.8"
# 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

View File

@ -7,9 +7,15 @@ license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
bevy = { workspace = true }
bevy_prototype_lyon = { workspace = true }
bevy-inspector-egui = { workspace = true, optional = true }
bevy_tweening = { workspace = true, optional = true }
num-traits = { workspace = true }
bevy = { version = "0.9" } [features]
bevy_prototype_lyon = {version="0.7"} default = []
bevy-inspector-egui = "0.16" # Enable debug inspector UI (bevy-inspector-egui)
num-traits = "0.2" inspector = ["dep:bevy-inspector-egui"]
bevy_tweening = {version="0.6"} # Enable animations (bevy_tweening)
tweening = ["dep:bevy_tweening"]

View File

@ -0,0 +1,240 @@
# Turtle Lib Bevy Upgrade Guide
## Goal and Scope
- upgrade path: `bevy` 0.10.x → 0.16.1
- crates covered: `turtle-lib`, example app(s), supporting tooling
- audience: maintainers comfortable with Rust & Bevy internals
## Dependency Targets
- `bevy` 0.10.x → 0.16.1
- `bevy_inspector_egui` 0.18 → 0.33
- `bevy_tweening` 0.6→0.13
- `bevy_prototype_lyon` 0.8 → 0.13
## Bevy relevant changes for the turtle-lib crate
src: https://bevy.org/news/
### 0.10 → 0.11
- Schedule-first API (mandatory): replace all add_system/add_startup_system calls with schedule-scoped add_systems
- In `turtle-lib/src/lib.rs::TurtlePlugin::build`:
- `.add_startup_system(setup)``.add_systems(Startup, setup)`
- `.add_system(keypresses)``.add_systems(Update, keypresses)`
- `.add_system(component_animator_system::<Path>)``.add_systems(Update, component_animator_system::<Path>)`
- `.add_system(close_on_esc)``.add_systems(Update, close_on_esc)`
- `.add_system(draw_lines)``.add_systems(Update, draw_lines)`
- Source: 0.10→0.11 “Schedule-First: the new and improved add_systems”
- Plugin API: `add_plugin` deprecated → use `add_plugins` (single items or tuples)
- In `TurtlePlugin::build`:
- `.add_plugin(debug::DebugPlugin)``.add_plugins(debug::DebugPlugin)`
- `.add_plugin(ShapePlugin)``.add_plugins(ShapePlugin)`
- `.add_plugin(TweeningPlugin)``.add_plugins(TweeningPlugin)`
- Source: 0.10→0.11 “Allow tuples and single plugins in add_plugins, deprecate add_plugin”
- Window configuration moved from `WindowDescriptor` to `Window` on `WindowPlugin`
- In `TurtlePlugin::build` replace:
- `DefaultPlugins.set(WindowPlugin { window: WindowDescriptor { title, width, height, present_mode, ..Default::default() }, ..default() })`
- with `DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title, resolution: (width, height).into(), present_mode, ..default() }), ..default() })`
- Notes: PresentMode variants are unchanged here; title now takes `String` via `.into()`; width/height become `resolution`.
- Source: Bevy 0.11 window API (examples/docs)
- Events must derive Event
- In `turtle-lib/src/events.rs`: add `#[derive(Event)]` to `DrawingStartedEvent`.
- Ensure `app.add_event::<DrawingStartedEvent>()` remains in `TurtlePlugin::build`.
- Source: 0.10→0.11 “Require #[derive(Event)] on all Events”
- Reflect derives: `FromReflect` is auto-derived by `#[derive(Reflect)]`
- In `turtle-lib/src/commands.rs`, many enums/structs derive both `Reflect` and `FromReflect`.
- You can remove explicit `FromReflect` derives unless you provide a manual impl; if you keep a manual impl, disable auto via `#[reflect(from_reflect = false)]`.
- Source: 0.10→0.11 “FromReflect Ergonomics Implementation”
### 0.11 → 0.12
- EventReader API rename: `iter()``read()`
- In `turtle-lib/src/lib.rs::draw_lines` change:
- `for _ev in query_event.iter() { ... }``for _ev in query_event.read() { ... }`
- Also, `&mut EventReader` no longer implements `IntoIterator`; prefer `.read()`.
- Source: 0.11→0.12 “Refactor EventReader::iter to read” and “Remove IntoIterator impl for &mut EventReader”
- Unified `configure_sets` API (FYI only)
- If you later configure system sets, prefer `app.configure_sets(ScheduleLabel, …)`. Turtle-lib doesnt currently call this, so no change.
- Source: 0.11→0.12 “Replace IntoSystemSetConfig with IntoSystemSetConfigs” and “Unified configure_sets API”
- Present mode/Window tweaks (FYI only)
- `PresentMode::Fifo` still valid; an additional `FifoRelaxed` variant exists—no change required.
- Hot reloading workflow changed to feature flag `bevy/file_watcher`; turtle-lib doesnt configure `AssetPlugin`, so no change.
- Source: 0.11→0.12 windowing and assets notes
### 0.12 → 0.13
- Scheduling and plugin API are unchanged from 0.12 for turtle-lib
- Existing schedule-first usage and `add_plugins` continue to work. No changes needed in `TurtlePlugin::build` for scheduling semantics.
- WindowPlugin: primary window field naming consistency
- If you already use the 0.11+ style (recommended earlier):
- `WindowPlugin { primary_window: Some(Window { title, resolution, present_mode, ..default() }), ..default() }` continues to be the correct pattern.
- If any code still uses `WindowPlugin { window: WindowDescriptor { .. } }`, migrate per 0.10→0.11 notes above (crate currently uses `WindowDescriptor` and needs that migration anyway).
- Camera clear color configuration remains valid
- `Camera2dBundle { camera_2d: Camera2d { clear_color: ClearColorConfig::Custom(Color::BEIGE) }, ..default() }` remains correct. No change needed.
- Input API is stable
- `Res<Input<KeyCode>>` and `keys.just_pressed(KeyCode::W)` continue to work without changes.
- Events ergonomics (carry-over from 0.11)
- Ensure custom events derive `#[derive(Event)]` and are registered with `app.add_event::<T>()`. This remains required and stable in 0.13.
- `EventReader::read()` introduced in 0.12 continues to be the correct method in 0.13; prefer `for ev in reader.read()` over the removed `iter()`.
- Bevy Inspector Egui (debug) still uses plugin as-is
- `WorldInspectorPlugin` continues to be added via `add_plugins` in debug builds; no API change affecting `turtle-lib` at this hop.
### 0.13 → 0.14
- Color API refinements (no code change needed for current usage)
- Named colors like `Color::BEIGE`, `Color::MIDNIGHT_BLUE`, `Color::BLACK` remain valid. No renames impacting `turtle-lib` in this hop.
- Type-safe color constructors improved, but we dont currently construct custom colors beyond constants.
- Windowing migrated to winit 0.30 (FYI)
- No direct use of backend-specific window APIs in `turtle-lib`; existing `Window { title, resolution, present_mode }` remains valid.
- If examples or downstream apps interact with window events, check their migrations separately; not applicable in this crate.
- Events are always updated in minimal apps (FYI)
- Bevy 0.14 guarantees events update even in minimal setups; `turtle-lib` already uses `DefaultPlugins` and explicit `add_event::<DrawingStartedEvent>()` so no action required.
- Scheduling/UI/Camera
- `Camera2dBundle` stays stable in this hop; our use of `ClearColorConfig::Custom(Color::BEIGE)` continues to be correct.
- `close_on_esc` remains available via `bevy::window::close_on_esc` with the same call pattern.
### 0.14 → 0.15
- Query::single family migrated to `Single` (N/A)
- `turtle-lib` does not call `Query::single()`/`single_mut()`; no migration needed here.
- State-scoped events (FYI)
- 0.15 introduces state-scoped events; `turtle-lib` uses a global `DrawingStartedEvent` and doesnt scope events to states. No change required.
- Color, camera, and window remain compatible
- Existing use of `Color::BEIGE`, `Camera2dBundle`, and `Window { resolution, present_mode }` remains valid; no API renames affecting our code in this hop.
- Scheduling order clarifications (FYI)
- 0.15 tightened ambiguities in system ordering; `turtle-lib` doesnt rely on ambiguous ordering between systems, and uses default sets. No action needed.
### 0.15 → 0.16
- ECS Relationships replace `Parent`/`Children` (N/A)
- `turtle-lib` doesnt use hierarchy components directly; no migration required.
- Improved spawn ergonomics (N/A)
- The new `children!`/`related!` helpers dont affect current code; we spawn via bundles without parent-child APIs.
- Unified ECS error handling (optional)
- Systems and commands can return `Result` in 0.16+. `turtle-lib` systems currently return `()`. You may adopt `Result` for clearer error paths later, but no change is required to compile.
- Window/Input/Camera remain compatible
- `close_on_esc`, `Input<KeyCode>`, `Camera2dBundle`, and `ClearColorConfig::Custom(Color::BEIGE)` continue to work. No API renames in these areas affecting current usage.
- Tweening and Lyon plugins
- API incompatibilities, if any, are tracked in their dedicated sections below. No direct Bevy-0.16-specific change required in our calls (`Animator`, `Tween`, `Path`).
## Bevy_inspector_egui relevant changes for the turtle-lib crate
src: https://github.com/jakobhellermann/bevy-inspector-egui/blob/main/docs/CHANGELOG.md
src: https://github.com/jakobhellermann/bevy-inspector-egui/blob/main/docs/MIGRATION_GUIDE_0.15_0.16.md
- Version alignment across Bevy upgrades
- 0.12 → 0.13: use bevy_inspector_egui ≥ 0.21 (changelog shows 0.21 updated to Bevy 0.12; 0.23 to Bevy 0.13). Our target path ends at 0.33 for Bevy 0.16.
- 0.14: use ≥ 0.25 (changelog: 0.25.0 updated to Bevy 0.14).
- 0.15: use ≥ 0.28 (changelog: 0.28.0 updated to Bevy 0.15).
- 0.16: use ≥ 0.31 (changelog: 0.31.0 updated to Bevy 0.16). Latest at time of writing is 0.33.x.
- WorldInspectorPlugin path and construction (0.15 → 0.16)
- The old `WorldInspectorPlugin` moved to `bevy_inspector_egui::quick::WorldInspectorPlugin`.
- In `turtle-lib/src/debug.rs`, ensure we import from `quick`:
- `use bevy_inspector_egui::quick::WorldInspectorPlugin;`
- Continue to add it conditionally in debug:
- `app.add_plugins(WorldInspectorPlugin);` (use `add_plugins`, not `add_plugin`).
- The removed `WorldInspectorParams` is not used in this crate, so no action is required.
- Reflect-driven inspector (0.15 → 0.16 rewrite)
- The inspector is centered on `Reflect` and type registration. We already register types we care about (`TurtleColors`, `TurtleCommands`). No additional registration is necessary for using the world inspector itself.
- If we later add per-resource inspectors (e.g., `ResourceInspectorPlugin<T>`), derive `Reflect` for `T` and call `app.register_type::<T>()`.
- Schedules/ambiguity fixes in quick plugins
- The crates quick plugins run in their own schedule to avoid system ambiguity since ~0.25. No overrides needed by `turtle-lib`.
- Known guardrails
- 0.18.4 added a guard for missing `PrimaryWindow`; our app defines a primary window via `DefaultPlugins.set(WindowPlugin { … })`, so the inspector will work in debug.
## Bevy_tweening relevant changes for the turtle-lib crate
src: https://github.com/djeedai/bevy_tweening/blob/main/CHANGELOG.md
- Version alignment with Bevy
- Bevy 0.10 → bevy_tweening 0.7
- Bevy 0.11 → bevy_tweening 0.8
- Bevy 0.12 → bevy_tweening 0.9
- Bevy 0.13 → bevy_tweening 0.10
- Bevy 0.14 → bevy_tweening 0.11
- Bevy 0.15 → bevy_tweening 0.12
- Bevy 0.16 → bevy_tweening 0.13
- Custom Lens signature change (bevy_tweening 0.11 for Bevy 0.14)
- Impacted files: `turtle-lib/src/drawing/animation/line_lens.rs`, `turtle-lib/src/drawing/animation/circle_lens.rs`.
- Update `Lens<T>::lerp(&mut self, target: &mut T, ratio: f32)``Lens<T>::lerp(&mut self, target: &mut dyn Targetable<T>, ratio: f32)`.
- Code using `target` can remain unchanged because `dyn Targetable<T>` implements `Defer`/`DeferMut` and derefs like `&mut T`.
- This change is required when moving to Bevy 0.14 (bevy_tweening 0.11+) and remains compatible up to 0.13.
- EaseFunction source update (bevy_tweening 0.12 for Bevy 0.15)
- `interpolation::EaseFunction` was replaced by `bevy_math::EaseFunction` and re-exported by bevy_tweening.
- Our code imports `bevy_tweening::EaseFunction`, which continues to work. Alternatively, import `bevy_math::EaseFunction` directly.
- Animator systems and Path animations
- Continue adding `TweeningPlugin` to the app.
- Continue registering `component_animator_system::<Path>` in `Update` to animate `Animator<Path>` (still required up to bevy_tweening 0.13).
- Note: The removal of `component_animator_system` is planned in a future bevy_tweening 0.14 (unreleased at the time of writing) and does not apply to our 0.13 target.
- Completion events
- `TweenCompleted` event remains available through 0.13. Our use of `.with_completed_event(state.segment_index() as u64)` is valid up to 0.13.
- If migrating beyond 0.13 later, the API changes (no `user_data`, new event types) will require adjustments; out of scope for Bevy 0.16 + bevy_tweening 0.13.
- Bevy 0.12 EventReader change applies here too
- When listening to `TweenCompleted`, use `EventReader<TweenCompleted>::read()` (not `iter()`), as documented in the Bevy 0.11→0.12 section above.
## Bevy_prototype_lyon relevant changes for the turtle-lib crate
src: https://github.com/Nilirad/bevy_prototype_lyon/blob/master/CHANGELOG.md
- Version alignment with Bevy
- Bevy 0.10 → lyon 0.8
- Bevy 0.11 → lyon 0.9
- Bevy 0.12 → lyon 0.10
- Bevy 0.13 → lyon 0.11
- Bevy 0.14 → lyon 0.12
- Bevy 0.15 → lyon 0.13
- Bevy 0.16 → lyon 0.14
- 0.8: Fill/Stroke rename
- `FillMode`/`StrokeMode` renamed to `Fill`/`Stroke` and became Components.
- In `turtle-lib/src/turtle_bundle.rs`, if still using the old names, update:
- `DrawMode::Outlined { fill_mode: FillMode::color(c), outline_mode: StrokeMode::new(c2, w) }`
- to the current API for your target lyon version (≤0.13 still supports DrawMode; in 0.14 see below).
- 0.13: `ShapeBundle` composition change (FYI)
- Deprecated `SpatialBundle` was removed from `ShapeBundle`; `Transform` and `Visibility` are added separately internally.
- We construct shapes via `GeometryBuilder::build_as(...)` returning `ShapeBundle`; we dont access its fields—no code change required.
- 0.14 (Bevy 0.16): Major API rework affecting turtle-lib
- Path component renamed to `Shape`; `Shape` now includes fill and stroke data.
- `Fill` and `Stroke` are no longer `Component`s; theyre part of `Shape` data.
- `GeometryBuilder` and `PathBuilder` removed. Use `ShapeBuilder` and `ShapePath`:
- Build shapes: `ShapeBuilder::with(&geometry).fill(color).stroke((color, width)).build()`.
- `ShapePath` now implements `Geometry` and replaces direct `PathBuilder` usage.
- `ShapeBundle` is deprecated and no longer exported from `prelude`.
Impacted code and migration sketch for 0.14:
- Imports in `turtle-lib/src/lib.rs` and lenses:
- Replace `use bevy_prototype_lyon::prelude::{Path, ShapePlugin};` with `use bevy_prototype_lyon::prelude::{Shape, ShapePath, ShapeBuilder, ShapePlugin};`
- Animator and systems:
- `Animator<Path>``Animator<Shape>`
- `component_animator_system::<Path>``component_animator_system::<Shape>`
- Lenses (`drawing/animation/line_lens.rs`, `circle_lens.rs`):
- `impl Lens<Path> for ...``impl Lens<Shape> for ...`
- Replace `PathBuilder` usage with creating/updating a `ShapePath` geometry and attaching it to the `Shape` target per the new API.
- Spawn/build (`turtle_bundle.rs`):
- Replace `GeometryBuilder::build_as(&shapes::turtle(), DrawMode::Outlined { ... }, Transform::IDENTITY)` and `ShapeBundle`
- with `ShapeBuilder::with(&shapes::turtle()).fill(Color::MIDNIGHT_BLUE).stroke((Color::BLACK, 1.0)).build()`.
Notes:
- If you stop at lyon 0.13 (Bevy 0.15), you dont need the 0.14 rework yet. If you upgrade to Bevy 0.16, adopt lyon 0.14 and apply the above migrations.

View File

@ -1,25 +1,22 @@
use bevy::{ use bevy::{prelude::Component, reflect::Reflect};
prelude::Component,
reflect::{FromReflect, Reflect},
};
use crate::{ use crate::{
builders::WithCommands, builders::WithCommands,
drawing::{ drawing::TurtleGraphElement,
animation::{
draw_circle_segment, draw_straight_segment, move_straight_segment, turtle_turn,
ToAnimationSegment, TurtleAnimationSegment,
},
TurtleGraphElement,
},
general::{angle::Angle, length::Length, Coordinate, Precision, Speed}, general::{angle::Angle, length::Length, Coordinate, Precision, Speed},
state::TurtleState, state::TurtleState,
}; };
#[cfg(feature = "tweening")]
use crate::drawing::animation::{
draw_circle_segment, draw_straight_segment, move_straight_segment, turtle_turn,
ToAnimationSegment, TurtleAnimationSegment,
};
/** /**
* All the possibilities to draw something with turtle. All the commands can get the position, heading, * All the possibilities to draw something with turtle. All the commands can get the position, heading,
* color and fill_color from the turtles state. * color and fill_color from the turtles state.
*/ */
#[derive(Component, Reflect, FromReflect, Debug, Clone)] #[derive(Component, Reflect, Debug, Clone)]
pub enum MoveCommand { pub enum MoveCommand {
Forward(Length), Forward(Length),
Backward(Length), Backward(Length),
@ -37,7 +34,7 @@ impl Default for MoveCommand {
} }
/// Different ways to drop breadcrumbs on the way like a dot or a stamp of the turtles shape. /// Different ways to drop breadcrumbs on the way like a dot or a stamp of the turtles shape.
#[derive(Component, Reflect, FromReflect, Default, Debug, Clone)] #[derive(Component, Reflect, Default, Debug, Clone)]
pub enum Breadcrumb { pub enum Breadcrumb {
Dot, Dot,
#[default] #[default]
@ -45,7 +42,7 @@ pub enum Breadcrumb {
} }
/// Different ways that change the orientation of the turtle. /// Different ways that change the orientation of the turtle.
#[derive(Component, Reflect, FromReflect, Debug, Clone)] #[derive(Component, Reflect, Debug, Clone)]
pub enum OrientationCommand { pub enum OrientationCommand {
Left(Angle<Precision>), Left(Angle<Precision>),
Right(Angle<Precision>), Right(Angle<Precision>),
@ -60,7 +57,7 @@ impl Default for OrientationCommand {
} }
/// A combination of all commands that can be used while drawing. /// A combination of all commands that can be used while drawing.
#[derive(Component, Reflect, FromReflect, Debug, Clone)] #[derive(Component, Reflect, Debug, Clone)]
pub enum DrawElement { pub enum DrawElement {
Draw(MoveCommand), Draw(MoveCommand),
Move(MoveCommand), Move(MoveCommand),
@ -73,6 +70,8 @@ impl Default for DrawElement {
Self::Draw(Default::default()) Self::Draw(Default::default())
} }
} }
#[cfg(feature = "tweening")]
impl ToAnimationSegment for DrawElement { impl ToAnimationSegment for DrawElement {
fn to_draw_segment( fn to_draw_segment(
&self, &self,
@ -104,7 +103,7 @@ impl ToAnimationSegment for DrawElement {
} }
} }
#[derive(Component, Reflect, FromReflect, Debug, Clone)] #[derive(Component, Reflect, Debug, Clone)]
pub enum TurtleSegment { pub enum TurtleSegment {
Single(DrawElement), Single(DrawElement),
Outline(Vec<DrawElement>), Outline(Vec<DrawElement>),
@ -116,6 +115,8 @@ impl Default for TurtleSegment {
Self::Single(Default::default()) Self::Single(Default::default())
} }
} }
#[cfg(feature = "tweening")]
impl ToAnimationSegment for TurtleSegment { impl ToAnimationSegment for TurtleSegment {
fn to_draw_segment( fn to_draw_segment(
&self, &self,
@ -156,8 +157,23 @@ impl TurtleCommands {
pub fn set_speed(&mut self, speed: Speed) { pub fn set_speed(&mut self, speed: Speed) {
self.state.set_speed(speed); self.state.set_speed(speed);
} }
// Public accessors for immediate drawing
pub(crate) fn animation_state(&self) -> usize {
self.animation_state
}
pub(crate) fn animation_state_mut(&mut self) -> &mut usize {
&mut self.animation_state
}
pub(crate) fn commands(&self) -> &[TurtleSegment] {
&self.commands
}
pub(crate) fn state_mut(&mut self) -> &mut TurtleState {
&mut self.state
}
} }
#[cfg(feature = "tweening")]
impl Iterator for TurtleCommands { impl Iterator for TurtleCommands {
type Item = TurtleAnimationSegment; type Item = TurtleAnimationSegment;

View File

@ -1,12 +1,14 @@
use bevy::prelude::Plugin; use bevy::prelude::Plugin;
#[cfg(feature = "inspector")]
use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_inspector_egui::quick::WorldInspectorPlugin;
pub struct DebugPlugin; pub struct DebugPlugin;
impl Plugin for DebugPlugin { impl Plugin for DebugPlugin {
fn build(&self, app: &mut bevy::prelude::App) { fn build(&self, _app: &mut bevy::prelude::App) {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
app.add_plugin(WorldInspectorPlugin); #[cfg(feature = "inspector")]
app.add_plugins(WorldInspectorPlugin::default());
} }
} }
} }

View File

@ -1,12 +1,16 @@
use bevy::reflect::{FromReflect, Reflect}; use bevy::reflect::Reflect;
pub use self::line_segments::{TurtleDrawCircle, TurtleDrawLine}; pub use self::line_segments::{TurtleDrawCircle, TurtleDrawLine};
#[cfg(feature = "tweening")]
pub mod animation; pub mod animation;
#[cfg(not(feature = "tweening"))]
pub(crate) mod immediate;
mod line_segments; mod line_segments;
#[cfg(feature = "tweening")]
pub(crate) mod run_step; pub(crate) mod run_step;
#[derive(Reflect, FromReflect, Default, Debug)] #[derive(Reflect, Default, Debug)]
pub enum TurtleGraphElement { pub enum TurtleGraphElement {
TurtleLine(TurtleDrawLine), TurtleLine(TurtleDrawLine),
TurtleCircle(TurtleDrawCircle), TurtleCircle(TurtleDrawCircle),

View File

@ -2,7 +2,7 @@ mod circle_lens;
mod line_lens; mod line_lens;
use bevy::prelude::{Quat, Transform, Vec2, Vec3}; use bevy::prelude::{Quat, Transform, Vec2, Vec3};
use bevy_prototype_lyon::prelude::Path; use bevy_prototype_lyon::prelude::Shape;
use bevy_tweening::{ use bevy_tweening::{
lens::{TransformPositionLens, TransformRotateZLens}, lens::{TransformPositionLens, TransformRotateZLens},
Animator, EaseFunction, Tween, Animator, EaseFunction, Tween,
@ -23,7 +23,7 @@ use super::{TurtleDrawCircle, TurtleDrawLine, TurtleGraphElement};
pub struct TurtleAnimationSegment { pub struct TurtleAnimationSegment {
pub turtle_animation: Option<Tween<Transform>>, pub turtle_animation: Option<Tween<Transform>>,
pub line_segment: Option<TurtleGraphElement>, pub line_segment: Option<TurtleGraphElement>,
pub line_animation: Option<Animator<Path>>, pub line_animation: Option<Animator<Shape>>,
} }
pub trait ToAnimationSegment { pub trait ToAnimationSegment {
@ -102,7 +102,7 @@ struct MoveStraightLineAnimation {
_start: Coordinate, _start: Coordinate,
_end: Coordinate, _end: Coordinate,
line: TurtleDrawLine, line: TurtleDrawLine,
animation: Tween<Path>, animation: Tween<Shape>,
} }
impl MoveStraightLineAnimation { impl MoveStraightLineAnimation {
@ -159,7 +159,7 @@ struct MoveCircleLineAnimation {
_start: Coordinate, _start: Coordinate,
_end: Coordinate, _end: Coordinate,
line: TurtleDrawCircle, line: TurtleDrawCircle,
animation: Tween<Path>, animation: Tween<Shape>,
} }
impl MoveCircleLineAnimation { impl MoveCircleLineAnimation {

View File

@ -1,5 +1,6 @@
use bevy::prelude::{Quat, Transform, Vec2}; use bevy::prelude::{Quat, Transform, Vec2};
use bevy_prototype_lyon::prelude::{Path, PathBuilder, ShapePath}; use bevy_prototype_lyon::{path::ShapePath, prelude::*, self as lyon_crate};
use lyon_crate::entity::Shape;
use bevy_tweening::Lens; use bevy_tweening::Lens;
use crate::general::{angle::Angle, Precision}; use crate::general::{angle::Angle, Precision};
@ -12,12 +13,12 @@ pub(crate) struct CircleAnimationLens {
pub end: Angle<Precision>, pub end: Angle<Precision>,
} }
impl Lens<Path> for CircleAnimationLens { impl Lens<Shape> for CircleAnimationLens {
fn lerp(&mut self, target: &mut Path, ratio: f32) { fn lerp(&mut self, target: &mut Shape, ratio: f32) {
let mut path_builder = PathBuilder::new(); let mut path = ShapePath::new();
path_builder.move_to(self.start_pos); path = path.move_to(self.start_pos);
// The center point of the radius, then the radii in x and y direction, then the angle that will be drawn, then the x_rotation ? // The center point of the radius, then the radii in x and y direction, then the angle that will be drawn, then the x_rotation ?
path_builder.arc( path = path.arc(
self.center, self.center,
self.radii, self.radii,
(self.start + ((self.end - self.start) * ratio)) (self.start + ((self.end - self.start) * ratio))
@ -25,8 +26,7 @@ impl Lens<Path> for CircleAnimationLens {
.value(), .value(),
0., 0.,
); );
let line = path_builder.build(); *target = Shape::from(path);
*target = ShapePath::build_as(&line);
} }
} }

View File

@ -1,8 +1,5 @@
use bevy::prelude::Vec2; use bevy::prelude::Vec2;
use bevy_prototype_lyon::{ use bevy_prototype_lyon::{prelude::{Shape, ShapePath}, shapes};
prelude::{Path, ShapePath},
shapes,
};
use bevy_tweening::Lens; use bevy_tweening::Lens;
pub(crate) struct LineAnimationLens { pub(crate) struct LineAnimationLens {
@ -16,9 +13,9 @@ impl LineAnimationLens {
} }
} }
impl Lens<Path> for LineAnimationLens { impl Lens<Shape> for LineAnimationLens {
fn lerp(&mut self, target: &mut Path, ratio: f32) { fn lerp(&mut self, target: &mut Shape, ratio: f32) {
let line = shapes::Line(self.start, self.start + ((self.end - self.start) * ratio)); let line = shapes::Line(self.start, self.start + ((self.end - self.start) * ratio));
*target = ShapePath::build_as(&line); *target = Shape::from(ShapePath::build_as(&line));
} }
} }

View File

@ -0,0 +1,159 @@
use bevy::prelude::{Commands, Query, Quat, Transform, Vec2, With};
use crate::{
commands::{DrawElement, MoveCommand, OrientationCommand, TurtleCommands, TurtleSegment},
general::angle::Angle,
shapes::TurtleShape,
};
use super::line_segments::{TurtleDrawCircle, TurtleDrawLine};
/// Executes all turtle commands immediately without animation
pub fn run_all_commands_immediately(
commands: &mut Commands,
tcmd: &mut TurtleCommands,
turtle: &mut Query<&mut Transform, With<TurtleShape>>,
) {
if let Ok(mut turtle_transform) = turtle.single_mut() {
// Execute all commands
loop {
let idx = tcmd.animation_state();
if idx >= tcmd.commands().len() {
break;
}
let segment = tcmd.commands()[idx].clone();
match segment {
TurtleSegment::Single(element) => {
execute_draw_element(commands, &element, tcmd, &mut turtle_transform);
}
TurtleSegment::Outline(elements) => {
for element in elements {
execute_draw_element(commands, &element, tcmd, &mut turtle_transform);
}
}
TurtleSegment::Filled(elements) => {
for element in elements {
execute_draw_element(commands, &element, tcmd, &mut turtle_transform);
}
}
}
*tcmd.animation_state_mut() += 1;
}
}
}
fn execute_draw_element(
commands: &mut Commands,
element: &DrawElement,
tcmd: &mut TurtleCommands,
turtle_transform: &mut Transform,
) {
match element {
DrawElement::Draw(move_cmd) => {
match move_cmd {
MoveCommand::Forward(length) => {
let start = tcmd.state_mut().position();
let end = start + (Vec2::from_angle(tcmd.state_mut().heading().to_radians().value()) * length.0);
tcmd.state_mut().set_position(end);
// Update turtle position
turtle_transform.translation.x = end.x;
turtle_transform.translation.y = end.y;
// Draw line
let line = TurtleDrawLine::new(start, end);
commands.spawn(line);
}
MoveCommand::Backward(length) => {
let start = tcmd.state_mut().position();
let end = start + (Vec2::from_angle(tcmd.state_mut().heading().to_radians().value()) * -length.0);
tcmd.state_mut().set_position(end);
// Update turtle position
turtle_transform.translation.x = end.x;
turtle_transform.translation.y = end.y;
// Draw line
let line = TurtleDrawLine::new(start, end);
commands.spawn(line);
}
MoveCommand::Circle { radius, angle } => {
let start = tcmd.state_mut().position();
let radii = Vec2::ONE * radius.0.abs();
let left_right = Angle::degrees(if radius.0 >= 0. { 90. } else { -90. });
let heading = tcmd.state_mut().heading();
let center = start + (Vec2::new(radius.0.abs(), 0.).rotate(Vec2::from_angle(
((heading + left_right).to_radians()).value(),
)));
let end_heading = heading + if radius.0 > 0. { *angle } else { -*angle };
let end = center + Vec2::new(radius.0.abs(), 0.).rotate(Vec2::from_angle(
(heading + *angle - left_right).to_radians().value(),
));
tcmd.state_mut().set_position(end);
tcmd.state_mut().set_heading(end_heading);
// Update turtle position and rotation
turtle_transform.translation.x = end.x;
turtle_transform.translation.y = end.y;
turtle_transform.rotation = Quat::from_rotation_z(end_heading.to_radians().value());
// Draw circle arc
let circle = TurtleDrawCircle::new(center, radii, *angle, start, end);
commands.spawn(circle);
}
MoveCommand::Goto(_coord) => {
// TODO: implement goto
}
}
}
DrawElement::Move(move_cmd) => {
match move_cmd {
MoveCommand::Forward(length) => {
let new_pos = tcmd.state_mut().position() + (Vec2::from_angle(tcmd.state_mut().heading().to_radians().value()) * length.0);
tcmd.state_mut().set_position(new_pos);
turtle_transform.translation.x = new_pos.x;
turtle_transform.translation.y = new_pos.y;
}
MoveCommand::Backward(length) => {
let new_pos = tcmd.state_mut().position() + (Vec2::from_angle(tcmd.state_mut().heading().to_radians().value()) * -length.0);
tcmd.state_mut().set_position(new_pos);
turtle_transform.translation.x = new_pos.x;
turtle_transform.translation.y = new_pos.y;
}
MoveCommand::Circle { .. } => {
// TODO: implement move circle
}
MoveCommand::Goto(_coord) => {
// TODO: implement goto
}
}
}
DrawElement::Orient(orient_cmd) => {
match orient_cmd {
OrientationCommand::Left(angle) => {
let new_heading = tcmd.state_mut().heading() - *angle;
tcmd.state_mut().set_heading(new_heading);
turtle_transform.rotation = Quat::from_rotation_z(new_heading.to_radians().value());
}
OrientationCommand::Right(angle) => {
let new_heading = tcmd.state_mut().heading() + *angle;
tcmd.state_mut().set_heading(new_heading);
turtle_transform.rotation = Quat::from_rotation_z(new_heading.to_radians().value());
}
OrientationCommand::SetHeading => {
// TODO: implement set_heading
}
OrientationCommand::LookAt(_coord) => {
// TODO: implement look_at
}
}
}
DrawElement::Drip(_breadcrumb) => {
// TODO: implement breadcrumbs
}
}
}

View File

@ -1,19 +1,22 @@
use bevy::{ use bevy::{
prelude::{Bundle, Color, Component, Name, Transform, Vec2}, prelude::{Bundle, Color, Component, Name, Vec2},
reflect::{FromReflect, Reflect}, reflect::Reflect,
}; };
use bevy_prototype_lyon::{ use bevy_prototype_lyon::{
entity::ShapeBundle, draw::{Fill, Stroke},
prelude::{DrawMode, FillMode, GeometryBuilder, PathBuilder, StrokeMode}, entity::Shape,
geometry::ShapeBuilder,
path::ShapePath,
prelude::ShapeBuilderBase as _,
shapes::Line, shapes::Line,
}; };
use crate::general::{angle::Angle, Precision}; use crate::general::{angle::Angle, Precision};
#[derive(Bundle, Reflect, FromReflect, Default)] #[derive(Bundle, Reflect, Default)]
pub struct TurtleDrawLine { pub struct TurtleDrawLine {
#[reflect(ignore)] #[reflect(ignore)]
line: ShapeBundle, line: Shape,
name: Name, name: Name,
marker: LineMarker, marker: LineMarker,
} }
@ -27,31 +30,28 @@ impl std::fmt::Debug for TurtleDrawLine {
} }
} }
#[derive(Component, Default, Reflect, FromReflect, Debug, Clone, Copy)] #[derive(Component, Default, Reflect, Debug, Clone, Copy)]
struct LineMarker; struct LineMarker;
impl TurtleDrawLine { impl TurtleDrawLine {
pub(crate) fn new(start: Vec2, end: Vec2) -> Self { pub(crate) fn new(start: Vec2, end: Vec2) -> Self {
let line = Line(start, end);
Self { Self {
line: GeometryBuilder::build_as( line: ShapeBuilder::with(&line)
&Line(start, start), .fill(Fill::color(Color::NONE))
DrawMode::Outlined { .stroke(Stroke::new(Color::srgb(0.0, 0.0, 0.0), 1.0))
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE), .build(),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Transform::IDENTITY,
),
name: Name::new(format!("Line {}-{}", start, end)), name: Name::new(format!("Line {}-{}", start, end)),
marker: LineMarker, marker: LineMarker,
} }
} }
} }
#[derive(Bundle, Reflect, FromReflect, Default)] #[derive(Bundle, Reflect, Default)]
pub struct TurtleDrawCircle { pub struct TurtleDrawCircle {
#[reflect(ignore)] #[reflect(ignore)]
line: ShapeBundle, line: Shape,
name: Name, name: Name,
marker: CircleMarker, marker: CircleMarker,
} }
@ -65,7 +65,7 @@ impl std::fmt::Debug for TurtleDrawCircle {
} }
} }
#[derive(Component, Default, Reflect, FromReflect, Debug, Clone)] #[derive(Component, Default, Reflect, Debug, Clone)]
struct CircleMarker; struct CircleMarker;
impl TurtleDrawCircle { impl TurtleDrawCircle {
@ -76,25 +76,22 @@ impl TurtleDrawCircle {
start: Vec2, start: Vec2,
_end: Vec2, _end: Vec2,
) -> Self { ) -> Self {
let mut path_builder = PathBuilder::new();
path_builder.move_to(start);
// The center point of the radius - this is responsible for the orientation of the ellipse, // The center point of the radius - this is responsible for the orientation of the ellipse,
// then the radii in x and y direction - this can be rotated using the x_rotation parameter, // then the radii in x and y direction - this can be rotated using the x_rotation parameter,
// then the angle - the part of the circle that will be drawn like (PI/2.0) for a quarter circle, // then the angle - the part of the circle that will be drawn like (PI/2.0) for a quarter circle,
// then the x_rotation (maybe the rotation of the radii?) // then the x_rotation (maybe the rotation of the radii?)
path_builder.arc(center, radii, angle.to_radians().value(), 0.); let path =
let line = path_builder.build(); ShapePath::new()
.move_to(start)
.arc(center, radii, angle.to_radians().value(), 0.);
println!("Draw Circle: {} {} {:?}", center, radii, angle); println!("Draw Circle: {} {} {:?}", center, radii, angle);
Self { Self {
line: GeometryBuilder::build_as( line: ShapeBuilder::with(&path)
&line, .fill(Fill::color(Color::NONE))
DrawMode::Outlined { .stroke(Stroke::new(Color::srgb(0.0, 0.0, 0.0), 1.0))
fill_mode: FillMode::color(Color::rgba(0., 0., 0., 0.)), .build(),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Transform::IDENTITY,
),
name: Name::new(format!("Circle at {}, {}", center.x, center.y)), name: Name::new(format!("Circle at {}, {}", center.x, center.y)),
marker: CircleMarker, marker: CircleMarker,
} }

View File

@ -1,3 +1,4 @@
use bevy::prelude::Entity; use bevy::prelude::{Entity, Message};
#[derive(Message)]
pub struct DrawingStartedEvent(pub Entity); pub struct DrawingStartedEvent(pub Entity);

View File

@ -3,28 +3,28 @@ use std::{
ops::{Add, Div, Mul, Neg, Rem, Sub}, ops::{Add, Div, Mul, Neg, Rem, Sub},
}; };
use bevy::reflect::{FromReflect, Reflect}; use bevy::reflect::Reflect;
use super::Precision; use super::Precision;
#[derive(Reflect, FromReflect, Copy, Clone, Debug, PartialEq, Eq)] #[derive(Reflect, Copy, Clone, Debug, PartialEq, Eq)]
pub enum AngleUnit<T: Default + Send + Sync + Reflect + Copy + FromReflect> { pub enum AngleUnit<T: Default + Send + Sync + Reflect + Copy> {
Degrees(T), Degrees(T),
Radians(T), Radians(T),
} }
impl<T: Default + Send + Sync + Reflect + Copy + FromReflect> Default for AngleUnit<T> { impl<T: Default + Send + Sync + Reflect + Copy> Default for AngleUnit<T> {
fn default() -> Self { fn default() -> Self {
Self::Degrees(Default::default()) Self::Degrees(Default::default())
} }
} }
#[derive(Reflect, FromReflect, Copy, Default, Clone, Debug, PartialEq, Eq)] #[derive(Reflect, Copy, Default, Clone, Debug, PartialEq, Eq)]
pub struct Angle<T: Default + Send + Sync + Reflect + Copy + FromReflect> { pub struct Angle<T: Default + Send + Sync + Reflect + Copy> {
value: AngleUnit<T>, value: AngleUnit<T>,
} }
impl<T: From<i16> + Default + Send + Sync + Reflect + Copy + FromReflect> From<i16> for Angle<T> { impl<T: From<i16> + Default + Send + Sync + Reflect + Copy> From<i16> for Angle<T> {
fn from(i: i16) -> Self { fn from(i: i16) -> Self {
Self { Self {
value: AngleUnit::Degrees(T::from(i)), value: AngleUnit::Degrees(T::from(i)),
@ -32,9 +32,7 @@ impl<T: From<i16> + Default + Send + Sync + Reflect + Copy + FromReflect> From<i
} }
} }
impl<T: Default + Send + Sync + Reflect + Clone + Copy + FromReflect + Rem<T, Output = T>> Rem<T> impl<T: Default + Send + Sync + Reflect + Clone + Copy + Rem<T, Output = T>> Rem<T> for Angle<T> {
for Angle<T>
{
type Output = Self; type Output = Self;
fn rem(self, rhs: T) -> Self::Output { fn rem(self, rhs: T) -> Self::Output {
@ -45,9 +43,7 @@ impl<T: Default + Send + Sync + Reflect + Clone + Copy + FromReflect + Rem<T, Ou
} }
} }
impl<T: Default + Clone + Send + Sync + Reflect + Copy + FromReflect + Mul<T, Output = T>> Mul<T> impl<T: Default + Clone + Send + Sync + Reflect + Copy + Mul<T, Output = T>> Mul<T> for Angle<T> {
for Angle<T>
{
type Output = Self; type Output = Self;
fn mul(self, rhs: T) -> Self::Output { fn mul(self, rhs: T) -> Self::Output {
@ -70,9 +66,7 @@ impl Angle<Precision> {
} }
} }
} }
impl<T: Default + Clone + Send + Sync + Reflect + Copy + FromReflect + Div<T, Output = T>> Div<T> impl<T: Default + Clone + Send + Sync + Reflect + Copy + Div<T, Output = T>> Div<T> for Angle<T> {
for Angle<T>
{
type Output = Self; type Output = Self;
fn div(self, rhs: T) -> Self::Output { fn div(self, rhs: T) -> Self::Output {
@ -83,9 +77,8 @@ impl<T: Default + Clone + Send + Sync + Reflect + Copy + FromReflect + Div<T, Ou
} }
} }
impl< impl<T: Default + Clone + Send + Sync + Reflect + Copy + std::ops::Neg<Output = T>> Neg
T: Default + Clone + Send + Sync + Reflect + Copy + FromReflect + std::ops::Neg<Output = T>, for Angle<T>
> Neg for Angle<T>
{ {
type Output = Self; type Output = Self;
@ -97,9 +90,8 @@ impl<
} }
} }
impl< impl<T: Default + Clone + Send + Sync + Reflect + Copy + std::ops::Neg<Output = T>> Neg
T: Default + Clone + Send + Sync + Reflect + Copy + FromReflect + std::ops::Neg<Output = T>, for &Angle<T>
> Neg for &Angle<T>
{ {
type Output = Angle<T>; type Output = Angle<T>;
@ -111,7 +103,7 @@ impl<
} }
} }
impl<T: Default + Clone + Send + Sync + Reflect + Copy + FromReflect> Angle<T> { impl<T: Default + Clone + Send + Sync + Reflect + Copy> Angle<T> {
pub fn degrees(value: T) -> Angle<T> { pub fn degrees(value: T) -> Angle<T> {
Self { Self {
value: AngleUnit::Degrees(value), value: AngleUnit::Degrees(value),
@ -130,7 +122,7 @@ impl<T: Default + Clone + Send + Sync + Reflect + Copy + FromReflect> Angle<T> {
} }
} }
impl<T: Default + Send + Sync + Reflect + Copy + FromReflect + num_traits::float::Float> Angle<T> { impl<T: Default + Send + Sync + Reflect + Copy + num_traits::float::Float> Angle<T> {
pub fn to_radians(self) -> Self { pub fn to_radians(self) -> Self {
match self.value { match self.value {
AngleUnit::Degrees(v) => Self { AngleUnit::Degrees(v) => Self {
@ -149,16 +141,8 @@ impl<T: Default + Send + Sync + Reflect + Copy + FromReflect + num_traits::float
} }
} }
impl< impl<T: Add<Output = T> + Send + Sync + Reflect + Copy + Default + num_traits::float::Float> Add
T: Add<Output = T> for Angle<T>
+ Send
+ Sync
+ Reflect
+ Copy
+ FromReflect
+ Default
+ num_traits::float::Float,
> Add for Angle<T>
{ {
type Output = Angle<T>; type Output = Angle<T>;
@ -180,16 +164,8 @@ impl<
} }
} }
impl< impl<T: Sub<Output = T> + Default + Send + Sync + Reflect + Copy + num_traits::float::Float> Sub
T: Sub<Output = T> for Angle<T>
+ Default
+ Send
+ Sync
+ Reflect
+ Copy
+ FromReflect
+ num_traits::float::Float,
> Sub for Angle<T>
{ {
type Output = Angle<T>; type Output = Angle<T>;

View File

@ -1,8 +1,8 @@
use bevy::reflect::{FromReflect, Reflect}; use bevy::reflect::Reflect;
use super::Precision; use super::Precision;
#[derive(Reflect, FromReflect, Default, Copy, Clone, Debug)] #[derive(Reflect, Default, Copy, Clone, Debug)]
pub struct Length(pub Precision); pub struct Length(pub Precision);
impl From<i16> for Length { impl From<i16> for Length {

View File

@ -1,10 +1,11 @@
use std::time::Duration; use bevy::{prelude::*, window::WindowResolution};
#[cfg(feature = "tweening")]
use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::close_on_esc}; use bevy_prototype_lyon::entity::Shape;
use bevy_prototype_lyon::prelude::{Path, ShapePlugin}; use bevy_prototype_lyon::prelude::ShapePlugin;
#[cfg(feature = "tweening")]
use bevy_tweening::{ use bevy_tweening::{
component_animator_system, lens::TransformScaleLens, Animator, EaseFunction, Tween, component_animator_system, lens::TransformScaleLens, Animator, EaseFunction, Tween,
TweenCompleted, TweeningPlugin, TweenCompleted,
}; };
use events::DrawingStartedEvent; use events::DrawingStartedEvent;
use shapes::{TurtleColors, TurtleShape}; use shapes::{TurtleColors, TurtleShape};
@ -38,39 +39,42 @@ pub struct TurtlePlugin;
impl Plugin for TurtlePlugin { impl Plugin for TurtlePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(DefaultPlugins.set(WindowPlugin { app.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor { primary_window: Some(Window {
title: "Immigration Game".to_string(), title: "Immigration Game".to_string(),
width: 1200., resolution: WindowResolution::new(1200, 800),
height: 800., ..default()
present_mode: bevy::window::PresentMode::Fifo, // vsync }),
..Default::default()
},
..default() ..default()
})) }))
.add_plugin(debug::DebugPlugin) .add_plugins(debug::DebugPlugin)
.add_plugin(ShapePlugin) .add_plugins(ShapePlugin)
.add_plugin(TweeningPlugin) .add_message::<DrawingStartedEvent>()
.add_event::<DrawingStartedEvent>() .add_systems(Startup, setup)
.add_startup_system(setup) .add_systems(
.add_system(keypresses) Update,
.add_system(component_animator_system::<Path>) (
.add_system(close_on_esc) keypresses,
.add_system(draw_lines) #[cfg(feature = "tweening")]
component_animator_system::<Transform>,
#[cfg(feature = "tweening")]
component_animator_system::<Shape>,
#[cfg(feature = "tweening")]
draw_lines,
#[cfg(not(feature = "tweening"))]
draw_immediate,
),
)
.register_type::<TurtleColors>() .register_type::<TurtleColors>()
.register_type::<TurtleCommands>(); .register_type::<TurtleCommands>();
} }
} }
fn setup(mut commands: Commands) { fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle { commands.spawn((Camera2d,));
camera_2d: Camera2d {
clear_color: ClearColorConfig::Custom(Color::BEIGE),
},
..default()
});
} }
pub fn get_a_turtle() -> AnimatedTurtle { pub fn get_a_turtle() -> AnimatedTurtle {
#[cfg(feature = "tweening")]
let animator = Animator::new(Tween::new( let animator = Animator::new(Tween::new(
EaseFunction::QuadraticInOut, EaseFunction::QuadraticInOut,
Duration::from_millis(3000), Duration::from_millis(3000),
@ -79,6 +83,8 @@ pub fn get_a_turtle() -> AnimatedTurtle {
end: Vec3::ONE * 1.3, end: Vec3::ONE * 1.3,
}, },
)); ));
#[cfg(not(feature = "tweening"))]
let animator = ();
let turtle_bundle = TurtleBundle::default(); let turtle_bundle = TurtleBundle::default();
AnimatedTurtle { AnimatedTurtle {
animator, animator,
@ -89,26 +95,33 @@ pub fn get_a_turtle() -> AnimatedTurtle {
fn keypresses( fn keypresses(
mut commands: Commands, mut commands: Commands,
keys: Res<Input<KeyCode>>, _keys: Res<ButtonInput<KeyCode>>,
mut tcmd: Query<(Entity, &mut TurtleCommands)>, mut tcmd: Query<(Entity, &mut TurtleCommands)>,
mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>, #[cfg(feature = "tweening")] mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>,
mut ev_start: EventWriter<DrawingStartedEvent>, #[cfg(not(feature = "tweening"))] mut turtle: Query<&mut Transform, With<TurtleShape>>,
mut _ev_start: MessageWriter<DrawingStartedEvent>,
) { ) {
if keys.just_pressed(KeyCode::W) { if _keys.just_pressed(KeyCode::KeyW) {
for (entity, mut tcmd) in tcmd.iter_mut() { for (entity, mut tcmd) in tcmd.iter_mut() {
#[cfg(feature = "tweening")]
crate::drawing::run_step::run_animation_step(&mut commands, &mut tcmd, &mut turtle); crate::drawing::run_step::run_animation_step(&mut commands, &mut tcmd, &mut turtle);
ev_start.send(DrawingStartedEvent(entity))
#[cfg(not(feature = "tweening"))]
crate::drawing::immediate::run_all_commands_immediately(&mut commands, &mut tcmd, &mut turtle);
_ev_start.write(DrawingStartedEvent(entity));
} }
} }
} }
#[cfg(feature = "tweening")]
fn draw_lines( fn draw_lines(
mut commands: Commands, mut commands: Commands,
mut tcmd: Query<&mut TurtleCommands>, mut tcmd: Query<&mut TurtleCommands>,
mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>, mut turtle: Query<&mut Animator<Transform>, With<TurtleShape>>,
mut query_event: EventReader<TweenCompleted>, // TODO: howto attach only to the right event? mut query_event: EventReader<TweenCompleted>, // TODO: howto attach only to the right event?
) { ) {
for _ev in query_event.iter() { for _ev in query_event.read() {
if let Ok(mut tcmd) = tcmd.get_single_mut() { if let Ok(mut tcmd) = tcmd.get_single_mut() {
crate::drawing::run_step::run_animation_step(&mut commands, &mut tcmd, &mut turtle) crate::drawing::run_step::run_animation_step(&mut commands, &mut tcmd, &mut turtle)
} else { } else {
@ -116,3 +129,17 @@ fn draw_lines(
} }
} }
} }
#[cfg(not(feature = "tweening"))]
fn draw_immediate(
mut commands: Commands,
mut tcmd: Query<&mut TurtleCommands>,
mut turtle: Query<&mut Transform, With<TurtleShape>>,
) {
for mut tcmd in tcmd.iter_mut() {
// Only draw if there are commands to execute
if tcmd.animation_state() < tcmd.commands().len() {
crate::drawing::immediate::run_all_commands_immediately(&mut commands, &mut tcmd, &mut turtle);
}
}
}

View File

@ -1,11 +1,11 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use bevy::prelude::Vec2; use bevy::prelude::Vec2;
use bevy_prototype_lyon::prelude::{Path, PathBuilder}; use bevy_prototype_lyon::prelude::ShapePath;
use crate::general::Precision; use crate::general::Precision;
pub fn turtle() -> Path { pub fn turtle() -> ShapePath {
let polygon: &[[Precision; 2]; 23] = &[ let polygon: &[[Precision; 2]; 23] = &[
[-2.5, 14.0], [-2.5, 14.0],
[-1.25, 10.0], [-1.25, 10.0],
@ -31,16 +31,16 @@ pub fn turtle() -> Path {
[1.25, 10.0], [1.25, 10.0],
[2.5, 14.0], [2.5, 14.0],
]; ];
let mut turtle_path = PathBuilder::new(); let mut turtle_path = ShapePath::new()
turtle_path.line_to(Vec2::new(1.0, 1.0)); .line_to(Vec2::new(1.0, 1.0))
turtle_path.line_to(Vec2::new(-1.0, 1.0)); .line_to(Vec2::new(-1.0, 1.0))
turtle_path.line_to(Vec2::new(-1.0, -1.0)); .line_to(Vec2::new(-1.0, -1.0))
turtle_path.line_to(Vec2::new(1.0, -1.0)); .line_to(Vec2::new(1.0, -1.0))
turtle_path.close(); .close()
turtle_path.move_to(Vec2::new(0.0, 16.0).rotate(Vec2::from_angle(-PI / 2.))); .move_to(Vec2::new(0.0, 16.0).rotate(Vec2::from_angle(-PI / 2.)));
for coord in polygon { for coord in polygon {
turtle_path.line_to(Vec2::from_array(*coord).rotate(Vec2::from_angle(-PI / 2.))); turtle_path = turtle_path.line_to(Vec2::from_array(*coord).rotate(Vec2::from_angle(-PI / 2.)));
} }
turtle_path.close(); turtle_path.close()
turtle_path.build()
} }

View File

@ -1,9 +1,11 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use bevy::prelude::{Bundle, Color, Name, Transform}; use bevy::prelude::{Bundle, Color, Name};
use bevy_prototype_lyon::{ use bevy_prototype_lyon::{
entity::ShapeBundle, draw::{Fill, Stroke},
prelude::{DrawMode, FillMode, GeometryBuilder, StrokeMode}, entity::Shape,
geometry::ShapeBuilder,
prelude::ShapeBuilderBase as _,
}; };
use crate::{ use crate::{
@ -21,7 +23,7 @@ pub struct TurtleBundle {
colors: TurtleColors, colors: TurtleColors,
pub commands: TurtleCommands, pub commands: TurtleCommands,
name: Name, name: Name,
shape: ShapeBundle, shape: Shape,
} }
impl Default for TurtleBundle { impl Default for TurtleBundle {
@ -30,14 +32,10 @@ impl Default for TurtleBundle {
colors: TurtleColors::default(), colors: TurtleColors::default(),
commands: TurtleCommands::new(vec![]), commands: TurtleCommands::new(vec![]),
name: Name::new("Turtle"), name: Name::new("Turtle"),
shape: GeometryBuilder::build_as( shape: ShapeBuilder::with(&shapes::turtle())
&shapes::turtle(), .fill(Fill::color(Color::srgb(0.098, 0.098, 0.439)))
DrawMode::Outlined { .stroke(Stroke::new(Color::srgb(0.0, 0.0, 0.0), 1.0))
fill_mode: FillMode::color(Color::MIDNIGHT_BLUE), .build(),
outline_mode: StrokeMode::new(Color::BLACK, 1.0),
},
Transform::IDENTITY,
),
} }
} }
} }
@ -62,7 +60,10 @@ impl TurtleBundle {
#[derive(Bundle)] #[derive(Bundle)]
pub struct AnimatedTurtle { pub struct AnimatedTurtle {
#[cfg(feature = "tweening")]
pub animator: bevy_tweening::Animator<bevy::prelude::Transform>, pub animator: bevy_tweening::Animator<bevy::prelude::Transform>,
#[cfg(not(feature = "tweening"))]
pub animator: (),
pub turtle_bundle: TurtleBundle, pub turtle_bundle: TurtleBundle,
pub turtle_shape: shapes::TurtleShape, pub turtle_shape: shapes::TurtleShape,
} }