moved more useful methods from editor to fyrox-graph

This commit is contained in:
Dmitry Stepanov
2026-10-10 19:36:49 +03:00
parent 1564436f1a
commit 594847eae4
5 changed files with 187 additions and 80 deletions
+2 -2
View File
@@ -141,8 +141,8 @@ impl MoveContext {
frame_size, frame_size,
|plane_point, gizmo_inv_transform, gizmo_origin| { |plane_point, gizmo_inv_transform, gizmo_origin| {
let graph = &scene.graph; let graph = &scene.graph;
selection graph
.root_nodes(graph) .root_nodes(selection.nodes())
.iter() .iter()
.map(|&node_handle| { .map(|&node_handle| {
let node = &graph[node_handle]; let node = &graph[node_handle];
+2 -1
View File
@@ -25,6 +25,7 @@ use crate::fyrox::{
}; };
use crate::{scene::GraphSelection, Engine}; use crate::{scene::GraphSelection, Engine};
use fyrox::core::blank_reflect_ref; use fyrox::core::blank_reflect_ref;
use fyrox::graph::SceneGraph;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug)] #[derive(Debug)]
@@ -94,7 +95,7 @@ impl Clipboard {
let scene = &engine.scenes[scene_handle]; 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); deep_clone_nodes(&root_nodes, &scene.graph, &mut self.graph);
+4 -22
View File
@@ -440,34 +440,16 @@ impl GameScene {
editor_selection: &Selection, editor_selection: &Selection,
graph: &Graph, graph: &Graph,
) -> bool { ) -> bool {
if let Some(selection) = editor_selection.as_graph() { editor_selection
// Convert selection to the full list of descendant nodes first. .as_graph()
let roots = selection.root_nodes(graph); .is_some_and(|s| !graph.external_refs(s.nodes()).is_empty())
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
} }
fn try_save_selection_as_prefab(&self, path: &Path, selection: &Selection, engine: &Engine) { fn try_save_selection_as_prefab(&self, path: &Path, selection: &Selection, engine: &Engine) {
let source_scene = &engine.scenes[self.scene]; let source_scene = &engine.scenes[self.scene];
let mut dest_scene = Scene::new(); let mut dest_scene = Scene::new();
if let Some(graph_selection) = selection.as_graph() { 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( source_scene.graph.copy_node(
root_node, root_node,
&mut dest_scene.graph, &mut dest_scene.graph,
+1 -33
View File
@@ -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 // 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. // 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 { for root_node in root_nodes {
command_group.push(DeleteSubGraphCommand::new(root_node)); command_group.push(DeleteSubGraphCommand::new(root_node));
@@ -207,38 +207,6 @@ impl GraphSelection {
self.nodes.extend_from_slice(&other.nodes) self.nodes.extend_from_slice(&other.nodes)
} }
pub fn root_nodes(&self, graph: &Graph) -> Vec<Handle<Node>> {
// Helper function.
fn is_descendant_of(handle: Handle<Node>, other: Handle<Node>, 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( pub fn global_rotation_position(
&self, &self,
graph: &Graph, graph: &Graph,
+177 -21
View File
@@ -28,9 +28,10 @@ pub mod prelude {
pub use crate::SceneGraph; pub use crate::SceneGraph;
} }
use fxhash::FxHashMap; use fxhash::{FxHashMap, FxHashSet};
use fyrox_core::{ use fyrox_core::{
log::{Log, MessageKind}, log::{Log, MessageKind},
ok_or_return,
pool::{Handle, ObjectOrVariant, PoolError}, pool::{Handle, ObjectOrVariant, PoolError},
reflect::{prelude::*, ReflectHandle}, reflect::{prelude::*, ReflectHandle},
uuid::Uuid, uuid::Uuid,
@@ -1521,6 +1522,77 @@ pub trait SceneGraph: 'static {
} }
references references
} }
/// Checks if a node with `handle` is a descendant node of some `other` node.
fn is_descendant_of(
&self,
handle: Handle<Self::NodeWrapper>,
other: Handle<Self::NodeWrapper>,
) -> 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<Self::NodeWrapper>]) -> Vec<Handle<Self::NodeWrapper>> {
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<Self::NodeWrapper>]) -> Vec<Handle<Self::NodeWrapper>> {
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. /// 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)] #[derive(Clone, Reflect, Visit, PartialEq, Default, Debug)]
#[reflect(derived_type = "TestNode")] #[reflect(derived_type = "TestNode")]
#[reflect(type_uuid = "17cc2d25-6ba4-4e2d-a31e-867e429bc659")] #[reflect(type_uuid = "17cc2d25-6ba4-4e2d-a31e-867e429bc659")]
pub struct RigidBody { pub struct TestRigidBody {
base: TestBase, base: TestBase,
} }
impl NodeTrait for RigidBody {} impl NodeTrait for TestRigidBody {}
impl Deref for RigidBody { impl Deref for TestRigidBody {
type Target = TestBase; type Target = TestBase;
fn deref(&self) -> &Self::Target { 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 { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base &mut self.base
} }
@@ -2187,8 +2259,8 @@ mod test {
#[reflect(type_uuid = "1f869298-37b1-4153-a2a9-6576daa0e8b3")] #[reflect(type_uuid = "1f869298-37b1-4153-a2a9-6576daa0e8b3")]
pub struct TestJoint { pub struct TestJoint {
base: TestBase, base: TestBase,
connected_body1: Handle<RigidBody>, connected_body1: Handle<TestRigidBody>,
connected_body2: Handle<RigidBody>, connected_body2: Handle<TestRigidBody>,
} }
impl NodeTrait for TestJoint {} impl NodeTrait for TestJoint {}
@@ -2265,8 +2337,8 @@ mod test {
let mut prefab_graph = TestGraph::default(); let mut prefab_graph = TestGraph::default();
prefab_graph.add_node(TestNode::new(TestPivot::default())); prefab_graph.add_node(TestNode::new(TestPivot::default()));
let rigid_body = 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(RigidBody::default())); let rigid_body2 = prefab_graph.add_node(TestNode::new(TestRigidBody::default()));
let joint = prefab_graph.add_node(TestNode::new(TestJoint { let joint = prefab_graph.add_node(TestNode::new(TestJoint {
base: TestBase::default(), base: TestBase::default(),
connected_body1: rigid_body.transmute(), connected_body1: rigid_body.transmute(),
@@ -2282,13 +2354,13 @@ mod test {
.get(&rigid_body) .get(&rigid_body)
.cloned() .cloned()
.unwrap() .unwrap()
.transmute::<RigidBody>(); .transmute::<TestRigidBody>();
let rigid_body2_copy = mapping let rigid_body2_copy = mapping
.inner() .inner()
.get(&rigid_body2) .get(&rigid_body2)
.cloned() .cloned()
.unwrap() .unwrap()
.transmute::<RigidBody>(); .transmute::<TestRigidBody>();
let joint_copy = mapping.inner().get(&joint).cloned().unwrap(); let joint_copy = mapping.inner().get(&joint).cloned().unwrap();
let joint_copy_ref = scene_graph.nodes[joint_copy] let joint_copy_ref = scene_graph.nodes[joint_copy]
.inner_ref() .inner_ref()
@@ -2470,18 +2542,102 @@ mod test {
drop(iter_mut); drop(iter_mut);
} }
#[test] struct TestGraphInstance {
fn test_find_references() { graph: TestGraph,
rigid_body: Handle<TestRigidBody>,
rigid_body_child: Handle<TestPivot>,
rigid_body2: Handle<TestRigidBody>,
rigid_body2_child: Handle<TestPivot>,
joint: Handle<TestJoint>,
}
impl TestGraphInstance {
fn new() -> Self {
let mut graph = TestGraph::default(); let mut graph = TestGraph::default();
graph.add_node(TestNode::new(TestPivot::default())); graph.add_node(TestNode::new(TestPivot::default()));
let rigid_body = graph.add_node(TestNode::new(RigidBody::default())); let rigid_body = graph
let rigid_body2 = graph.add_node(TestNode::new(RigidBody::default())); .add_node(TestNode::new(TestRigidBody::default()))
let joint = graph.add_node(TestNode::new(TestJoint { .transmute::<TestRigidBody>();
let rigid_body_child = graph
.add_node(TestNode::new(TestPivot::default()))
.transmute::<TestPivot>();
let rigid_body2 = graph
.add_node(TestNode::new(TestRigidBody::default()))
.transmute::<TestRigidBody>();
let rigid_body2_child = graph
.add_node(TestNode::new(TestPivot::default()))
.transmute::<TestPivot>();
let joint = graph
.add_node(TestNode::new(TestJoint {
base: TestBase::default(), base: TestBase::default(),
connected_body1: rigid_body.transmute(), connected_body1: rigid_body,
connected_body2: rigid_body2.transmute(), connected_body2: rigid_body2,
})); }))
assert_eq!(graph.find_references_to(rigid_body), vec![joint]); .transmute::<TestJoint>();
assert_eq!(graph.find_references_to(rigid_body2), vec![joint]); 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 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::<Handle<TestNode>>::new();
let empty_nodes = Vec::<Handle<TestNode>>::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::<Handle<TestNode>>::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]
);
} }
} }