From 594847eae4758e7dee9e4d6befeea1273e6ac159 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Sat, 10 Oct 2026 19:36:49 +0300 Subject: [PATCH] moved more useful methods from editor to fyrox-graph --- editor/src/interaction/move_mode.rs | 4 +- editor/src/scene/clipboard.rs | 3 +- editor/src/scene/mod.rs | 26 +--- editor/src/world/selection.rs | 34 +---- fyrox-graph/src/lib.rs | 200 +++++++++++++++++++++++++--- 5 files changed, 187 insertions(+), 80 deletions(-) diff --git a/editor/src/interaction/move_mode.rs b/editor/src/interaction/move_mode.rs index 812ea893d..4667adf26 100644 --- a/editor/src/interaction/move_mode.rs +++ b/editor/src/interaction/move_mode.rs @@ -141,8 +141,8 @@ impl MoveContext { frame_size, |plane_point, gizmo_inv_transform, gizmo_origin| { let graph = &scene.graph; - selection - .root_nodes(graph) + graph + .root_nodes(selection.nodes()) .iter() .map(|&node_handle| { let node = &graph[node_handle]; diff --git a/editor/src/scene/clipboard.rs b/editor/src/scene/clipboard.rs index b953e4b0d..6cb1263e7 100644 --- a/editor/src/scene/clipboard.rs +++ b/editor/src/scene/clipboard.rs @@ -25,6 +25,7 @@ use crate::fyrox::{ }; use crate::{scene::GraphSelection, Engine}; use fyrox::core::blank_reflect_ref; +use fyrox::graph::SceneGraph; use std::collections::HashMap; #[derive(Debug)] @@ -94,7 +95,7 @@ impl Clipboard { let scene = &engine.scenes[scene_handle]; - let root_nodes = selection.root_nodes(&scene.graph); + let root_nodes = scene.graph.root_nodes(selection.nodes()); deep_clone_nodes(&root_nodes, &scene.graph, &mut self.graph); diff --git a/editor/src/scene/mod.rs b/editor/src/scene/mod.rs index f44dfaea8..2949a8b78 100644 --- a/editor/src/scene/mod.rs +++ b/editor/src/scene/mod.rs @@ -440,34 +440,16 @@ impl GameScene { editor_selection: &Selection, graph: &Graph, ) -> bool { - if let Some(selection) = editor_selection.as_graph() { - // Convert selection to the full list of descendant nodes first. - let roots = selection.root_nodes(graph); - let mut all_nodes = FxHashSet::default(); - for root in roots { - for (descendant_handle, _) in graph.traverse_iter(root) { - all_nodes.insert(descendant_handle); - } - } - - for node in selection.nodes() { - for (descendant_handle, _) in graph.traverse_iter(*node) { - for reference in graph.find_references_to(descendant_handle) { - if !all_nodes.contains(&reference) { - return true; - } - } - } - } - } - false + editor_selection + .as_graph() + .is_some_and(|s| !graph.external_refs(s.nodes()).is_empty()) } fn try_save_selection_as_prefab(&self, path: &Path, selection: &Selection, engine: &Engine) { let source_scene = &engine.scenes[self.scene]; let mut dest_scene = Scene::new(); if let Some(graph_selection) = selection.as_graph() { - for root_node in graph_selection.root_nodes(&source_scene.graph) { + for root_node in source_scene.graph.root_nodes(graph_selection.nodes()) { source_scene.graph.copy_node( root_node, &mut dest_scene.graph, diff --git a/editor/src/world/selection.rs b/editor/src/world/selection.rs index 597111e27..e917ee2fa 100644 --- a/editor/src/world/selection.rs +++ b/editor/src/world/selection.rs @@ -152,7 +152,7 @@ impl SelectionContainer for GraphSelection { // by engine's design when we delete a node, we also delete all its children. So we have to keep // this behaviour in editor too. - let root_nodes = selection.root_nodes(graph); + let root_nodes = graph.root_nodes(selection.nodes()); for root_node in root_nodes { command_group.push(DeleteSubGraphCommand::new(root_node)); @@ -207,38 +207,6 @@ impl GraphSelection { self.nodes.extend_from_slice(&other.nodes) } - pub fn root_nodes(&self, graph: &Graph) -> Vec> { - // Helper function. - fn is_descendant_of(handle: Handle, other: Handle, graph: &Graph) -> bool { - for &child in graph[other].children() { - if child == handle { - return true; - } - - let inner = is_descendant_of(handle, child, graph); - if inner { - return true; - } - } - false - } - - let mut root_nodes = Vec::new(); - for &node in self.nodes().iter() { - let mut descendant = false; - for &other_node in self.nodes().iter() { - if is_descendant_of(node, other_node, graph) { - descendant = true; - break; - } - } - if !descendant { - root_nodes.push(node); - } - } - root_nodes - } - pub fn global_rotation_position( &self, graph: &Graph, diff --git a/fyrox-graph/src/lib.rs b/fyrox-graph/src/lib.rs index d879c674d..748a659b4 100644 --- a/fyrox-graph/src/lib.rs +++ b/fyrox-graph/src/lib.rs @@ -28,9 +28,10 @@ pub mod prelude { pub use crate::SceneGraph; } -use fxhash::FxHashMap; +use fxhash::{FxHashMap, FxHashSet}; use fyrox_core::{ log::{Log, MessageKind}, + ok_or_return, pool::{Handle, ObjectOrVariant, PoolError}, reflect::{prelude::*, ReflectHandle}, uuid::Uuid, @@ -1521,6 +1522,77 @@ pub trait SceneGraph: 'static { } references } + + /// Checks if a node with `handle` is a descendant node of some `other` node. + fn is_descendant_of( + &self, + handle: Handle, + other: Handle, + ) -> bool { + let other_ref = ok_or_return!(self.try_get(other), false); + + for &child in other_ref.children() { + if child == handle { + return true; + } + + let inner = self.is_descendant_of(handle, child); + if inner { + return true; + } + } + false + } + + /// Returns a set of root nodes of arbitrary set of graph nodes. For example: + /// + /// ```text + /// A_ + /// B_ <- + /// | C | these are in `nodes` argument + /// | D_ <- + /// | E + /// F + /// ``` + /// + /// Then this method will return only a handle to `B` node. + fn root_nodes(&self, nodes: &[Handle]) -> Vec> { + let mut root_nodes = Vec::new(); + for &node in nodes.iter() { + let mut descendant = false; + for &other_node in nodes.iter() { + if self.is_descendant_of(node, other_node) { + descendant = true; + break; + } + } + if !descendant { + root_nodes.push(node); + } + } + root_nodes + } + + /// Tries to find all handles to nodes outside the subgraphs formed by the given set of nodes. + fn external_refs(&self, nodes: &[Handle]) -> Vec> { + let roots = self.root_nodes(nodes); + let mut all_nodes = FxHashSet::default(); + for root in roots { + for (descendant_handle, _) in self.traverse_iter(root) { + all_nodes.insert(descendant_handle); + } + } + + let mut external_refs = FxHashSet::default(); + for &descendant_handle in all_nodes.iter() { + for reference in self.find_references_to(descendant_handle) { + if !all_nodes.contains(&reference) { + external_refs.insert(reference); + } + } + } + external_refs.into_iter().collect() + } } /// Iterator that traverses tree in depth and returns shared references to nodes. @@ -2162,13 +2234,13 @@ mod test { #[derive(Clone, Reflect, Visit, PartialEq, Default, Debug)] #[reflect(derived_type = "TestNode")] #[reflect(type_uuid = "17cc2d25-6ba4-4e2d-a31e-867e429bc659")] - pub struct RigidBody { + pub struct TestRigidBody { base: TestBase, } - impl NodeTrait for RigidBody {} + impl NodeTrait for TestRigidBody {} - impl Deref for RigidBody { + impl Deref for TestRigidBody { type Target = TestBase; fn deref(&self) -> &Self::Target { @@ -2176,7 +2248,7 @@ mod test { } } - impl DerefMut for RigidBody { + impl DerefMut for TestRigidBody { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.base } @@ -2187,8 +2259,8 @@ mod test { #[reflect(type_uuid = "1f869298-37b1-4153-a2a9-6576daa0e8b3")] pub struct TestJoint { base: TestBase, - connected_body1: Handle, - connected_body2: Handle, + connected_body1: Handle, + connected_body2: Handle, } impl NodeTrait for TestJoint {} @@ -2265,8 +2337,8 @@ mod test { let mut prefab_graph = TestGraph::default(); prefab_graph.add_node(TestNode::new(TestPivot::default())); - let rigid_body = prefab_graph.add_node(TestNode::new(RigidBody::default())); - let rigid_body2 = prefab_graph.add_node(TestNode::new(RigidBody::default())); + let rigid_body = prefab_graph.add_node(TestNode::new(TestRigidBody::default())); + let rigid_body2 = prefab_graph.add_node(TestNode::new(TestRigidBody::default())); let joint = prefab_graph.add_node(TestNode::new(TestJoint { base: TestBase::default(), connected_body1: rigid_body.transmute(), @@ -2282,13 +2354,13 @@ mod test { .get(&rigid_body) .cloned() .unwrap() - .transmute::(); + .transmute::(); let rigid_body2_copy = mapping .inner() .get(&rigid_body2) .cloned() .unwrap() - .transmute::(); + .transmute::(); let joint_copy = mapping.inner().get(&joint).cloned().unwrap(); let joint_copy_ref = scene_graph.nodes[joint_copy] .inner_ref() @@ -2470,18 +2542,102 @@ mod test { drop(iter_mut); } + struct TestGraphInstance { + graph: TestGraph, + rigid_body: Handle, + rigid_body_child: Handle, + rigid_body2: Handle, + rigid_body2_child: Handle, + joint: Handle, + } + + impl TestGraphInstance { + fn new() -> Self { + let mut graph = TestGraph::default(); + graph.add_node(TestNode::new(TestPivot::default())); + let rigid_body = graph + .add_node(TestNode::new(TestRigidBody::default())) + .transmute::(); + let rigid_body_child = graph + .add_node(TestNode::new(TestPivot::default())) + .transmute::(); + let rigid_body2 = graph + .add_node(TestNode::new(TestRigidBody::default())) + .transmute::(); + let rigid_body2_child = graph + .add_node(TestNode::new(TestPivot::default())) + .transmute::(); + let joint = graph + .add_node(TestNode::new(TestJoint { + base: TestBase::default(), + connected_body1: rigid_body, + connected_body2: rigid_body2, + })) + .transmute::(); + graph.link_nodes(rigid_body_child, rigid_body); + graph.link_nodes(rigid_body2_child, rigid_body2); + Self { + graph, + rigid_body, + rigid_body_child, + rigid_body2, + rigid_body2_child, + joint, + } + } + } + #[test] fn test_find_references() { - let mut graph = TestGraph::default(); - graph.add_node(TestNode::new(TestPivot::default())); - let rigid_body = graph.add_node(TestNode::new(RigidBody::default())); - let rigid_body2 = graph.add_node(TestNode::new(RigidBody::default())); - let joint = graph.add_node(TestNode::new(TestJoint { - base: TestBase::default(), - connected_body1: rigid_body.transmute(), - connected_body2: rigid_body2.transmute(), - })); - assert_eq!(graph.find_references_to(rigid_body), vec![joint]); - assert_eq!(graph.find_references_to(rigid_body2), vec![joint]); + let instance = TestGraphInstance::new(); + assert_eq!( + instance.graph.find_references_to(instance.rigid_body), + vec![instance.joint] + ); + assert_eq!( + instance.graph.find_references_to(instance.rigid_body2), + vec![instance.joint] + ); + } + + #[test] + fn test_root_nodes() { + let instance = TestGraphInstance::new(); + assert_eq!( + instance.graph.root_nodes(&[ + instance.rigid_body.to_base(), + instance.rigid_body_child.to_base() + ]), + vec![instance.rigid_body] + ); + assert_eq!( + instance.graph.root_nodes(&[ + instance.rigid_body.to_base(), + instance.rigid_body_child.to_base(), + instance.rigid_body2.to_base(), + instance.rigid_body2_child.to_base() + ]), + vec![instance.rigid_body, instance.rigid_body2] + ); + let nodes = Vec::>::new(); + let empty_nodes = Vec::>::new(); + assert_eq!(instance.graph.root_nodes(&nodes), empty_nodes); + } + + #[test] + fn test_is_nodes_have_external_refs() { + let instance = TestGraphInstance::new(); + let empty_nodes = Vec::>::new(); + assert_eq!( + instance.graph.external_refs(&[instance.joint.to_base()],), + empty_nodes + ); + assert_eq!( + instance.graph.external_refs(&[ + instance.rigid_body.to_base(), + instance.rigid_body2.to_base() + ]), + vec![instance.joint] + ); } }