Node tree structure

This commit is contained in:
Carter Anderson 2020-01-12 22:18:17 -08:00
parent dd34c1e237
commit ae325846ff
8 changed files with 175 additions and 69 deletions

View File

@ -17,6 +17,7 @@ fn setup(world: &mut World) {
mesh_storage.add(cube) mesh_storage.add(cube)
}; };
// cube
world.insert( world.insert(
(), (),
vec![( vec![(
@ -26,6 +27,7 @@ fn setup(world: &mut World) {
)], )],
); );
// light
world.insert( world.insert(
(), (),
vec![( vec![(
@ -46,6 +48,7 @@ fn setup(world: &mut World) {
)], )],
); );
// 3d camera
world.insert( world.insert(
(), (),
vec![ vec![
@ -67,26 +70,23 @@ fn setup(world: &mut World) {
], ],
); );
// 2d camera
world.insert( world.insert(
(), (),
vec![ vec![(
// camera Camera::new(CameraType::Orthographic {
( left: 0.0,
Camera::new(CameraType::Orthographic { right: 0.0,
left: 0.0, bottom: 0.0,
right: 0.0, top: 0.0,
bottom: 0.0, near: 0.0,
top: 0.0, far: 1.0,
near: 0.0, }),
far: 1.0, ActiveCamera2d,
}), )],
ActiveCamera2d,
),
],
); );
// bottom left anchor with vertical fill // bottom left anchor with vertical fill
world.insert( world.insert(
(), (),
vec![(Node::new( vec![(Node::new(
@ -98,7 +98,6 @@ fn setup(world: &mut World) {
); );
// top right anchor with vertical fill // top right anchor with vertical fill
world.insert( world.insert(
(), (),
vec![(Node::new( vec![(Node::new(
@ -110,7 +109,6 @@ fn setup(world: &mut World) {
); );
// render order test: reddest in the back, whitest in the front // render order test: reddest in the back, whitest in the front
world.insert( world.insert(
(), (),
vec![(Node::new( vec![(Node::new(
@ -150,4 +148,31 @@ fn setup(world: &mut World) {
math::vec4(1.0, 0.7, 0.7, 1.0), math::vec4(1.0, 0.7, 0.7, 1.0),
),)], ),)],
); );
// parenting
let parent = *world
.insert(
(),
vec![(Node::new(
math::vec2(300.0, 300.0),
Anchors::new(0.0, 0.0, 0.0, 0.0),
Margins::new(0.0, 200.0, 0.0, 200.0),
math::vec4(0.1, 0.1, 1.0, 1.0),
),)],
)
.first()
.unwrap();
world.insert(
(),
vec![(
Node::new(
math::vec2(0.0, 0.0),
Anchors::new(0.0, 1.0, 0.0, 1.0),
Margins::new(20.0, 20.0, 20.0, 20.0),
math::vec4(0.6, 0.6, 1.0, 1.0),
),
Parent(parent),
)],
);
} }

60
src/ecs/mod.rs Normal file
View File

@ -0,0 +1,60 @@
use crate::{legion::system::SubWorld, Children, Entity, World};
pub fn run_on_hierarchy<T>(
world: &mut World,
entity: Entity,
input: T,
func: &mut dyn FnMut(&mut World, Entity, T) -> Option<T>,
) where
T: Copy,
{
// TODO: not a huge fan of this pattern. are there ways to do recursive updates in legion without allocactions?
let children = match world.get_component::<Children>(entity) {
Some(children) => Some(
children
.iter()
.map(|entity| *entity)
.collect::<Vec<Entity>>(),
),
None => None,
};
let result = func(world, entity, input);
if let Some(result) = result {
if let Some(children) = children {
for child in children {
run_on_hierarchy(world, child, result, func);
}
}
}
}
pub fn run_on_hierarchy_subworld<T>(
world: &mut legion::system::SubWorld,
entity: Entity,
input: T,
func: &dyn Fn(&mut SubWorld, Entity, T) -> Option<T>,
) where
T: Copy,
{
// TODO: not a huge fan of this pattern. are there ways to do recursive updates in legion without allocactions?
let children = match world.get_component::<Children>(entity) {
Some(children) => Some(
children
.iter()
.map(|entity| *entity)
.collect::<Vec<Entity>>(),
),
None => None,
};
let result = func(world, entity, input);
if let Some(result) = result {
if let Some(children) = children {
for child in children {
run_on_hierarchy_subworld(world, child, result, func);
}
}
}
}

View File

@ -1,12 +1,14 @@
mod app; mod app;
pub mod asset; pub mod asset;
mod core; mod core;
pub mod ecs;
pub mod render; pub mod render;
pub mod ui; pub mod ui;
pub use crate::core::*; pub use crate::core::*;
pub use app::{App, AppBuilder}; pub use app::{App, AppBuilder};
pub use ecs::*;
pub use glam as math; pub use glam as math;
pub use legion; pub use legion;
pub use legion::prelude::*; pub use legion::prelude::*;

View File

@ -1,9 +1,10 @@
use crate::{ use crate::{
asset::*, asset::*,
math, ecs, math,
render::mesh::*, render::mesh::*,
render::{instancing::InstanceBufferInfo, *}, render::{instancing::InstanceBufferInfo, *},
ui::Node, ui::Node,
Parent,
}; };
use legion::prelude::*; use legion::prelude::*;
use wgpu::SwapChainOutput; use wgpu::SwapChainOutput;
@ -13,7 +14,7 @@ use zerocopy::{AsBytes, FromBytes};
#[derive(Clone, Copy, AsBytes, FromBytes)] #[derive(Clone, Copy, AsBytes, FromBytes)]
pub struct RectData { pub struct RectData {
pub position: [f32; 2], pub position: [f32; 2],
pub dimensions: [f32; 2], pub size: [f32; 2],
pub color: [f32; 4], pub color: [f32; 4],
pub z_index: f32, pub z_index: f32,
} }
@ -38,27 +39,39 @@ impl UiPipeline {
pub fn create_rect_buffers( pub fn create_rect_buffers(
&self, &self,
device: &wgpu::Device, device: &wgpu::Device,
world: &World, world: &mut World,
) -> Vec<InstanceBufferInfo> { ) -> Vec<InstanceBufferInfo> {
let node_query = <Read<Node>>::query(); let node_query = <Read<Node>>::query().filter(!component::<Parent>());
let node_count = node_query.iter(world).count();
if node_count == 0 { if node_query.iter(world).count() == 0 {
return Vec::new(); return Vec::new();
} }
let mut data = Vec::with_capacity(node_count); let mut data = Vec::new();
// TODO: this probably isn't the best way to handle z-ordering // TODO: this probably isn't the best way to handle z-ordering
let mut z = 0.9999; let mut z = 0.9999;
for node in node_query.iter(world) { {
data.push(RectData { let mut add_data: Box<dyn FnMut(&mut World, Entity, ()) -> Option<()>> =
position: node.global_position.into(), Box::new(|world, entity, _| {
dimensions: node.dimensions.into(), let node = world.get_component::<Node>(entity).unwrap();
color: node.color.into(), data.push(RectData {
z_index: z, position: node.global_position.into(),
}); size: node.size.into(),
color: node.color.into(),
z_index: z,
});
z -= 0.0001; z -= 0.0001;
Some(())
});
for entity in node_query
.iter_entities(world)
.map(|(entity, _)| entity)
.collect::<Vec<Entity>>()
{
ecs::run_on_hierarchy(world, entity, (), &mut add_data);
}
} }
let buffer = device.create_buffer_with_data( let buffer = device.create_buffer_with_data(
@ -72,7 +85,7 @@ impl UiPipeline {
instance_buffer_infos.push(InstanceBufferInfo { instance_buffer_infos.push(InstanceBufferInfo {
mesh_id: mesh_id, mesh_id: mesh_id,
buffer: buffer, buffer: buffer,
instance_count: node_count, instance_count: data.len(),
}); });
instance_buffer_infos instance_buffer_infos

View File

@ -6,7 +6,7 @@ layout(location = 1) in vec4 a_Normal;
// instanced attributes (RectData) // instanced attributes (RectData)
layout (location = 2) in vec2 a_RectPosition; layout (location = 2) in vec2 a_RectPosition;
layout (location = 3) in vec2 a_RectDimensions; layout (location = 3) in vec2 a_RectSize;
layout (location = 4) in vec4 a_RectColor; layout (location = 4) in vec4 a_RectColor;
layout (location = 5) in float a_RectZIndex; layout (location = 5) in float a_RectZIndex;
@ -18,7 +18,7 @@ layout(set = 0, binding = 0) uniform Globals {
void main() { void main() {
v_Color = a_RectColor; v_Color = a_RectColor;
vec4 position = a_Pos * vec4(a_RectDimensions, 0.0, 1.0); vec4 position = a_Pos * vec4(a_RectSize, 0.0, 1.0);
position = position + vec4(a_RectPosition, -a_RectZIndex, 0.0); position = position + vec4(a_RectPosition + a_RectSize / 2.0, -a_RectZIndex, 0.0);
gl_Position = u_ViewProj * position; gl_Position = u_ViewProj * position;
} }

View File

@ -7,9 +7,7 @@ pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
let missing_previous_parent_system = SystemBuilder::<()>::new("MissingPreviousParentSystem") let missing_previous_parent_system = SystemBuilder::<()>::new("MissingPreviousParentSystem")
// Entities with missing `PreviousParent` // Entities with missing `PreviousParent`
.with_query(<Read<Parent>>::query().filter( .with_query(<Read<Parent>>::query().filter(
component::<LocalToParent>() !component::<PreviousParent>(),
& component::<LocalToWorld>()
& !component::<PreviousParent>(),
)) ))
.build(move |commands, world, _resource, query| { .build(move |commands, world, _resource, query| {
// Add missing `PreviousParent` components // Add missing `PreviousParent` components
@ -24,10 +22,9 @@ pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
.with_query(<Read<PreviousParent>>::query().filter(!component::<Parent>())) .with_query(<Read<PreviousParent>>::query().filter(!component::<Parent>()))
// Entities with a changed `Parent` // Entities with a changed `Parent`
.with_query(<(Read<Parent>, Write<PreviousParent>)>::query().filter( .with_query(<(Read<Parent>, Write<PreviousParent>)>::query().filter(
component::<LocalToParent>() & component::<LocalToWorld>() & changed::<Parent>(), changed::<Parent>(),
)) ))
// Deleted Parents (ie Entities with `Children` and without a `LocalToWorld`). // Deleted Parents (ie Entities with `Children` and without a `LocalToWorld`).
.with_query(<Read<Children>>::query().filter(!component::<LocalToWorld>()))
.write_component::<Children>() .write_component::<Children>()
.build(move |commands, world, _resource, queries| { .build(move |commands, world, _resource, queries| {
// Entities with a missing `Parent` (ie. ones that have a `PreviousParent`), remove // Entities with a missing `Parent` (ie. ones that have a `PreviousParent`), remove
@ -96,22 +93,6 @@ pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
} }
} }
// 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 // 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 // collect multiple new children that point to the same parent into the same
// SmallVec, and to prevent redundant add+remove operations. // SmallVec, and to prevent redundant add+remove operations.

View File

@ -12,7 +12,7 @@ enum GrowDirection {
pub struct Node { pub struct Node {
pub position: Vec2, pub position: Vec2,
pub global_position: Vec2, pub global_position: Vec2,
pub dimensions: Vec2, pub size: Vec2,
pub parent_dimensions: Vec2, pub parent_dimensions: Vec2,
pub anchors: Anchors, pub anchors: Anchors,
pub margins: Margins, pub margins: Margins,
@ -24,7 +24,7 @@ impl Default for Node {
Node { Node {
position: Vec2::default(), position: Vec2::default(),
global_position: Vec2::default(), global_position: Vec2::default(),
dimensions: Vec2::default(), size: Vec2::default(),
parent_dimensions: Vec2::default(), parent_dimensions: Vec2::default(),
anchors: Anchors::default(), anchors: Anchors::default(),
margins: Margins::default(), margins: Margins::default(),
@ -38,7 +38,7 @@ impl Node {
Node { Node {
position, position,
global_position: Vec2::default(), global_position: Vec2::default(),
dimensions: Vec2::default(), size: Vec2::default(),
parent_dimensions: Vec2::default(), parent_dimensions: Vec2::default(),
anchors, anchors,
margins, margins,
@ -46,7 +46,7 @@ impl Node {
} }
} }
pub fn update(&mut self, parent_dimensions: Vec2) { pub fn update(&mut self, parent_dimensions: Vec2, parent_position: Vec2) {
let (rect_x, rect_width) = Self::compute_dimension_properties( let (rect_x, rect_width) = Self::compute_dimension_properties(
self.position.x(), self.position.x(),
self.margins.left, self.margins.left,
@ -64,8 +64,8 @@ impl Node {
parent_dimensions.y(), parent_dimensions.y(),
); );
self.global_position = math::vec2(rect_x, rect_y); self.size = math::vec2(rect_width, rect_height);
self.dimensions = math::vec2(rect_width, rect_height); self.global_position = math::vec2(rect_x, rect_y) + parent_position;
} }
fn compute_dimension_properties( fn compute_dimension_properties(
@ -93,8 +93,11 @@ impl Node {
let p0 = Self::compute_rect_position(offset, margin0, anchor_p0, p0_grow_direction); let p0 = Self::compute_rect_position(offset, margin0, anchor_p0, p0_grow_direction);
let p1 = Self::compute_rect_position(offset, margin1, anchor_p1, p1_grow_direction); let p1 = Self::compute_rect_position(offset, margin1, anchor_p1, p1_grow_direction);
let p = (p0 + p1) / 2.0;
let final_width = p1 - p0; let final_width = p1 - p0;
let mut p = (p0 + p1) / 2.0;
// move position to "origin" in bottom left hand corner
p = p - final_width / 2.0;
(p, final_width) (p, final_width)
} }

View File

@ -1,15 +1,37 @@
use crate::{ui::Node, *}; use crate::{legion::system::SubWorld, math::Vec2, ui::Node, *};
use winit::window::Window; use winit::window::Window;
pub fn build_ui_update_system() -> Box<dyn Schedulable> { pub fn build_ui_update_system() -> Box<dyn Schedulable> {
SystemBuilder::new("ui_update_system") SystemBuilder::new("ui_update_system")
.read_resource::<Window>() .read_resource::<Window>()
.with_query(<(Write<Node>,)>::query().filter(!component::<Children>())) .with_query(<(Write<Node>,)>::query().filter(!component::<Parent>()))
.write_component::<Node>()
.read_component::<Children>()
.build(move |_, world, window, node_query| { .build(move |_, world, window, node_query| {
let window_size = window.inner_size(); let window_size = window.inner_size();
let parent_dimensions = math::vec2(window_size.width as f32, window_size.height as f32); let parent_size = math::vec2(window_size.width as f32, window_size.height as f32);
for (mut node,) in node_query.iter_mut(world) { let parent_position = math::vec2(0.0, 0.0);
node.update(parent_dimensions); for (entity, _) in node_query.iter_entities_mut(world) {
ecs::run_on_hierarchy_subworld(
world,
entity,
(parent_size, parent_position),
&update_node_entity,
);
} }
}) })
} }
fn update_node_entity(
world: &mut SubWorld,
entity: Entity,
parent_properties: (Vec2, Vec2),
) -> Option<(Vec2, Vec2)> {
let (parent_size, parent_position) = parent_properties;
if let Some(mut node) = world.get_component_mut::<Node>(entity) {
node.update(parent_size, parent_position);
return Some((node.size, node.global_position));
}
None
}