Files
Fyrox/fyrox-impl/src/scene/graph/mod.rs
T

2784 lines
101 KiB
Rust

// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Contains all methods and structures to create and manage scene graphs.
//!
//! Scene graph is the foundation of the engine. Graph is a hierarchical data
//! structure where each element called node. Each node can have zero to one parent
//! node, and any children nodes. Node with no parent node called root, with no
//! children nodes - leaf. Graphical representation can be something like this:
//!
//! ```text
//! Root____
//! | |
//! D A___
//! | | |
//! E C B
//! ............
//! ```
//!
//! This picture clearly shows relations between nodes. Such structure allows us
//! to create scenes of any complexity by just linking nodes with each other.
//! Connections between nodes are used to traverse tree, to calculate global
//! transforms, global visibility and many other things. Most interesting here -
//! is global transform calculation - it allows you to produce complex movements
//! just by linking nodes to each other. Good example of this is skeleton which
//! is used in skinning (animating 3d model by set of bones).
use crate::{
asset::untyped::UntypedResource,
core::{
algebra::{Matrix4, Rotation3, UnitQuaternion, Vector2, Vector3},
dyntype::DynTypeContainer,
instant,
log::{Log, MessageKind},
math::{aabb::AxisAlignedBoundingBox, Matrix4Ext},
pool::{Handle, MultiBorrowContext, ObjectOrVariant, Pool, PoolError, Ticket},
reflect::prelude::*,
visitor::{Visit, VisitResult, Visitor},
},
graph::{NodeHandleMap, NodeWrapper, SceneGraph},
material::{MaterialResourceBinding, MaterialTextureBinding},
resource::model::{Model, ModelResource, ModelResourceExtension},
scene::{
base::{NodeMessage, NodeMessageKind, NodeScriptMessage, SceneNodeId},
dim2::{self},
graph::{
event::{GraphEvent, GraphEventBroadcaster},
physics::{PhysicsPerformanceStatistics, PhysicsWorld},
},
mesh::Mesh,
navmesh,
node::{container::NodeContainer, Node, NodeAsAny, SyncContext, UpdateContext},
pivot::Pivot,
sound::context::SoundContext,
transform::TransformBuilder,
},
script::ScriptTrait,
utils::lightmap::{self, Lightmap},
};
use bitflags::bitflags;
use fxhash::{FxHashMap, FxHashSet};
use fyrox_material::MaterialResourceExtension;
use std::{
any::TypeId,
fmt::{Debug, Display, Formatter, Write},
ops::{Deref, Index, IndexMut},
sync::mpsc::{channel, Receiver, Sender},
time::Duration,
};
pub mod event;
pub mod physics;
/// Graph performance statistics. Allows you to find out "hot" parts of the scene graph, which
/// parts takes the most time to update.
#[derive(Clone, PartialEq, Default, Debug)]
pub struct GraphPerformanceStatistics {
/// Amount of time that was needed to update global transform, visibility, and every other
/// property of every object which depends on the state of a parent node.
pub hierarchical_properties_time: Duration,
/// Amount of time that was needed to synchronize state of the graph with the state of
/// backing native objects (Rapier's rigid bodies, colliders, joints, sound sources, etc.)
pub sync_time: Duration,
/// Physics performance statistics.
pub physics: PhysicsPerformanceStatistics,
/// 2D Physics performance statistics.
pub physics2d: PhysicsPerformanceStatistics,
/// A time which was required to render sounds.
pub sound_update_time: Duration,
}
impl GraphPerformanceStatistics {
/// Returns total amount of time.
pub fn total(&self) -> Duration {
self.hierarchical_properties_time
+ self.sync_time
+ self.physics.total()
+ self.physics2d.total()
+ self.sound_update_time
}
}
/// A helper type alias for node pool.
pub type NodePool = Pool<Node, NodeContainer>;
/// See module docs.
#[derive(Reflect)]
#[reflect(type_uuid = "92b8e89c-3934-4876-a60a-2970286a56f6")]
pub struct Graph {
#[reflect(hidden)]
root: Handle<Node>,
pool: NodePool,
#[reflect(hidden)]
stack: Vec<Handle<Node>>,
/// User-defined data. Could be any registered [`crate::core::dyntype::DynType`] instance.
pub user_data: DynTypeContainer,
/// Backing physics "world". It is responsible for the physics simulation.
pub physics: PhysicsWorld,
/// Backing 2D physics "world". It is responsible for the 2D physics simulation.
pub physics2d: dim2::physics::PhysicsWorld,
/// Backing sound context. It is responsible for sound rendering.
#[reflect(hidden)]
pub sound_context: SoundContext,
/// Performance statistics of a last [`Graph::update`] call.
#[reflect(hidden)]
pub performance_statistics: GraphPerformanceStatistics,
/// Allows you to "subscribe" for graph events.
#[reflect(hidden)]
pub event_broadcaster: GraphEventBroadcaster,
/// Current lightmap.
lightmap: Option<Lightmap>,
#[reflect(hidden)]
pub(crate) script_message_sender: Sender<NodeScriptMessage>,
#[reflect(hidden)]
pub(crate) script_message_receiver: Receiver<NodeScriptMessage>,
#[reflect(hidden)]
pub(crate) message_sender: Sender<NodeMessage>,
#[reflect(hidden)]
pub(crate) message_receiver: Receiver<NodeMessage>,
#[reflect(read_only)]
instance_id_map: FxHashMap<SceneNodeId, Handle<Node>>,
}
impl PartialEq for Graph {
fn eq(&self, other: &Self) -> bool {
self.root == other.root && self.pool == other.pool
}
}
impl Debug for Graph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Graph")
.field("physics", &self.physics)
.field("physics2d", &self.physics2d)
.field("sound_context", &self.sound_context)
.field("performance_statistics", &self.performance_statistics)
.field("event_broadcaster", &self.event_broadcaster)
.field("lightmap", &self.lightmap)
.field("instance_id_map", &self.instance_id_map)
.finish()?;
f.write_char('\n')?;
f.write_str(&self.summary())
}
}
impl Clone for Graph {
fn clone(&self) -> Self {
self.clone_ex(
self.root,
true,
&mut |_, _| true,
&mut |_, _| {},
&mut |_, _, _| {},
)
.0
}
}
impl Default for Graph {
fn default() -> Self {
let (script_message_sender, script_message_receiver) = channel();
let (message_sender, message_receiver) = channel();
Self {
physics: PhysicsWorld::new(),
physics2d: dim2::physics::PhysicsWorld::new(),
root: Handle::NONE,
pool: Pool::new(),
stack: Vec::new(),
sound_context: Default::default(),
performance_statistics: Default::default(),
event_broadcaster: Default::default(),
script_message_receiver,
message_sender,
script_message_sender,
lightmap: None,
instance_id_map: Default::default(),
message_receiver,
user_data: Default::default(),
}
}
}
/// Sub-graph is a piece of graph that was extracted from a graph. It has ownership
/// over its nodes. It is used to temporarily take ownership of a sub-graph. This could
/// be used if you're making a scene editor with a command stack - once you reverted a command,
/// that created a complex nodes hierarchy (for example you loaded a model) you must store
/// all added nodes somewhere to be able to put nodes back into graph when user decide to re-do
/// command. Sub-graph allows you to do this without invalidating handles to nodes.
#[derive(Debug)]
pub struct SubGraph {
/// A root node and its [ticket](/fyrox-core/model/struct.Ticket.html).
pub root: (Ticket<Node>, Node),
/// A set of descendant nodes with their tickets.
pub descendants: Vec<(Ticket<Node>, Node)>,
/// A handle to the parent node from which the sub-graph was extracted (it it parent node of
/// the root of this sub-graph).
pub parent: Handle<Node>,
}
/// Perform remapping for every node in the destination graph that is included
/// in the given `old_new_mapping`. This uses reflection to update every node handle
/// that is part of the data of each node.
fn remap_handles(old_new_mapping: &NodeHandleMap<Node>, dest_graph: &mut Graph) {
// Iterate over instantiated nodes and remap handles.
for (_, &new_node_handle) in old_new_mapping.inner().iter() {
old_new_mapping.remap_handles(
&mut dest_graph.pool[new_node_handle],
&[TypeId::of::<UntypedResource>()],
);
}
}
/// Calculates local transform of a scene node without scaling.
pub fn isometric_local_transform(
nodes: &NodePool,
node: Handle<impl ObjectOrVariant<Node>>,
) -> Matrix4<f32> {
let transform = nodes[node.to_base()].local_transform();
TransformBuilder::new()
.with_local_position(**transform.position())
.with_local_rotation(**transform.rotation())
.with_pre_rotation(**transform.pre_rotation())
.with_post_rotation(**transform.post_rotation())
.build()
.matrix()
}
/// Calculates global transform of a scene node without scaling.
pub fn isometric_global_transform(
nodes: &NodePool,
node: Handle<impl ObjectOrVariant<Node>>,
) -> Matrix4<f32> {
let node = node.to_base();
let parent = nodes[node].parent();
if parent.is_some() {
isometric_global_transform(nodes, parent) * isometric_local_transform(nodes, node)
} else {
isometric_local_transform(nodes, node)
}
}
// Clears all information about parent-child relations of a given node. This is needed in some
// cases (mostly when copying a node), because `Graph::add_node` uses children list to attach
// children to the given node, and when copying a node it is important that this step is skipped.
fn clear_links(mut node: Node) -> Node {
node.children.clear();
node.parent = Handle::NONE;
node
}
/// A set of switches that allows you to disable a particular step of graph update pipeline.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphUpdateSwitches {
/// Enables the physics update to have a non-zero `dt`.
pub physics_dt: bool,
/// Enables or disables update of the 2D physics.
pub physics2d: bool,
/// Enables or disables update of the 3D physics.
pub physics: bool,
/// A set of nodes that will be updated, everything else won't be updated.
pub node_overrides: Option<FxHashSet<Handle<Node>>>,
/// Enables or disables deletion of the nodes with ended lifetime (lifetime <= 0.0). If set to `false` the lifetime
/// of the nodes won't be changed.
pub delete_dead_nodes: bool,
/// Whether the graph update is paused or not. Paused graphs won't be updated and their sound content will be also paused
/// so it won't emit any sounds.
pub paused: bool,
}
impl Default for GraphUpdateSwitches {
fn default() -> Self {
Self {
physics_dt: true,
physics2d: true,
physics: true,
node_overrides: Default::default(),
delete_dead_nodes: true,
paused: false,
}
}
}
/// A set of potential errors that may occur when using the [`Graph`] API.
#[derive(PartialEq)]
pub enum GraphError {
/// An error from the underlying scene node storage. See [`PoolError`] for more info.
PoolError(PoolError),
/// There's no script of the requested type.
NoScript {
/// Handle of the node.
handle: Handle<Node>,
/// Type name of the script.
script_type_name: &'static str,
},
/// There's no script field of the requested type.
NoScriptField {
/// Handle of the node.
handle: Handle<Node>,
/// Type name of the script field.
field_type_name: &'static str,
},
/// There's no scene node with the given id.
UnknownId(SceneNodeId),
}
impl Display for GraphError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
GraphError::PoolError(err) => std::fmt::Display::fmt(&err, f),
GraphError::NoScript {
handle,
script_type_name,
} => {
write!(
f,
"There's no script {script_type_name} on the scene node {handle}"
)
}
GraphError::NoScriptField {
handle,
field_type_name,
} => {
write!(
f,
"There's no script field {field_type_name} on the scene node {handle}"
)
}
GraphError::UnknownId(id) => {
write!(f, "There's no scene node with {id:?} id!")
}
}
}
}
impl Debug for GraphError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl std::error::Error for GraphError {}
impl From<PoolError> for GraphError {
fn from(value: PoolError) -> Self {
Self::PoolError(value)
}
}
impl Graph {
/// Creates new graph instance with single root node.
#[inline]
pub fn new() -> Self {
let (script_message_sender, script_message_receiver) = channel();
let (message_sender, message_receiver) = channel();
// Create root node.
let mut root_node = Pivot::default();
let instance_id = root_node.instance_id;
root_node.set_name("SceneRoot");
// Add it to the pool.
let mut pool = Pool::new();
let root = pool.spawn(Node::new(root_node));
pool[root].on_connected_to_graph(
root,
message_sender.clone(),
script_message_sender.clone(),
);
let instance_id_map = FxHashMap::from_iter([(instance_id, root)]);
Self {
physics: Default::default(),
stack: Vec::new(),
root,
pool,
physics2d: Default::default(),
sound_context: SoundContext::new(),
performance_statistics: Default::default(),
event_broadcaster: Default::default(),
script_message_receiver,
message_sender,
script_message_sender,
lightmap: None,
instance_id_map,
message_receiver,
user_data: Default::default(),
}
}
/// Begins multi-borrowing and gives access to the 3D physics world at the same time. The physics
/// world could be used to perform shape or ray casting while having the ability to modify scene
/// nodes.
pub fn begin_multi_borrow_with_physics_3d(
&mut self,
) -> (MultiBorrowContext<Node, NodeContainer>, &PhysicsWorld) {
(self.pool.begin_multi_borrow(), &self.physics)
}
/// Begins multi-borrowing and gives access to the 2D physics world at the same time. The physics
/// world could be used to perform shape or ray casting while having the ability to modify scene
/// nodes.
pub fn begin_multi_borrow_with_physics_2d(
&mut self,
) -> (MultiBorrowContext<Node, NodeContainer>, &PhysicsWorld) {
(self.pool.begin_multi_borrow(), &mut self.physics)
}
/// Begins multi-borrowing and gives access to the 3D and 2D physics world at the same time. The
/// physics world could be used to perform shape or ray casting while having the ability to modify
/// scene nodes.
pub fn begin_multi_borrow_with_physics(
&mut self,
) -> (
MultiBorrowContext<Node, NodeContainer>,
&PhysicsWorld,
&dim2::physics::PhysicsWorld,
) {
(
self.pool.begin_multi_borrow(),
&self.physics,
&self.physics2d,
)
}
/// Creates a new graph using a hierarchy of nodes specified by the `root`.
pub fn from_hierarchy(root: Handle<Node>, other_graph: &Self) -> Self {
let mut graph = Self::default();
other_graph.copy_node(
root,
&mut graph,
false,
&mut |_, _| true,
&mut |_, _| {},
&mut |_, _, _| {},
);
graph
}
fn recursive_summary(
&self,
indent: usize,
current: Handle<impl ObjectOrVariant<Node>>,
result: &mut String,
) {
let current = current.to_base();
for _ in 0..indent {
result.push_str(" ");
}
let Ok(node) = self.try_get(current) else {
use std::fmt::Write;
writeln!(result, "{}: Failed to get", current).unwrap();
return;
};
result.push_str(&node.summary());
result.push('\n');
for script in node.scripts() {
for _ in 0..indent + 1 {
result.push_str(" ");
}
result.push_str("+ Script: ");
result.push_str(&script.summary());
result.push('\n');
}
for child in node.children() {
self.recursive_summary(indent + 1, *child, result);
}
}
/// Sets new root of the graph and attaches the old root to the new root. Old root becomes a child
/// node of the new root.
pub fn change_root_node(&mut self, root: Node) {
let prev_root = self.root;
self.root = Handle::NONE;
let handle = self.add_node(root);
assert_eq!(self.root, handle);
self.link_nodes(prev_root, handle);
}
/// Tries to find references of the given node in other scene nodes. It could be used to check if the node is
/// used by some other scene node or not. Returns an array of nodes, that references the given node. This method
/// is reflection-based, so it is quite slow and should not be used every frame.
pub fn find_references_to(
&self,
target: Handle<impl ObjectOrVariant<Node>>,
) -> Vec<Handle<Node>> {
let mut references = Vec::new();
for (node_handle, node) in self.pair_iter() {
(node as &dyn Reflect).apply_recursively(
&mut |object| {
if let Some(handle) = object.downcast_ref::<Handle<Node>>() {
if *handle == target {
references.push(node_handle);
}
}
},
&[TypeId::of::<UntypedResource>()],
);
}
references
}
/// Sets global position of a scene node. Internally, this method converts the given position
/// to the local space of the parent node of the given scene node and sets it as local position
/// of the node. In other words, this method does not modify global position itself, but calculates
/// new local position of the given node so that its global position will be as requested.
///
/// ## Important
///
/// This method relies on pre-calculated global transformation of the hierarchy. This may give
/// unexpected results if you've modified transforms of ancestors in the hierarchy of the node
/// and then called this method. This happens because global transform calculation is deferred
/// to the end of the frame. If you want to ensure that everything works as expected, call
/// [`Self::update_hierarchical_data`] before calling this method. It is not called automatically,
/// because it is quite heavy, and in most cases this method works ok without it.
pub fn set_global_position(
&mut self,
node_handle: Handle<impl ObjectOrVariant<Node>>,
position: Vector3<f32>,
) {
let node_handle = node_handle.to_base();
let (node, parent) = self
.pool
.try_borrow_dependant_mut(node_handle, |node| node.parent());
if let Ok(node) = node {
if let Ok(parent) = parent {
let relative_position = parent
.global_transform()
.try_inverse()
.unwrap_or_default()
.transform_point(&position.into())
.coords;
node.local_transform_mut().set_position(relative_position);
} else {
node.local_transform_mut().set_position(position);
}
self.update_hierarchical_data_for_descendants(node_handle);
}
}
/// Sets global rotation of a scene node. Internally, this method converts the given rotation
/// to the local space of the parent node of the given scene node and sets it as local rotation
/// of the node. In other words, this method does not modify global rotation itself, but calculates
/// new local rotation of the given node so that its global rotation will be as requested.
///
/// ## Important
///
/// This method relies on pre-calculated global transformation of the hierarchy. This may give
/// unexpected results if you've modified transforms of ancestors in the hierarchy of the node
/// and then called this method. This happens because global transform calculation is deferred
/// to the end of the frame. If you want to ensure that everything works as expected, call
/// [`Self::update_hierarchical_data`] before calling this method. It is not called automatically,
/// because it is quite heavy, and in most cases this method works ok without it.
pub fn set_global_rotation(
&mut self,
node: Handle<impl ObjectOrVariant<Node>>,
rotation: UnitQuaternion<f32>,
) {
let node = node.to_base();
let (node, parent) = self
.pool
.try_borrow_dependant_mut(node, |node| node.parent());
if let Ok(node) = node {
if let Ok(parent) = parent {
let basis = parent
.global_transform()
.try_inverse()
.unwrap_or_default()
.basis();
let relative_rotation = UnitQuaternion::from_matrix(&basis) * rotation;
node.local_transform_mut().set_rotation(relative_rotation);
} else {
node.local_transform_mut().set_rotation(rotation);
}
}
}
/// Tries to borrow mutable references to two nodes at the same time by given handles. Will
/// panic if handles overlaps (points to same node).
#[inline]
pub fn get_two_mut(&mut self, nodes: (Handle<Node>, Handle<Node>)) -> (&mut Node, &mut Node) {
self.pool.borrow_two_mut(nodes)
}
/// Tries to borrow mutable references to three nodes at the same time by given handles. Will
/// return Err of handles overlaps (points to same node).
#[inline]
pub fn get_three_mut(
&mut self,
nodes: (Handle<Node>, Handle<Node>, Handle<Node>),
) -> (&mut Node, &mut Node, &mut Node) {
self.pool.borrow_three_mut(nodes)
}
/// Tries to borrow mutable references to four nodes at the same time by given handles. Will
/// panic if handles overlaps (points to same node).
#[inline]
pub fn get_four_mut(
&mut self,
nodes: (Handle<Node>, Handle<Node>, Handle<Node>, Handle<Node>),
) -> (&mut Node, &mut Node, &mut Node, &mut Node) {
self.pool.borrow_four_mut(nodes)
}
/// Returns root node of current graph.
#[inline]
pub fn get_root(&self) -> Handle<Node> {
self.root
}
/// Begins multi-borrow that allows you borrow to as many shared references to the graph
/// nodes as you need and only one mutable reference to a node. See
/// [`MultiBorrowContext::try_get`] for more info.
///
/// ## Examples
///
/// ```rust
/// # use fyrox_impl::{
/// # core::pool::Handle,
/// # scene::{base::BaseBuilder, graph::Graph, node::Node, pivot::PivotBuilder},
/// # };
/// #
/// let mut graph = Graph::new();
///
/// let handle1 = PivotBuilder::new(BaseBuilder::new()).build(&mut graph);
/// let handle2 = PivotBuilder::new(BaseBuilder::new()).build(&mut graph);
/// let handle3 = PivotBuilder::new(BaseBuilder::new()).build(&mut graph);
/// let handle4 = PivotBuilder::new(BaseBuilder::new()).build(&mut graph);
///
/// let mut ctx = graph.begin_multi_borrow();
///
/// let node1 = ctx.try_get(handle1);
/// let node2 = ctx.try_get(handle2);
/// let node3 = ctx.try_get(handle3);
/// let node4 = ctx.try_get(handle4);
///
/// assert!(node1.is_ok());
/// assert!(node2.is_ok());
/// assert!(node3.is_ok());
/// assert!(node4.is_ok());
///
/// // An attempt to borrow the same node twice as immutable and mutable will fail.
/// assert!(ctx.try_get_mut(handle1).is_err());
/// ```
#[inline]
pub fn begin_multi_borrow(&mut self) -> MultiBorrowContext<Node, NodeContainer> {
self.pool.begin_multi_borrow()
}
/// Links specified child with specified parent while keeping the
/// child's global position and rotation.
#[inline]
pub fn link_nodes_keep_global_transform(
&mut self,
child: Handle<impl ObjectOrVariant<Node>>,
parent: Handle<impl ObjectOrVariant<Node>>,
) {
let child = child.to_base();
let parent = parent.to_base();
let parent_global_transform_inv = self.pool[parent]
.global_transform()
.try_inverse()
.unwrap_or_default();
let child_global_transform = self.pool[child].global_transform();
let relative_transform = parent_global_transform_inv * child_global_transform;
let local_position = relative_transform.position();
let parent_inv_global_rotation = self.global_rotation(parent).inverse();
let local_rotation = parent_inv_global_rotation * self.global_rotation(child);
let local_scale = self
.global_scale(child)
.component_div(&self.global_scale(parent));
self.pool[child]
.local_transform_mut()
.set_position(local_position)
.set_rotation(local_rotation)
.set_scale(local_scale);
self.link_nodes(child, parent);
}
/// Searches for a **first** node with a script of the given type `S` in the hierarchy starting from the
/// given `root_node`.
#[inline]
pub fn find_first_by_script<S>(
&self,
root_node: Handle<impl ObjectOrVariant<Node>>,
) -> Option<(Handle<Node>, &Node)>
where
S: ScriptTrait,
{
self.find(root_node, &mut |node| {
for script in &node.scripts {
if script.as_ref().and_then(|s| s.cast::<S>()).is_some() {
return true;
}
}
false
})
}
/// Creates deep copy of node with all children. This is relatively heavy operation!
/// In case if any error happened it returns `Handle::NONE`. This method can be used
/// to create exact copy of given node hierarchy. For example you can prepare rocket
/// model: case of rocket will be mesh, and fire from nozzle will be particle system,
/// and when you fire from rocket launcher you just need to create a copy of such
/// "prefab".
///
/// * `node_handle`: The handle of the node to copy.
/// * `dest_graph`: A mutable borrow of the node graph that will contain the newly created copy.
/// * `filter`: A function that takes a node handle and a borrow of a node and returns a bool.
/// The is called with the handle and node of each child of `node_handle` to determine if
/// that child should be recursively copied, and then the same filter is applied to each recursive step.
/// * `pre_processing_callback`: A function that takes a node handle and a mutable node.
/// The handle belongs to the original node in this graph. The node is a copy of the original
/// node, except that parent handle and child handles have been removed. This is a node
/// that will be inserted into `dest_graph`.
/// * `post_processing_callback`: A function much like `pre_processing_callback` except that
/// it is called after the new node and all its children have been inserted into the graph.
/// The function takes (new handle, original handle, node) as parameters. The node has handles
/// for its newly copied children, but no handle for its parent.
///
/// # 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.
#[inline]
pub fn copy_node<F, Pre, Post>(
&self,
node_handle: Handle<impl ObjectOrVariant<Node>>,
dest_graph: &mut Graph,
preserve_handles: bool,
filter: &mut F,
pre_process_callback: &mut Pre,
post_process_callback: &mut Post,
) -> (Handle<Node>, NodeHandleMap<Node>)
where
F: FnMut(Handle<Node>, &Node) -> bool,
Pre: FnMut(Handle<Node>, &mut Node),
Post: FnMut(Handle<Node>, Handle<Node>, &mut Node),
{
let mut old_new_mapping = NodeHandleMap::default();
let root_handle = self.copy_node_raw(
node_handle,
dest_graph,
preserve_handles,
&mut old_new_mapping,
filter,
pre_process_callback,
post_process_callback,
);
remap_handles(&old_new_mapping, dest_graph);
(root_handle, old_new_mapping)
}
/// * `node_handle`: The handle of the node to copy.
/// * `dest_graph`: A mutable borrow of the node graph that will contain the newly created copy.
/// * `old_new_mapping`: A mutable hashmap of node handles which records which original nodes were copied to
/// become which new nodes.
/// * `filter`: A function that takes a node handle and a borrow of a node and returns a bool.
/// The is called with the handle and node of each child of `node_handle` to determine if
/// that child should be recursively copied, and then the same filter is applied to each recursive step.
/// * `pre_processing_callback`: A function that takes a node handle and a mutable node.
/// The handle belongs to the original node in this graph. The node is a copy of the original
/// node, except that parent handle and child handles have been removed. This is a node
/// that will be inserted into `dest_graph`.
/// * `post_processing_callback`: A function much like `pre_processing_callback` except that
/// it is called after the new node and all its children have been inserted into the graph.
/// The function takes (new handle, original handle, node) as parameters. The node has handles
/// for its newly copied children, but no handle for its parent.
fn copy_node_raw<F, Pre, Post>(
&self,
root_handle: Handle<impl ObjectOrVariant<Node>>,
dest_graph: &mut Graph,
preserve_handles: bool,
old_new_mapping: &mut NodeHandleMap<Node>,
filter: &mut F,
pre_process_callback: &mut Pre,
post_process_callback: &mut Post,
) -> Handle<Node>
where
F: FnMut(Handle<Node>, &Node) -> bool,
Pre: FnMut(Handle<Node>, &mut Node),
Post: FnMut(Handle<Node>, Handle<Node>, &mut Node),
{
let root_handle = root_handle.to_base();
let src_node = &self.pool[root_handle];
let mut dest_node = clear_links(src_node.clone_box());
pre_process_callback(root_handle, &mut dest_node);
let dest_copy_handle = if preserve_handles {
dest_graph.add_node_at_handle(dest_node, root_handle);
root_handle
} else {
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,
preserve_handles,
old_new_mapping,
filter,
pre_process_callback,
post_process_callback,
);
if !dest_child_handle.is_none() {
dest_graph.link_nodes(dest_child_handle, dest_copy_handle);
}
}
}
post_process_callback(
dest_copy_handle,
root_handle,
&mut dest_graph[dest_copy_handle],
);
dest_copy_handle
}
/// Creates deep copy of node with all children. This is relatively heavy operation! Almost the
/// same as [`Self::copy_node`], but puts the cloned nodes directly to the same graph. In case
/// if any error happened it returns `Handle::NONE`. This method can be used to create exact copy
/// of given node hierarchy.
///
/// # 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.
#[inline]
pub fn copy_node_inplace<F>(
&mut self,
node_handle: Handle<Node>,
filter: &mut F,
) -> (Handle<Node>, NodeHandleMap<Node>)
where
F: FnMut(Handle<Node>, &Node) -> bool,
{
let mut old_new_mapping = NodeHandleMap::default();
let root_handle = self.copy_node_raw_in_place(node_handle, &mut old_new_mapping, filter);
remap_handles(&old_new_mapping, self);
(root_handle, old_new_mapping)
}
/// * `node_handle`: The handle of the node to copy.
/// * `old_new_mapping`: A mutable hashmap of node handles which records which original nodes were copied to
/// become which new nodes.
/// * `filter`: A function that takes a node handle and a borrow of a node and returns a bool.
/// The is called with the handle and node of each child of `node_handle` to determine if
/// that child should be recursively copied, and then the same filter is applied to each recursive step.
fn copy_node_raw_in_place<F>(
&mut self,
root_handle: Handle<impl ObjectOrVariant<Node>>,
old_new_mapping: &mut NodeHandleMap<Node>,
filter: &mut F,
) -> Handle<Node>
where
F: FnMut(Handle<Node>, &Node) -> bool,
{
let root_handle = root_handle.to_base();
let src_node = &self.pool[root_handle];
let dest_node = clear_links(src_node.clone_box());
let src_children = src_node.children().to_vec();
let dest_copy_handle = self.add_node(dest_node);
old_new_mapping.insert(root_handle, dest_copy_handle);
for src_child_handle in src_children {
if filter(src_child_handle, &self.pool[src_child_handle]) {
let dest_child_handle =
self.copy_node_raw_in_place(src_child_handle, old_new_mapping, filter);
if !dest_child_handle.is_none() {
self.link_nodes(dest_child_handle, dest_copy_handle);
}
}
}
dest_copy_handle
}
/// Creates copy of a node and breaks all connections with other nodes. Keep in mind that
/// this method may give unexpected results when the node has connections with other nodes.
/// For example if you'll try to copy a skinned mesh, its copy won't be skinned anymore -
/// you'll get just a "shallow" mesh. Also unlike [copy_node](struct.Graph.html#method.copy_node)
/// this method returns copied node directly, it does not inserts it in any graph.
#[inline]
pub fn copy_single_node(&self, node_handle: Handle<Node>) -> Node {
let node = &self.pool[node_handle];
let mut clone = clear_links(node.clone_box());
if let Some(ref mut mesh) = clone.cast_mut::<Mesh>() {
for surface in mesh.surfaces_mut() {
surface.bones.clear();
}
}
clone
}
/// Reconnects every node to the graph by calling its [`Base::on_connected_to_graph`](crate::scene::base::Base::on_connected_to_graph)
/// method, giving the node its `self_handle` and the graph's `message_sender` and `script_message_sender`.
fn restore_dynamic_node_data(&mut self) {
for (handle, node) in self.pool.pair_iter_mut() {
node.on_connected_to_graph(
handle,
self.message_sender.clone(),
self.script_message_sender.clone(),
);
}
}
/// Fix property flags for scenes made before inheritance system was fixed. By default, all inheritable properties
/// must be marked as modified in nodes without any parent resource.
pub(crate) fn mark_ancestor_nodes_as_modified(&mut self) {
for node in self.linear_iter_mut() {
if node.resource.is_none() {
node.mark_inheritable_variables_as_modified();
}
}
}
/// Synchronizes the state of the graph with external resources.
pub(crate) fn resolve(&mut self) {
Log::writeln(MessageKind::Information, "Resolving graph...");
self.restore_dynamic_node_data();
self.mark_ancestor_nodes_as_modified();
// Since the parent resources may have changed, iterate through every node and try to find
// the corresponding node in its resource, either by name or by handle depending on the resource's
// 'NodeMapping`. If a corresponding node cannot be found in the resource, then delete the node.
self.restore_original_handles_and_inherit_properties(
&[TypeId::of::<navmesh::Container>()],
|resource_node, node| {
node.inv_bind_pose_transform = resource_node.inv_bind_pose_transform;
},
);
self.update_hierarchical_data();
// Create a list of (handle, resource) pairs where the handle points to a node that is the root of some instance
// of the resource within this graph. At the same time, modify each node so that its unmodified values match the
// corresponding values from within the instance's resource, so if the resource has changed the nodes will now
// reflect that change.
let instances = self.restore_integrity(|model, model_data, handle, dest_graph| {
ModelResource::instantiate_from(model, model_data, handle, dest_graph, &mut |_, _| {})
});
// Iterate through the list of (handle, resource) pairs and use reflection to replace each handle within each instance
// with the handle that corresponds to the associated copy in this graph. This is necessary because after
// `restore_original_handles_and_inherit_properties` the handles will have been reset back to their original values
// which refer to nodes *within* the resource instead of nodes in this graph.
self.remap_handles(&instances);
self.apply_lightmap();
Log::writeln(MessageKind::Information, "Graph resolved successfully!");
}
/// Tries to set new lightmap to scene.
pub fn set_lightmap(
&mut self,
new_lightmap: Option<Lightmap>,
) -> Result<Option<Lightmap>, &'static str> {
if let Some(lightmap) = new_lightmap.as_ref() {
// Assign textures to surfaces.
for (handle, lightmaps) in lightmap.map.iter() {
if let Ok(mesh) = self.try_get_mut_of_type::<Mesh>(*handle) {
if mesh.surfaces().len() != lightmaps.len() {
return Err("failed to set lightmap, surface count mismatch");
}
for (surface, entry) in mesh.surfaces_mut().iter_mut().zip(lightmaps) {
// This unwrap() call must never panic in normal conditions, because texture wrapped in Option
// only to implement Default trait to be serializable.
let texture = entry.texture.clone().unwrap();
let material = surface.material.deep_copy();
let mut material_state = material.state();
if let Some(material) = material_state.data() {
material.bind(
&lightmap.texture_name,
MaterialResourceBinding::Texture(MaterialTextureBinding {
value: Some(texture),
}),
);
}
drop(material_state);
surface.material.set_value_and_mark_modified(material);
}
}
}
} else if let Some(existing_lightmap) = self.lightmap.as_ref() {
let texture_name = existing_lightmap.texture_name.clone();
for node in self.linear_iter_mut() {
if let Some(mesh) = node.cast_mut::<Mesh>() {
for surface in mesh.surfaces_mut() {
let mut material_state = surface.material().state();
if let Some(material) = material_state.data() {
material.bind(
&texture_name,
MaterialResourceBinding::Texture(MaterialTextureBinding {
value: None,
}),
);
}
}
}
}
}
Ok(std::mem::replace(&mut self.lightmap, new_lightmap))
}
/// Returns current lightmap.
pub fn lightmap(&self) -> Option<&Lightmap> {
self.lightmap.as_ref()
}
fn apply_lightmap(&mut self) {
// Re-apply lightmap if any. This has to be done after resolve because we must patch surface
// data at this stage, but if we'd do this before we wouldn't be able to do this because
// meshes contains invalid surface data.
if let Some(lightmap) = self.lightmap.as_mut() {
// Patch surface data first. To do this we gather all surface data instances and
// look in patch data if we have patch for data.
let mut unique_data_set = FxHashMap::default();
for &handle in lightmap.map.keys() {
if let Some(mesh) = self.pool[handle].cast_mut::<Mesh>() {
for surface in mesh.surfaces() {
let data = surface.data();
unique_data_set.entry(data.key()).or_insert(data);
}
}
}
for (_, surface_data) in unique_data_set.into_iter() {
let mut data = surface_data.data_ref();
if let Some(patch) = lightmap.patches.get(&data.content_hash()) {
lightmap::apply_surface_data_patch(
&mut data,
&patch.0,
lightmap.second_tex_coord_location,
);
}
}
// Apply textures.
for (&handle, entries) in lightmap.map.iter_mut() {
if let Some(mesh) = self.pool[handle].cast_mut::<Mesh>() {
for (entry, surface) in entries.iter_mut().zip(mesh.surfaces_mut()) {
let material = surface.material.deep_copy();
let mut material_state = material.state();
if let Some(material) = material_state.data() {
material.bind(
&lightmap.texture_name,
MaterialResourceBinding::Texture(MaterialTextureBinding {
value: entry.texture.clone(),
}),
);
}
drop(material_state);
surface.material.set_value_and_mark_modified(material);
}
}
}
}
}
/// Tries to compute combined axis-aligned bounding box (AABB) in world-space of the hierarchy starting from the given
/// scene node. It will return [`None`] if the scene node handle is invalid, otherwise it will return AABB that enclosing
/// all the nodes in the hierarchy.
pub fn aabb_of_descendants<F>(
&self,
root: Handle<impl ObjectOrVariant<Node>>,
mut filter: F,
) -> Option<AxisAlignedBoundingBox>
where
F: FnMut(Handle<Node>, &Node) -> bool,
{
let root = root.to_base();
fn aabb_of_descendants_recursive<F>(
graph: &Graph,
node: Handle<Node>,
filter: &mut F,
) -> Option<AxisAlignedBoundingBox>
where
F: FnMut(Handle<Node>, &Node) -> bool,
{
graph.try_get_node(node).ok().and_then(|n| {
if filter(node, n) {
let mut aabb = n.local_bounding_box();
if aabb.is_invalid_or_degenerate() {
aabb = AxisAlignedBoundingBox::collapsed().transform(&n.global_transform());
} else {
aabb = aabb.transform(&n.global_transform());
}
for child in n.children() {
if let Some(child_aabb) =
aabb_of_descendants_recursive(graph, *child, filter)
{
aabb.add_box(child_aabb);
}
}
Some(aabb)
} else {
None
}
})
}
aabb_of_descendants_recursive(self, root, &mut filter)
}
pub(crate) fn update_enabled_flag_recursively(nodes: &NodePool, node_handle: Handle<Node>) {
let Ok(node) = nodes.try_borrow(node_handle) else {
return;
};
let parent_enabled = nodes
.try_borrow(node.parent())
.ok()
.is_none_or(|p| p.is_globally_enabled());
node.global_enabled.set(parent_enabled && node.is_enabled());
for &child in node.children() {
Self::update_enabled_flag_recursively(nodes, child);
}
}
pub(crate) fn update_visibility_recursively(nodes: &NodePool, node_handle: Handle<Node>) {
let Ok(node) = nodes.try_borrow(node_handle) else {
return;
};
let parent_visibility = nodes
.try_borrow(node.parent())
.ok()
.is_none_or(|p| p.global_visibility());
node.global_visibility
.set(parent_visibility && node.visibility());
for &child in node.children() {
Self::update_visibility_recursively(nodes, child);
}
}
pub(crate) fn update_global_transform_recursively(
nodes: &NodePool,
sound_context: &mut SoundContext,
physics: &mut PhysicsWorld,
physics2d: &mut dim2::physics::PhysicsWorld,
node_handle: Handle<Node>,
) {
let Ok(node) = nodes.try_borrow(node_handle) else {
return;
};
let parent_global_transform = if let Ok(parent) = nodes.try_borrow(node.parent()) {
parent.global_transform()
} else {
Matrix4::identity()
};
let new_global_transform = parent_global_transform * node.local_transform().matrix();
// TODO: Detect changes from user code here.
node.on_global_transform_changed(
&new_global_transform,
&mut SyncContext {
nodes,
physics,
physics2d,
sound_context,
switches: None,
},
);
node.global_transform.set(new_global_transform);
for &child in node.children() {
Self::update_global_transform_recursively(
nodes,
sound_context,
physics,
physics2d,
child,
);
}
}
/// Calculates local and global transform, global visibility for each node in graph starting from the
/// specified node and down the tree. The main use case of the method is to update global position (etc.)
/// of an hierarchy of the nodes of some new prefab instance.
///
/// # Important Notes
///
/// This method could be slow for large hierarchies. You should call it only when absolutely needed.
#[inline]
pub fn update_hierarchical_data_for_descendants(
&mut self,
node_handle: Handle<impl ObjectOrVariant<Node>>,
) {
Self::update_hierarchical_data_recursively(
&self.pool,
&mut self.sound_context,
&mut self.physics,
&mut self.physics2d,
node_handle.to_base(),
);
}
/// Calculates local and global transform, global visibility for each node in graph starting from the
/// specified node and down the tree. The main use case of the method is to update global position (etc.)
/// of an hierarchy of the nodes of some new prefab instance.
///
/// # Important Notes
///
/// This method could be slow for large graph. You should call it only when absolutely needed.
#[inline]
pub fn update_hierarchical_data(&mut self) {
self.update_hierarchical_data_for_descendants(self.root);
}
pub(crate) fn update_hierarchical_data_recursively(
nodes: &NodePool,
sound_context: &mut SoundContext,
physics: &mut PhysicsWorld,
physics2d: &mut dim2::physics::PhysicsWorld,
node_handle: Handle<Node>,
) {
Self::update_global_transform_recursively(
nodes,
sound_context,
physics,
physics2d,
node_handle,
);
Self::update_enabled_flag_recursively(nodes, node_handle);
Self::update_visibility_recursively(nodes, node_handle);
}
// This method processes messages from scene nodes and propagates changes on descendant nodes
// in the hierarchy. Scene nodes have global transform, visibility and enabled flags and their
// values depend on the values of ancestors in the hierarchy. This method uses optimized changes
// propagation that propagates changes on small "chains" of nodes instead of updating the entire
// graph. This is much faster since most scene nodes remain unchanged most of the time.
//
// Performance of this method is detached from the amount of scene nodes in the graph and only
// correlates with the amount of changing nodes, allowing to have large scene graphs with tons
// of static nodes.
pub(crate) fn process_node_messages(&mut self, switches: Option<&GraphUpdateSwitches>) {
bitflags! {
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Flags: u8 {
const NONE = 0;
const TRANSFORM_CHANGED = 0b0001;
const VISIBILITY_CHANGED = 0b0010;
const ENABLED_FLAG_CHANGED = 0b0100;
}
}
let mut visited_flags = vec![Flags::NONE; self.pool.get_capacity() as usize];
let mut roots = FxHashMap::default();
while let Ok(message) = self.message_receiver.try_recv() {
if let NodeMessageKind::TransformChanged = message.kind {
if let Ok(node) = self.pool.try_borrow(message.node) {
node.on_local_transform_changed(&mut SyncContext {
nodes: &self.pool,
physics: &mut self.physics,
physics2d: &mut self.physics2d,
sound_context: &mut self.sound_context,
switches,
})
}
}
// Prepare for hierarchy propagation.
let message_flag = match message.kind {
NodeMessageKind::TransformChanged => Flags::TRANSFORM_CHANGED,
NodeMessageKind::VisibilityChanged => Flags::VISIBILITY_CHANGED,
NodeMessageKind::EnabledFlagChanged => Flags::ENABLED_FLAG_CHANGED,
};
let visit_flags = &mut visited_flags[message.node.index() as usize];
if visit_flags.contains(message_flag) {
continue;
}
visit_flags.insert(message_flag);
roots
.entry(message.node)
.or_insert(Flags::NONE)
.insert(message_flag);
// Mark the entire hierarchy as visited.
fn traverse_recursive(
graph: &Graph,
from: Handle<Node>,
func: &mut impl FnMut(Handle<Node>),
) {
func(from);
if let Ok(node) = graph.try_get_node(from) {
for &child in node.children() {
traverse_recursive(graph, child, func)
}
}
}
traverse_recursive(self, message.node, &mut |h| {
visited_flags[h.index() as usize].insert(message_flag);
// Remove a descendant from the list of potential roots.
if h != message.node {
if let Some(flags) = roots.get_mut(&h) {
flags.remove(message_flag);
if flags.is_empty() {
roots.remove(&h);
}
}
}
})
}
for (node, flags) in roots {
if flags.contains(Flags::TRANSFORM_CHANGED) {
Self::update_global_transform_recursively(
&self.pool,
&mut self.sound_context,
&mut self.physics,
&mut self.physics2d,
node,
);
}
// All these calls could be combined into one with the above, but visibility/enabled
// flags changes so rare, that it isn't worth spending CPU cycles on useless checks.
if flags.contains(Flags::VISIBILITY_CHANGED) {
Self::update_visibility_recursively(&self.pool, node);
}
if flags.contains(Flags::ENABLED_FLAG_CHANGED) {
Self::update_enabled_flag_recursively(&self.pool, node)
}
}
}
fn sync_native(&mut self, switches: &GraphUpdateSwitches) {
let mut sync_context = SyncContext {
nodes: &self.pool,
physics: &mut self.physics,
physics2d: &mut self.physics2d,
sound_context: &mut self.sound_context,
switches: Some(switches),
};
for (handle, node) in self.pool.pair_iter() {
node.sync_native(handle, &mut sync_context);
}
}
fn update_node(
&mut self,
handle: Handle<Node>,
frame_size: Vector2<f32>,
dt: f32,
delete_dead_nodes: bool,
) {
if let Ok((ticket, mut node)) = self.pool.try_take_reserve(handle) {
let mut is_alive = node.is_alive();
if node.is_globally_enabled() {
node.update(&mut UpdateContext {
frame_size,
dt,
nodes: &mut self.pool,
physics: &mut self.physics,
physics2d: &mut self.physics2d,
sound_context: &mut self.sound_context,
});
if delete_dead_nodes {
if let Some(lifetime) = node.lifetime.get_value_mut_silent().as_mut() {
*lifetime -= dt;
if *lifetime <= 0.0 {
is_alive = false;
}
}
}
}
self.pool.put_back(ticket, node);
if !is_alive && delete_dead_nodes {
self.remove_node(handle);
}
}
}
/// Updates nodes in the graph using given delta time.
///
/// # Update Switches
///
/// Update switches allows you to disable update for parts of the update pipeline, it could be useful for editors
/// where you need to have preview mode to update only specific set of nodes, etc.
pub fn update(&mut self, frame_size: Vector2<f32>, dt: f32, switches: GraphUpdateSwitches) {
self.sound_context.state().pause(switches.paused);
if switches.paused {
return;
}
let last_time = instant::Instant::now();
self.process_node_messages(Some(&switches));
self.performance_statistics.hierarchical_properties_time =
instant::Instant::now() - last_time;
let last_time = instant::Instant::now();
self.sync_native(&switches);
self.performance_statistics.sync_time = instant::Instant::now() - last_time;
if switches.physics {
self.physics.performance_statistics.reset();
self.physics.update(dt, switches.physics_dt);
self.performance_statistics.physics = self.physics.performance_statistics.clone();
}
if switches.physics2d {
self.physics2d.performance_statistics.reset();
self.physics2d.update(dt, switches.physics_dt);
self.performance_statistics.physics2d = self.physics2d.performance_statistics.clone();
}
self.performance_statistics.sound_update_time =
self.sound_context.state().full_render_duration();
if let Some(overrides) = switches.node_overrides.as_ref() {
for handle in overrides {
self.update_node(*handle, frame_size, dt, switches.delete_dead_nodes);
}
} else {
for i in 0..self.pool.get_capacity() {
self.update_node(
self.pool.handle_from_index(i),
frame_size,
dt,
switches.delete_dead_nodes,
);
}
}
}
/// Returns capacity of internal pool. Can be used to iterate over all **potentially**
/// available indices and try to convert them to handles.
///
/// ```
/// # use fyrox_impl::scene::node::Node;
/// # use fyrox_impl::scene::graph::Graph;
/// # use fyrox_impl::scene::pivot::Pivot;
/// # use fyrox_graph::SceneGraph;
/// let mut graph = Graph::new();
/// graph.add_node(Node::new(Pivot::default()));
/// graph.add_node(Node::new(Pivot::default()));
/// for i in 0..graph.capacity() {
/// let handle = graph.handle_from_index(i);
/// if handle.is_some() {
/// let node = &mut graph[handle];
/// // Do something with node.
/// }
/// }
/// ```
#[inline]
pub fn capacity(&self) -> u32 {
self.pool.get_capacity()
}
/// Makes new handle from given index. Handle will be none if index was either out-of-bounds
/// or point to a vacant pool entry.
///
/// ```
/// # use fyrox_impl::scene::node::Node;
/// # use fyrox_impl::scene::graph::Graph;
/// # use fyrox_impl::scene::pivot::Pivot;
/// # use fyrox_graph::SceneGraph;
/// let mut graph = Graph::new();
/// graph.add_node(Node::new(Pivot::default()));
/// graph.add_node(Node::new(Pivot::default()));
/// for i in 0..graph.capacity() {
/// let handle = graph.handle_from_index(i);
/// if handle.is_some() {
/// let node = &mut graph[handle];
/// // Do something with node.
/// }
/// }
/// ```
#[inline]
pub fn handle_from_index(&self, index: u32) -> Handle<Node> {
self.pool.handle_from_index(index)
}
/// Generates a set of handles that could be used to spawn a set of nodes.
#[inline]
pub fn generate_free_handles(&self, amount: usize) -> Vec<Handle<Node>> {
self.pool.generate_free_handles(amount)
}
/// Creates an iterator that has linear iteration order over internal collection
/// of nodes. It does *not* perform any tree traversal!
#[inline]
pub fn linear_iter(&self) -> impl Iterator<Item = &Node> {
self.pool.iter()
}
/// Creates new iterator that iterates over internal collection giving (handle; node) pairs.
#[inline]
pub fn pair_iter_mut(&mut self) -> impl Iterator<Item = (Handle<Node>, &mut Node)> {
self.pool.pair_iter_mut()
}
/// Extracts node from graph and reserves its handle. It is used to temporarily take
/// ownership over node, and then put node back using given ticket. Extracted node is
/// detached from its parent!
#[inline]
pub fn take_reserve(&mut self, handle: Handle<Node>) -> (Ticket<Node>, Node) {
self.isolate_node(handle);
let (ticket, node) = self.take_reserve_internal(handle);
self.instance_id_map.remove(&node.instance_id);
(ticket, node)
}
pub(crate) fn take_reserve_internal(&mut self, handle: Handle<Node>) -> (Ticket<Node>, Node) {
let (ticket, mut node) = self.pool.take_reserve(handle);
self.instance_id_map.remove(&node.instance_id);
node.on_removed_from_graph(self);
(ticket, node)
}
/// Puts node back by given ticket. Attaches back to root node of graph.
#[inline]
pub fn put_back(&mut self, ticket: Ticket<Node>, node: Node) -> Handle<Node> {
let handle = self.put_back_internal(ticket, node);
self.link_nodes(handle, self.root);
handle
}
pub(crate) fn put_back_internal(&mut self, ticket: Ticket<Node>, node: Node) -> Handle<Node> {
let instance_id = node.instance_id;
let handle = self.pool.put_back(ticket, node);
self.instance_id_map.insert(instance_id, handle);
handle
}
/// Makes node handle vacant again.
#[inline]
pub fn forget_ticket(&mut self, ticket: Ticket<Node>, node: Node) -> Node {
self.pool.forget_ticket(ticket);
node
}
/// Extracts sub-graph starting from a given node. All handles to extracted nodes
/// becomes reserved and will be marked as "occupied", an attempt to borrow a node
/// at such handle will result in panic!. Please note that root node will be
/// detached from its parent!
#[inline]
pub fn take_reserve_sub_graph(&mut self, root: Handle<Node>) -> SubGraph {
// Take out descendants first.
let mut descendants = Vec::new();
let root_ref = &mut self[root];
let mut stack = root_ref.children().to_vec();
let parent = root_ref.parent;
while let Some(handle) = stack.pop() {
stack.extend_from_slice(self[handle].children());
descendants.push(self.take_reserve_internal(handle));
}
SubGraph {
// Root must be extracted with detachment from its parent (if any).
root: self.take_reserve(root),
descendants,
parent,
}
}
/// Puts previously extracted sub-graph into graph. Handles to nodes will become valid
/// again. After that you probably want to re-link returned handle with its previous
/// parent.
#[inline]
pub fn put_sub_graph_back(&mut self, sub_graph: SubGraph) -> Handle<Node> {
for (ticket, node) in sub_graph.descendants {
self.pool.put_back(ticket, node);
}
let (ticket, node) = sub_graph.root;
let root_handle = self.put_back(ticket, node);
self.link_nodes(root_handle, sub_graph.parent);
root_handle
}
/// Forgets the entire sub-graph making handles to nodes invalid.
#[inline]
pub fn forget_sub_graph(&mut self, sub_graph: SubGraph) {
for (ticket, _) in sub_graph.descendants {
self.pool.forget_ticket(ticket);
}
let (ticket, _) = sub_graph.root;
self.pool.forget_ticket(ticket);
}
/// Returns the number of nodes in the graph.
#[inline]
pub fn node_count(&self) -> u32 {
self.pool.alive_count()
}
/// Creates deep copy of graph. Allows filtering while copying, returns copy and
/// old-to-new node mapping.
#[inline]
pub fn clone_ex<F, Pre, Post>(
&self,
root: Handle<impl ObjectOrVariant<Node>>,
preserve_handles: bool,
filter: &mut F,
pre_process_callback: &mut Pre,
post_process_callback: &mut Post,
) -> (Self, NodeHandleMap<Node>)
where
F: FnMut(Handle<Node>, &Node) -> bool,
Pre: FnMut(Handle<Node>, &mut Node),
Post: FnMut(Handle<Node>, Handle<Node>, &mut Node),
{
let root = root.to_base();
let mut copy = Self {
sound_context: self.sound_context.deep_clone(),
physics: self.physics.clone(),
physics2d: self.physics2d.clone(),
user_data: self.user_data.clone(),
..Default::default()
};
let (copy_root, old_new_map) = self.copy_node(
root,
&mut copy,
preserve_handles,
filter,
pre_process_callback,
post_process_callback,
);
assert_eq!(copy.root, copy_root);
let mut lightmap = self.lightmap.clone();
if let Some(lightmap) = lightmap.as_mut() {
let mut map = FxHashMap::default();
for (mut handle, mut entries) in std::mem::take(&mut lightmap.map) {
for entry in entries.iter_mut() {
for light_handle in entry.lights.iter_mut() {
old_new_map.try_map(light_handle);
}
}
if old_new_map.try_map(&mut handle) {
map.insert(handle, entries);
}
}
lightmap.map = map;
}
copy.lightmap = lightmap;
if let Some(user_data) = copy.user_data.0.as_mut() {
old_new_map.remap_handles_any(
user_data,
"UserData",
&[TypeId::of::<UntypedResource>()],
);
}
(copy, old_new_map)
}
/// Returns local transformation matrix of a node without scale.
#[inline]
pub fn local_transform_no_scale(
&self,
node: Handle<impl ObjectOrVariant<Node>>,
) -> Matrix4<f32> {
let mut transform = self[node.to_base()].local_transform().clone();
transform.set_scale(Vector3::new(1.0, 1.0, 1.0));
transform.matrix()
}
/// Returns world transformation matrix of a node without scale.
#[inline]
pub fn global_transform_no_scale(
&self,
node: Handle<impl ObjectOrVariant<Node>>,
) -> Matrix4<f32> {
let node = node.to_base();
let parent = self[node].parent();
if parent.is_some() {
self.global_transform_no_scale(parent) * self.local_transform_no_scale(node)
} else {
self.local_transform_no_scale(node)
}
}
/// Returns isometric local transformation matrix of a node. Such transform has
/// only translation and rotation.
#[inline]
pub fn isometric_local_transform(
&self,
node: Handle<impl ObjectOrVariant<Node>>,
) -> Matrix4<f32> {
isometric_local_transform(&self.pool, node)
}
/// Returns world transformation matrix of a node only. Such transform has
/// only translation and rotation.
#[inline]
pub fn isometric_global_transform(
&self,
node: Handle<impl ObjectOrVariant<Node>>,
) -> Matrix4<f32> {
isometric_global_transform(&self.pool, node)
}
/// Returns global scale matrix of a node.
#[inline]
pub fn global_scale_matrix(&self, node: Handle<impl ObjectOrVariant<Node>>) -> Matrix4<f32> {
Matrix4::new_nonuniform_scaling(&self.global_scale(node))
}
/// Returns rotation quaternion of a node in world coordinates.
#[inline]
pub fn global_rotation(&self, node: Handle<impl ObjectOrVariant<Node>>) -> UnitQuaternion<f32> {
UnitQuaternion::from(Rotation3::from_matrix_eps(
&self.global_transform_no_scale(node).basis(),
f32::EPSILON,
16,
Rotation3::identity(),
))
}
/// Returns rotation quaternion of a node in world coordinates without pre- and post-rotations.
#[inline]
pub fn isometric_global_rotation(
&self,
node: Handle<impl ObjectOrVariant<Node>>,
) -> UnitQuaternion<f32> {
UnitQuaternion::from(Rotation3::from_matrix_eps(
&self.isometric_global_transform(node).basis(),
f32::EPSILON,
16,
Rotation3::identity(),
))
}
/// Returns rotation quaternion and position of a node in world coordinates, scale is eliminated.
#[inline]
pub fn global_rotation_position_no_scale(
&self,
node: Handle<impl ObjectOrVariant<Node>>,
) -> (UnitQuaternion<f32>, Vector3<f32>) {
let node = node.to_base();
(self.global_rotation(node), self[node].global_position())
}
/// Returns isometric global rotation and position.
#[inline]
pub fn isometric_global_rotation_position(
&self,
node: Handle<impl ObjectOrVariant<Node>>,
) -> (UnitQuaternion<f32>, Vector3<f32>) {
let node = node.to_base();
(
self.isometric_global_rotation(node),
self[node].global_position(),
)
}
/// Returns global scale of a node.
#[inline]
pub fn global_scale(&self, handle: Handle<impl ObjectOrVariant<Node>>) -> Vector3<f32> {
let mut handle = handle.to_base();
let mut global_scale = Vector3::repeat(1.0);
while let Ok(node_ref) = self.try_get_node(handle) {
global_scale = global_scale.component_mul(node_ref.local_transform().scale());
handle = node_ref.parent;
}
global_scale
}
/// Tries to borrow a node using the given handle and searches the script buffer for a script
/// of type T and cast the first script, that could be found to the specified type.
#[inline]
pub fn try_get_script_of<T>(
&self,
handle: Handle<impl ObjectOrVariant<Node>>,
) -> Result<&T, GraphError>
where
T: ScriptTrait,
{
let handle = handle.to_base();
let node = self.try_get_node(handle)?;
node.try_get_script::<T>()
.ok_or_else(|| GraphError::NoScript {
handle,
script_type_name: std::any::type_name::<T>(),
})
}
/// Tries to borrow a node and query all scripts of the given type `T`. This method returns
/// [`None`] if the given node handle is invalid, otherwise it returns an iterator over the
/// scripts of the type `T`.
#[inline]
pub fn try_get_scripts_of<T: ScriptTrait>(
&self,
handle: Handle<impl ObjectOrVariant<Node>>,
) -> Result<impl Iterator<Item = &T>, GraphError> {
let handle = handle.to_base();
let node = self.try_get_node(handle)?;
Ok(node.try_get_scripts())
}
/// Tries to borrow a node using the given handle and searches the script buffer for a script
/// of type T and cast the first script, that could be found to the specified type.
#[inline]
pub fn try_get_script_of_mut<T>(
&mut self,
handle: Handle<impl ObjectOrVariant<Node>>,
) -> Result<&mut T, GraphError>
where
T: ScriptTrait,
{
let handle = handle.to_base();
let node = self.try_get_node_mut(handle)?;
node.try_get_script_mut::<T>()
.ok_or_else(|| GraphError::NoScript {
handle,
script_type_name: std::any::type_name::<T>(),
})
}
/// Tries to borrow a node and query all scripts of the given type `T`. This method returns
/// [`None`] if the given node handle is invalid, otherwise it returns an iterator over the
/// scripts of the type `T`.
#[inline]
pub fn try_get_scripts_of_mut<T>(
&mut self,
handle: Handle<impl ObjectOrVariant<Node>>,
) -> Result<impl Iterator<Item = &mut T>, GraphError>
where
T: ScriptTrait,
{
let handle = handle.to_base();
let node = self.try_get_node_mut(handle)?;
Ok(node.try_get_scripts_mut())
}
/// Tries to borrow a node and find a field of the given type `C` across **all** available
/// scripts of the node. If you want to search a field `C` in a particular script, then use
/// [`Self::try_get_script_of`] and then search for field in it.
#[inline]
pub fn try_get_script_field_of<C>(
&self,
handle: Handle<impl ObjectOrVariant<Node>>,
) -> Result<&C, GraphError>
where
C: Reflect,
{
let handle = handle.to_base();
let node = self.try_get_node(handle)?;
node.try_get_script_field()
.ok_or_else(|| GraphError::NoScriptField {
handle,
field_type_name: std::any::type_name::<C>(),
})
}
/// Tries to borrow a node and find a field of the given type `C` across **all** available
/// scripts of the node. If you want to search a field `C` in a particular script, then use
/// [`Self::try_get_script_of_mut`] and then search for field in it.
#[inline]
pub fn try_get_script_field_of_mut<C>(
&mut self,
handle: Handle<impl ObjectOrVariant<Node>>,
) -> Result<&mut C, GraphError>
where
C: Reflect,
{
let handle = handle.to_base();
let node = self.try_get_node_mut(handle)?;
node.try_get_script_field_mut()
.ok_or_else(|| GraphError::NoScriptField {
handle,
field_type_name: std::any::type_name::<C>(),
})
}
/// Returns a handle of the node that has the given id.
pub fn id_to_node_handle(&self, id: SceneNodeId) -> Option<&Handle<Node>> {
self.instance_id_map.get(&id)
}
/// Tries to borrow a node by its id.
pub fn node_by_id(&self, id: SceneNodeId) -> Result<(Handle<Node>, &Node), GraphError> {
self.instance_id_map
.get(&id)
.ok_or(GraphError::UnknownId(id))
.and_then(|h| Ok(self.pool.try_borrow(*h).map(|n| (*h, n))?))
}
/// Tries to borrow a node by its id.
pub fn node_by_id_mut(
&mut self,
id: SceneNodeId,
) -> Result<(Handle<Node>, &mut Node), GraphError> {
self.instance_id_map
.get(&id)
.ok_or(GraphError::UnknownId(id))
.and_then(|h| Ok(self.pool.try_borrow_mut(*h).map(|n| (*h, n))?))
}
}
impl<T: ObjectOrVariant<Node>> Index<Handle<T>> for Graph {
type Output = T;
#[inline]
fn index(&self, index: Handle<T>) -> &Self::Output {
self.try_get(index).unwrap()
}
}
impl<T: ObjectOrVariant<Node>> IndexMut<Handle<T>> for Graph {
#[inline]
fn index_mut(&mut self, index: Handle<T>) -> &mut Self::Output {
self.try_get_mut(index).unwrap()
}
}
impl Visit for Graph {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
// Pool must be empty, otherwise handles will be invalid and everything will blow up.
if visitor.is_reading() && self.pool.get_capacity() != 0 {
panic!("Graph pool must be empty on load!")
}
let mut region = visitor.enter_region(name)?;
self.root.visit("Root", &mut region)?;
self.pool.visit("Pool", &mut region)?;
self.sound_context.visit("SoundContext", &mut region)?;
self.physics.visit("PhysicsWorld", &mut region)?;
self.physics2d.visit("PhysicsWorld2D", &mut region)?;
self.lightmap.visit("Lightmap", &mut region)?;
Log::verify(self.user_data.visit("UserData", &mut region));
Ok(())
}
}
impl SceneGraph for Graph {
type Prefab = Model;
type NodeWrapper = Node;
/// Create a brief debug summary of the contents of this graph.
fn summary(&self) -> String {
let mut result = String::new();
self.recursive_summary(0, self.root, &mut result);
result
}
#[inline]
fn actual_type_id(&self, handle: Handle<Self::NodeWrapper>) -> Result<TypeId, PoolError> {
self.pool
.try_borrow(handle)
.map(|n| NodeAsAny::as_any(n.0.deref()).type_id())
}
#[inline]
fn root(&self) -> Handle<Self::NodeWrapper> {
self.root
}
#[inline]
fn set_root(&mut self, root: Handle<Self::NodeWrapper>) {
self.root = root;
}
#[inline]
fn is_valid_handle(&self, handle: Handle<impl ObjectOrVariant<Self::NodeWrapper>>) -> bool {
self.pool.is_valid_handle(handle)
}
#[inline]
fn add_node(&mut self, node: Self::NodeWrapper) -> Handle<Self::NodeWrapper> {
let handle = self.pool.next_free_handle();
self.add_node_at_handle(node, handle);
handle
}
fn add_node_at_handle(
&mut self,
mut node: Self::NodeWrapper,
handle: Handle<Self::NodeWrapper>,
) {
let children = node.children.clone();
node.children.clear();
let script_count = node.scripts.len();
self.pool
.spawn_at_handle(handle, node)
.expect("The handle must be valid!");
if self.root.is_none() {
self.root = handle;
} else {
self.link_nodes(handle, self.root);
}
for child in children {
self.link_nodes(child, handle);
}
self.event_broadcaster.broadcast(GraphEvent::Added(handle));
for i in 0..script_count {
self.script_message_sender
.send(NodeScriptMessage::InitializeScript {
handle,
script_index: i,
})
.unwrap();
}
let script_message_sender = self.script_message_sender.clone();
let message_sender = self.message_sender.clone();
let node = &mut self.pool[handle];
node.on_connected_to_graph(handle, message_sender, script_message_sender);
self.instance_id_map.insert(node.instance_id, handle);
}
#[inline]
fn remove_node(&mut self, node_handle: Handle<impl ObjectOrVariant<Self::NodeWrapper>>) {
let node_handle = node_handle.to_base();
self.isolate_node(node_handle);
self.stack.clear();
self.stack.push(node_handle);
while let Some(handle) = self.stack.pop() {
for &child in self.pool[handle].children().iter() {
self.stack.push(child);
}
// Remove associated entities.
let mut node = self.pool.free(handle);
self.instance_id_map.remove(&node.instance_id);
node.on_removed_from_graph(self);
self.event_broadcaster
.broadcast(GraphEvent::Removed(handle));
}
}
#[inline]
fn link_nodes(
&mut self,
child: Handle<impl ObjectOrVariant<Self::NodeWrapper>>,
parent: Handle<impl ObjectOrVariant<Self::NodeWrapper>>,
) {
let child = child.to_base();
let parent = parent.to_base();
self.isolate_node(child);
self.pool[child].parent = parent;
self.pool[parent].children.push(child);
// Force update of global transform of the node being attached.
self.message_sender
.send(NodeMessage::new(child, NodeMessageKind::TransformChanged))
.unwrap();
}
#[inline]
fn unlink_node(&mut self, node_handle: Handle<impl ObjectOrVariant<Self::NodeWrapper>>) {
let node_handle = node_handle.to_base();
self.isolate_node(node_handle);
self.link_nodes(node_handle, self.root);
self.pool[node_handle]
.local_transform_mut()
.set_position(Vector3::default());
}
#[inline]
fn isolate_node(&mut self, node_handle: Handle<impl ObjectOrVariant<Self::NodeWrapper>>) {
let node_handle = node_handle.to_base();
// Replace parent handle of child
let parent_handle = std::mem::replace(&mut self.pool[node_handle].parent, Handle::NONE);
// Remove child from parent's children list
if let Ok(parent) = self.pool.try_borrow_mut(parent_handle) {
if let Some(i) = parent.children().iter().position(|h| *h == node_handle) {
parent.children.remove(i);
}
}
let (ticket, mut node) = self.pool.take_reserve(node_handle);
node.on_unlink(self);
self.pool.put_back(ticket, node);
}
#[inline]
fn try_get_node(
&self,
handle: Handle<Self::NodeWrapper>,
) -> Result<&Self::NodeWrapper, PoolError> {
self.pool.try_borrow(handle)
}
#[inline]
fn try_get_node_mut(
&mut self,
handle: Handle<Self::NodeWrapper>,
) -> Result<&mut Self::NodeWrapper, PoolError> {
self.pool.try_borrow_mut(handle)
}
fn derived_type_ids(
&self,
handle: Handle<Self::NodeWrapper>,
) -> Result<Vec<TypeId>, PoolError> {
self.pool
.try_borrow(handle)
.map(|n| Box::deref(&n.0).type_info_ref().derived_types.to_vec())
}
fn actual_type_name(
&self,
handle: Handle<Self::NodeWrapper>,
) -> Result<&'static str, PoolError> {
self.pool
.try_borrow(handle)
.map(|n| n.0.deref().type_info_ref().type_name)
}
#[inline]
fn pair_iter(&self) -> impl Iterator<Item = (Handle<Self::NodeWrapper>, &Self::NodeWrapper)> {
self.pool.pair_iter()
}
#[inline]
fn linear_iter(&self) -> impl Iterator<Item = &Self::NodeWrapper> {
self.pool.iter()
}
#[inline]
fn linear_iter_mut(&mut self) -> impl Iterator<Item = &mut Self::NodeWrapper> {
self.pool.iter_mut()
}
fn try_get<U: ObjectOrVariant<Node>>(&self, handle: Handle<U>) -> Result<&U, PoolError> {
self.pool.try_get(handle)
}
fn try_get_mut<U: ObjectOrVariant<Node>>(
&mut self,
handle: Handle<U>,
) -> Result<&mut U, PoolError> {
self.pool.try_get_mut(handle)
}
}
#[cfg(test)]
mod test {
use crate::scene::rigidbody::{RigidBody, RigidBodyBuilder};
use crate::{
asset::{io::FsResourceIo, manager::ResourceManager},
core::{
algebra::{Matrix4, Vector3},
futures::executor::block_on,
pool::Handle,
reflect::prelude::*,
visitor::prelude::*,
},
engine::{self, SerializationContext},
graph::SceneGraph,
resource::model::{Model, ModelResourceExtension},
scene::{
base::BaseBuilder,
graph::Graph,
mesh::{
surface::{SurfaceBuilder, SurfaceData, SurfaceResource},
MeshBuilder,
},
node::Node,
pivot::{Pivot, PivotBuilder},
transform::TransformBuilder,
Scene, SceneLoader,
},
script::ScriptTrait,
};
use fyrox_core::algebra::Vector2;
use fyrox_resource::untyped::ResourceKind;
use std::{fs, path::Path, sync::Arc};
#[derive(Clone, Debug, PartialEq, Reflect, Visit)]
#[reflect(type_uuid = "722feb80-a10b-4ee0-8cef-5d1473df8457")]
struct MyScript {
foo: String,
bar: f32,
}
impl ScriptTrait for MyScript {}
#[derive(Clone, Debug, PartialEq, Reflect, Visit)]
#[reflect(type_uuid = "722feb80-a10b-4ee0-8cef-5d1473df8458")]
struct MyOtherScript {
baz: u32,
foobar: Vec<u32>,
}
impl ScriptTrait for MyOtherScript {}
#[test]
fn test_graph_scripts() {
let node = PivotBuilder::new(
BaseBuilder::new()
.with_script(MyScript {
foo: "Stuff".to_string(),
bar: 123.321,
})
.with_script(MyScript {
foo: "OtherStuff".to_string(),
bar: 321.123,
})
.with_script(MyOtherScript {
baz: 321,
foobar: vec![1, 2, 3],
}),
)
.build_node();
let mut graph = Graph::new();
let handle = graph.add_node(node);
assert_eq!(
graph.try_get_script_of::<MyScript>(handle),
Ok(&MyScript {
foo: "Stuff".to_string(),
bar: 123.321,
})
);
assert_eq!(
graph.try_get_script_of_mut::<MyScript>(handle),
Ok(&mut MyScript {
foo: "Stuff".to_string(),
bar: 123.321,
})
);
let mut immutable_iterator = graph
.try_get_scripts_of::<MyScript>(handle)
.expect("The handle expected to be valid!");
assert_eq!(
immutable_iterator.next(),
Some(&MyScript {
foo: "Stuff".to_string(),
bar: 123.321,
})
);
assert_eq!(
immutable_iterator.next(),
Some(&MyScript {
foo: "OtherStuff".to_string(),
bar: 321.123,
})
);
drop(immutable_iterator);
assert_eq!(
graph.try_get_script_of::<MyOtherScript>(handle),
Ok(&MyOtherScript {
baz: 321,
foobar: vec![1, 2, 3],
})
);
assert_eq!(
graph.try_get_script_of_mut::<MyOtherScript>(handle),
Ok(&mut MyOtherScript {
baz: 321,
foobar: vec![1, 2, 3],
})
);
let mut mutable_iterator = graph
.try_get_scripts_of_mut::<MyScript>(handle)
.expect("The handle expected to be valid!");
assert_eq!(
mutable_iterator.next(),
Some(&mut MyScript {
foo: "Stuff".to_string(),
bar: 123.321,
})
);
assert_eq!(
mutable_iterator.next(),
Some(&mut MyScript {
foo: "OtherStuff".to_string(),
bar: 321.123,
})
);
}
#[test]
fn graph_init_test() {
let graph = Graph::new();
assert_ne!(graph.root, Handle::<Node>::NONE);
assert_eq!(graph.pool.alive_count(), 1);
}
#[test]
fn graph_node_test() {
let mut graph = Graph::new();
graph.add_node(Node::new(Pivot::default()));
graph.add_node(Node::new(Pivot::default()));
graph.add_node(Node::new(Pivot::default()));
assert_eq!(graph.pool.alive_count(), 4);
}
#[test]
fn test_copy_node_inplace() {
let mut graph = Graph::new();
// Root_
// |_A_
// |_B
// |_C_
// |_D
let b;
let c;
let d;
let a = PivotBuilder::new(
BaseBuilder::new()
.with_name("A")
.with_child({
b = PivotBuilder::new(BaseBuilder::new().with_name("B")).build(&mut graph);
b
})
.with_child({
c = PivotBuilder::new(BaseBuilder::new().with_name("C").with_child({
d = PivotBuilder::new(BaseBuilder::new().with_name("D")).build(&mut graph);
d
}))
.build(&mut graph);
c
}),
)
.build(&mut graph);
let (a_copy, old_new_map) = graph.copy_node_inplace(a.to_base(), &mut |_, _| true);
let old_new_map = old_new_map.inner();
assert_eq!(old_new_map.get(&a.to_base()), Some(&a_copy));
assert!(old_new_map.get(&b.to_base()).is_some());
assert!(old_new_map.get(&c.to_base()).is_some());
assert!(old_new_map.get(&d.to_base()).is_some());
}
#[test]
fn test_graph_search() {
let mut graph = Graph::new();
// Root_
// |_A_
// |_B
// |_C_
// |_D
let b;
let c;
let d;
let a = PivotBuilder::new(
BaseBuilder::new()
.with_name("A")
.with_child({
b = PivotBuilder::new(BaseBuilder::new().with_name("B")).build(&mut graph);
b
})
.with_child({
c = PivotBuilder::new(BaseBuilder::new().with_name("C").with_child({
d = PivotBuilder::new(BaseBuilder::new().with_name("D")).build(&mut graph);
d
}))
.build(&mut graph);
c
}),
)
.build(&mut graph);
// Test down search.
assert!(graph.find_by_name(a, "X").is_none());
assert_eq!(graph.find_by_name(a, "A").unwrap().0, a);
assert_eq!(graph.find_by_name(a, "D").unwrap().0, d);
let result = graph
.find_map(a, &mut |n| if n.name() == "D" { Some("D") } else { None })
.unwrap();
assert_eq!(result.0, d);
assert_eq!(result.1, "D");
// Test up search.
assert!(graph.find_up_by_name(d, "X").is_none());
assert_eq!(graph.find_up_by_name(d, "D").unwrap().0, d);
assert_eq!(graph.find_up_by_name(d, "A").unwrap().0, a);
let result = graph
.find_up_map(d, &mut |n| if n.name() == "A" { Some("A") } else { None })
.unwrap();
assert_eq!(result.0, a);
assert_eq!(result.1, "A");
}
fn create_scene() -> Scene {
let mut scene = Scene::new();
PivotBuilder::new(BaseBuilder::new().with_name("Pivot")).build(&mut scene.graph);
PivotBuilder::new(BaseBuilder::new().with_name("MeshPivot").with_child({
MeshBuilder::new(
BaseBuilder::new().with_name("Mesh").with_local_transform(
TransformBuilder::new()
.with_local_position(Vector3::new(3.0, 2.0, 1.0))
.build(),
),
)
.with_surfaces(vec![SurfaceBuilder::new(SurfaceResource::new_ok(
Uuid::new_v4(),
ResourceKind::Embedded,
SurfaceData::make_cone(16, 1.0, 1.0, &Matrix4::identity()),
))
.build()])
.build(&mut scene.graph)
}))
.build(&mut scene.graph);
scene
}
fn save_scene(scene: &mut Scene, path: &Path) {
let mut visitor = Visitor::new();
scene.save("Scene", &mut visitor).unwrap();
visitor.save_ascii_to_file(path).unwrap();
}
fn make_resource_manager(root: &Path) -> ResourceManager {
let resource_manager =
ResourceManager::new(Arc::new(FsResourceIo), Arc::new(Default::default()));
resource_manager
.state()
.resource_registry
.lock()
.set_path(root.join("resources.registry"));
engine::initialize_resource_manager_loaders(
&resource_manager,
Arc::new(SerializationContext::new()),
Default::default(),
Default::default(),
);
resource_manager.update_or_load_registry();
resource_manager
}
#[test]
fn test_restore_integrity() {
let root = Path::new("test_restore_integrity");
if !root.exists() {
fs::create_dir_all(root).unwrap();
}
let root_asset_path = root.join("root2.rgs");
let derived_asset_path = root.join("derived2.rgs");
// Create root scene and save it.
{
let mut scene = create_scene();
save_scene(&mut scene, &root_asset_path);
}
// Create root resource instance in a derived resource. This creates a derived asset.
{
let resource_manager = make_resource_manager(root);
let root_asset = block_on(resource_manager.request::<Model>(&root_asset_path)).unwrap();
let mut derived = Scene::new();
root_asset.instantiate(&mut derived);
save_scene(&mut derived, &derived_asset_path);
}
// Now load the root asset, modify it, save it back and reload the derived asset.
{
let resource_manager = make_resource_manager(root);
let mut scene = block_on(
block_on(SceneLoader::from_file(
&root_asset_path,
&FsResourceIo,
Arc::new(SerializationContext::new()),
Default::default(),
resource_manager.clone(),
))
.unwrap()
.0
.finish(),
);
// Add a new node to the root node of the scene.
PivotBuilder::new(BaseBuilder::new().with_name("AddedLater")).build(&mut scene.graph);
// Add a new node to the mesh.
let mesh = scene.graph.find_by_name_from_root("Mesh").unwrap().0;
let pivot = PivotBuilder::new(BaseBuilder::new().with_name("NewChildOfMesh"))
.build(&mut scene.graph);
scene.graph.link_nodes(pivot, mesh);
// Remove existing nodes.
let existing_pivot = scene.graph.find_by_name_from_root("Pivot").unwrap().0;
scene.graph.remove_node(existing_pivot);
// Save the scene back.
save_scene(&mut scene, &root_asset_path);
}
// Load the derived scene and check if its content was synced with the content of the root asset.
{
let resource_manager = make_resource_manager(root);
let derived_asset =
block_on(resource_manager.request::<Model>(derived_asset_path)).unwrap();
let derived_data = derived_asset.data_ref();
let derived_scene = derived_data.get_scene();
// Pivot must also be removed from the derived asset, because it is deleted in the root asset.
assert_eq!(
derived_scene
.graph
.find_by_name_from_root("Pivot")
.map(|(h, _)| h),
None
);
let mesh_pivot = derived_scene
.graph
.find_by_name_from_root("MeshPivot")
.expect("Missing MeshPivot")
.0;
let mesh = derived_scene
.graph
.find_by_name(mesh_pivot, "Mesh")
.expect("Missing Mesh")
.0;
derived_scene
.graph
.find_by_name_from_root("AddedLater")
.expect("Missing AddedLater");
derived_scene
.graph
.find_by_name(mesh, "NewChildOfMesh")
.expect("Missing NewChildOfMesh");
}
}
#[test]
fn test_global_scale() {
let mut graph = Graph::new();
let b;
let c;
let a = PivotBuilder::new(
BaseBuilder::new()
.with_local_transform(
TransformBuilder::new()
.with_local_scale(Vector3::new(1.0, 1.0, 2.0))
.build(),
)
.with_child({
b = PivotBuilder::new(
BaseBuilder::new()
.with_local_transform(
TransformBuilder::new()
.with_local_scale(Vector3::new(3.0, 2.0, 1.0))
.build(),
)
.with_child({
c = PivotBuilder::new(
BaseBuilder::new().with_local_transform(
TransformBuilder::new()
.with_local_scale(Vector3::new(1.0, 2.0, 3.0))
.build(),
),
)
.build(&mut graph);
c
}),
)
.build(&mut graph);
b
}),
)
.build(&mut graph);
assert_eq!(graph.global_scale(a), Vector3::new(1.0, 1.0, 2.0));
assert_eq!(graph.global_scale(b), Vector3::new(3.0, 2.0, 2.0));
assert_eq!(graph.global_scale(c), Vector3::new(3.0, 4.0, 6.0));
}
#[test]
fn test_hierarchy_changes_propagation() {
let mut graph = Graph::new();
let b;
let c;
let d;
let a = PivotBuilder::new(
BaseBuilder::new()
.with_local_transform(
TransformBuilder::new()
.with_local_position(Vector3::new(1.0, 0.0, 0.0))
.build(),
)
.with_child({
b = PivotBuilder::new(
BaseBuilder::new()
.with_visibility(false)
.with_enabled(false)
.with_local_transform(
TransformBuilder::new()
.with_local_position(Vector3::new(0.0, 1.0, 0.0))
.build(),
)
.with_child({
c = PivotBuilder::new(
BaseBuilder::new().with_local_transform(
TransformBuilder::new()
.with_local_position(Vector3::new(0.0, 0.0, 1.0))
.build(),
),
)
.build(&mut graph);
c
}),
)
.build(&mut graph);
b
})
.with_child({
d = PivotBuilder::new(
BaseBuilder::new().with_local_transform(
TransformBuilder::new()
.with_local_position(Vector3::new(1.0, 1.0, 1.0))
.build(),
),
)
.build(&mut graph);
d
}),
)
.build(&mut graph);
graph.update(Vector2::new(1.0, 1.0), 1.0 / 60.0, Default::default());
assert_eq!(graph[a].global_position(), Vector3::new(1.0, 0.0, 0.0));
assert_eq!(graph[b].global_position(), Vector3::new(1.0, 1.0, 0.0));
assert_eq!(graph[c].global_position(), Vector3::new(1.0, 1.0, 1.0));
assert_eq!(graph[d].global_position(), Vector3::new(2.0, 1.0, 1.0));
assert!(graph[a].global_visibility());
assert!(!graph[b].global_visibility());
assert!(!graph[c].global_visibility());
assert!(graph[d].global_visibility());
assert!(graph[a].is_globally_enabled());
assert!(!graph[b].is_globally_enabled());
assert!(!graph[c].is_globally_enabled());
assert!(graph[d].is_globally_enabled());
// Change something
graph[b]
.local_transform_mut()
.set_position(Vector3::new(0.0, 2.0, 0.0));
graph[a].set_enabled(false);
graph[b].set_visibility(true);
graph.update(Vector2::new(1.0, 1.0), 1.0 / 60.0, Default::default());
assert_eq!(graph[a].global_position(), Vector3::new(1.0, 0.0, 0.0));
assert_eq!(graph[b].global_position(), Vector3::new(1.0, 2.0, 0.0));
assert_eq!(graph[c].global_position(), Vector3::new(1.0, 2.0, 1.0));
assert_eq!(graph[d].global_position(), Vector3::new(2.0, 1.0, 1.0));
assert!(graph[a].global_visibility());
assert!(graph[b].global_visibility());
assert!(graph[c].global_visibility());
assert!(graph[d].global_visibility());
assert!(!graph.pool.try_get(a).unwrap().is_globally_enabled());
assert!(!graph[b].is_globally_enabled());
assert!(!graph[c].is_globally_enabled());
assert!(!graph[d].is_globally_enabled());
}
#[test]
fn test_typed_borrow() {
let mut graph = Graph::new();
let pivot = PivotBuilder::new(BaseBuilder::new()).build(&mut graph);
let rigid_body = RigidBodyBuilder::new(BaseBuilder::new()).build(&mut graph);
assert!(graph.pool.try_get(pivot).is_ok());
assert!(graph.pool.try_get(pivot.transmute::<Pivot>()).is_ok());
assert!(graph.pool.try_get(pivot.transmute::<RigidBody>()).is_err());
assert!(graph.pool.try_get(rigid_body).is_ok());
assert!(graph
.pool
.try_get(rigid_body.transmute::<RigidBody>())
.is_ok());
assert!(graph.pool.try_get(rigid_body.transmute::<Pivot>()).is_err());
}
}