Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
33a9b29
Initial `motiongfx_scene`
nixonyh Jul 24, 2026
890315b
Add action resolvers keyed via `TypeId` & `OpRef`
nixonyh Jul 27, 2026
576b89d
Use typarena to simplify some type erasures
nixonyh Jul 27, 2026
47990e0
Collapse `field` maps in `SceneRegistry` into `TypeTable`
nixonyh Jul 27, 2026
5f1ab87
Doc improvements
nixonyh Jul 27, 2026
417cf8c
Generalize motiongfx_scene over a single SceneBackend type
nixonyh Jul 27, 2026
f26e18a
Enforce serialization trait bound for backend types
nixonyh Jul 27, 2026
41189f0
Add `Storable` trait
nixonyh Jul 27, 2026
2ebc008
Human readable `Duration` serialization
nixonyh Jul 28, 2026
b03d7e9
Drop bevy_math from motiongfx core, fixing no_std for motiongfx_scene
nixonyh Jul 28, 2026
bfba43e
Replace SceneBackend::Value with a per-backend ValuePool
nixonyh Jul 28, 2026
f4f0058
Implement the Bevy scene backend end to end
nixonyh Jul 29, 2026
65a66ed
Simplify field registry and add better guarantees
nixonyh Jul 29, 2026
07881d7
Add more helper functions for `SceneRegistryExt`
nixonyh Jul 29, 2026
ada9156
Add bundle registrations
nixonyh Jul 29, 2026
d62c2c6
Sync SceneUidMap via component lifecycle observers
nixonyh Jul 29, 2026
1f2447f
Support multiple subject id kinds via IntoSubjectId
nixonyh Jul 29, 2026
1ec01e6
Include serialization of the bevy scene alongside!
nixonyh Jul 30, 2026
098a577
Clippy fmt fixes
nixonyh Jul 30, 2026
a201b32
Remove `Delayed` variant from `Node` enum
nixonyh Jul 30, 2026
2feafe5
Replace `sparse_map` with `indexmap` & `hashbrown`
nixonyh Jul 31, 2026
8564f01
Fix stage to include all fields!
nixonyh Jul 31, 2026
f6f4158
Add `Scene::stage` to seed initial values
nixonyh Jul 31, 2026
513f171
Gate duplicate/corrupted `EntityUid`
nixonyh Jul 31, 2026
7d899ca
Update Cargo.toml
nixonyh Jul 31, 2026
74c72b5
Remove custom duration serialization
nixonyh Jul 31, 2026
044f8aa
Improve asset test
nixonyh Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ repository = "https://github.com/voxell-tech/motiongfx"

[workspace.dependencies]
# motiongfx
motiongfx = { version = "0.3.0", path = "crates/motiongfx" }
bevy_motiongfx = { version = "0.3.0", path = "crates/bevy_motiongfx" }
motiongfx = { version = "0.3.0", path = "crates/motiongfx", default-features = false }
motiongfx_scene = { version = "0.3.0", path = "crates/motiongfx_scene", default-features = false }
bevy_motiongfx = { version = "0.3.0", path = "crates/bevy_motiongfx", default-features = false }
peniko_motiongfx = { version = "0.3.0", path = "crates/peniko_motiongfx" }
motiongfx_editor = { version = "0.3.0", path = "editor/motiongfx_editor" }
motiongfx_editor_ui = { version = "0.3.0", path = "editor/motiongfx_editor_ui" }
Expand All @@ -25,15 +26,23 @@ bevy_platform = { version = "0.19", default-features = false }
bevy_derive = { version = "0.19", default-features = false }
bevy_math = { version = "0.19", default-features = false, features = [ "libm" ] }
bevy_asset = { version = "0.19", default-features = false }
bevy_reflect = { version = "0.19", default-features = false }
bevy_time = { version = "0.19", default-features = false }
bevy_transform = { version = "0.19", default-features = false, features = ["bevy-support"] }
bevy_color = { version = "0.19", default-features = false }

