use crate::reflect::{ComponentKind, addable_components, present_components};
use nightshade::ecs::animation::components::AnimationPlayer;
use nightshade::ecs::audio::components::AudioSource;
use nightshade::ecs::camera::components::Camera;
use nightshade::ecs::decal::components::Decal;
use nightshade::ecs::material::components::Material;
use nightshade::ecs::particles::components::ParticleEmitter;
use nightshade::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
use nightshade::ecs::text::components::TextProperties;
use nightshade::prelude::*;
use serde::{Deserialize, Serialize};
pub fn quat_to_euler_xyz(quat: &Quat) -> (f32, f32, f32) {
let sinr = 2.0 * (quat.w * quat.i + quat.j * quat.k);
let cosr = 1.0 - 2.0 * (quat.i * quat.i + quat.j * quat.j);
let roll = sinr.atan2(cosr);
let sinp = 2.0 * (quat.w * quat.j - quat.k * quat.i);
let pitch = if sinp.abs() >= 1.0 {
std::f32::consts::FRAC_PI_2.copysign(sinp)
} else {
sinp.asin()
};
let siny = 2.0 * (quat.w * quat.k + quat.i * quat.j);
let cosy = 1.0 - 2.0 * (quat.j * quat.j + quat.k * quat.k);
let yaw = siny.atan2(cosy);
(roll, pitch, yaw)
}
pub fn euler_xyz_to_quat(roll: f32, pitch: f32, yaw: f32) -> Quat {
let cr = (roll * 0.5).cos();
let sr = (roll * 0.5).sin();
let cp = (pitch * 0.5).cos();
let sp = (pitch * 0.5).sin();
let cy = (yaw * 0.5).cos();
let sy = (yaw * 0.5).sin();
Quat::from_parts(
cr * cp * cy + sr * sp * sy,
nalgebra_glm::Vec3::new(
sr * cp * cy - cr * sp * sy,
cr * sp * cy + sr * cp * sy,
cr * cp * sy - sr * sp * cy,
),
)
}
pub fn material_of(world: &World, entity: Entity) -> Option<Material> {
let material_ref = world.core.get_material_ref(entity)?;
registry_entry_by_name(
&world.resources.assets.material_registry.registry,
&material_ref.name,
)
.cloned()
}
pub fn get_color(world: &World, entity: Entity) -> Option<[f32; 4]> {
material_of(world, entity).map(|material| material.base_color)
}
pub fn get_metallic_roughness(world: &World, entity: Entity) -> Option<(f32, f32)> {
material_of(world, entity).map(|material| (material.metallic, material.roughness))
}
pub fn get_emissive(world: &World, entity: Entity) -> Option<([f32; 3], f32)> {
material_of(world, entity)
.map(|material| (material.emissive_factor, material.emissive_strength))
}
pub fn get_unlit(world: &World, entity: Entity) -> Option<bool> {
material_of(world, entity).map(|material| material.unlit)
}
pub fn get_texture(world: &World, entity: Entity) -> Option<String> {
material_of(world, entity).and_then(|material| material.base_texture)
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct AnimationView {
pub clips: Vec<String>,
pub current: Option<u32>,
pub playing: bool,
pub time: f32,
pub duration: f32,
pub speed: f32,
pub looping: bool,
}
pub fn animation_view(player: &AnimationPlayer) -> AnimationView {
let duration = player
.get_current_clip()
.map(|clip| clip.duration)
.unwrap_or(0.0);
AnimationView {
clips: player.clips.iter().map(|clip| clip.name.clone()).collect(),
current: player.current_clip.map(|index| index as u32),
playing: player.playing,
time: player.time,
duration,
speed: player.speed,
looping: player.looping,
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct EntityView {
pub id: u32,
pub name: String,
pub translation: [f32; 3],
pub rotation: [f32; 3],
pub scale: [f32; 3],
pub mesh: Option<String>,
pub material_name: Option<String>,
pub tags: Vec<String>,
pub animation: Option<AnimationView>,
pub morph_weights: Option<Vec<f32>>,
pub visibility: Option<bool>,
pub casts_shadow: bool,
pub light: Option<Light>,
pub camera: Option<Camera>,
pub is_active_camera: bool,
pub rigid_body: Option<RigidBodyComponent>,
pub collider: Option<ColliderComponent>,
pub character_controller: Option<CharacterControllerComponent>,
pub navmesh_agent: Option<NavMeshAgent>,
pub particle_emitter: Option<Box<ParticleEmitter>>,
pub decal: Option<Decal>,
pub audio_source: Option<AudioSource>,
pub render_layer: Option<RenderLayer>,
pub culling_mask: Option<CullingMask>,
pub camera_culling_mask: Option<CameraCullingMask>,
pub ignore_parent_scale: bool,
pub text: Option<(String, TextProperties)>,
pub script: Option<String>,
pub present: Vec<ComponentKind>,
pub addable: Vec<ComponentKind>,
}
pub fn describe_entity(world: &World, entity: Entity) -> Option<EntityView> {
let transform = world.core.get_local_transform(entity).copied()?;
let (roll, pitch, yaw) = quat_to_euler_xyz(&transform.rotation);
let name = world
.core
.get_name(entity)
.map(|name| name.0.clone())
.filter(|name| !name.is_empty())
.unwrap_or_else(|| format!("Entity {}", entity.id));
let text = world.core.get_text(entity).map(|text| {
let content = world
.resources
.text
.cache
.get_text(text.text_index)
.map(str::to_string)
.unwrap_or_default();
(content, text.properties.clone())
});
Some(EntityView {
id: entity.id,
name,
translation: [
transform.translation.x,
transform.translation.y,
transform.translation.z,
],
rotation: [roll.to_degrees(), pitch.to_degrees(), yaw.to_degrees()],
scale: [transform.scale.x, transform.scale.y, transform.scale.z],
mesh: world
.core
.get_render_mesh(entity)
.map(|mesh| mesh.name.clone()),
material_name: world
.core
.get_material_ref(entity)
.map(|reference| reference.name.clone()),
tags: world
.resources
.entities
.tags
.get(&entity)
.cloned()
.unwrap_or_default(),
animation: world.core.get_animation_player(entity).map(animation_view),
morph_weights: world
.core
.get_morph_weights(entity)
.map(|weights| weights.weights.clone()),
visibility: world
.core
.get_visibility(entity)
.map(|visibility| visibility.visible),
casts_shadow: world.core.entity_has_casts_shadow(entity),
light: world.core.get_light(entity).cloned(),
camera: world.core.get_camera(entity).copied(),
is_active_camera: world.resources.active_camera == Some(entity),
rigid_body: world.core.get_rigid_body(entity).cloned(),
collider: world.core.get_collider(entity).cloned(),
character_controller: world.core.get_character_controller(entity).cloned(),
navmesh_agent: world.core.get_navmesh_agent(entity).cloned(),
particle_emitter: world
.core
.get_particle_emitter(entity)
.cloned()
.map(Box::new),
decal: world.core.get_decal(entity).cloned(),
audio_source: world.core.get_audio_source(entity).cloned(),
render_layer: world.core.get_render_layer(entity).copied(),
culling_mask: world.core.get_culling_mask(entity).copied(),
camera_culling_mask: world.core.get_camera_culling_mask(entity).copied(),
ignore_parent_scale: world.core.entity_has_ignore_parent_scale(entity),
text,
script: world
.core
.get_script(entity)
.map(|script| script.source_text().to_string()),
present: present_components(world, entity),
addable: addable_components(world, entity),
})
}