mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 05:33:04 +00:00
asset loading, hierarchies, more refactoring
This commit is contained in:
parent
188d355d10
commit
a96f3cfda5
38 changed files with 2195 additions and 240 deletions
|
@ -5,7 +5,8 @@ authors = ["Carter Anderson <mcanders1@gmail.com>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
legion = { git = "https://github.com/TomGillen/legion.git" }
|
||||
legion = { git = "https://github.com/TomGillen/legion.git", rev = "8628b227bcbe57582fffb5e80e73c634ec4eebd9" }
|
||||
legion_transform = { path = "src/legion_transform" }
|
||||
nalgebra-glm = "0.5.0"
|
||||
wgpu = { git = "https://github.com/gfx-rs/wgpu-rs.git", rev = "44fa1bc2fa208fa92f80944253e0da56cb7ac1fe"}
|
||||
winit = "0.20.0-alpha4"
|
||||
|
|
|
@ -1,18 +1,9 @@
|
|||
use bevy::{Application, Transform};
|
||||
use bevy::{Application};
|
||||
use legion::prelude::*;
|
||||
|
||||
fn main() {
|
||||
Application::run();
|
||||
// Create a world to store our entities
|
||||
let universe = Universe::new();
|
||||
let mut world = universe.create_world();
|
||||
world.insert((), vec![(Transform::new(),)]);
|
||||
|
||||
// Create a query which finds all `Position` and `Velocity` components
|
||||
let mut query = Read::<Transform>::query();
|
||||
|
||||
// // Iterate through all entities that match the query in the world
|
||||
for _ in query.iter(&mut world) {
|
||||
// println!("{} hi", trans.global);
|
||||
}
|
||||
Application::run(universe, world);
|
||||
}
|
||||
|
|
|
@ -10,12 +10,13 @@ use legion::prelude::*;
|
|||
use std::sync::Arc;
|
||||
use std::mem;
|
||||
|
||||
use crate::{temp::*, vertex::*, render::*, math, Transform};
|
||||
use crate::{temp::*, vertex::*, render::*, math, LocalToWorld, Translation, ApplicationStage};
|
||||
|
||||
pub struct Application
|
||||
{
|
||||
pub universe: Universe,
|
||||
pub world: World,
|
||||
pub scheduler: SystemScheduler<ApplicationStage>,
|
||||
pub shadow_pass: ShadowPass,
|
||||
pub forward_pass: ForwardPass,
|
||||
camera_position: math::Vec3,
|
||||
|
@ -26,35 +27,13 @@ impl Application {
|
|||
pub const MAX_LIGHTS: usize = 10;
|
||||
|
||||
fn init(
|
||||
universe: Universe,
|
||||
mut world: World,
|
||||
sc_desc: &wgpu::SwapChainDescriptor,
|
||||
device: &wgpu::Device,
|
||||
) -> (Self, Option<wgpu::CommandBuffer>)
|
||||
{
|
||||
let universe = Universe::new();
|
||||
let mut world = universe.create_world();
|
||||
|
||||
let vertex_size = mem::size_of::<Vertex>();
|
||||
let (cube_vertex_data, cube_index_data) = create_cube();
|
||||
let cube_vertex_buf = Arc::new(
|
||||
device.create_buffer_with_data(cube_vertex_data.as_bytes(), wgpu::BufferUsage::VERTEX),
|
||||
);
|
||||
|
||||
let cube_index_buf = Arc::new(
|
||||
device.create_buffer_with_data(cube_index_data.as_bytes(), wgpu::BufferUsage::INDEX),
|
||||
);
|
||||
|
||||
let (plane_vertex_data, plane_index_data) = create_plane(7);
|
||||
let plane_vertex_buf =
|
||||
device.create_buffer_with_data(plane_vertex_data.as_bytes(), wgpu::BufferUsage::VERTEX);
|
||||
|
||||
let plane_index_buf =
|
||||
device.create_buffer_with_data(plane_index_data.as_bytes(), wgpu::BufferUsage::INDEX);
|
||||
|
||||
let entity_uniform_size = mem::size_of::<EntityUniforms>() as wgpu::BufferAddress;
|
||||
let plane_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
size: entity_uniform_size,
|
||||
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
});
|
||||
|
||||
let local_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
|
@ -65,81 +44,33 @@ impl Application {
|
|||
}],
|
||||
});
|
||||
|
||||
let mut entities = vec![{
|
||||
|
||||
let mut entities = <Write<CubeEnt>>::query();
|
||||
for mut entity in entities.iter(&mut world) {
|
||||
let entity_uniform_size = mem::size_of::<EntityUniforms>() as wgpu::BufferAddress;
|
||||
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
size: entity_uniform_size,
|
||||
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &local_bind_group_layout,
|
||||
bindings: &[wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &plane_uniform_buf,
|
||||
buffer: &uniform_buf,
|
||||
range: 0 .. entity_uniform_size,
|
||||
},
|
||||
}],
|
||||
});
|
||||
(CubeEnt {
|
||||
rotation_speed: 0.0,
|
||||
color: wgpu::Color::WHITE,
|
||||
vertex_buf: Arc::new(plane_vertex_buf),
|
||||
index_buf: Arc::new(plane_index_buf),
|
||||
index_count: plane_index_data.len(),
|
||||
bind_group,
|
||||
uniform_buf: plane_uniform_buf,
|
||||
}, Transform::new())
|
||||
}];
|
||||
|
||||
entity.bind_group = Some(bind_group);
|
||||
entity.uniform_buf = Some(uniform_buf);
|
||||
}
|
||||
|
||||
let camera_position = math::vec3(3.0f32, -10.0, 6.0);
|
||||
let camera_fov = math::quarter_pi();
|
||||
|
||||
struct CubeDesc {
|
||||
offset: math::Vec3,
|
||||
rotation: f32,
|
||||
}
|
||||
let cube_descs = [
|
||||
CubeDesc {
|
||||
offset: math::vec3(-2.0, -2.0, 2.0),
|
||||
rotation: 0.1,
|
||||
},
|
||||
CubeDesc {
|
||||
offset: math::vec3(2.0, -2.0, 2.0),
|
||||
rotation: 0.2,
|
||||
},
|
||||
CubeDesc {
|
||||
offset: math::vec3(-2.0, 2.0, 2.0),
|
||||
rotation: 0.3,
|
||||
},
|
||||
CubeDesc {
|
||||
offset: math::vec3(2.0, 2.0, 2.0),
|
||||
rotation: 0.4,
|
||||
},
|
||||
];
|
||||
|
||||
for cube in &cube_descs {
|
||||
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
size: entity_uniform_size,
|
||||
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
});
|
||||
entities.push((CubeEnt {
|
||||
rotation_speed: cube.rotation,
|
||||
color: wgpu::Color::GREEN,
|
||||
vertex_buf: Arc::clone(&cube_vertex_buf),
|
||||
index_buf: Arc::clone(&cube_index_buf),
|
||||
index_count: cube_index_data.len(),
|
||||
bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &local_bind_group_layout,
|
||||
bindings: &[wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &uniform_buf,
|
||||
range: 0 .. entity_uniform_size,
|
||||
},
|
||||
}],
|
||||
}),
|
||||
uniform_buf,
|
||||
}, Transform { value: math::translation(&cube.offset)}));
|
||||
}
|
||||
|
||||
world.insert((), entities);
|
||||
|
||||
let vb_desc = wgpu::VertexBufferDescriptor {
|
||||
stride: vertex_size as wgpu::BufferAddress,
|
||||
step_mode: wgpu::InputStepMode::Vertex,
|
||||
|
@ -227,6 +158,7 @@ impl Application {
|
|||
let this = Application {
|
||||
universe,
|
||||
world,
|
||||
scheduler: SystemScheduler::new(),
|
||||
shadow_pass,
|
||||
forward_pass,
|
||||
camera_position,
|
||||
|
@ -272,7 +204,7 @@ impl Application {
|
|||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 });
|
||||
|
||||
{
|
||||
let mut entities = <(Read<CubeEnt>, Read<Transform>)>::query();
|
||||
let mut entities = <(Read<CubeEnt>, Read<LocalToWorld>)>::query();
|
||||
let entities_count = entities.iter(&mut self.world).count();
|
||||
let size = mem::size_of::<EntityUniforms>();
|
||||
let temp_buf_data = device
|
||||
|
@ -283,12 +215,12 @@ impl Application {
|
|||
{
|
||||
slot.copy_from_slice(
|
||||
EntityUniforms {
|
||||
model: transform.value.into(),
|
||||
model: transform.0.into(),
|
||||
color: [
|
||||
entity.color.r as f32,
|
||||
entity.color.g as f32,
|
||||
entity.color.b as f32,
|
||||
entity.color.a as f32,
|
||||
entity.color.x as f32,
|
||||
entity.color.y as f32,
|
||||
entity.color.z as f32,
|
||||
entity.color.w as f32,
|
||||
],
|
||||
}
|
||||
.as_bytes(),
|
||||
|
@ -301,7 +233,7 @@ impl Application {
|
|||
encoder.copy_buffer_to_buffer(
|
||||
&temp_buf,
|
||||
(i * size) as wgpu::BufferAddress,
|
||||
&entity.uniform_buf,
|
||||
entity.uniform_buf.as_ref().unwrap(),
|
||||
0,
|
||||
size as wgpu::BufferAddress,
|
||||
);
|
||||
|
@ -315,7 +247,7 @@ impl Application {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn run() {
|
||||
pub fn run(universe: Universe, world: World) {
|
||||
env_logger::init();
|
||||
let event_loop = EventLoop::new();
|
||||
log::info!("Initializing the window...");
|
||||
|
@ -354,7 +286,7 @@ impl Application {
|
|||
let mut swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||
|
||||
log::info!("Initializing the example...");
|
||||
let (mut example, init_command_buf) = Application::init(&sc_desc, &device);
|
||||
let (mut example, init_command_buf) = Application::init(universe, world, &sc_desc, &device);
|
||||
if let Some(command_buf) = init_command_buf {
|
||||
queue.submit(&[command_buf]);
|
||||
}
|
||||
|
@ -402,6 +334,7 @@ impl Application {
|
|||
let frame = swap_chain
|
||||
.get_next_texture()
|
||||
.expect("Timeout when acquiring next swap chain texture");
|
||||
example.scheduler.execute(&mut example.world);
|
||||
let command_buf = example.render(&frame, &device);
|
||||
queue.submit(&[command_buf]);
|
||||
}
|
||||
|
|
73
src/asset/mod.rs
Normal file
73
src/asset/mod.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use std::{sync::Arc, marker::PhantomData, ops::Drop};
|
||||
|
||||
pub struct Handle<T>
|
||||
{
|
||||
pub id: Arc<usize>,
|
||||
marker: PhantomData<T>,
|
||||
free_indices: Arc<Vec<usize>>
|
||||
}
|
||||
|
||||
impl<T> Drop for Handle<T> {
|
||||
fn drop(&mut self) {
|
||||
// TODO: Maybe this should be 1
|
||||
// TODO: Is this even necessary?
|
||||
if Arc::strong_count(&self.id) == 0 {
|
||||
Arc::get_mut(&mut self.free_indices).unwrap().push(*self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Asset<D> {
|
||||
fn load(descriptor: D) -> Self;
|
||||
}
|
||||
|
||||
pub struct AssetStorage<T, D> where T: Asset<D> {
|
||||
assets: Vec<Option<T>>,
|
||||
free_indices: Arc<Vec<usize>>,
|
||||
marker: PhantomData<D>,
|
||||
}
|
||||
|
||||
impl<T, D> AssetStorage<T, D> where T: Asset<D> {
|
||||
pub fn new() -> AssetStorage<T, D> {
|
||||
AssetStorage {
|
||||
assets: Vec::new(),
|
||||
free_indices: Arc::new(Vec::new()),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, asset: T) -> Handle<T> {
|
||||
match Arc::get_mut(&mut self.free_indices).unwrap().pop() {
|
||||
Some(id) => {
|
||||
self.assets[id as usize] = Some(asset);
|
||||
Handle {
|
||||
id: Arc::new(id),
|
||||
marker: PhantomData,
|
||||
free_indices: self.free_indices.clone()
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.assets.push(Some(asset));
|
||||
Handle {
|
||||
id: Arc::new(self.assets.len() - 1),
|
||||
marker: PhantomData,
|
||||
free_indices: self.free_indices.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&mut self, id: usize) -> Option<&mut T> {
|
||||
if id >= self.assets.len() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
if let Some(ref mut asset) = self.assets[id] {
|
||||
Some(asset)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,3 +1,18 @@
|
|||
mod transform;
|
||||
use legion::schedule::Stage;
|
||||
|
||||
pub use transform::*;
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ApplicationStage {
|
||||
Update,
|
||||
Render,
|
||||
}
|
||||
|
||||
impl Stage for ApplicationStage {}
|
||||
|
||||
impl std::fmt::Display for ApplicationStage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ApplicationStage::Update => write!(f, "update"),
|
||||
ApplicationStage::Render => write!(f, "draw"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
use crate::math;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Transform {
|
||||
pub value: math::Mat4,
|
||||
}
|
||||
|
||||
impl Transform {
|
||||
pub fn new() -> Transform {
|
||||
Transform {
|
||||
value: math::identity(),
|
||||
}
|
||||
}
|
||||
}
|
24
src/legion_transform/.gitignore
vendored
Normal file
24
src/legion_transform/.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
book/book
|
||||
target
|
||||
Cargo.lock
|
||||
*.log
|
||||
|
||||
# Backup files
|
||||
.DS_Store
|
||||
thumbs.db
|
||||
*~
|
||||
*.rs.bk
|
||||
*.swp
|
||||
|
||||
# IDE / Editor files
|
||||
*.iml
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
|
||||
#Added by cargo
|
||||
#
|
||||
#already existing elements are commented out
|
||||
|
||||
/target
|
||||
**/*.rs.bk
|
8
src/legion_transform/.travis.yml
Normal file
8
src/legion_transform/.travis.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- nightly
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
fast_finish: true
|
20
src/legion_transform/Cargo.toml
Normal file
20
src/legion_transform/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "legion_transform"
|
||||
version = "0.3.0"
|
||||
authors = ["Alec Thilenius <alec@thilenius.com>"]
|
||||
edition = "2018"
|
||||
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
legion = { git = "https://github.com/TomGillen/legion.git" }
|
||||
#legion = { path = "../legion" }
|
||||
log = "0.4"
|
||||
nalgebra = { version = "0.19.0" }
|
||||
rayon = "1.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
smallvec = "0.6"
|
||||
shrinkwraprs = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.7"
|
21
src/legion_transform/LICENSE
Normal file
21
src/legion_transform/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Alec Thilenius
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
211
src/legion_transform/README.md
Normal file
211
src/legion_transform/README.md
Normal file
|
@ -0,0 +1,211 @@
|
|||
# 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)
|
46
src/legion_transform/benches/local_to_world.rs
Normal file
46
src/legion_transform/benches/local_to_world.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use legion::prelude::*;
|
||||
use legion_transform::{local_to_world_system, prelude::*};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn local_to_world_update_without_change(b: &mut Bencher) {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
|
||||
let mut world = Universe::new().create_world();
|
||||
let system = local_to_world_system::build(&mut world);
|
||||
|
||||
let ltw = LocalToWorld::identity();
|
||||
let t = Translation::new(1.0, 2.0, 3.0);
|
||||
let r = Rotation::from_euler_angles(1.0, 2.0, 3.0);
|
||||
let s = Scale(2.0);
|
||||
let nus = NonUniformScale::new(1.0, 2.0, 3.0);
|
||||
|
||||
// Add N of every combination of transform types.
|
||||
let n = 1000;
|
||||
let _translation = *world.insert((), vec![(ltw, t); n]).first().unwrap();
|
||||
let _rotation = *world.insert((), vec![(ltw, r); n]).first().unwrap();
|
||||
let _scale = *world.insert((), vec![(ltw, s); n]).first().unwrap();
|
||||
let _non_uniform_scale = *world.insert((), vec![(ltw, nus); n]).first().unwrap();
|
||||
let _translation_and_rotation = *world.insert((), vec![(ltw, t, r); n]).first().unwrap();
|
||||
let _translation_and_scale = *world.insert((), vec![(ltw, t, s); n]).first().unwrap();
|
||||
let _translation_and_nus = *world.insert((), vec![(ltw, t, nus); n]).first().unwrap();
|
||||
let _rotation_scale = *world.insert((), vec![(ltw, r, s); n]).first().unwrap();
|
||||
let _rotation_nus = *world.insert((), vec![(ltw, r, nus); n]).first().unwrap();
|
||||
let _translation_rotation_scale = *world.insert((), vec![(ltw, t, r, s); n]).first().unwrap();
|
||||
let _translation_rotation_nus = *world.insert((), vec![(ltw, t, r, nus); n]).first().unwrap();
|
||||
|
||||
// Run the system once outside the test (which should compute everything and it shouldn't be
|
||||
// touched again).
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
|
||||
// Then time the already-computed updates.
|
||||
b.iter(|| {
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
});
|
||||
}
|
155
src/legion_transform/examples/hierarchy.rs
Normal file
155
src/legion_transform/examples/hierarchy.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
extern crate legion;
|
||||
extern crate legion_transform;
|
||||
|
||||
use legion::prelude::*;
|
||||
use legion_transform::prelude::*;
|
||||
|
||||
#[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 = transform_system_bundle::build(&mut world);
|
||||
|
||||
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
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// 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 = transform_system_bundle::build(&mut world);
|
||||
|
||||
// See `./types_of_transforms.rs` for an explanation of space-transform types.
|
||||
let parent_entity = *world
|
||||
.insert(
|
||||
(),
|
||||
vec![(LocalToWorld::identity(), Translation::new(100.0, 0.0, 0.0))],
|
||||
)
|
||||
.first()
|
||||
.unwrap();
|
||||
|
||||
let four_children: Vec<_> = world
|
||||
.insert(
|
||||
(),
|
||||
vec![
|
||||
(
|
||||
LocalToWorld::identity(),
|
||||
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
|
||||
],
|
||||
)
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// At this point the parent does NOT have a `Children` component attached to it. The `Children`
|
||||
// component is updated by the transform system bundle and thus can be out of date (or
|
||||
// non-existent for newly added members). By this logic, the `Parent` components should be
|
||||
// considered the always-correct 'source of truth' for any hierarchy.
|
||||
for system in transform_system_bundle.iter() {
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
}
|
||||
|
||||
// At this point all parents with children have a correct `Children` component.
|
||||
let parents_children = world
|
||||
.get_component::<Children>(parent_entity)
|
||||
.unwrap()
|
||||
.0
|
||||
.clone();
|
||||
|
||||
println!("Parent {}", parent_entity);
|
||||
for child in parents_children.iter() {
|
||||
println!(" -> Has child: {}", child);
|
||||
}
|
||||
|
||||
// Each child will also have a `LocalToParent` component attached to it, which is a
|
||||
// space-transform from its local space to that of its parent.
|
||||
for child in four_children.iter() {
|
||||
println!("The child {}", child);
|
||||
println!(
|
||||
" -> Has a LocalToParent matrix: {}",
|
||||
*world.get_component::<LocalToParent>(*child).unwrap()
|
||||
);
|
||||
println!(
|
||||
" -> Has a LocalToWorld matrix: {}",
|
||||
*world.get_component::<LocalToWorld>(*child).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
// Re-parent the second child to be a grandchild of the first.
|
||||
world.add_component(four_children[1], Parent(four_children[0]));
|
||||
|
||||
// Re-running the system will cleanup and fix all `Children` components.
|
||||
for system in transform_system_bundle.iter() {
|
||||
system.run(&world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
}
|
||||
|
||||
println!("After the second child was re-parented as a grandchild of the first child...");
|
||||
|
||||
for child in world
|
||||
.get_component::<Children>(parent_entity)
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
{
|
||||
println!("Parent {} has child: {}", parent_entity, child);
|
||||
}
|
||||
|
||||
for grandchild in world
|
||||
.get_component::<Children>(four_children[0])
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
{
|
||||
println!("Child {} has grandchild: {}", four_children[0], grandchild);
|
||||
}
|
||||
|
||||
println!("Grandchild: {}", four_children[1]);
|
||||
println!(
|
||||
" -> Has a LocalToWorld matrix: {}",
|
||||
*world
|
||||
.get_component::<LocalToWorld>(four_children[1])
|
||||
.unwrap()
|
||||
);
|
||||
}
|
94
src/legion_transform/examples/types_of_transforms.rs
Normal file
94
src/legion_transform/examples/types_of_transforms.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
extern crate legion;
|
||||
extern crate legion_transform;
|
||||
|
||||
use legion::prelude::*;
|
||||
use legion_transform::prelude::*;
|
||||
|
||||
fn main() {
|
||||
// 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 = transform_system_bundle::build(&mut world);
|
||||
|
||||
// A user-defined space transform is split into 4 different components: [`Translation`,
|
||||
// `Rotation`, `Scale`, `NonUniformScale`]. Any combination of these components can be added to
|
||||
// an entity to transform it's space (exception: `Scale` and `NonUniformScale` are mutually
|
||||
// exclusive).
|
||||
|
||||
// Note that all entities need an explicitly added `LocalToWorld` component to be considered for
|
||||
// processing during transform system passes.
|
||||
|
||||
// Add an entity with just a Translation
|
||||
// See: https://www.nalgebra.org/rustdoc/nalgebra/geometry/struct.Translation.html
|
||||
// API on Translation, as a LegionTransform `Translation` is just a nalgebra `Translation3`.
|
||||
world.insert(
|
||||
(),
|
||||
vec![(LocalToWorld::identity(), Translation::new(1.0, 2.0, 3.0))],
|
||||
);
|
||||
|
||||
// Add an entity with just a Rotation.
|
||||
// See: https://www.nalgebra.org/rustdoc/nalgebra/geometry/type.UnitQuaternion.html for the full
|
||||
// API on Rotation, as a LegionTransform `Rotation` is just a nalgebra `UnityQuaternion`.
|
||||
world.insert(
|
||||
(),
|
||||
vec![(
|
||||
LocalToWorld::identity(),
|
||||
Rotation::from_euler_angles(3.14, 0.0, 0.0),
|
||||
)],
|
||||
);
|
||||
|
||||
// Add an entity with just a uniform Scale (the default and strongly-preferred scale component).
|
||||
// This is simply a `f32` wrapper.
|
||||
world.insert((), vec![(LocalToWorld::identity(), Scale(2.0))]);
|
||||
|
||||
// Add an entity with just a NonUniformScale (This should be avoided unless you **really** need
|
||||
// non-uniform scaling as it breaks things like physics colliders.
|
||||
// See: https://docs.rs/nalgebra/0.10.1/nalgebra/struct.Vector3.html for the full API on
|
||||
// NonUniformScale, as a LegionTransform `NonUniformScale` is simply a nalgebra `Vector3`,
|
||||
// although note that it is wrapped in a tuple-struct.
|
||||
world.insert(
|
||||
(),
|
||||
vec![(
|
||||
LocalToWorld::identity(),
|
||||
NonUniformScale::new(1.0, 2.0, 1.0),
|
||||
)],
|
||||
);
|
||||
|
||||
// Add an entity with a combination of Translation and Rotation
|
||||
world.insert(
|
||||
(),
|
||||
vec![(
|
||||
LocalToWorld::identity(),
|
||||
Translation::new(1.0, 2.0, 3.0),
|
||||
Rotation::from_euler_angles(3.14, 0.0, 0.0),
|
||||
)],
|
||||
);
|
||||
|
||||
// Add an entity with a combination of Translation and Rotation and uniform Scale.
|
||||
world.insert(
|
||||
(),
|
||||
vec![(
|
||||
LocalToWorld::identity(),
|
||||
Translation::new(1.0, 2.0, 3.0),
|
||||
Rotation::from_euler_angles(3.14, 0.0, 0.0),
|
||||
Scale(2.0),
|
||||
)],
|
||||
);
|
||||
|
||||
// Run the system bundle (this API will likely change).
|
||||
for system in transform_system_bundle.iter() {
|
||||
system.run(&world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
}
|
||||
|
||||
// At this point all `LocalToWorld` components have correct values in them. Running the system
|
||||
// again will result in a short-circuit as only changed components are considered for update.
|
||||
let mut query = <Read<LocalToWorld>>::query();
|
||||
for (entity, local_to_world) in query.iter_entities(&mut world) {
|
||||
println!(
|
||||
"Entity {} and a LocalToWorld matrix: {}",
|
||||
entity, *local_to_world
|
||||
);
|
||||
}
|
||||
}
|
13
src/legion_transform/src/components/children.rs
Normal file
13
src/legion_transform/src/components/children.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use crate::ecs::prelude::*;
|
||||
use shrinkwraprs::Shrinkwrap;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[derive(Shrinkwrap, Default, Clone)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct Children(pub SmallVec<[Entity; 8]>);
|
||||
|
||||
impl Children {
|
||||
pub fn with(entity: &[Entity]) -> Self {
|
||||
Self(SmallVec::from_slice(entity))
|
||||
}
|
||||
}
|
25
src/legion_transform/src/components/local_to_parent.rs
Normal file
25
src/legion_transform/src/components/local_to_parent.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::math::Matrix4;
|
||||
use shrinkwraprs::Shrinkwrap;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct LocalToParent(pub Matrix4<f32>);
|
||||
|
||||
impl LocalToParent {
|
||||
pub fn identity() -> Self {
|
||||
Self(Matrix4::identity())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LocalToParent {
|
||||
fn default() -> Self {
|
||||
Self::identity()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalToParent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
26
src/legion_transform/src/components/local_to_world.rs
Normal file
26
src/legion_transform/src/components/local_to_world.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use crate::math::Matrix4;
|
||||
use shrinkwraprs::Shrinkwrap;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct LocalToWorld(pub Matrix4<f32>);
|
||||
|
||||
impl LocalToWorld {
|
||||
#[inline(always)]
|
||||
pub fn identity() -> Self {
|
||||
Self(Matrix4::identity())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LocalToWorld {
|
||||
fn default() -> Self {
|
||||
Self::identity()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalToWorld {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
17
src/legion_transform/src/components/mod.rs
Normal file
17
src/legion_transform/src/components/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
mod children;
|
||||
mod local_to_parent;
|
||||
mod local_to_world;
|
||||
mod non_uniform_scale;
|
||||
mod parent;
|
||||
mod rotation;
|
||||
mod scale;
|
||||
mod translation;
|
||||
|
||||
pub use children::Children;
|
||||
pub use local_to_parent::*;
|
||||
pub use local_to_world::*;
|
||||
pub use non_uniform_scale::*;
|
||||
pub use parent::{Parent, PreviousParent};
|
||||
pub use rotation::*;
|
||||
pub use scale::*;
|
||||
pub use translation::*;
|
41
src/legion_transform/src/components/non_uniform_scale.rs
Normal file
41
src/legion_transform/src/components/non_uniform_scale.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use crate::math::Vector3;
|
||||
use shrinkwraprs::Shrinkwrap;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct NonUniformScale(pub Vector3<f32>);
|
||||
|
||||
impl NonUniformScale {
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(Vector3::new(x, y, z))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector3<f32>> for NonUniformScale {
|
||||
fn from(scale: Vector3<f32>) -> Self {
|
||||
Self(scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vector3<f32>> for NonUniformScale {
|
||||
fn from(scale: &Vector3<f32>) -> Self {
|
||||
Self(*scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&mut Vector3<f32>> for NonUniformScale {
|
||||
fn from(scale: &mut Vector3<f32>) -> Self {
|
||||
Self(*scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NonUniformScale {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"NonUniformScale({}, {}, {})",
|
||||
self.0.x, self.0.y, self.0.z
|
||||
)
|
||||
}
|
||||
}
|
10
src/legion_transform/src/components/parent.rs
Normal file
10
src/legion_transform/src/components/parent.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use crate::ecs::prelude::*;
|
||||
use shrinkwraprs::Shrinkwrap;
|
||||
|
||||
#[derive(Shrinkwrap, Debug, Copy, Clone, Eq, PartialEq)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct Parent(pub Entity);
|
||||
|
||||
#[derive(Shrinkwrap, Debug, Copy, Clone, Eq, PartialEq)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct PreviousParent(pub Option<Entity>);
|
29
src/legion_transform/src/components/rotation.rs
Normal file
29
src/legion_transform/src/components/rotation.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use crate::math::UnitQuaternion;
|
||||
use shrinkwraprs::Shrinkwrap;
|
||||
|
||||
#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct Rotation(pub UnitQuaternion<f32>);
|
||||
impl Rotation {
|
||||
#[inline(always)]
|
||||
pub fn identity() -> Self {
|
||||
Self(UnitQuaternion::identity())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_euler_angles(roll: f32, pitch: f32, yaw: f32) -> Self {
|
||||
Self(UnitQuaternion::from_euler_angles(roll, pitch, yaw))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Rotation {
|
||||
fn default() -> Self {
|
||||
Self::identity()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnitQuaternion<f32>> for Rotation {
|
||||
fn from(rotation: UnitQuaternion<f32>) -> Self {
|
||||
Self(rotation)
|
||||
}
|
||||
}
|
32
src/legion_transform/src/components/scale.rs
Normal file
32
src/legion_transform/src/components/scale.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use shrinkwraprs::Shrinkwrap;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct Scale(pub f32);
|
||||
|
||||
impl From<f32> for Scale {
|
||||
#[inline(always)]
|
||||
fn from(scale: f32) -> Self {
|
||||
Self(scale)
|
||||
}
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
#[inline(always)]
|
||||
pub fn identity() -> Self {
|
||||
Scale(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Scale {
|
||||
fn default() -> Self {
|
||||
Self::identity()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Scale {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Scale({})", self.0)
|
||||
}
|
||||
}
|
36
src/legion_transform/src/components/translation.rs
Normal file
36
src/legion_transform/src/components/translation.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use crate::math::{Translation3, Vector3};
|
||||
use shrinkwraprs::Shrinkwrap;
|
||||
|
||||
#[derive(Shrinkwrap, Debug, PartialEq, Clone, Copy)]
|
||||
#[shrinkwrap(mutable)]
|
||||
pub struct Translation(pub Translation3<f32>);
|
||||
|
||||
impl Translation {
|
||||
#[inline(always)]
|
||||
pub fn identity() -> Self {
|
||||
Self(Translation3::identity())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(Translation3::new(x, y, z))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Translation {
|
||||
fn default() -> Self {
|
||||
Self::identity()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector3<f32>> for Translation {
|
||||
fn from(translation: Vector3<f32>) -> Self {
|
||||
Self(Translation3::from(translation))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Translation3<f32>> for Translation {
|
||||
fn from(translation: Translation3<f32>) -> Self {
|
||||
Self(translation)
|
||||
}
|
||||
}
|
234
src/legion_transform/src/hierarchy_maintenance_system.rs
Normal file
234
src/legion_transform/src/hierarchy_maintenance_system.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
#![allow(dead_code)]
|
||||
use crate::{components::*, ecs::prelude::*};
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
|
||||
let missing_previous_parent_system = SystemBuilder::<()>::new("MissingPreviousParentSystem")
|
||||
// Entities with missing `PreviousParent`
|
||||
.with_query(<Read<Parent>>::query().filter(
|
||||
component::<LocalToParent>()
|
||||
& component::<LocalToWorld>()
|
||||
& !component::<PreviousParent>(),
|
||||
))
|
||||
.build(move |commands, world, _resource, query| {
|
||||
// Add missing `PreviousParent` components
|
||||
for (entity, _parent) in query.iter_entities(world) {
|
||||
log::trace!("Adding missing PreviousParent to {}", entity);
|
||||
commands.add_component(entity, PreviousParent(None));
|
||||
}
|
||||
});
|
||||
|
||||
let parent_update_system = SystemBuilder::<()>::new("ParentUpdateSystem")
|
||||
// Entities with a removed `Parent`
|
||||
.with_query(<Read<PreviousParent>>::query().filter(!component::<Parent>()))
|
||||
// Entities with a changed `Parent`
|
||||
.with_query(<(Read<Parent>, Write<PreviousParent>)>::query().filter(
|
||||
component::<LocalToParent>() & component::<LocalToWorld>() & changed::<Parent>(),
|
||||
))
|
||||
// Deleted Parents (ie Entities with `Children` and without a `LocalToWorld`).
|
||||
.with_query(<Read<Children>>::query().filter(!component::<LocalToWorld>()))
|
||||
.write_component::<Children>()
|
||||
.build(move |commands, world, _resource, queries| {
|
||||
// Entities with a missing `Parent` (ie. ones that have a `PreviousParent`), remove
|
||||
// them from the `Children` of the `PreviousParent`.
|
||||
for (entity, previous_parent) in queries.0.iter_entities(world) {
|
||||
log::trace!("Parent was removed from {}", entity);
|
||||
if let Some(previous_parent_entity) = previous_parent.0 {
|
||||
if let Some(mut previous_parent_children) =
|
||||
world.get_component_mut::<Children>(previous_parent_entity)
|
||||
{
|
||||
log::trace!(" > Removing {} from it's prev parent's children", entity);
|
||||
previous_parent_children.0.retain(|e| *e != entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks all newly created `Children` Components this frame.
|
||||
let mut children_additions =
|
||||
HashMap::<Entity, SmallVec<[Entity; 8]>>::with_capacity(16);
|
||||
|
||||
// Entities with a changed Parent (that also have a PreviousParent, even if None)
|
||||
for (entity, (parent, mut previous_parent)) in queries.1.iter_entities(world) {
|
||||
log::trace!("Parent changed for {}", entity);
|
||||
|
||||
// If the `PreviousParent` is not None.
|
||||
if let Some(previous_parent_entity) = previous_parent.0 {
|
||||
// New and previous point to the same Entity, carry on, nothing to see here.
|
||||
if previous_parent_entity == parent.0 {
|
||||
log::trace!(" > But the previous parent is the same, ignoring...");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove from `PreviousParent.Children`.
|
||||
if let Some(mut previous_parent_children) =
|
||||
world.get_component_mut::<Children>(previous_parent_entity)
|
||||
{
|
||||
log::trace!(" > Removing {} from prev parent's children", entity);
|
||||
(*previous_parent_children).0.retain(|e| *e != entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Set `PreviousParent = Parent`.
|
||||
*previous_parent = PreviousParent(Some(parent.0));
|
||||
|
||||
// Add to the parent's `Children` (either the real component, or
|
||||
// `children_additions`).
|
||||
log::trace!("Adding {} to it's new parent {}", entity, parent.0);
|
||||
if let Some(mut new_parent_children) = world.get_component_mut::<Children>(parent.0)
|
||||
{
|
||||
// This is the parent
|
||||
log::trace!(
|
||||
" > The new parent {} already has a `Children`, adding to it.",
|
||||
parent.0
|
||||
);
|
||||
(*new_parent_children).0.push(entity);
|
||||
} else {
|
||||
// The parent doesn't have a children entity, lets add it
|
||||
log::trace!(
|
||||
"The new parent {} doesn't yet have `Children` component.",
|
||||
parent.0
|
||||
);
|
||||
children_additions
|
||||
.entry(parent.0)
|
||||
.or_insert_with(Default::default)
|
||||
.push(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Deleted `Parents` (ie. Entities with a `Children` but no `LocalToWorld`).
|
||||
for (entity, children) in queries.2.iter_entities(world) {
|
||||
log::trace!("The entity {} doesn't have a LocalToWorld", entity);
|
||||
if children_additions.remove(&entity).is_none() {
|
||||
log::trace!(" > It needs to be remove from the ECS.");
|
||||
for child_entity in children.0.iter() {
|
||||
commands.remove_component::<Parent>(*child_entity);
|
||||
commands.remove_component::<PreviousParent>(*child_entity);
|
||||
commands.remove_component::<LocalToParent>(*child_entity);
|
||||
}
|
||||
commands.remove_component::<Children>(entity);
|
||||
} else {
|
||||
log::trace!(" > It was a new addition, removing it from additions map");
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the `children_additions` to the command buffer. It is stored separate to
|
||||
// collect multiple new children that point to the same parent into the same
|
||||
// SmallVec, and to prevent redundant add+remove operations.
|
||||
children_additions.iter().for_each(|(k, v)| {
|
||||
log::trace!("Flushing: Entity {} adding `Children` component {:?}", k, v);
|
||||
commands.add_component(*k, Children::with(v));
|
||||
});
|
||||
});
|
||||
|
||||
vec![missing_previous_parent_system, parent_update_system]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn correct_children() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
|
||||
let mut world = Universe::new().create_world();
|
||||
|
||||
let systems = build(&mut world);
|
||||
|
||||
// Add parent entities
|
||||
let parent = *world
|
||||
.insert(
|
||||
(),
|
||||
vec![(Translation::identity(), LocalToWorld::identity())],
|
||||
)
|
||||
.first()
|
||||
.unwrap();
|
||||
let children = world.insert(
|
||||
(),
|
||||
vec![
|
||||
(
|
||||
Translation::identity(),
|
||||
LocalToParent::identity(),
|
||||
LocalToWorld::identity(),
|
||||
),
|
||||
(
|
||||
Translation::identity(),
|
||||
LocalToParent::identity(),
|
||||
LocalToWorld::identity(),
|
||||
),
|
||||
],
|
||||
);
|
||||
let (e1, e2) = (children[0], children[1]);
|
||||
|
||||
// Parent `e1` and `e2` to `parent`.
|
||||
world.add_component(e1, Parent(parent));
|
||||
world.add_component(e2, Parent(parent));
|
||||
|
||||
for system in systems.iter() {
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<Children>(parent)
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![e1, e2]
|
||||
);
|
||||
|
||||
// Parent `e1` to `e2`.
|
||||
(*world.get_component_mut::<Parent>(e1).unwrap()).0 = e2;
|
||||
|
||||
// Run the system on it
|
||||
for system in systems.iter() {
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<Children>(parent)
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![e2]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<Children>(e2)
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![e1]
|
||||
);
|
||||
|
||||
world.delete(e1);
|
||||
|
||||
// Run the system on it
|
||||
for system in systems.iter() {
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<Children>(parent)
|
||||
.unwrap()
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![e2]
|
||||
);
|
||||
}
|
||||
}
|
18
src/legion_transform/src/lib.rs
Normal file
18
src/legion_transform/src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
pub use legion as ecs;
|
||||
pub use nalgebra as math;
|
||||
|
||||
pub mod components;
|
||||
pub mod hierarchy_maintenance_system;
|
||||
pub mod local_to_parent_system;
|
||||
pub mod local_to_world_propagate_system;
|
||||
pub mod local_to_world_system;
|
||||
pub mod transform_system_bundle;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::components::*;
|
||||
pub use crate::hierarchy_maintenance_system;
|
||||
pub use crate::local_to_parent_system;
|
||||
pub use crate::local_to_world_propagate_system;
|
||||
pub use crate::local_to_world_system;
|
||||
pub use crate::transform_system_bundle;
|
||||
}
|
323
src/legion_transform/src/local_to_parent_system.rs
Normal file
323
src/legion_transform/src/local_to_parent_system.rs
Normal file
|
@ -0,0 +1,323 @@
|
|||
#![allow(dead_code)]
|
||||
use crate::{components::*, ecs::prelude::*, math::Matrix4};
|
||||
|
||||
pub fn build(_: &mut World) -> Box<dyn Schedulable> {
|
||||
SystemBuilder::<()>::new("LocalToParentUpdateSystem")
|
||||
// Translation
|
||||
.with_query(<(Write<LocalToParent>, Read<Translation>)>::query().filter(
|
||||
!component::<Rotation>()
|
||||
& !component::<Scale>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Translation>()),
|
||||
))
|
||||
// Rotation
|
||||
.with_query(<(Write<LocalToParent>, Read<Rotation>)>::query().filter(
|
||||
!component::<Translation>()
|
||||
& !component::<Scale>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Rotation>()),
|
||||
))
|
||||
// Scale
|
||||
.with_query(<(Write<LocalToParent>, Read<Scale>)>::query().filter(
|
||||
!component::<Translation>()
|
||||
& !component::<Rotation>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Scale>()),
|
||||
))
|
||||
// NonUniformScale
|
||||
.with_query(
|
||||
<(Write<LocalToParent>, Read<NonUniformScale>)>::query().filter(
|
||||
!component::<Translation>()
|
||||
& !component::<Rotation>()
|
||||
& !component::<Scale>()
|
||||
& (changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Translation + Rotation
|
||||
.with_query(
|
||||
<(Write<LocalToParent>, Read<Translation>, Read<Rotation>)>::query().filter(
|
||||
!component::<Scale>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Translation>() | changed::<Rotation>()),
|
||||
),
|
||||
)
|
||||
// Translation + Scale
|
||||
.with_query(
|
||||
<(Write<LocalToParent>, Read<Translation>, Read<Scale>)>::query().filter(
|
||||
!component::<Rotation>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Translation>() | changed::<Scale>()),
|
||||
),
|
||||
)
|
||||
// Translation + NonUniformScale
|
||||
.with_query(
|
||||
<(
|
||||
Write<LocalToParent>,
|
||||
Read<Translation>,
|
||||
Read<NonUniformScale>,
|
||||
)>::query()
|
||||
.filter(
|
||||
!component::<Rotation>()
|
||||
& !component::<Scale>()
|
||||
& (changed::<Translation>() | changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Rotation + Scale
|
||||
.with_query(
|
||||
<(Write<LocalToParent>, Read<Rotation>, Read<Scale>)>::query().filter(
|
||||
!component::<Translation>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Rotation>() | changed::<Scale>()),
|
||||
),
|
||||
)
|
||||
// Rotation + NonUniformScale
|
||||
.with_query(
|
||||
<(Write<LocalToParent>, Read<Rotation>, Read<NonUniformScale>)>::query().filter(
|
||||
!component::<Translation>()
|
||||
& !component::<Scale>()
|
||||
& (changed::<Rotation>() | changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Translation + Rotation + Scale
|
||||
.with_query(
|
||||
<(
|
||||
Write<LocalToParent>,
|
||||
Read<Translation>,
|
||||
Read<Rotation>,
|
||||
Read<Scale>,
|
||||
)>::query()
|
||||
.filter(
|
||||
!component::<NonUniformScale>()
|
||||
& (changed::<Translation>() | changed::<Rotation>() | changed::<Scale>()),
|
||||
),
|
||||
)
|
||||
// Translation + Rotation + NonUniformScale
|
||||
.with_query(
|
||||
<(
|
||||
Write<LocalToParent>,
|
||||
Read<Translation>,
|
||||
Read<Rotation>,
|
||||
Read<NonUniformScale>,
|
||||
)>::query()
|
||||
.filter(
|
||||
!component::<Scale>()
|
||||
& (changed::<Translation>()
|
||||
| changed::<Rotation>()
|
||||
| changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Just to issue warnings: Scale + NonUniformScale
|
||||
.with_query(<(Read<LocalToParent>, Read<Scale>, Read<NonUniformScale>)>::query())
|
||||
.build(move |_commands, world, _, queries| {
|
||||
let (a, b, c, d, e, f, g, h, i, j, k, l) = queries;
|
||||
rayon::scope(|s| {
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation
|
||||
a.for_each_unchecked(world, |(mut ltw, translation)| {
|
||||
*ltw = LocalToParent(translation.to_homogeneous());
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Rotation
|
||||
b.for_each_unchecked(world, |(mut ltw, rotation)| {
|
||||
*ltw = LocalToParent(rotation.to_homogeneous());
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Scale
|
||||
c.for_each_unchecked(world, |(mut ltw, scale)| {
|
||||
*ltw = LocalToParent(Matrix4::new_scaling(scale.0));
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// NonUniformScale
|
||||
d.for_each_unchecked(world, |(mut ltw, non_uniform_scale)| {
|
||||
*ltw = LocalToParent(Matrix4::new_nonuniform_scaling(&non_uniform_scale.0));
|
||||
});
|
||||
|
||||
// Translation + Rotation
|
||||
e.for_each_unchecked(world, |(mut ltw, translation, rotation)| {
|
||||
*ltw = LocalToParent(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.append_translation(&translation.vector),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + Scale
|
||||
f.for_each_unchecked(world, |(mut ltw, translation, scale)| {
|
||||
*ltw = LocalToParent(translation.to_homogeneous().prepend_scaling(scale.0));
|
||||
});
|
||||
|
||||
// Translation + NonUniformScale
|
||||
g.for_each_unchecked(world, |(mut ltw, translation, non_uniform_scale)| {
|
||||
*ltw = LocalToParent(
|
||||
translation
|
||||
.to_homogeneous()
|
||||
.prepend_nonuniform_scaling(&non_uniform_scale.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Rotation + Scale
|
||||
h.for_each_unchecked(world, |(mut ltw, rotation, scale)| {
|
||||
*ltw = LocalToParent(rotation.to_homogeneous().prepend_scaling(scale.0));
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Rotation + NonUniformScale
|
||||
i.for_each_unchecked(world, |(mut ltw, rotation, non_uniform_scale)| {
|
||||
*ltw = LocalToParent(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.prepend_nonuniform_scaling(&non_uniform_scale.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + Rotation + Scale
|
||||
j.for_each_unchecked(world, |(mut ltw, translation, rotation, scale)| {
|
||||
*ltw = LocalToParent(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.append_translation(&translation.vector)
|
||||
.prepend_scaling(scale.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + Rotation + NonUniformScale
|
||||
k.for_each_unchecked(
|
||||
world,
|
||||
|(mut ltw, translation, rotation, non_uniform_scale)| {
|
||||
*ltw = LocalToParent(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.append_translation(&translation.vector)
|
||||
.prepend_nonuniform_scaling(&non_uniform_scale.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
// Just to issue warnings: Scale + NonUniformScale
|
||||
l.iter_entities(world)
|
||||
.for_each(|(entity, (mut _ltw, _scale, _non_uniform_scale))| {
|
||||
log::warn!(
|
||||
"Entity {:?} has both a Scale and NonUniformScale component.",
|
||||
entity
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn correct_parent_transformation() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
|
||||
let mut world = Universe::new().create_world();
|
||||
let system = build(&mut world);
|
||||
|
||||
let ltw = LocalToParent::identity();
|
||||
let t = Translation::new(1.0, 2.0, 3.0);
|
||||
let r = Rotation::from_euler_angles(1.0, 2.0, 3.0);
|
||||
let s = Scale(2.0);
|
||||
let nus = NonUniformScale::new(1.0, 2.0, 3.0);
|
||||
|
||||
// Add every combination of transform types.
|
||||
let translation = *world.insert((), vec![(ltw, t)]).first().unwrap();
|
||||
let rotation = *world.insert((), vec![(ltw, r)]).first().unwrap();
|
||||
let scale = *world.insert((), vec![(ltw, s)]).first().unwrap();
|
||||
let non_uniform_scale = *world.insert((), vec![(ltw, nus)]).first().unwrap();
|
||||
let translation_and_rotation = *world.insert((), vec![(ltw, t, r)]).first().unwrap();
|
||||
let translation_and_scale = *world.insert((), vec![(ltw, t, s)]).first().unwrap();
|
||||
let translation_and_nus = *world.insert((), vec![(ltw, t, nus)]).first().unwrap();
|
||||
let rotation_scale = *world.insert((), vec![(ltw, r, s)]).first().unwrap();
|
||||
let rotation_nus = *world.insert((), vec![(ltw, r, nus)]).first().unwrap();
|
||||
let translation_rotation_scale = *world.insert((), vec![(ltw, t, r, s)]).first().unwrap();
|
||||
let translation_rotation_nus = *world.insert((), vec![(ltw, t, r, nus)]).first().unwrap();
|
||||
|
||||
// Run the system
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
|
||||
// Verify that each was transformed correctly.
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToParent>(translation).unwrap().0,
|
||||
t.to_homogeneous()
|
||||
);
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToParent>(rotation).unwrap().0,
|
||||
r.to_homogeneous()
|
||||
);
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToParent>(scale).unwrap().0,
|
||||
Matrix4::new_scaling(s.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(non_uniform_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
Matrix4::new_nonuniform_scaling(&nus.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(translation_and_rotation)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous().append_translation(&t.vector),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(translation_and_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
t.to_homogeneous().prepend_scaling(s.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(translation_and_nus)
|
||||
.unwrap()
|
||||
.0,
|
||||
t.to_homogeneous().prepend_nonuniform_scaling(&nus.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(rotation_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous().prepend_scaling(s.0)
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(rotation_nus)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous().prepend_nonuniform_scaling(&nus.0)
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(translation_rotation_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous()
|
||||
.append_translation(&t.vector)
|
||||
.prepend_scaling(s.0)
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToParent>(translation_rotation_nus)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous()
|
||||
.append_translation(&t.vector)
|
||||
.prepend_nonuniform_scaling(&nus.0)
|
||||
);
|
||||
}
|
||||
}
|
133
src/legion_transform/src/local_to_world_propagate_system.rs
Normal file
133
src/legion_transform/src/local_to_world_propagate_system.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
#![allow(dead_code)]
|
||||
use crate::{
|
||||
components::*,
|
||||
ecs::{prelude::*, system::PreparedWorld},
|
||||
};
|
||||
|
||||
pub fn build(_: &mut World) -> Box<dyn Schedulable> {
|
||||
SystemBuilder::<()>::new("LocalToWorldPropagateSystem")
|
||||
// Entities with a `Children` and `LocalToWorld` but NOT a `Parent` (ie those that are
|
||||
// roots of a hierarchy).
|
||||
.with_query(<(Read<Children>, Read<LocalToWorld>)>::query().filter(!component::<Parent>()))
|
||||
.read_component::<Children>()
|
||||
.read_component::<LocalToParent>()
|
||||
.build(move |commands, world, _resource, query| {
|
||||
for (children, local_to_world) in query.iter(world) {
|
||||
for child in children.0.iter() {
|
||||
propagate_recursive(*local_to_world, world, *child, commands);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn propagate_recursive(
|
||||
parent_local_to_world: LocalToWorld,
|
||||
world: &mut PreparedWorld,
|
||||
entity: Entity,
|
||||
commands: &mut CommandBuffer,
|
||||
) {
|
||||
log::trace!("Updating LocalToWorld for {}", entity);
|
||||
let local_to_parent = {
|
||||
if let Some(local_to_parent) = world.get_component::<LocalToParent>(entity) {
|
||||
*local_to_parent
|
||||
} else {
|
||||
log::warn!(
|
||||
"Entity {} is a child in the hierarchy but does not have a LocalToParent",
|
||||
entity
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let new_local_to_world = LocalToWorld(parent_local_to_world.0 * local_to_parent.0);
|
||||
commands.add_component(entity, new_local_to_world);
|
||||
|
||||
// Collect children
|
||||
let children = world
|
||||
.get_component::<Children>(entity)
|
||||
.map(|e| e.0.iter().cloned().collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
for child in children {
|
||||
propagate_recursive(new_local_to_world, world, child, commands);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
hierarchy_maintenance_system, local_to_parent_system, local_to_world_propagate_system,
|
||||
local_to_world_system,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn did_propagate() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
|
||||
let mut world = Universe::new().create_world();
|
||||
|
||||
let hierarchy_maintenance_systems = hierarchy_maintenance_system::build(&mut world);
|
||||
let local_to_parent_system = local_to_parent_system::build(&mut world);
|
||||
let local_to_world_system = local_to_world_system::build(&mut world);
|
||||
let local_to_world_propagate_system = local_to_world_propagate_system::build(&mut world);
|
||||
|
||||
// Root entity
|
||||
let parent = *world
|
||||
.insert(
|
||||
(),
|
||||
vec![(Translation::new(1.0, 0.0, 0.0), LocalToWorld::identity())],
|
||||
)
|
||||
.first()
|
||||
.unwrap();
|
||||
|
||||
let children = world.insert(
|
||||
(),
|
||||
vec![
|
||||
(
|
||||
Translation::new(0.0, 2.0, 0.0),
|
||||
LocalToParent::identity(),
|
||||
LocalToWorld::identity(),
|
||||
),
|
||||
(
|
||||
Translation::new(0.0, 0.0, 3.0),
|
||||
LocalToParent::identity(),
|
||||
LocalToWorld::identity(),
|
||||
),
|
||||
],
|
||||
);
|
||||
let (e1, e2) = (children[0], children[1]);
|
||||
|
||||
// Parent `e1` and `e2` to `parent`.
|
||||
world.add_component(e1, Parent(parent));
|
||||
world.add_component(e2, Parent(parent));
|
||||
|
||||
// Run the needed systems on it.
|
||||
for system in hierarchy_maintenance_systems.iter() {
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
}
|
||||
local_to_parent_system.run(&mut world);
|
||||
local_to_parent_system
|
||||
.command_buffer_mut()
|
||||
.write(&mut world);
|
||||
local_to_world_system.run(&mut world);
|
||||
local_to_world_system.command_buffer_mut().write(&mut world);
|
||||
local_to_world_propagate_system.run(&mut world);
|
||||
local_to_world_propagate_system
|
||||
.command_buffer_mut()
|
||||
.write(&mut world);
|
||||
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToWorld>(e1).unwrap().0,
|
||||
Translation::new(1.0, 0.0, 0.0).to_homogeneous()
|
||||
* Translation::new(0.0, 2.0, 0.0).to_homogeneous()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToWorld>(e2).unwrap().0,
|
||||
Translation::new(1.0, 0.0, 0.0).to_homogeneous()
|
||||
* Translation::new(0.0, 0.0, 3.0).to_homogeneous()
|
||||
);
|
||||
}
|
||||
}
|
340
src/legion_transform/src/local_to_world_system.rs
Normal file
340
src/legion_transform/src/local_to_world_system.rs
Normal file
|
@ -0,0 +1,340 @@
|
|||
#![allow(dead_code)]
|
||||
use crate::{components::*, ecs::prelude::*, math::Matrix4};
|
||||
|
||||
pub fn build(_: &mut World) -> Box<dyn Schedulable> {
|
||||
SystemBuilder::<()>::new("LocalToWorldUpdateSystem")
|
||||
// Translation
|
||||
.with_query(<(Write<LocalToWorld>, Read<Translation>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Rotation>()
|
||||
& !component::<Scale>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Translation>()),
|
||||
))
|
||||
// Rotation
|
||||
.with_query(<(Write<LocalToWorld>, Read<Rotation>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Translation>()
|
||||
& !component::<Scale>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Rotation>()),
|
||||
))
|
||||
// Scale
|
||||
.with_query(<(Write<LocalToWorld>, Read<Scale>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Translation>()
|
||||
& !component::<Rotation>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Scale>()),
|
||||
))
|
||||
// NonUniformScale
|
||||
.with_query(
|
||||
<(Write<LocalToWorld>, Read<NonUniformScale>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Translation>()
|
||||
& !component::<Rotation>()
|
||||
& !component::<Scale>()
|
||||
& (changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Translation + Rotation
|
||||
.with_query(
|
||||
<(Write<LocalToWorld>, Read<Translation>, Read<Rotation>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Scale>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Translation>() | changed::<Rotation>()),
|
||||
),
|
||||
)
|
||||
// Translation + Scale
|
||||
.with_query(
|
||||
<(Write<LocalToWorld>, Read<Translation>, Read<Scale>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Rotation>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Translation>() | changed::<Scale>()),
|
||||
),
|
||||
)
|
||||
// Translation + NonUniformScale
|
||||
.with_query(
|
||||
<(
|
||||
Write<LocalToWorld>,
|
||||
Read<Translation>,
|
||||
Read<NonUniformScale>,
|
||||
)>::query()
|
||||
.filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Rotation>()
|
||||
& !component::<Scale>()
|
||||
& (changed::<Translation>() | changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Rotation + Scale
|
||||
.with_query(
|
||||
<(Write<LocalToWorld>, Read<Rotation>, Read<Scale>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Translation>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Rotation>() | changed::<Scale>()),
|
||||
),
|
||||
)
|
||||
// Rotation + NonUniformScale
|
||||
.with_query(
|
||||
<(Write<LocalToWorld>, Read<Rotation>, Read<NonUniformScale>)>::query().filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Translation>()
|
||||
& !component::<Scale>()
|
||||
& (changed::<Rotation>() | changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Translation + Rotation + Scale
|
||||
.with_query(
|
||||
<(
|
||||
Write<LocalToWorld>,
|
||||
Read<Translation>,
|
||||
Read<Rotation>,
|
||||
Read<Scale>,
|
||||
)>::query()
|
||||
.filter(
|
||||
!component::<Parent>()
|
||||
& !component::<NonUniformScale>()
|
||||
& (changed::<Translation>() | changed::<Rotation>() | changed::<Scale>()),
|
||||
),
|
||||
)
|
||||
// Translation + Rotation + NonUniformScale
|
||||
.with_query(
|
||||
<(
|
||||
Write<LocalToWorld>,
|
||||
Read<Translation>,
|
||||
Read<Rotation>,
|
||||
Read<NonUniformScale>,
|
||||
)>::query()
|
||||
.filter(
|
||||
!component::<Parent>()
|
||||
& !component::<Scale>()
|
||||
& (changed::<Translation>()
|
||||
| changed::<Rotation>()
|
||||
| changed::<NonUniformScale>()),
|
||||
),
|
||||
)
|
||||
// Just to issue warnings: Scale + NonUniformScale
|
||||
.with_query(
|
||||
<(Read<LocalToWorld>, Read<Scale>, Read<NonUniformScale>)>::query()
|
||||
.filter(!component::<Parent>()),
|
||||
)
|
||||
.build(move |_commands, world, _, queries| {
|
||||
let (a, b, c, d, e, f, g, h, i, j, k, l) = queries;
|
||||
rayon::scope(|s| {
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation
|
||||
a.for_each_unchecked(world, |(mut ltw, translation)| {
|
||||
*ltw = LocalToWorld(translation.to_homogeneous());
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Rotation
|
||||
b.for_each_unchecked(world, |(mut ltw, rotation)| {
|
||||
*ltw = LocalToWorld(rotation.to_homogeneous());
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Scale
|
||||
c.for_each_unchecked(world, |(mut ltw, scale)| {
|
||||
*ltw = LocalToWorld(Matrix4::new_scaling(scale.0));
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// NonUniformScale
|
||||
d.for_each_unchecked(world, |(mut ltw, non_uniform_scale)| {
|
||||
*ltw = LocalToWorld(Matrix4::new_nonuniform_scaling(&non_uniform_scale.0));
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + Rotation
|
||||
e.for_each_unchecked(world, |(mut ltw, translation, rotation)| {
|
||||
*ltw = LocalToWorld(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.append_translation(&translation.vector),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + Scale
|
||||
f.for_each_unchecked(world, |(mut ltw, translation, scale)| {
|
||||
*ltw = LocalToWorld(translation.to_homogeneous().prepend_scaling(scale.0));
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + NonUniformScale
|
||||
g.for_each_unchecked(world, |(mut ltw, translation, non_uniform_scale)| {
|
||||
*ltw = LocalToWorld(
|
||||
translation
|
||||
.to_homogeneous()
|
||||
.prepend_nonuniform_scaling(&non_uniform_scale.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Rotation + Scale
|
||||
h.for_each_unchecked(world, |(mut ltw, rotation, scale)| {
|
||||
*ltw = LocalToWorld(rotation.to_homogeneous().prepend_scaling(scale.0));
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Rotation + NonUniformScale
|
||||
i.for_each_unchecked(world, |(mut ltw, rotation, non_uniform_scale)| {
|
||||
*ltw = LocalToWorld(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.prepend_nonuniform_scaling(&non_uniform_scale.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + Rotation + Scale
|
||||
j.for_each_unchecked(world, |(mut ltw, translation, rotation, scale)| {
|
||||
*ltw = LocalToWorld(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.append_translation(&translation.vector)
|
||||
.prepend_scaling(scale.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
s.spawn(|_| unsafe {
|
||||
// Translation + Rotation + NonUniformScale
|
||||
k.for_each_unchecked(
|
||||
world,
|
||||
|(mut ltw, translation, rotation, non_uniform_scale)| {
|
||||
*ltw = LocalToWorld(
|
||||
rotation
|
||||
.to_homogeneous()
|
||||
.append_translation(&translation.vector)
|
||||
.prepend_nonuniform_scaling(&non_uniform_scale.0),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Just to issue warnings: Scale + NonUniformScale
|
||||
unsafe {
|
||||
l.iter_entities_immutable(world).for_each(
|
||||
|(entity, (mut _ltw, _scale, _non_uniform_scale))| {
|
||||
log::warn!(
|
||||
"Entity {:?} has both a Scale and NonUniformScale component.",
|
||||
entity
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn correct_world_transformation() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
|
||||
let mut world = Universe::new().create_world();
|
||||
let system = build(&mut world);
|
||||
|
||||
let ltw = LocalToWorld::identity();
|
||||
let t = Translation::new(1.0, 2.0, 3.0);
|
||||
let r = Rotation::from_euler_angles(1.0, 2.0, 3.0);
|
||||
let s = Scale(2.0);
|
||||
let nus = NonUniformScale::new(1.0, 2.0, 3.0);
|
||||
|
||||
// Add every combination of transform types.
|
||||
let translation = *world.insert((), vec![(ltw, t)]).first().unwrap();
|
||||
let rotation = *world.insert((), vec![(ltw, r)]).first().unwrap();
|
||||
let scale = *world.insert((), vec![(ltw, s)]).first().unwrap();
|
||||
let non_uniform_scale = *world.insert((), vec![(ltw, nus)]).first().unwrap();
|
||||
let translation_and_rotation = *world.insert((), vec![(ltw, t, r)]).first().unwrap();
|
||||
let translation_and_scale = *world.insert((), vec![(ltw, t, s)]).first().unwrap();
|
||||
let translation_and_nus = *world.insert((), vec![(ltw, t, nus)]).first().unwrap();
|
||||
let rotation_scale = *world.insert((), vec![(ltw, r, s)]).first().unwrap();
|
||||
let rotation_nus = *world.insert((), vec![(ltw, r, nus)]).first().unwrap();
|
||||
let translation_rotation_scale = *world.insert((), vec![(ltw, t, r, s)]).first().unwrap();
|
||||
let translation_rotation_nus = *world.insert((), vec![(ltw, t, r, nus)]).first().unwrap();
|
||||
|
||||
// Run the system
|
||||
system.run(&mut world);
|
||||
system.command_buffer_mut().write(&mut world);
|
||||
|
||||
// Verify that each was transformed correctly.
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToWorld>(translation).unwrap().0,
|
||||
t.to_homogeneous()
|
||||
);
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToWorld>(rotation).unwrap().0,
|
||||
r.to_homogeneous()
|
||||
);
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToWorld>(scale).unwrap().0,
|
||||
Matrix4::new_scaling(s.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToWorld>(non_uniform_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
Matrix4::new_nonuniform_scaling(&nus.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToWorld>(translation_and_rotation)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous().append_translation(&t.vector),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToWorld>(translation_and_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
t.to_homogeneous().prepend_scaling(s.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToWorld>(translation_and_nus)
|
||||
.unwrap()
|
||||
.0,
|
||||
t.to_homogeneous().prepend_nonuniform_scaling(&nus.0),
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToWorld>(rotation_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous().prepend_scaling(s.0)
|
||||
);
|
||||
assert_eq!(
|
||||
world.get_component::<LocalToWorld>(rotation_nus).unwrap().0,
|
||||
r.to_homogeneous().prepend_nonuniform_scaling(&nus.0)
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToWorld>(translation_rotation_scale)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous()
|
||||
.append_translation(&t.vector)
|
||||
.prepend_scaling(s.0)
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.get_component::<LocalToWorld>(translation_rotation_nus)
|
||||
.unwrap()
|
||||
.0,
|
||||
r.to_homogeneous()
|
||||
.append_translation(&t.vector)
|
||||
.prepend_nonuniform_scaling(&nus.0)
|
||||
);
|
||||
}
|
||||
}
|
20
src/legion_transform/src/transform_system_bundle.rs
Normal file
20
src/legion_transform/src/transform_system_bundle.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use crate::{
|
||||
ecs::prelude::*, hierarchy_maintenance_system, local_to_parent_system,
|
||||
local_to_world_propagate_system, local_to_world_system,
|
||||
};
|
||||
|
||||
pub fn build(world: &mut World) -> Vec<Box<dyn Schedulable>> {
|
||||
let mut all_systems = Vec::with_capacity(5);
|
||||
|
||||
let mut hierarchy_maintenance_systems = hierarchy_maintenance_system::build(world);
|
||||
let local_to_parent_system = local_to_parent_system::build(world);
|
||||
let local_to_world_system = local_to_world_system::build(world);
|
||||
let local_to_world_propagate_system = local_to_world_propagate_system::build(world);
|
||||
|
||||
all_systems.append(&mut hierarchy_maintenance_systems);
|
||||
all_systems.push(local_to_parent_system);
|
||||
all_systems.push(local_to_world_system);
|
||||
all_systems.push(local_to_world_propagate_system);
|
||||
|
||||
all_systems
|
||||
}
|
11
src/lib.rs
11
src/lib.rs
|
@ -1,11 +1,16 @@
|
|||
mod core;
|
||||
pub mod render;
|
||||
pub mod asset;
|
||||
mod application;
|
||||
mod vertex;
|
||||
mod temp;
|
||||
mod render;
|
||||
pub mod temp;
|
||||
mod core;
|
||||
|
||||
pub use application::Application;
|
||||
pub use crate::core::*;
|
||||
|
||||
pub use wgpu;
|
||||
pub use legion;
|
||||
pub use legion_transform;
|
||||
pub use legion::prelude::*;
|
||||
pub use legion_transform::prelude::*;
|
||||
pub use nalgebra_glm as math;
|
|
@ -1,5 +1,9 @@
|
|||
use crate::math;
|
||||
|
||||
pub struct Camera {
|
||||
projection: math::Mat4,
|
||||
}
|
||||
|
||||
pub fn get_projection_view_matrix(eye: &math::Vec3, fov: f32, aspect_ratio: f32, near: f32, far: f32) -> math::Mat4 {
|
||||
let projection = math::perspective(aspect_ratio, fov, near, far);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{render::*, temp::*};
|
||||
use crate::{render::*, temp::*, asset::*, render::mesh::*};
|
||||
use legion::prelude::*;
|
||||
use std::{mem, sync::Arc};
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
@ -21,7 +21,7 @@ pub struct ForwardPass {
|
|||
|
||||
impl Pass for ForwardPass {
|
||||
fn render(&mut self, device: &Device, frame: &SwapChainOutput, encoder: &mut CommandEncoder, world: &mut World) {
|
||||
let mut entities = <Read<CubeEnt>>::query();
|
||||
let mut mesh_query = <(Read<CubeEnt>, Read<Handle<Mesh>>)>::query();
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &frame.view,
|
||||
|
@ -48,11 +48,16 @@ impl Pass for ForwardPass {
|
|||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
|
||||
for entity in entities.iter_immutable(world) {
|
||||
pass.set_bind_group(1, &entity.bind_group, &[]);
|
||||
pass.set_index_buffer(&entity.index_buf, 0);
|
||||
pass.set_vertex_buffers(0, &[(&entity.vertex_buf, 0)]);
|
||||
pass.draw_indexed(0 .. entity.index_count as u32, 0, 0 .. 1);
|
||||
let mut mesh_storage = world.resources.get_mut::<AssetStorage<Mesh, MeshType>>().unwrap();
|
||||
for (entity, mesh) in mesh_query.iter_immutable(world) {
|
||||
if let Some(mesh_asset) = mesh_storage.get(*mesh.id) {
|
||||
mesh_asset.setup_buffers(device);
|
||||
pass.set_bind_group(1, entity.bind_group.as_ref().unwrap(), &[]);
|
||||
pass.set_index_buffer(mesh_asset.index_buffer.as_ref().unwrap(), 0);
|
||||
pass.set_vertex_buffers(0, &[(&mesh_asset.vertex_buffer.as_ref().unwrap(), 0)]);
|
||||
pass.draw_indexed(0 .. mesh_asset.indices.len() as u32, 0, 0 .. 1);
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
111
src/render/mesh.rs
Normal file
111
src/render/mesh.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use crate::{vertex::Vertex, asset::Asset};
|
||||
use wgpu::{Buffer, Device};
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
||||
pub enum MeshType {
|
||||
Cube,
|
||||
Plane {
|
||||
size: i8
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mesh {
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub indices: Vec<u16>,
|
||||
pub vertex_buffer: Option<Buffer>,
|
||||
pub index_buffer: Option<Buffer>,
|
||||
}
|
||||
|
||||
impl Mesh {
|
||||
pub fn setup_buffers(&mut self, device: &Device) {
|
||||
if let None = self.vertex_buffer {
|
||||
self.vertex_buffer = Some(device.create_buffer_with_data(self.vertices.as_bytes(), wgpu::BufferUsage::VERTEX));
|
||||
}
|
||||
|
||||
if let None = self.index_buffer {
|
||||
self.index_buffer = Some(device.create_buffer_with_data(self.indices.as_bytes(), wgpu::BufferUsage::INDEX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset<MeshType> for Mesh {
|
||||
fn load(descriptor: MeshType) -> Self {
|
||||
let (vertices, indices) = match descriptor {
|
||||
MeshType::Cube => create_cube(),
|
||||
MeshType::Plane { size } => create_plane(size),
|
||||
};
|
||||
|
||||
Mesh {
|
||||
vertices,
|
||||
indices,
|
||||
vertex_buffer: None,
|
||||
index_buffer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vertex(pos: [i8; 3], nor: [i8; 3]) -> Vertex {
|
||||
Vertex {
|
||||
pos: [pos[0], pos[1], pos[2], 1],
|
||||
normal: [nor[0], nor[1], nor[2], 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cube() -> (Vec<Vertex>, Vec<u16>) {
|
||||
let vertex_data = [
|
||||
// top (0, 0, 1)
|
||||
vertex([-1, -1, 1], [0, 0, 1]),
|
||||
vertex([1, -1, 1], [0, 0, 1]),
|
||||
vertex([1, 1, 1], [0, 0, 1]),
|
||||
vertex([-1, 1, 1], [0, 0, 1]),
|
||||
// bottom (0, 0, -1)
|
||||
vertex([-1, 1, -1], [0, 0, -1]),
|
||||
vertex([1, 1, -1], [0, 0, -1]),
|
||||
vertex([1, -1, -1], [0, 0, -1]),
|
||||
vertex([-1, -1, -1], [0, 0, -1]),
|
||||
// right (1, 0, 0)
|
||||
vertex([1, -1, -1], [1, 0, 0]),
|
||||
vertex([1, 1, -1], [1, 0, 0]),
|
||||
vertex([1, 1, 1], [1, 0, 0]),
|
||||
vertex([1, -1, 1], [1, 0, 0]),
|
||||
// left (-1, 0, 0)
|
||||
vertex([-1, -1, 1], [-1, 0, 0]),
|
||||
vertex([-1, 1, 1], [-1, 0, 0]),
|
||||
vertex([-1, 1, -1], [-1, 0, 0]),
|
||||
vertex([-1, -1, -1], [-1, 0, 0]),
|
||||
// front (0, 1, 0)
|
||||
vertex([1, 1, -1], [0, 1, 0]),
|
||||
vertex([-1, 1, -1], [0, 1, 0]),
|
||||
vertex([-1, 1, 1], [0, 1, 0]),
|
||||
vertex([1, 1, 1], [0, 1, 0]),
|
||||
// back (0, -1, 0)
|
||||
vertex([1, -1, 1], [0, -1, 0]),
|
||||
vertex([-1, -1, 1], [0, -1, 0]),
|
||||
vertex([-1, -1, -1], [0, -1, 0]),
|
||||
vertex([1, -1, -1], [0, -1, 0]),
|
||||
];
|
||||
|
||||
let index_data: &[u16] = &[
|
||||
0, 1, 2, 2, 3, 0, // top
|
||||
4, 5, 6, 6, 7, 4, // bottom
|
||||
8, 9, 10, 10, 11, 8, // right
|
||||
12, 13, 14, 14, 15, 12, // left
|
||||
16, 17, 18, 18, 19, 16, // front
|
||||
20, 21, 22, 22, 23, 20, // back
|
||||
];
|
||||
|
||||
(vertex_data.to_vec(), index_data.to_vec())
|
||||
}
|
||||
|
||||
pub fn create_plane(size: i8) -> (Vec<Vertex>, Vec<u16>) {
|
||||
let vertex_data = [
|
||||
vertex([size, -size, 0], [0, 0, 1]),
|
||||
vertex([size, size, 0], [0, 0, 1]),
|
||||
vertex([-size, -size, 0], [0, 0, 1]),
|
||||
vertex([-size, size, 0], [0, 0, 1]),
|
||||
];
|
||||
|
||||
let index_data: &[u16] = &[0, 1, 2, 2, 1, 3];
|
||||
|
||||
(vertex_data.to_vec(), index_data.to_vec())
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
pub mod camera;
|
||||
pub mod shader;
|
||||
pub mod mesh;
|
||||
mod forward;
|
||||
mod shadow;
|
||||
mod light;
|
||||
|
@ -11,7 +12,13 @@ pub use light::*;
|
|||
pub use shader::*;
|
||||
pub use pass::*;
|
||||
|
||||
use wgpu::BindGroup;
|
||||
|
||||
pub struct UniformBuffer {
|
||||
pub buffer: wgpu::Buffer,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
pub struct Rendered {
|
||||
pub bind_group: Option<BindGroup>,
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{render::*, temp::*};
|
||||
use crate::{render::*, temp::*, asset::*, render::mesh::*};
|
||||
use wgpu::{BindGroupLayout, CommandEncoder, Device, VertexBufferDescriptor, SwapChainOutput};
|
||||
use legion::prelude::*;
|
||||
use zerocopy::AsBytes;
|
||||
|
@ -23,7 +23,7 @@ pub struct ShadowUniforms {
|
|||
impl Pass for ShadowPass {
|
||||
fn render(&mut self, device: &Device, _: &SwapChainOutput, encoder: &mut CommandEncoder, world: &mut World) {
|
||||
let mut light_query = <Read<Light>>::query();
|
||||
let mut entity_query = <Read<CubeEnt>>::query();
|
||||
let mut mesh_query = <(Read<CubeEnt>, Read<Handle<Mesh>>)>::query();
|
||||
let light_count = light_query.iter(world).count();
|
||||
|
||||
if self.lights_are_dirty {
|
||||
|
@ -73,11 +73,17 @@ impl Pass for ShadowPass {
|
|||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
|
||||
for entity in entity_query.iter_immutable(world) {
|
||||
pass.set_bind_group(1, &entity.bind_group, &[]);
|
||||
pass.set_index_buffer(&entity.index_buf, 0);
|
||||
pass.set_vertex_buffers(0, &[(&entity.vertex_buf, 0)]);
|
||||
pass.draw_indexed(0 .. entity.index_count as u32, 0, 0 .. 1);
|
||||
let mut mesh_storage = world.resources.get_mut::<AssetStorage<Mesh, MeshType>>().unwrap();
|
||||
for (entity, mesh) in mesh_query.iter_immutable(world) {
|
||||
if let Some(mut mesh_asset) = mesh_storage.get(*mesh.id) {
|
||||
mesh_asset.setup_buffers(device);
|
||||
|
||||
pass.set_bind_group(1, entity.bind_group.as_ref().unwrap(), &[]);
|
||||
pass.set_index_buffer(&mesh_asset.index_buffer.as_ref().unwrap(), 0);
|
||||
pass.set_vertex_buffers(0, &[(&mesh_asset.vertex_buffer.as_ref().unwrap(), 0)]);
|
||||
pass.draw_indexed(0 .. mesh_asset.indices.len() as u32, 0, 0 .. 1);
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
src/temp.rs
35
src/temp.rs
|
@ -1,14 +1,11 @@
|
|||
use std::{sync::Arc};
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
use crate::math;
|
||||
|
||||
pub struct CubeEnt {
|
||||
pub rotation_speed: f32,
|
||||
pub color: wgpu::Color,
|
||||
pub vertex_buf: Arc<wgpu::Buffer>,
|
||||
pub index_buf: Arc<wgpu::Buffer>,
|
||||
pub index_count: usize,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
pub uniform_buf: wgpu::Buffer,
|
||||
pub color: math::Vec4,
|
||||
pub bind_group: Option<wgpu::BindGroup>,
|
||||
pub uniform_buf: Option<wgpu::Buffer>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -16,4 +13,28 @@ pub struct CubeEnt {
|
|||
pub struct EntityUniforms {
|
||||
pub model: [[f32; 4]; 4],
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_texels(size: usize) -> Vec<u8> {
|
||||
use std::iter;
|
||||
|
||||
(0 .. size * size)
|
||||
.flat_map(|id| {
|
||||
// get high five for recognizing this ;)
|
||||
let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
|
||||
let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
|
||||
let (mut x, mut y, mut count) = (cx, cy, 0);
|
||||
while count < 0xFF && x * x + y * y < 4.0 {
|
||||
let old_x = x;
|
||||
x = x * x - y * y + cx;
|
||||
y = 2.0 * old_x * y + cy;
|
||||
count += 1;
|
||||
}
|
||||
iter::once(0xFF - (count * 5) as u8)
|
||||
.chain(iter::once(0xFF - (count * 15) as u8))
|
||||
.chain(iter::once(0xFF - (count * 50) as u8))
|
||||
.chain(iter::once(1))
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -5,94 +5,4 @@ use zerocopy::{AsBytes, FromBytes};
|
|||
pub struct Vertex {
|
||||
pub pos: [i8; 4],
|
||||
pub normal: [i8; 4],
|
||||
}
|
||||
|
||||
pub fn vertex(pos: [i8; 3], nor: [i8; 3]) -> Vertex {
|
||||
Vertex {
|
||||
pos: [pos[0], pos[1], pos[2], 1],
|
||||
normal: [nor[0], nor[1], nor[2], 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cube() -> (Vec<Vertex>, Vec<u16>) {
|
||||
let vertex_data = [
|
||||
// top (0, 0, 1)
|
||||
vertex([-1, -1, 1], [0, 0, 1]),
|
||||
vertex([1, -1, 1], [0, 0, 1]),
|
||||
vertex([1, 1, 1], [0, 0, 1]),
|
||||
vertex([-1, 1, 1], [0, 0, 1]),
|
||||
// bottom (0, 0, -1)
|
||||
vertex([-1, 1, -1], [0, 0, -1]),
|
||||
vertex([1, 1, -1], [0, 0, -1]),
|
||||
vertex([1, -1, -1], [0, 0, -1]),
|
||||
vertex([-1, -1, -1], [0, 0, -1]),
|
||||
// right (1, 0, 0)
|
||||
vertex([1, -1, -1], [1, 0, 0]),
|
||||
vertex([1, 1, -1], [1, 0, 0]),
|
||||
vertex([1, 1, 1], [1, 0, 0]),
|
||||
vertex([1, -1, 1], [1, 0, 0]),
|
||||
// left (-1, 0, 0)
|
||||
vertex([-1, -1, 1], [-1, 0, 0]),
|
||||
vertex([-1, 1, 1], [-1, 0, 0]),
|
||||
vertex([-1, 1, -1], [-1, 0, 0]),
|
||||
vertex([-1, -1, -1], [-1, 0, 0]),
|
||||
// front (0, 1, 0)
|
||||
vertex([1, 1, -1], [0, 1, 0]),
|
||||
vertex([-1, 1, -1], [0, 1, 0]),
|
||||
vertex([-1, 1, 1], [0, 1, 0]),
|
||||
vertex([1, 1, 1], [0, 1, 0]),
|
||||
// back (0, -1, 0)
|
||||
vertex([1, -1, 1], [0, -1, 0]),
|
||||
vertex([-1, -1, 1], [0, -1, 0]),
|
||||
vertex([-1, -1, -1], [0, -1, 0]),
|
||||
vertex([1, -1, -1], [0, -1, 0]),
|
||||
];
|
||||
|
||||
let index_data: &[u16] = &[
|
||||
0, 1, 2, 2, 3, 0, // top
|
||||
4, 5, 6, 6, 7, 4, // bottom
|
||||
8, 9, 10, 10, 11, 8, // right
|
||||
12, 13, 14, 14, 15, 12, // left
|
||||
16, 17, 18, 18, 19, 16, // front
|
||||
20, 21, 22, 22, 23, 20, // back
|
||||
];
|
||||
|
||||
(vertex_data.to_vec(), index_data.to_vec())
|
||||
}
|
||||
|
||||
pub fn create_plane(size: i8) -> (Vec<Vertex>, Vec<u16>) {
|
||||
let vertex_data = [
|
||||
vertex([size, -size, 0], [0, 0, 1]),
|
||||
vertex([size, size, 0], [0, 0, 1]),
|
||||
vertex([-size, -size, 0], [0, 0, 1]),
|
||||
vertex([-size, size, 0], [0, 0, 1]),
|
||||
];
|
||||
|
||||
let index_data: &[u16] = &[0, 1, 2, 2, 1, 3];
|
||||
|
||||
(vertex_data.to_vec(), index_data.to_vec())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_texels(size: usize) -> Vec<u8> {
|
||||
use std::iter;
|
||||
|
||||
(0 .. size * size)
|
||||
.flat_map(|id| {
|
||||
// get high five for recognizing this ;)
|
||||
let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
|
||||
let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
|
||||
let (mut x, mut y, mut count) = (cx, cy, 0);
|
||||
while count < 0xFF && x * x + y * y < 4.0 {
|
||||
let old_x = x;
|
||||
x = x * x - y * y + cx;
|
||||
y = 2.0 * old_x * y + cy;
|
||||
count += 1;
|
||||
}
|
||||
iter::once(0xFF - (count * 5) as u8)
|
||||
.chain(iter::once(0xFF - (count * 15) as u8))
|
||||
.chain(iter::once(0xFF - (count * 50) as u8))
|
||||
.chain(iter::once(1))
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -1,8 +1,19 @@
|
|||
use bevy::*;
|
||||
use bevy::legion::prelude::*;
|
||||
use bevy::{render::mesh::{Mesh, MeshType}, asset::{Asset, AssetStorage}, temp::*, math};
|
||||
|
||||
fn main() {
|
||||
let universe = Universe::new();
|
||||
let mut world = universe.create_world();
|
||||
// Create a query which finds all `Position` and `Velocity` components
|
||||
// let mut query = Read::<Transform>::query();
|
||||
Application::run();
|
||||
let cube = Mesh::load(MeshType::Cube);
|
||||
let mut mesh_storage = AssetStorage::<Mesh, MeshType>::new();
|
||||
let handle = mesh_storage.add(cube);
|
||||
world.resources.insert(mesh_storage);
|
||||
|
||||
world.insert((), vec![
|
||||
(CubeEnt { color: math::Vec4::identity(), bind_group: None, uniform_buf: None }, handle, LocalToWorld::identity(), Translation::new(0.0, 0.0, 0.0))
|
||||
]);
|
||||
|
||||
Application::run(universe, world);
|
||||
}
|
Loading…
Reference in a new issue