#![warn(missing_docs)]
use crate::{
asset::{
io::ResourceIo, manager::ResourceManager, options::ImportOptions, untyped::ResourceKind,
Resource, ResourceData,
},
core::{
algebra::{Point3, UnitQuaternion, Vector3},
dyntype::DynTypeConstructorContainer,
log::{Log, MessageKind},
math,
pool::Handle,
reflect::prelude::*,
uuid::Uuid,
uuid_provider,
variable::InheritableVariable,
visitor::error::VisitError,
visitor::{Visit, VisitResult, Visitor},
NameProvider, TypeUuidProvider,
},
engine::SerializationContext,
generic_animation::AnimationContainer,
graph::{NodeHandleMap, NodeMapping, PrefabData, SceneGraph, SceneGraphNode},
resource::fbx::{self, error::FbxError},
scene::{
animation::Animation, base::SceneNodeId, graph::Graph, node::Node, transform::Transform,
Scene, SceneLoader,
},
};
use fxhash::FxHashMap;
use fyrox_core::pool::ObjectOrVariant;
use fyrox_ui::{UiNode, UserInterface};
use serde::{Deserialize, Serialize};
use std::{
error::Error,
fmt::{Display, Formatter},
path::{Path, PathBuf},
sync::Arc,
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
use uuid::uuid;
pub mod loader;
#[derive(Debug, Clone, Visit, Reflect)]
pub struct Model {
#[visit(skip)]
pub(crate) mapping: NodeMapping,
#[visit(skip)]
pub(crate) scene: Scene,
}
impl PrefabData for Model {
type Graph = Graph;
#[inline]
fn graph(&self) -> &Self::Graph {
&self.scene.graph
}
#[inline]
fn mapping(&self) -> NodeMapping {
self.mapping
}
}
impl TypeUuidProvider for Model {
fn type_uuid() -> Uuid {
uuid!("44cd768f-b4ca-4804-a98c-0adf85577ada")
}
}
pub struct InstantiationContext<'a> {
model: &'a ModelResource,
dest_scene: &'a mut Scene,
local_transform: Option<Transform>,
ids: Option<&'a FxHashMap<Handle<Node>, SceneNodeId>>,
}
impl<'a> InstantiationContext<'a> {
pub fn with_rotation(mut self, rotation: UnitQuaternion<f32>) -> Self {
self.local_transform
.get_or_insert_with(Default::default)
.set_rotation(rotation);
self
}
pub fn with_position(mut self, position: Vector3<f32>) -> Self {
self.local_transform
.get_or_insert_with(Default::default)
.set_position(position);
self
}
pub fn with_scale(mut self, scale: Vector3<f32>) -> Self {
self.local_transform
.get_or_insert_with(Default::default)
.set_scale(scale);
self
}
pub fn with_transform(mut self, transform: Transform) -> Self {
self.local_transform = Some(transform);
self
}
pub fn with_ids(mut self, ids: &'a FxHashMap<Handle<Node>, SceneNodeId>) -> Self {
self.ids = Some(ids);
self
}
pub fn finish(self) -> Handle<Node> {
let model = self.model.clone();
let data = model.data_ref();
let resource_root = data.scene.graph.get_root();
let (root, _) = ModelResource::instantiate_from(
model.clone(),
&data,
resource_root,
&mut self.dest_scene.graph,
&mut |original_handle, node| {
if original_handle == resource_root {
if let Some(transform) = self.local_transform.clone() {
*node.local_transform_mut() = transform;
}
}
if let Some(ids) = self.ids.as_ref() {
if let Some(id) = ids.get(&original_handle) {
node.instance_id = *id;
} else {
Log::warn(format!(
"No id specified for node {original_handle}! Random id will be used."
))
}
}
},
);
self.dest_scene.graph[root].is_resource_instance_root = true;
root
}
}
pub trait AnimationSource {
type Prefab: PrefabData<Graph = Self::SceneGraph>;
type SceneGraph: SceneGraph<Node = Self::Node, Prefab = Self::Prefab>;
type Node: SceneGraphNode<SceneGraph = Self::SceneGraph, ResourceData = Self::Prefab>;
fn inner_graph(&self) -> &Self::SceneGraph;
fn retarget_animations_directly(
&self,
root: Handle<Self::Node>,
graph: &Self::SceneGraph,
self_kind: ResourceKind,
) -> Vec<fyrox_animation::Animation<Handle<Self::Node>>> {
let mut retargetted_animations = Vec::new();
let model_graph = self.inner_graph();
for src_node_ref in model_graph.linear_iter() {
if let Some(src_animations) = src_node_ref
.component_ref::<InheritableVariable<AnimationContainer<Handle<Self::Node>>>>()
{
for src_anim in src_animations.iter() {
let mut anim_copy = src_anim.clone();
let src_anim_track_data = src_anim.tracks_data().state();
let Some(src_anim_track_data) = src_anim_track_data.data_ref() else {
continue;
};
for ref_track in src_anim_track_data.tracks.iter() {
let Some(ref_track_binding) =
src_anim.track_bindings().get(&ref_track.id())
else {
continue;
};
let ref_node = &model_graph.node(ref_track_binding.target);
let track_binding = anim_copy
.track_bindings_mut()
.get_mut(&ref_track.id())
.unwrap();
match graph.find_by_name(root, ref_node.name()) {
Some((instance_node, _)) => {
track_binding.set_target(instance_node);
}
None => {
track_binding.set_target(Default::default());
Log::writeln(
MessageKind::Error,
format!(
"Failed to retarget animation {:?} for node {}",
self_kind,
ref_node.name()
),
);
}
}
}
retargetted_animations.push(anim_copy);
}
}
}
retargetted_animations
}
fn retarget_animations_to_player(
&self,
root: Handle<Self::Node>,
dest_animation_player: Handle<Self::Node>,
graph: &mut Self::SceneGraph,
self_kind: ResourceKind,
) -> Vec<Handle<fyrox_animation::Animation<Handle<Self::Node>>>> {
let mut animation_handles = Vec::new();
let animations = self.retarget_animations_directly(root, graph, self_kind);
let dest_animations = graph
.node_mut(dest_animation_player)
.component_mut::<InheritableVariable<AnimationContainer<Handle<Self::Node>>>>()
.unwrap();
for animation in animations {
animation_handles.push(dest_animations.add(animation));
}
animation_handles
}
fn retarget_animations(
&self,
root: Handle<Self::Node>,
graph: &mut Self::SceneGraph,
self_kind: ResourceKind,
) -> Vec<Handle<fyrox_animation::Animation<Handle<Self::Node>>>> {
if let Some((animation_player, _)) = graph.find(root, &mut |n| {
n.component_ref::<InheritableVariable<AnimationContainer<Handle<Self::Node>>>>()
.is_some()
}) {
self.retarget_animations_to_player(root, animation_player, graph, self_kind)
} else {
Default::default()
}
}
}
pub type ModelResource = Resource<Model>;
pub trait ModelResourceExtension: Sized {
fn instantiate_from<Pre>(
model: ModelResource,
model_data: &Model,
handle: Handle<Node>,
dest_graph: &mut Graph,
pre_processing_callback: &mut Pre,
) -> (Handle<Node>, NodeHandleMap<Node>)
where
Pre: FnMut(Handle<Node>, &mut Node);
fn begin_instantiation<'a>(&'a self, dest_scene: &'a mut Scene) -> InstantiationContext<'a>;
fn instantiate(&self, dest_scene: &mut Scene) -> Handle<Node>;
fn instantiate_at(
&self,
scene: &mut Scene,
position: Vector3<f32>,
orientation: UnitQuaternion<f32>,
) -> Handle<Node>;
fn instantiate_and_attach(
&self,
scene: &mut Scene,
parent: Handle<impl ObjectOrVariant<Node>>,
position: Vector3<f32>,
face_towards: Vector3<f32>,
scale: Vector3<f32>,
) -> Handle<Node>;
fn retarget_animations_directly(&self, root: Handle<Node>, graph: &Graph) -> Vec<Animation>;
fn retarget_animations_to_player(
&self,
root: Handle<Node>,
dest_animation_player: Handle<Node>,
graph: &mut Graph,
) -> Vec<Handle<Animation>>;
fn retarget_animations(&self, root: Handle<Node>, graph: &mut Graph) -> Vec<Handle<Animation>>;
fn generate_ids(&self) -> FxHashMap<Handle<Node>, SceneNodeId>;
}
impl AnimationSource for Model {
type Prefab = Model;
type SceneGraph = Graph;
type Node = Node;
fn inner_graph(&self) -> &Self::SceneGraph {
&self.scene.graph
}
}
impl AnimationSource for UserInterface {
type Prefab = UserInterface;
type SceneGraph = UserInterface;
type Node = UiNode;
fn inner_graph(&self) -> &Self::SceneGraph {
self
}
}
impl ModelResourceExtension for ModelResource {
fn instantiate_from<Pre>(
model: ModelResource,
model_data: &Model,
handle: Handle<Node>,
dest_graph: &mut Graph,
pre_processing_callback: &mut Pre,
) -> (Handle<Node>, NodeHandleMap<Node>)
where
Pre: FnMut(Handle<Node>, &mut Node),
{
let (root, old_to_new) = model_data.scene.graph.copy_node(
handle,
dest_graph,
false,
&mut |_, _| true,
pre_processing_callback,
&mut |_, original_handle, node| {
node.set_inheritance_data(original_handle, model.clone());
},
);
dest_graph.update_hierarchical_data_for_descendants(root);
(root, old_to_new)
}
fn begin_instantiation<'a>(&'a self, dest_scene: &'a mut Scene) -> InstantiationContext<'a> {
if !self.is_ok() {
Log::err(format!(
"Instantiating a model from a resource that is not loaded: {self:?}"
));
}
InstantiationContext {
model: self,
dest_scene,
local_transform: None,
ids: None,
}
}
fn instantiate(&self, dest_scene: &mut Scene) -> Handle<Node> {
self.begin_instantiation(dest_scene).finish()
}
fn instantiate_at(
&self,
scene: &mut Scene,
position: Vector3<f32>,
orientation: UnitQuaternion<f32>,
) -> Handle<Node> {
self.begin_instantiation(scene)
.with_rotation(orientation)
.with_position(position)
.finish()
}
fn instantiate_and_attach(
&self,
scene: &mut Scene,
parent: Handle<impl ObjectOrVariant<Node>>,
position: Vector3<f32>,
face_towards: Vector3<f32>,
scale: Vector3<f32>,
) -> Handle<Node> {
let parent = parent.to_base();
let parent_scale = scene.graph.global_scale(parent);
let parent_inv_transform = scene.graph[parent]
.global_transform()
.try_inverse()
.unwrap_or_default();
let local_position = parent_inv_transform
.transform_point(&Point3::from(position))
.coords;
let local_rotation =
math::vector_to_quat(parent_inv_transform.transform_vector(&face_towards));
let local_scale = scale.component_div(&parent_scale);
let instance = self
.begin_instantiation(scene)
.with_position(local_position)
.with_rotation(local_rotation)
.with_scale(local_scale)
.finish();
scene.graph.link_nodes(instance, parent);
instance
}
fn retarget_animations_directly(&self, root: Handle<Node>, graph: &Graph) -> Vec<Animation> {
let mut header = self.state();
let self_kind = header.kind();
if let Some(model) = header.data() {
model.retarget_animations_directly(root, graph, self_kind)
} else {
Default::default()
}
}
fn retarget_animations_to_player(
&self,
root: Handle<Node>,
dest_animation_player: Handle<Node>,
graph: &mut Graph,
) -> Vec<Handle<Animation>> {
let mut header = self.state();
let self_kind = header.kind();
if let Some(model) = header.data() {
model.retarget_animations_to_player(root, dest_animation_player, graph, self_kind)
} else {
Default::default()
}
}
fn retarget_animations(&self, root: Handle<Node>, graph: &mut Graph) -> Vec<Handle<Animation>> {
let mut header = self.state();
let self_kind = header.kind();
if let Some(model) = header.data() {
model.retarget_animations(root, graph, self_kind)
} else {
Default::default()
}
}
fn generate_ids(&self) -> FxHashMap<Handle<Node>, SceneNodeId> {
let data = self.data_ref();
data.scene
.graph
.pair_iter()
.map(|(h, _)| (h, SceneNodeId(Uuid::new_v4())))
.collect()
}
}
impl ResourceData for Model {
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
let mut visitor = Visitor::new();
self.scene.save("Scene", &mut visitor)?;
visitor.save_ascii_to_file(path)?;
Ok(())
}
fn can_be_saved(&self) -> bool {
true
}
fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
Some(Box::new(self.clone()))
}
}
impl Default for Model {
fn default() -> Self {
Self {
mapping: NodeMapping::UseNames,
scene: Scene::new(),
}
}
}
#[derive(
Clone,
Debug,
Visit,
PartialEq,
Eq,
Deserialize,
Serialize,
Reflect,
AsRefStr,
EnumString,
VariantNames,
Default,
)]
pub enum MaterialSearchOptions {
MaterialsDirectory(PathBuf),
#[default]
RecursiveUp,
WorkingDirectory,
UsePathDirectly,
}
uuid_provider!(MaterialSearchOptions = "11634aa0-cf8f-4532-a8cd-c0fa6ef804f1");
impl MaterialSearchOptions {
pub fn materials_directory<P: AsRef<Path>>(path: P) -> Self {
Self::MaterialsDirectory(path.as_ref().to_path_buf())
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default, Reflect, Eq)]
pub struct ModelImportOptions {
#[serde(default)]
pub material_search_options: MaterialSearchOptions,
}
impl ImportOptions for ModelImportOptions {}
#[derive(Debug)]
pub enum ModelLoadError {
Visit(VisitError),
NotSupported(String),
Fbx(FbxError),
}
impl Display for ModelLoadError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ModelLoadError::Visit(v) => {
write!(f, "An error occurred while reading a data source: {v}")
}
ModelLoadError::NotSupported(v) => {
write!(f, "Model format is not supported: {v}")
}
ModelLoadError::Fbx(v) => v.fmt(f),
}
}
}
impl From<FbxError> for ModelLoadError {
fn from(fbx: FbxError) -> Self {
ModelLoadError::Fbx(fbx)
}
}
impl From<VisitError> for ModelLoadError {
fn from(e: VisitError) -> Self {
ModelLoadError::Visit(e)
}
}
impl Model {
pub fn new(mapping: NodeMapping, scene: Scene) -> Self {
Self { mapping, scene }
}
pub(crate) async fn load<P: AsRef<Path>>(
path: P,
io: &dyn ResourceIo,
serialization_context: Arc<SerializationContext>,
dyn_type_constructors: Arc<DynTypeConstructorContainer>,
resource_manager: ResourceManager,
model_import_options: ModelImportOptions,
) -> Result<Self, ModelLoadError> {
let extension = path
.as_ref()
.extension()
.unwrap_or_default()
.to_string_lossy()
.as_ref()
.to_lowercase();
let (scene, mapping) = match extension.as_ref() {
"fbx" => {
let mut scene = Scene::new();
if let Some(filename) = path.as_ref().file_name() {
let root = scene.graph.get_root();
scene.graph[root].set_name(filename.to_string_lossy());
}
fbx::load_to_scene(
&mut scene,
resource_manager,
io,
path.as_ref(),
&model_import_options,
)
.await?;
(scene, NodeMapping::UseNames)
}
"rgs" => (
SceneLoader::from_file(
path.as_ref(),
io,
serialization_context,
dyn_type_constructors,
resource_manager.clone(),
)
.await?
.0
.finish()
.await,
NodeMapping::UseHandles,
),
_ => {
return Err(ModelLoadError::NotSupported(format!(
"Unsupported model resource format: {extension}"
)))
}
};
Ok(Self { scene, mapping })
}
pub fn get_scene(&self) -> &Scene {
&self.scene
}
pub fn find_node_by_name(&self, name: &str) -> Option<(Handle<Node>, &Node)> {
self.scene.graph.find_by_name_from_root(name)
}
pub(crate) fn get_scene_mut(&mut self) -> &mut Scene {
&mut self.scene
}
}