From 20dc40337d67926259e7c87470b9def6a9f3c7f0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 16 May 2021 16:09:51 +0300 Subject: [PATCH] added ability to copy 2d scenes/graph/nodes --- README.md | 3 +- rg3d-core/src/visitor.rs | 2 +- src/renderer/renderer2d/mod.rs | 12 +- src/scene2d/base.rs | 18 ++- src/scene2d/camera.rs | 10 ++ src/scene2d/graph.rs | 201 +++++++++++++++++++++++++++++++++ src/scene2d/light/mod.rs | 16 +++ src/scene2d/light/point.rs | 7 ++ src/scene2d/light/spot.rs | 19 ++++ src/scene2d/mod.rs | 37 ++++++ src/scene2d/node.rs | 9 ++ src/scene2d/sprite.rs | 9 ++ 12 files changed, 332 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 65e156a0e..6ec9467fa 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,7 @@ [![Discord](https://img.shields.io/discord/756573453561102427)](https://discord.gg/xENF5Uh) [![Lines of code](https://tokei.rs/b1/github/mrDIMAS/rg3d)](https://github.com/mrDIMAS/rg3d) -A feature-rich, general purpose 2D/3D game engine written in Rust with a scene editor. Most powerful -3D game engine written in Rust so far. +A feature-rich, general purpose 2D/3D game engine written in Rust with a scene editor. ## Support diff --git a/rg3d-core/src/visitor.rs b/rg3d-core/src/visitor.rs index b3d07db86..d0cb6f985 100644 --- a/rg3d-core/src/visitor.rs +++ b/rg3d-core/src/visitor.rs @@ -1280,7 +1280,7 @@ mod test { // Load { - let mut visitor = Visitor::load_binary(path).unwrap(); + let mut visitor = futures::executor::block_on(Visitor::load_binary(path)).unwrap(); let mut resource: Rc = Rc::new(Default::default()); resource.visit("SharedResource", &mut visitor).unwrap(); diff --git a/src/renderer/renderer2d/mod.rs b/src/renderer/renderer2d/mod.rs index 05d6dcdc6..784a89059 100644 --- a/src/renderer/renderer2d/mod.rs +++ b/src/renderer/renderer2d/mod.rs @@ -1,9 +1,9 @@ -use crate::core::algebra::Vector4; -use crate::core::math::Rect; -use crate::scene2d::light::Light; +//! A renderer responsible for drawing 2D scenes. + use crate::{ core::{ - algebra::{Matrix4, Vector2}, + algebra::{Matrix4, Vector2, Vector4}, + math::Rect, pool::Handle, }, physics::parry::utils::hashmap::Entry, @@ -21,7 +21,7 @@ use crate::{ RenderPassStatistics, TextureCache, }, resource::texture::TextureKind, - scene2d::{node::Node, Scene2d, Scene2dContainer}, + scene2d::{light::Light, node::Node, Scene2d, Scene2dContainer}, utils::log::{Log, MessageKind}, }; use std::{cell::RefCell, collections::HashMap, rc::Rc}; @@ -97,7 +97,7 @@ impl RenderTarget { } } -pub struct Renderer2d { +pub(in crate) struct Renderer2d { sprite_shader: SpriteShader, quad: Mesh, geometry_cache: GeometryCache, diff --git a/src/scene2d/base.rs b/src/scene2d/base.rs index 25d4342ec..a034f1592 100644 --- a/src/scene2d/base.rs +++ b/src/scene2d/base.rs @@ -95,6 +95,20 @@ impl Base { pub fn physics_binding(&self) -> PhysicsBinding { self.physics_binding } + + pub fn raw_copy(&self) -> Self { + Self { + transform: self.transform.clone(), + global_transform: self.global_transform.clone(), + visibility: self.visibility, + global_visibility: self.global_visibility.clone(), + physics_binding: self.physics_binding, + name: self.name.clone(), + // Handles to parent/children are intentionally not copied! + parent: Default::default(), + children: Default::default(), + } + } } pub struct BaseBuilder { @@ -140,8 +154,8 @@ impl BaseBuilder { self } - pub fn with_name(mut self, name: String) -> Self { - self.name = name; + pub fn with_name>(mut self, name: N) -> Self { + self.name = name.as_ref().to_owned(); self } diff --git a/src/scene2d/camera.rs b/src/scene2d/camera.rs index e5c4e0827..b41ec9416 100644 --- a/src/scene2d/camera.rs +++ b/src/scene2d/camera.rs @@ -87,6 +87,16 @@ impl Camera { pub fn set_enabled(&mut self, enabled: bool) { self.enabled = enabled; } + + pub fn raw_copy(&self) -> Self { + Self { + base: self.base.raw_copy(), + viewport: self.viewport, + view_matrix: self.view_matrix, + projection_matrix: self.projection_matrix, + enabled: self.enabled, + } + } } pub struct CameraBuilder { diff --git a/src/scene2d/graph.rs b/src/scene2d/graph.rs index 77a419622..2b7bb76ed 100644 --- a/src/scene2d/graph.rs +++ b/src/scene2d/graph.rs @@ -8,6 +8,7 @@ use crate::{ }, scene2d::node::Node, }; +use std::collections::HashMap; use std::ops::{Index, IndexMut}; #[derive(Default, Visit)] @@ -322,6 +323,161 @@ impl Graph { let m = self.global_scale_matrix(node); Vector2::new(m[0], m[5]) } + + /// Creates deep copy of node with all children. This is relatively heavy operation! + /// In case if any error happened it returns `Handle::NONE`. + /// + /// # Implementation notes + /// + /// Returns tuple where first element is handle to copy of node, and second element - + /// old-to-new hash map, which can be used to easily find copy of node by its original. + /// + /// Filter allows to exclude some nodes from copied hierarchy. It must return false for + /// odd nodes. Filtering applied only to descendant nodes. + pub fn copy_node( + &self, + node_handle: Handle, + dest_graph: &mut Graph, + filter: &mut F, + ) -> (Handle, HashMap, Handle>) + where + F: FnMut(Handle, &Node) -> bool, + { + let mut old_new_mapping = HashMap::new(); + let root_handle = self.copy_node_raw(node_handle, dest_graph, &mut old_new_mapping, filter); + (root_handle, old_new_mapping) + } + + /// Creates deep copy of node with all children. This is relatively heavy operation! + /// In case if any error happened it returns `Handle::NONE`. + /// + /// # Notes + /// + /// This method has exactly the same functionality as `copy_node`, but copies not in-place. + /// + /// # Implementation notes + /// + /// Returns tuple where first element is handle to copy of node, and second element - + /// old-to-new hash map, which can be used to easily find copy of node by its original. + /// + /// Filter allows to exclude some nodes from copied hierarchy. It must return false for + /// odd nodes. Filtering applied only to descendant nodes. + pub fn copy_node_inplace( + &mut self, + node_handle: Handle, + filter: &mut F, + ) -> (Handle, HashMap, Handle>) + where + F: FnMut(Handle, &Node) -> bool, + { + let mut old_new_mapping = HashMap::new(); + + let to_copy = self + .traverse_handle_iter(node_handle) + .map(|node| (node, self.pool[node].children.clone())) + .collect::>(); + + let mut root_handle = Handle::NONE; + + for (parent, children) in to_copy.iter() { + // Copy parent first. + let parent_copy = self.pool[*parent].raw_copy(); + let parent_copy_handle = self.add_node(parent_copy); + old_new_mapping.insert(*parent, parent_copy_handle); + + if root_handle.is_none() { + root_handle = parent_copy_handle; + } + + // Copy children and link to new parent. + for &child in children { + if filter(child, &self.pool[child]) { + let child_copy = self.pool[child].raw_copy(); + let child_copy_handle = self.add_node(child_copy); + old_new_mapping.insert(child, child_copy_handle); + self.link_nodes(child_copy_handle, parent_copy_handle); + } + } + } + + (root_handle, old_new_mapping) + } + + /// Creates copy of a node and breaks all connections with other nodes. + pub fn copy_single_node(&self, node_handle: Handle) -> Node { + let node = &self.pool[node_handle]; + let mut clone = node.raw_copy(); + clone.parent = Handle::NONE; + clone.children.clear(); + clone + } + + fn copy_node_raw( + &self, + root_handle: Handle, + dest_graph: &mut Graph, + old_new_mapping: &mut HashMap, Handle>, + filter: &mut F, + ) -> Handle + where + F: FnMut(Handle, &Node) -> bool, + { + let src_node = &self.pool[root_handle]; + let dest_node = src_node.raw_copy(); + let dest_copy_handle = dest_graph.add_node(dest_node); + old_new_mapping.insert(root_handle, dest_copy_handle); + for &src_child_handle in src_node.children() { + if filter(src_child_handle, &self.pool[src_child_handle]) { + let dest_child_handle = + self.copy_node_raw(src_child_handle, dest_graph, old_new_mapping, filter); + if !dest_child_handle.is_none() { + dest_graph.link_nodes(dest_child_handle, dest_copy_handle); + } + } + } + dest_copy_handle + } + + /// Creates deep copy of graph. Allows filtering while copying, returns copy and + /// old-to-new node mapping. + pub fn clone(&self, filter: &mut F) -> (Self, HashMap, Handle>) + where + F: FnMut(Handle, &Node) -> bool, + { + let mut copy = Self::default(); + let (root, old_new_map) = self.copy_node(self.root, &mut copy, filter); + copy.root = root; + (copy, old_new_map) + } + + /// Create graph depth traversal iterator. + /// + /// # Notes + /// + /// This method allocates temporal array so it is not cheap! Should not be + /// used on each frame. + pub fn traverse_iter(&self, from: Handle) -> impl Iterator { + GraphTraverseIterator { + graph: self, + stack: vec![from], + } + } + + /// Create graph depth traversal iterator which will emit *handles* to nodes. + /// + /// # Notes + /// + /// This method allocates temporal array so it is not cheap! Should not be + /// used on each frame. + pub fn traverse_handle_iter( + &self, + from: Handle, + ) -> impl Iterator> + '_ { + GraphHandleTraverseIterator { + graph: self, + stack: vec![from], + } + } } impl Index> for Graph { @@ -337,3 +493,48 @@ impl IndexMut> for Graph { &mut self.pool[index] } } + +/// Iterator that traverses tree in depth and returns shared references to nodes. +pub struct GraphTraverseIterator<'a> { + graph: &'a Graph, + stack: Vec>, +} + +impl<'a> Iterator for GraphTraverseIterator<'a> { + type Item = &'a Node; + + fn next(&mut self) -> Option { + if let Some(handle) = self.stack.pop() { + let node = &self.graph[handle]; + + for child_handle in node.children() { + self.stack.push(*child_handle); + } + + return Some(node); + } + + None + } +} + +/// Iterator that traverses tree in depth and returns handles to nodes. +pub struct GraphHandleTraverseIterator<'a> { + graph: &'a Graph, + stack: Vec>, +} + +impl<'a> Iterator for GraphHandleTraverseIterator<'a> { + type Item = Handle; + + fn next(&mut self) -> Option { + if let Some(handle) = self.stack.pop() { + for child_handle in self.graph[handle].children() { + self.stack.push(*child_handle); + } + + return Some(handle); + } + None + } +} diff --git a/src/scene2d/light/mod.rs b/src/scene2d/light/mod.rs index 85be623a3..0a8b0df14 100644 --- a/src/scene2d/light/mod.rs +++ b/src/scene2d/light/mod.rs @@ -36,6 +36,15 @@ impl DerefMut for Light { } } +impl Light { + pub fn raw_copy(&self) -> Self { + match self { + Light::Point(v) => Light::Point(v.raw_copy()), + Light::Spot(v) => Light::Spot(v.raw_copy()), + } + } +} + impl Default for Light { fn default() -> Self { Self::Spot(Default::default()) @@ -70,6 +79,13 @@ impl BaseLight { pub fn set_color(&mut self, color: Color) { self.color = color; } + + pub fn raw_copy(&self) -> Self { + Self { + base: self.base.raw_copy(), + color: self.color, + } + } } pub struct BaseLightBuilder { diff --git a/src/scene2d/light/point.rs b/src/scene2d/light/point.rs index feb68d45b..7cca82f96 100644 --- a/src/scene2d/light/point.rs +++ b/src/scene2d/light/point.rs @@ -18,6 +18,13 @@ impl PointLight { pub fn radius(&self) -> f32 { self.radius } + + pub fn raw_copy(&self) -> Self { + Self { + base_light: self.base_light.raw_copy(), + radius: self.radius, + } + } } impl Deref for PointLight { diff --git a/src/scene2d/light/spot.rs b/src/scene2d/light/spot.rs index c1d0fcaa1..55b7dc512 100644 --- a/src/scene2d/light/spot.rs +++ b/src/scene2d/light/spot.rs @@ -36,6 +36,15 @@ impl SpotLight { pub fn half_hotspot_cone_angle(&self) -> f32 { (self.hotspot_angle * 0.5).cos() } + + pub fn raw_copy(&self) -> Self { + Self { + base_light: self.base_light.raw_copy(), + radius: self.radius, + hotspot_angle: self.hotspot_angle, + falloff_angle_delta: self.falloff_angle_delta, + } + } } impl Deref for SpotLight { @@ -85,6 +94,16 @@ impl SpotLightBuilder { self } + pub fn with_hotspot_angle(mut self, angle: f32) -> Self { + self.hotspot = angle; + self + } + + pub fn with_angle_delta(mut self, delta: f32) -> Self { + self.delta = delta.abs(); + self + } + pub fn build(self, graph: &mut Graph) -> Handle { graph.add_node(Node::Light(Light::Spot(SpotLight { base_light: self.base_light_builder.build(), diff --git a/src/scene2d/mod.rs b/src/scene2d/mod.rs index 107a119c8..e07b973c2 100644 --- a/src/scene2d/mod.rs +++ b/src/scene2d/mod.rs @@ -12,6 +12,7 @@ use crate::{ scene2d::{graph::Graph, node::Node, physics::Physics, physics::PhysicsPerformanceStatistics}, sound::{context::SoundContext, engine::SoundEngine}, }; +use std::collections::HashMap; use std::{ ops::{Index, IndexMut}, sync::{Arc, Mutex}, @@ -130,6 +131,42 @@ impl Scene2d { pub(in crate) fn resolve(&mut self) { self.physics.resolve(); } + + /// Creates deep copy of a scene, filter predicate allows you to filter out nodes + /// by your criteria. + pub fn clone(&self, filter: &mut F) -> (Self, HashMap, Handle>) + where + F: FnMut(Handle, &Node) -> bool, + { + let (graph, old_new_map) = self.graph.clone(filter); + + // It is ok to use old binder here, because handles maps one-to-one. + let physics = self.physics.deep_copy(); + let mut physics_binder = PhysicsBinder::default(); + for (node, &body) in self.physics_binder.forward_map().iter() { + // Make sure we bind existing node with new physical body. + if let Some(&new_node) = old_new_map.get(node) { + // Re-use of body handle is fine here because physics copy bodies + // directly and handles from previous pool is still suitable for copy. + physics_binder.bind(new_node, body); + } + } + ( + Self { + graph, + physics, + physics_binder, + // Render target is intentionally not copied, because it does not makes sense - a copy + // will redraw frame completely. + render_target: Default::default(), + sound_context: self.sound_context.deep_clone(), + performance_statistics: Default::default(), + ambient_light_color: self.ambient_light_color, + enabled: self.enabled, + }, + old_new_map, + ) + } } #[derive(Visit)] diff --git a/src/scene2d/node.rs b/src/scene2d/node.rs index 531cd17b8..ce90ffa29 100644 --- a/src/scene2d/node.rs +++ b/src/scene2d/node.rs @@ -47,4 +47,13 @@ impl Node { define_is_as!(Node : Camera -> ref Camera => fn is_camera, fn as_camera, fn as_camera_mut); define_is_as!(Node : Light -> ref Light => fn is_light, fn as_light, fn as_light_mut); define_is_as!(Node : Sprite -> ref Sprite => fn is_sprite, fn as_sprite, fn as_sprite_mut); + + pub fn raw_copy(&self) -> Self { + match self { + Node::Base(v) => Node::Base(v.raw_copy()), + Node::Camera(v) => Node::Camera(v.raw_copy()), + Node::Light(v) => Node::Light(v.raw_copy()), + Node::Sprite(v) => Node::Sprite(v.raw_copy()), + } + } } diff --git a/src/scene2d/sprite.rs b/src/scene2d/sprite.rs index a8e9886e6..0ab6f46e3 100644 --- a/src/scene2d/sprite.rs +++ b/src/scene2d/sprite.rs @@ -100,6 +100,15 @@ impl Sprite { bounds.push(global_bottom_right.xy()); bounds } + + pub fn raw_copy(&self) -> Self { + Self { + base: self.base.raw_copy(), + texture: self.texture.clone(), + color: self.color, + size: self.size, + } + } } pub struct SpriteBuilder {