# other
field_path = "0.4.1"
typarena = { version = "0.1.0", features = ["send", "sync"] }
libm = "0.2"
nonempty = { version = "0.12", default-features = false }
hashbrown = { version = "0.17", default-features = false }
indexmap = { version = "2.14", default-features = false }
uuid = { version = "1.24", default-features = false }
tracing = { version = "0.1", default-features = false }
serde = { version = "1", default-features = false }
educe = { version = "0.7.4", default-features = false }
ron = { version = "0.12", default-features = false, features = ["std"] }
peniko = { version = "0.6", default-features = false }
vello = "0.9.0"
winit = "0.30"
Expand Down
28 changes: 27 additions & 1 deletion crates/bevy_motiongfx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repository.workspace = true
[dependencies]
# motiongfx
motiongfx = { workspace = true }
motiongfx_scene = { workspace = true, optional = true }

# bevy
bevy_ecs = { workspace = true }
Expand All @@ -24,6 +25,13 @@ bevy_time = { workspace = true }
bevy_asset = { workspace = true, optional = true }
bevy_transform = { workspace = true, optional = true }
bevy_color = { workspace = true, optional = true }
bevy_reflect = { workspace = true, optional = true, features = ["uuid"] }

# other
serde = { workspace = true, features = ["derive"] }
indexmap = { workspace = true, features = ["serde"], optional = true }
tracing = { workspace = true, optional = true }
ron = { workspace = true, optional = true }

[dev-dependencies]
bevy = { workspace = true, features = ["bevy_pbr"] }
Expand All @@ -33,7 +41,25 @@ workspace = true

[features]
default = ["std", "asset", "transform", "color"]
std = ["motiongfx/std", "bevy_ecs/std", "bevy_app/std", "bevy_platform/std", "bevy_time/std"]
std = [
"motiongfx/std",
"motiongfx_scene?/std",
"bevy_ecs/std",
"bevy_app/std",
"bevy_platform/std",
"bevy_time/std",
]
asset = ["dep:bevy_asset"]
transform = ["dep:bevy_transform"]
color = ["dep:bevy_color"]
scene = [
"dep:motiongfx_scene",
"dep:indexmap",
"dep:tracing",
"dep:ron",
"dep:bevy_reflect",
"bevy_math/serialize",
"bevy_transform/bevy_reflect",
"asset",
"transform",
]
3 changes: 2 additions & 1 deletion crates/bevy_motiongfx/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use core::time::Duration;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_time::prelude::*;
use motiongfx::prelude::*;

use crate::MotionGfxSystems;
use crate::manager::{MotionGfxManager, TimelineId};
Expand Down Expand Up @@ -198,7 +199,7 @@ impl FixedRatePlayer {
return Duration::ZERO;
}

Duration::from_secs(self.curr_frame) / self.fps as u32
s(self.curr_frame) / self.fps as u32
}

/// Setter method for setting [`Self::is_playing`].
Expand Down
13 changes: 13 additions & 0 deletions crates/bevy_motiongfx/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
#![doc = include_str!("../README.md")]
#![no_std]

extern crate alloc;

use bevy_app::prelude::*;
use bevy_ecs::prelude::*;

use crate::controller::ControllerPlugin;
use crate::manager::MotionGfxManagerPlugin;
#[cfg(feature = "scene")]
use crate::scene::MotionGfxScenePlugin;

pub mod controller;
pub mod interpolation;
pub mod manager;
pub mod world;

#[cfg(feature = "scene")]
pub mod scene;

pub mod prelude {
pub use motiongfx::prelude::*;

pub use crate::controller::{FixedRatePlayer, RealtimePlayer};
pub use crate::manager::{MotionGfxManager, TimelineId};
pub use crate::world::{BevyTimeline, BevyTimelineBuilder};

#[cfg(feature = "scene")]
pub use crate::scene::id::EntityUid;
}

pub use motiongfx;
Expand All @@ -40,6 +50,9 @@ impl Plugin for BevyMotionGfxPlugin {
.chain(),
);
app.add_plugins((MotionGfxManagerPlugin, ControllerPlugin));

#[cfg(feature = "scene")]
app.add_plugins(MotionGfxScenePlugin);
}
}

Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_motiongfx/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ impl MotionGfxManager {
TimelineBuilder::new(&mut self.registry)
}

