#![warn(missing_docs, rust_2018_idioms, rust_2018_compatibility)]
use amethyst_animation::{AnimatablePrefab, SkinnablePrefab};
use amethyst_assets::{
AssetStorage, Handle, Loader, Prefab, PrefabData, PrefabLoaderSystem, ProgressCounter,
};
use amethyst_core::{
ecs::prelude::{Entity, Read, ReadExpect, Write, WriteStorage},
math::{convert, Point3, Vector3},
transform::Transform,
Float, Named,
};
use amethyst_error::Error;
use amethyst_rendy::{
formats::mtl::MaterialPrefab, rendy::mesh::MeshBuilder, types::Mesh, visibility::BoundingSphere,
};
use derivative::Derivative;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, ops::Range};
pub use crate::format::GltfSceneFormat;
mod error;
mod format;
pub type GltfSceneLoaderSystem = PrefabLoaderSystem<GltfPrefab>;
pub type GltfSceneAsset = Prefab<GltfPrefab>;
#[derive(Debug, Default)]
pub struct GltfPrefab {
pub transform: Option<Transform>,
pub mesh: Option<MeshBuilder<'static>>,
pub mesh_handle: Option<Handle<Mesh>>,
pub material: Option<MaterialPrefab>,
pub animatable: Option<AnimatablePrefab<usize, Transform>>,
pub skinnable: Option<SkinnablePrefab>,
pub extent: Option<GltfNodeExtent>,
pub name: Option<Named>,
pub(crate) materials: Option<GltfMaterialSet>,
pub(crate) material_id: Option<usize>,
}
impl GltfPrefab {
pub fn move_to(&mut self, target: Point3<f32>) {
if let Some(ref extent) = self.extent {
let diff = convert::<_, Vector3<Float>>(target - extent.centroid());
*self
.transform
.get_or_insert_with(Transform::default)
.translation_mut() += diff;
}
}
pub fn scale_to(&mut self, max_distance: f32) {
if let Some(ref extent) = self.extent {
let distance = extent.distance();
let max = distance.x.max(distance.y).max(distance.z);
let scale: Float = (max_distance / max).into();
self.transform
.get_or_insert_with(Transform::default)
.set_scale(Vector3::new(scale, scale, scale));
}
}
}
#[derive(Clone, Debug)]
pub struct GltfNodeExtent {
pub start: Point3<f32>,
pub end: Point3<f32>,
}
impl Default for GltfNodeExtent {
fn default() -> Self {
Self {
start: Point3::from(Vector3::from_element(std::f32::MAX)),
end: Point3::from(Vector3::from_element(std::f32::MIN)),
}
}
}
impl GltfNodeExtent {
pub fn extend_range(&mut self, other: &Range<[f32; 3]>) {
for i in 0..3 {
if other.start[i] < self.start[i] {
self.start[i] = other.start[i];
}
if other.end[i] > self.end[i] {
self.end[i] = other.end[i];
}
}
}
pub fn extend(&mut self, other: &GltfNodeExtent) {
for i in 0..3 {
if other.start[i] < self.start[i] {
self.start[i] = other.start[i];
}
if other.end[i] > self.end[i] {
self.end[i] = other.end[i];
}
}
}
pub fn centroid(&self) -> Point3<f32> {
(self.start + self.end.coords) / 2.
}
pub fn distance(&self) -> Vector3<f32> {
self.end - self.start
}
pub fn valid(&self) -> bool {
for i in 0..3 {
if self.start[i] > self.end[i] {
return false;
}
}
true
}
}
impl Into<BoundingSphere> for GltfNodeExtent {
fn into(self) -> BoundingSphere {
BoundingSphere {
center: convert(self.centroid()),
radius: convert(self.distance().magnitude() * 0.5),
}
}
}
impl From<Range<[f32; 3]>> for GltfNodeExtent {
fn from(range: Range<[f32; 3]>) -> Self {
GltfNodeExtent {
start: Point3::from(range.start),
end: Point3::from(range.end),
}
}
}
#[derive(Debug, Derivative)]
#[derivative(Default(bound = ""))]
pub struct GltfMaterialSet {
pub(crate) materials: HashMap<usize, MaterialPrefab>,
}
#[derive(Debug, Clone, Derivative, Serialize, Deserialize)]
#[derivative(Default)]
#[serde(default)]
pub struct GltfSceneOptions {
pub generate_tex_coords: (f32, f32),
#[derivative(Default(value = "true"))]
pub load_normals: bool,
#[derivative(Default(value = "true"))]
pub load_colors: bool,
#[derivative(Default(value = "true"))]
pub load_texcoords: bool,
#[derivative(Default(value = "true"))]
pub load_tangents: bool,
#[derivative(Default(value = "true"))]
pub load_animations: bool,
pub flip_v_coord: bool,
pub scene_index: Option<usize>,
}
impl<'a> PrefabData<'a> for GltfPrefab {
type SystemData = (
<Transform as PrefabData<'a>>::SystemData,
<Named as PrefabData<'a>>::SystemData,
<MaterialPrefab as PrefabData<'a>>::SystemData,
<AnimatablePrefab<usize, Transform> as PrefabData<'a>>::SystemData,
<SkinnablePrefab as PrefabData<'a>>::SystemData,
WriteStorage<'a, BoundingSphere>,
WriteStorage<'a, Handle<Mesh>>,
Read<'a, AssetStorage<Mesh>>,
ReadExpect<'a, Loader>,
Write<'a, GltfMaterialSet>,
);
type Result = ();
fn add_to_entity(
&self,
entity: Entity,
system_data: &mut Self::SystemData,
entities: &[Entity],
children: &[Entity],
) -> Result<(), Error> {
let (transforms, names, materials, animatables, skinnables, bound, meshes, _, _, _) =
system_data;
if let Some(transform) = &self.transform {
transform.add_to_entity(entity, transforms, entities, children)?;
}
if let Some(mesh) = &self.mesh_handle {
meshes.insert(entity, mesh.clone())?;
}
if let Some(name) = &self.name {
name.add_to_entity(entity, names, entities, children)?;
}
if let Some(material) = &self.material {
material.add_to_entity(entity, materials, entities, children)?;
}
if let Some(animatable) = &self.animatable {
animatable.add_to_entity(entity, animatables, entities, children)?;
}
if let Some(skinnable) = &self.skinnable {
skinnable.add_to_entity(entity, skinnables, entities, children)?;
}
if let Some(extent) = &self.extent {
bound.insert(entity, extent.clone().into())?;
}
Ok(())
}
fn load_sub_assets(
&mut self,
progress: &mut ProgressCounter,
system_data: &mut Self::SystemData,
) -> Result<bool, Error> {
let (_, _, materials, animatables, _, _, _, meshes_storage, loader, mat_set) = system_data;
let mut ret = false;
if let Some(mut mats) = self.materials.take() {
mat_set.materials.clear();
for (id, mut material) in mats.materials.drain() {
ret |= material.load_sub_assets(progress, materials)?;
mat_set.materials.insert(id, material);
}
}
if let Some(material_id) = self.material_id {
if let Some(mat) = mat_set.materials.get(&material_id) {
self.material.replace(mat.clone_loaded());
}
}
if let Some(mesh) = self.mesh.take() {
self.mesh_handle =
Some(loader.load_from_data(mesh.clone().into(), &mut *progress, meshes_storage));
ret = true;
}
if let Some(animatable) = &mut self.animatable {
ret |= animatable.load_sub_assets(progress, animatables)?;
}
Ok(ret)
}
}