mirror of
https://github.com/bevyengine/bevy
synced 2024-11-23 05:03:47 +00:00
212 lines
10 KiB
Markdown
212 lines
10 KiB
Markdown
|
# 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)
|