#![warn(missing_docs)]
use crate::engine::resource_manager::ImportOptions;
use crate::{
animation::Animation,
asset::{define_new_resource, Resource, ResourceData},
core::{
inspect::{Inspect, PropertyInfo},
pool::Handle,
visitor::{Visit, VisitError, VisitResult, Visitor},
},
engine::resource_manager::ResourceManager,
resource::fbx::{self, error::FbxError},
scene::{node::Node, Scene},
utils::log::{Log, MessageKind},
};
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use strum_macros::{AsRefStr, EnumString, EnumVariantNames};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(u32)]
pub(in crate) enum NodeMapping {
UseNames = 0,
UseHandles = 1,
}
#[derive(Debug)]
pub struct ModelData {
pub(in crate) path: PathBuf,
pub(in crate) mapping: NodeMapping,
scene: Scene,
}
define_new_resource!(
Model<ModelData, ModelLoadError>
);
impl Model {
pub fn instantiate_geometry(&self, dest_scene: &mut Scene) -> Handle<Node> {
let data = self.data_ref();
let (root, old_to_new) = data.scene.graph.copy_node(
data.scene.graph.get_root(),
&mut dest_scene.graph,
&mut |_, _| true,
);
dest_scene.graph[root].is_resource_instance_root = true;
let mut stack = vec![root];
while let Some(node_handle) = stack.pop() {
let node = &mut dest_scene.graph[node_handle];
node.resource = Some(self.clone());
stack.extend_from_slice(node.children());
}
for (&old, &new) in old_to_new.iter() {
dest_scene.graph[new].original_handle_in_resource = old;
}
for navmesh in data.scene.navmeshes.iter() {
dest_scene.navmeshes.add(navmesh.clone());
}
std::mem::drop(data);
root
}
pub fn instantiate(&self, dest_scene: &mut Scene) -> ModelInstance {
let root = self.instantiate_geometry(dest_scene);
ModelInstance {
root,
animations: self.retarget_animations(root, dest_scene),
}
}
pub fn retarget_animations(
&self,
root: Handle<Node>,
dest_scene: &mut Scene,
) -> Vec<Handle<Animation>> {
let data = self.data_ref();
let mut animation_handles = Vec::new();
for ref_anim in data.scene.animations.iter() {
let mut anim_copy = ref_anim.clone();
anim_copy.resource = Some(self.clone());
for (i, ref_track) in ref_anim.get_tracks().iter().enumerate() {
let ref_node = &data.scene.graph[ref_track.get_node()];
let instance_node = dest_scene.graph.find_by_name(root, ref_node.name());
if instance_node.is_none() {
Log::writeln(
MessageKind::Error,
format!(
"Failed to retarget animation {:?} for node {}",
data.path(),
ref_node.name()
),
);
}
anim_copy.get_tracks_mut()[i].set_node(instance_node);
}
animation_handles.push(dest_scene.animations.add(anim_copy));
}
animation_handles
}
}
impl ResourceData for ModelData {
fn path(&self) -> Cow<Path> {
Cow::Borrowed(&self.path)
}
fn set_path(&mut self, path: PathBuf) {
self.path = path;
}
}
impl Default for ModelData {
fn default() -> Self {
Self {
path: PathBuf::new(),
mapping: NodeMapping::UseNames,
scene: Scene::new(),
}
}
}
impl Visit for ModelData {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
self.path.visit("Path", visitor)?;
visitor.leave_region()
}
}
#[derive(
Clone,
Debug,
Visit,
PartialEq,
Deserialize,
Serialize,
Inspect,
AsRefStr,
EnumString,
EnumVariantNames,
)]
pub enum MaterialSearchOptions {
MaterialsDirectory(PathBuf),
RecursiveUp,
WorkingDirectory,
UsePathDirectly,
}
impl Default for MaterialSearchOptions {
fn default() -> Self {
Self::RecursiveUp
}
}
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, Inspect)]
pub struct ModelImportOptions {
#[serde(default)]
pub material_search_options: MaterialSearchOptions,
}
impl ImportOptions for ModelImportOptions {}
#[derive(Debug)]
pub struct ModelInstance {
pub root: Handle<Node>,
pub animations: Vec<Handle<Animation>>,
}
#[derive(Debug, thiserror::Error)]
pub enum ModelLoadError {
#[error("An error occurred while reading a data source {0:?}")]
Visit(VisitError),
#[error("Model format is not supported: {0}")]
NotSupported(String),
#[error(transparent)]
Fbx(FbxError),
}
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 ModelData {
pub(in crate) async fn load<P: AsRef<Path>>(
path: P,
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,
path.as_ref(),
&model_import_options,
)
.await?;
(scene, NodeMapping::UseNames)
}
"rgs" => (
Scene::from_file(path.as_ref(), resource_manager).await?,
NodeMapping::UseHandles,
),
_ => {
return Err(ModelLoadError::NotSupported(format!(
"Unsupported model resource format: {}",
extension
)))
}
};
Ok(Self {
path: path.as_ref().to_owned(),
scene,
mapping,
})
}
pub fn get_scene(&self) -> &Scene {
&self.scene
}
pub fn find_node_by_name(&self, name: &str) -> Handle<Node> {
self.scene.graph.find_by_name_from_root(name)
}
}