diff --git a/crates/bevy_core/src/transform/hierarchy.rs b/crates/bevy_core/src/transform/hierarchy.rs index cfac77c525..41167d0f75 100644 --- a/crates/bevy_core/src/transform/hierarchy.rs +++ b/crates/bevy_core/src/transform/hierarchy.rs @@ -17,6 +17,7 @@ where let children = match children_query.get::(entity) { Ok(children) => Some( children + .0 .iter() .map(|entity| *entity) .collect::>(), diff --git a/crates/bevy_transform/.travis.yml b/crates/bevy_transform/.travis.yml deleted file mode 100644 index 198fc4dc1f..0000000000 --- a/crates/bevy_transform/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: rust -rust: - - stable - - nightly -matrix: - allow_failures: - - rust: nightly - fast_finish: true diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index 83dfe5988f..c56213fd78 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_transform" -version = "0.3.0" -authors = ["Alec Thilenius "] +version = "0.1.0" +authors = ["Carter Anderson ", "Alec Thilenius "] edition = "2018" license = "MIT" @@ -11,10 +11,7 @@ bevy_ecs = { path = "../bevy_ecs"} bevy_property = { path = "../bevy_property" } glam = "0.8.7" log = "0.4" -rayon = "1.2" -serde = { version = "1", features = ["derive"] } smallvec = { version = "1.4", features = ["serde"] } -shrinkwraprs = "0.2" [dev-dependencies] env_logger = "0.7" diff --git a/crates/bevy_transform/README.md b/crates/bevy_transform/README.md index 29ea8e0ac0..9aa75a175e 100644 --- a/crates/bevy_transform/README.md +++ b/crates/bevy_transform/README.md @@ -1,211 +1,3 @@ -# Hierarchical Legion Transform - -[![Build Status][build_img]][build_lnk] - -[build_img]: https://travis-ci.org/AThilenius/legion_transform.svg?branch=master -[build_lnk]: https://travis-ci.org/AThilenius/legion_transform - -A hierarchical space transform system, implemented using [Legion -ECS](https://github.com/TomGillen/legion). The implementation is based heavily -on the new Unity ECS Transformation layout. - -## Usage - -### TL;DR - Just show me the secret codes and incantations! - -See [examples/hierarchy.rs](examples/hierarchy.rs) - -```rust -#[allow(unused)] -fn tldr_sample() { - // Create a normal Legion World - let mut world = Universe::default().create_world(); - - // Create a system bundle (vec of systems) for LegionTransform - let transform_system_bundle = TransformSystemBundle::default().build(); - - let parent_entity = *world - .insert( - (), - vec![( - // Always needed for an Entity that has any space transform - LocalToWorld::identity(), - // The only mutable space transform a parent has is a translation. - Translation::new(100.0, 0.0, 0.0), - )], - ) - .first() - .unwrap(); - - world.insert( - (), - vec![ - ( - // Again, always need a `LocalToWorld` component for the Entity to have a custom - // space transform. - LocalToWorld::identity(), - // Here we define a Translation, Rotation and uniform Scale. - Translation::new(1.0, 2.0, 3.0), - Rotation::from_euler_angles(3.14, 0.0, 0.0), - Scale(2.0), - // Add a Parent and LocalToParent component to attach a child to a parent. - Parent(parent_entity), - LocalToParent::identity(), - ); - 4 - ], - ); -} -``` - -See [examples](/examples) for both transform and hierarchy examples. - -### Transform Overview - -The Transform and Hierarchy parts of Legion Transform are largely separate and -can thus be explained independently. We will start with space transforms, so for -now completely put hierarchies out of mind (all entities have space transforms -directly from their space to world space). - -A 3D space transform can come in many forms. The most generic of these is a -matrix 4x4 which can represent any arbitrary (linear) space transform, including -projections and sheers. These are not rarely useful for entity transformations -though, which are normally defined by things like - -- A **Translation** - movement along the X, Y or Z axis. -- A **Rotation** - 3D rotation encoded as a Unit Quaternion to prevent [gimbal - lock](https://en.wikipedia.org/wiki/Gimbal_lock). -- A **Scale** - Defined as a single floating point values, but often - **incorrectly defined as a Vector3** (which is a `NonUniformScale`) in other - engines and 3D applications. -- A **NonUniformScale** - Defined as a scale for the X, Y and Z axis - independently from each other. - -In fact, in Legion Transform, each of the above is it's own `Component` type. -These components can be added in any combination to an `Entity` with the only -exception being that `Scale` and `NonUniformScale` are mutually exclusive. - -Higher-order transformations can be built out of combinations of these -components, for example: - -- Isometry: `Translation` + `Rotation` -- Similarity: `Translation` + `Rotation` + `Scale` -- Affine: `Translation` + `Rotation` + `NonUniformScale` - -The combination of these components will be processed (when they change) by the -`LocalToWorldSystem` which will produce a correct `LocalToWorld` based on the -attached transformations. This `LocalToWorld` is a homogeneous matrix4x4 -computed as: `(Translation * (Rotation * (Scale | NonUniformScale)))`. - -Breaking apart the transform into separate components means that you need only -pay the runtime cost of computing the actual transform you need per-entity. -Further, having `LocalToWorld` be a separate component means that any static -entity (including those in static hierarchies) can be pre-baked into a -`LocalToWorld` component and the rest of the transform data need not be loaded -or stored in the final build of the game. - -In the event that the Entity is a member of a hierarchy, the `LocalToParent` -matrix will house the `(Translation * (Rotation * (Scale | NonUniformScale)))` -computation instead, and the `LocalToWorld` matrix will house the final local -space to world space transformation (after all it's parent transformations have -been computed). In other words, the `LocalToWorld` matrix is **always** the -transformation from an entities local space, directly into world space, -regardless of if the entity is a member of a hierarchy or not. - -### Why not just NonUniformScale always? - -NonUniformScale is somewhat evil. It has been used (and abused) in countless -game engines and 3D applications. A Transform with a non-uniform scale is known -as an `Affine Transform` and it cannot be applied to things like a sphere -collider in a physics engine without some serious gymnastics, loss of precision -and/or detrimental performance impacts. For this reason, you should always use a -uniform `Scale` component when possible. This component was named `Scale` over -something like "UniformScale" to imply it's status as the default scale -component and `NonUniformScale`'s status as a special case component. - -For more info on space transformations, see [nalgebra Points and -Transformations](https://www.nalgebra.org/points_and_transformations/). - -### Hierarchies - -Hierarchies in Legion Transform are defined in two parts. The first is the -_Source Of Truth_ for the hierarchy, it is always correct and always up-to-date: -the `Parent` Component. This is a component attached to children of a parent (ie -a child 'has a' `Parent`). Users can update this component directly, and because -it points toward the root of the hierarchy tree, it is impossible to form any -other type of graph apart from a tree. - -Each time the Legion Transform system bundle is run, the -`LocalToParentPropagateSystem` will also add/modify/remove a `Children` -component on any entity that has children (ie entities that have a `Parent` -component pointing to the parent entity). Because this component is only updated -during the system bundle run, **it can be out of date, incorrect or missing -altogether** after world mutations. - -It is important to note that as of today, any member of a hierarchy has it's -`LocalToWorld` matrix re-computed each system bundle run, regardless of -changes. This may someday change, but it is expected that the number of entities -in a dynamic hierarchy for a final game should be small (static hierarchies can -be pre-baked, where each entity gets a pre-baked `LocalToWorld` matrix). - -## This is no good 'tall, why didn't you do is way? - -The first implementation used Legion `Tags` to store the Parent component for -any child. This allowed for things like `O(1)` lookup of children, but was -deemed way too much fragmentation (Legion is an archetypical, chunked ECS). - -The second implementation was based on [this fine article by Michele -Caini](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) which structures -the hierarchy as explicit parent pointer, a pointer to the first (and only -first) child, and implicitly forms a linked-list of siblings. While elegant, the -actual implementation was both complicated an near-impossible to multi-thread. -For example, iterating through children entities required a global query to the -Legion `World` for each child. I decided a small amount of memory by storing a -possibly-out-of-date `SmallVec` of children was worth sacrificing on parent -entities to make code both simpler and faster (theoretically, I never tested -it). - -A lot of other options were considered as well, for example storing the entire -hierarchy out-of-band from the ECS (much like Amethyst pre-Legion does). This -has some pretty nasty drawbacks though. It makes streaming entities much harder, -it means that hierarchies need to be special-case serialized/deserialized with -initialization code being run on the newly deserialized entities. And it means -that the hierarchy does not conform to the rest of the ECS. It also means that -Legion, and all the various optimizations for querying / iterating large numbers -of entities, was going to be mostly unused and a lot of global queries would -need to be made against the `World` while syncing the `World` and out-of-band -data-structure. I felt very strongly against an out-of-band implementation -despite it being simpler to implement upfront. - -## Todo - -- [ ] Hierarchy maintenance - - [x] Remove changed `Parent` from `Children` list of the previous parent. - - [x] Add changed `Parent` to `Children` list of the new parent. - - [x] Update `PreviousParent` to the new Parent. - - [x] Handle Entities with removed `Parent` components. - - [x] Handle Entities with `Children` but without `LocalToWorld` (move their - children to non-hierarchical). - - [ ] Handle deleted Legion Entities (requires - [Legion #13](https://github.com/TomGillen/legion/issues/13)) -- [x] Local to world and parent transformation - - [x] Handle homogeneous `Matrix4` calculation for combinations of: - - [x] Translation - - [x] Rotation - - [x] Scale - - [x] NonUniformScale - - [x] Handle change detection and only recompute `LocalToWorld` when needed. - - [x] Multi-threaded updates for non-hierarchical `LocalToWorld` computation. - - [x] Recompute `LocalToParent` each run, always. -- [ ] Transform hierarchy propagation - - [x] Collect roots of the hierarchy forest - - [x] Recursively re-compute `LocalToWorld` from the `Parent`'s `LocalToWorld` - and the `LocalToParent` of each child. - - [ ] Multi-threaded updates for hierarchical `LocalToWorld` computation. - - [ ] Compute all changes and flush them to a `CommandBuffer` rather than - direct mutation of components. - -## Blockers - -- Legion has no ability to detect deleted entities or components. - [GitHub Issue #13](https://github.com/TomGillen/legion/issues/13) +# Bevy Transform + +This crate is largely a 1:1 port from [legion_transform](https://github.com/AThilenius/legion_transform) (ecs: legion, math: nalgebra) to bevy (ecs: bevy_ecs, math: glam) diff --git a/crates/bevy_transform/src/components/children.rs b/crates/bevy_transform/src/components/children.rs index 199ed29fb3..af0dc3c3dc 100644 --- a/crates/bevy_transform/src/components/children.rs +++ b/crates/bevy_transform/src/components/children.rs @@ -1,10 +1,8 @@ use bevy_ecs::Entity; use bevy_property::Properties; -use shrinkwraprs::Shrinkwrap; use smallvec::SmallVec; -#[derive(Shrinkwrap, Default, Clone, Properties, Debug)] -#[shrinkwrap(mutable)] +#[derive(Default, Clone, Properties, Debug)] pub struct Children(pub SmallVec<[Entity; 8]>); impl Children { diff --git a/crates/bevy_transform/src/components/local_transform.rs b/crates/bevy_transform/src/components/local_transform.rs index 7e69c6e76b..9619ec4d44 100644 --- a/crates/bevy_transform/src/components/local_transform.rs +++ b/crates/bevy_transform/src/components/local_transform.rs @@ -1,10 +1,8 @@ use crate::math::Mat4; use bevy_property::Properties; -use shrinkwraprs::Shrinkwrap; use std::fmt; -#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy, Properties)] -#[shrinkwrap(mutable)] +#[derive(Debug, PartialEq, Clone, Copy, Properties)] pub struct LocalTransform(pub Mat4); impl LocalTransform { diff --git a/crates/bevy_transform/src/components/non_uniform_scale.rs b/crates/bevy_transform/src/components/non_uniform_scale.rs index 42c0b0d911..9ce633394a 100644 --- a/crates/bevy_transform/src/components/non_uniform_scale.rs +++ b/crates/bevy_transform/src/components/non_uniform_scale.rs @@ -1,10 +1,8 @@ use crate::math::Vec3; use bevy_property::Properties; -use shrinkwraprs::Shrinkwrap; use std::fmt; -#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy, Properties)] -#[shrinkwrap(mutable)] +#[derive(Debug, PartialEq, Clone, Copy, Properties)] pub struct NonUniformScale(pub Vec3); impl NonUniformScale { diff --git a/crates/bevy_transform/src/components/parent.rs b/crates/bevy_transform/src/components/parent.rs index b19cb359a4..f408a93810 100644 --- a/crates/bevy_transform/src/components/parent.rs +++ b/crates/bevy_transform/src/components/parent.rs @@ -1,11 +1,8 @@ use bevy_ecs::Entity; use bevy_property::Properties; -use shrinkwraprs::Shrinkwrap; -#[derive(Shrinkwrap, Debug, Copy, Clone, Eq, PartialEq, Properties)] -#[shrinkwrap(mutable)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Properties)] pub struct Parent(pub Entity); -#[derive(Shrinkwrap, Debug, Copy, Clone, Eq, PartialEq)] -#[shrinkwrap(mutable)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct PreviousParent(pub Option); diff --git a/crates/bevy_transform/src/components/rotation.rs b/crates/bevy_transform/src/components/rotation.rs index 87dbc81faf..9c0245d086 100644 --- a/crates/bevy_transform/src/components/rotation.rs +++ b/crates/bevy_transform/src/components/rotation.rs @@ -1,9 +1,7 @@ use crate::math::Quat; use bevy_property::Properties; -use shrinkwraprs::Shrinkwrap; -#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy, Properties)] -#[shrinkwrap(mutable)] +#[derive(Debug, PartialEq, Clone, Copy, Properties)] pub struct Rotation(pub Quat); impl Rotation { #[inline(always)] diff --git a/crates/bevy_transform/src/components/scale.rs b/crates/bevy_transform/src/components/scale.rs index 81f81bca69..bc56aae7eb 100644 --- a/crates/bevy_transform/src/components/scale.rs +++ b/crates/bevy_transform/src/components/scale.rs @@ -1,9 +1,7 @@ use bevy_property::Properties; -use shrinkwraprs::Shrinkwrap; use std::fmt; -#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy, Properties)] -#[shrinkwrap(mutable)] +#[derive(Debug, PartialEq, Clone, Copy, Properties)] pub struct Scale(pub f32); impl From for Scale { diff --git a/crates/bevy_transform/src/components/translation.rs b/crates/bevy_transform/src/components/translation.rs index 82ef20451b..1c332873e6 100644 --- a/crates/bevy_transform/src/components/translation.rs +++ b/crates/bevy_transform/src/components/translation.rs @@ -1,9 +1,7 @@ use crate::math::Vec3; use bevy_property::Properties; -use shrinkwraprs::Shrinkwrap; -#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy, Properties)] -#[shrinkwrap(mutable)] +#[derive(Debug, PartialEq, Clone, Copy, Properties)] pub struct Translation(pub Vec3); impl Translation {