/// The runtime [`Registry`] backing this manager's timelines.
#[cfg(feature = "scene")]
pub(crate) fn registry_mut(&mut self) -> &mut Registry {
&mut self.registry
}

pub fn add_timeline(
&mut self,
timeline: BevyTimeline,
Expand Down
28 changes: 28 additions & 0 deletions crates/bevy_motiongfx/src/scene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Serializable scenes for Bevy: a [`SceneBackend`](motiongfx_scene::backend::SceneBackend)
//! implementation, plus loading one as a Bevy [`Asset`](bevy_asset::Asset).
//! Gated behind the `scene` feature.

use asset::{MotionGfxScene, SceneAssetLoader};
use bevy_app::prelude::*;
use bevy_asset::AssetApp as _;
use id::{
EntityUid, SceneUidMap, on_add_entity_uid, on_remove_entity_uid,
};

pub mod asset;
pub mod backend;
pub mod id;
pub mod value_pool;

pub struct MotionGfxScenePlugin;

impl Plugin for MotionGfxScenePlugin {
fn build(&self, app: &mut App) {
app.register_type::<EntityUid>()
.init_resource::<SceneUidMap>()
.init_asset::<MotionGfxScene>()
.register_asset_loader(SceneAssetLoader)
.add_observer(on_add_entity_uid)
.add_observer(on_remove_entity_uid);
}
}
85 changes: 85 additions & 0 deletions crates/bevy_motiongfx/src/scene/asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Loads a [`Scene`] as a Bevy [`Asset`] from a `.mgx.ron` file.

use alloc::vec::Vec;

use bevy_asset::io::Reader;
use bevy_asset::{Asset, AssetLoader, LoadContext};
use bevy_ecs::error::BevyError;
use bevy_ecs::world::World;
use bevy_reflect::TypePath;
use motiongfx_scene::error::CompileError;
use motiongfx_scene::scene::Scene;

use crate::manager::{MotionGfxManager, TimelineId};
use crate::scene::backend::{Backend, BackendRegistry};
use crate::world::BevyWorld;

/// A [`Scene<BevyBackend>`], loadable as a Bevy asset.
///
/// A newtype rather than a blanket impl on `Scene` itself: `Asset`/
/// `TypePath` are foreign traits and `Scene` is a foreign type from
/// this crate's perspective, so the orphan rule requires a local
/// wrapper.
#[derive(Asset, TypePath)]
pub struct MotionGfxScene(pub Scene<Backend>);

impl MotionGfxScene {
/// Compiles this scene into a `BevyTimeline` through
/// `scene_registry`, then registers it on `motiongfx` exactly
/// like [`MotionGfxManager::add_timeline`]. This scene's subjects
/// must already be materialized - however the app chooses to do
/// that - so their [`EntityUid`](crate::scene::id::EntityUid)s
/// resolve through the world's
/// [`SceneUidMap`](crate::scene::id::SceneUidMap).
pub fn compile(
&self,
scene_registry: &BackendRegistry,
motiongfx: &mut MotionGfxManager,
) -> Result<TimelineId, CompileError<Backend>> {
let timeline = self
.0
.compile(scene_registry, motiongfx.registry_mut())?;
Ok(motiongfx.add_timeline(timeline))
}

/// Writes this scene's initial values onto its already-materialized
/// subjects.
///
/// Baking reads each track's starting value off the world, and that
/// happens in [`MotionGfxSystems::Sample`](crate::MotionGfxSystems::Sample),
/// so call this any time before then - alongside [`Self::compile`]
/// is the natural place. Skip it and the animation starts from
/// whatever the entities already held.
pub fn stage(
&self,
scene_registry: &BackendRegistry,
world: &mut World,
) -> Result<(), CompileError<Backend>> {
self.0.stage(scene_registry, BevyWorld::from_mut(world))
}
}

#[derive(Default, TypePath)]
pub struct SceneAssetLoader;

impl AssetLoader for SceneAssetLoader {
type Asset = MotionGfxScene;
type Settings = ();
type Error = BevyError;

async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
_load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let scene = ron::de::from_bytes(&bytes)?;
Ok(MotionGfxScene(scene))
}

fn extensions(&self) -> &[&str] {
&["mgx.ron"]
}
}
Loading
Loading