mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Node tree structure
This commit is contained in:
parent
dd34c1e237
commit
ae325846ff
8 changed files with 175 additions and 69 deletions
|
@ -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
60
src/ecs/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue