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)
};
// cube
world.insert(
(),
vec![(
@ -26,6 +27,7 @@ fn setup(world: &mut World) {
)],
);
// light
world.insert(
(),
vec![(
@ -46,6 +48,7 @@ fn setup(world: &mut World) {
)],
);
// 3d camera
world.insert(
(),
vec![
@ -67,26 +70,23 @@ fn setup(world: &mut World) {
],
);
// 2d camera
world.insert(
(),
vec![
// camera
(
Camera::new(CameraType::Orthographic {
left: 0.0,
right: 0.0,
bottom: 0.0,
top: 0.0,
near: 0.0,
far: 1.0,
}),
ActiveCamera2d,
),
],
vec![(
Camera::new(CameraType::Orthographic {
left: 0.0,
right: 0.0,
bottom: 0.0,
top: 0.0,
near: 0.0,
far: 1.0,
}),
ActiveCamera2d,
)],
);
// bottom left anchor with vertical fill
world.insert(
(),
vec![(Node::new(
@ -98,7 +98,6 @@ fn setup(world: &mut World) {
);
// top right anchor with vertical fill
world.insert(
(),
vec![(Node::new(
@ -110,7 +109,6 @@ fn setup(world: &mut World) {
);
// render order test: reddest in the back, whitest in the front
world.insert(
(),
vec![(Node::new(
@ -150,4 +148,31 @@ fn setup(world: &mut World) {
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;
pub mod asset;
mod core;
pub mod ecs;
pub mod render;
pub mod ui;
pub use crate::core::*;
pub use app::{App, AppBuilder};
pub use ecs::*;
pub use glam as math;
pub use legion;
pub use legion::prelude::*;

View file

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

View file

@ -6,7 +6,7 @@ layout(location = 1) in vec4 a_Normal;
// instanced attributes (RectData)
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 = 5) in float a_RectZIndex;
@ -18,7 +18,7 @@ layout(set = 0, binding = 0) uniform Globals {
void main() {
v_Color = a_RectColor;
vec4 position = a_Pos * vec4(a_RectDimensions, 0.0, 1.0);
position = position + vec4(a_RectPosition, -a_RectZIndex, 0.0);
vec4 position = a_Pos * vec4(a_RectSize, 0.0, 1.0);
position = position + vec4(a_RectPosition + a_RectSize / 2.0, -a_RectZIndex, 0.0);
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")
// Entities with missing `PreviousParent`
.with_query(<Read<Parent>>::query().filter(
component::<LocalToParent>()
& component::<LocalToWorld>()
& !component::<PreviousParent>(),
!component::<PreviousParent>(),
))
.build(move |commands, world, _resource, query| {
// 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>()))
// Entities with a changed `Parent`
.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`).
.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
@ -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
// collect multiple new children that point to the same parent into the same
// SmallVec, and to prevent redundant add+remove operations.

View file

@ -12,7 +12,7 @@ enum GrowDirection {
pub struct Node {
pub position: Vec2,
pub global_position: Vec2,
pub dimensions: Vec2,
pub size: Vec2,
pub parent_dimensions: Vec2,
pub anchors: Anchors,
pub margins: Margins,
@ -24,7 +24,7 @@ impl Default for Node {
Node {
position: Vec2::default(),
global_position: Vec2::default(),
dimensions: Vec2::default(),
size: Vec2::default(),
parent_dimensions: Vec2::default(),
anchors: Anchors::default(),
margins: Margins::default(),
@ -38,7 +38,7 @@ impl Node {
Node {
position,
global_position: Vec2::default(),
dimensions: Vec2::default(),
size: Vec2::default(),
parent_dimensions: Vec2::default(),
anchors,
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(
self.position.x(),
self.margins.left,
@ -64,8 +64,8 @@ impl Node {
parent_dimensions.y(),
);
self.global_position = math::vec2(rect_x, rect_y);
self.dimensions = math::vec2(rect_width, rect_height);
self.size = math::vec2(rect_width, rect_height);
self.global_position = math::vec2(rect_x, rect_y) + parent_position;
}
fn compute_dimension_properties(
@ -93,8 +93,11 @@ impl Node {
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 p = (p0 + p1) / 2.0;
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)
}

View file

@ -1,15 +1,37 @@
use crate::{ui::Node, *};
use crate::{legion::system::SubWorld, math::Vec2, ui::Node, *};
use winit::window::Window;
pub fn build_ui_update_system() -> Box<dyn Schedulable> {
SystemBuilder::new("ui_update_system")
.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| {
let window_size = window.inner_size();
let parent_dimensions = math::vec2(window_size.width as f32, window_size.height as f32);
for (mut node,) in node_query.iter_mut(world) {
node.update(parent_dimensions);
let parent_size = math::vec2(window_size.width as f32, window_size.height as f32);
let parent_position = math::vec2(0.0, 0.0);
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
}