asset loading, hierarchies, more refactoring

This commit is contained in:
Carter Anderson 2019-12-02 01:31:07 -08:00
parent 188d355d10
commit a96f3cfda5
38 changed files with 2195 additions and 240 deletions

View file

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

View file

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

View file

@ -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,66 +44,16 @@ impl Application {
}],
});
let mut entities = vec![{
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,
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())
}];
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 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,
});
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 {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &local_bind_group_layout,
bindings: &[wgpu::Binding {
binding: 0,
@ -133,12 +62,14 @@ impl Application {
range: 0 .. entity_uniform_size,
},
}],
}),
uniform_buf,
}, Transform { value: math::translation(&cube.offset)}));
});
entity.bind_group = Some(bind_group);
entity.uniform_buf = Some(uniform_buf);
}
world.insert((), entities);
let camera_position = math::vec3(3.0f32, -10.0, 6.0);
let camera_fov = math::quarter_pi();
let vb_desc = wgpu::VertexBufferDescriptor {
stride: vertex_size as wgpu::BufferAddress,
@ -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
View 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
}
}
}
}

View file

@ -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"),
}
}
}

View file

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

View file

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

View 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"

View 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.

View 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)

View 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);
});
}

View 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()
);
}

View 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
);
}
}

View 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))
}
}

View 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)
}
}

View 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)
}
}

View 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::*;

View 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
)
}
}

View 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>);

View 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)
}
}

View 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)
}
}

View 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)
}
}

View 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]
);
}
}

View 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;
}

View 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)
);
}
}

View 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()
);
}
}

View 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)
);
}
}

View 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
}

View file

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

View file

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

View file

@ -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
View 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())
}

View file

@ -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>,
}

View file

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

View file

@ -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)]
@ -17,3 +14,27 @@ 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()
}

View file

@ -6,93 +6,3 @@ 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()
}

View file

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