transform: remove some dependencies and cleanup

This commit is contained in:
Carter Anderson 2020-07-10 01:49:23 -07:00
parent c81ab99dac
commit 3b68c7cc4a
11 changed files with 14 additions and 247 deletions

View file

@ -17,6 +17,7 @@ where
let children = match children_query.get::<Children>(entity) {
Ok(children) => Some(
children
.0
.iter()
.map(|entity| *entity)
.collect::<Vec<Entity>>(),

View file

@ -1,8 +0,0 @@
language: rust
rust:
- stable
- nightly
matrix:
allow_failures:
- rust: nightly
fast_finish: true

View file

@ -1,7 +1,7 @@
[package]
name = "bevy_transform"
version = "0.3.0"
authors = ["Alec Thilenius <alec@thilenius.com>"]
version = "0.1.0"
authors = ["Carter Anderson <mcanders1@gmail.com>", "Alec Thilenius <alec@thilenius.com>"]
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"

View file

@ -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 <this> 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<f32>` 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)

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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<Entity>);

View file

@ -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)]

View file

@ -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<f32> for Scale {

View file

@ -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 {