1use crate::reflect::{ComponentKind, addable_components, present_components};
6use nightshade::ecs::animation::components::AnimationPlayer;
7use nightshade::ecs::audio::components::AudioSource;
8use nightshade::ecs::camera::components::Camera;
9use nightshade::ecs::decal::components::Decal;
10use nightshade::ecs::material::components::Material;
11use nightshade::ecs::particles::components::ParticleEmitter;
12use nightshade::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
13use nightshade::ecs::text::components::TextProperties;
14use nightshade::prelude::*;
15use serde::{Deserialize, Serialize};
16
17pub fn quat_to_euler_xyz(quat: &Quat) -> (f32, f32, f32) {
20 let sinr = 2.0 * (quat.w * quat.i + quat.j * quat.k);
21 let cosr = 1.0 - 2.0 * (quat.i * quat.i + quat.j * quat.j);
22 let roll = sinr.atan2(cosr);
23
24 let sinp = 2.0 * (quat.w * quat.j - quat.k * quat.i);
25 let pitch = if sinp.abs() >= 1.0 {
26 std::f32::consts::FRAC_PI_2.copysign(sinp)
27 } else {
28 sinp.asin()
29 };
30
31 let siny = 2.0 * (quat.w * quat.k + quat.i * quat.j);
32 let cosy = 1.0 - 2.0 * (quat.j * quat.j + quat.k * quat.k);
33 let yaw = siny.atan2(cosy);
34
35 (roll, pitch, yaw)
36}
37
38pub fn euler_xyz_to_quat(roll: f32, pitch: f32, yaw: f32) -> Quat {
41 let cr = (roll * 0.5).cos();
42 let sr = (roll * 0.5).sin();
43 let cp = (pitch * 0.5).cos();
44 let sp = (pitch * 0.5).sin();
45 let cy = (yaw * 0.5).cos();
46 let sy = (yaw * 0.5).sin();
47
48 Quat::from_parts(
49 cr * cp * cy + sr * sp * sy,
50 nalgebra_glm::Vec3::new(
51 sr * cp * cy - cr * sp * sy,
52 cr * sp * cy + sr * cp * sy,
53 cr * cp * sy - sr * sp * cy,
54 ),
55 )
56}
57
58pub fn material_of(world: &World, entity: Entity) -> Option<Material> {
61 let material_ref = world.core.get_material_ref(entity)?;
62 registry_entry_by_name(
63 &world.resources.assets.material_registry.registry,
64 &material_ref.name,
65 )
66 .cloned()
67}
68
69pub fn get_color(world: &World, entity: Entity) -> Option<[f32; 4]> {
72 material_of(world, entity).map(|material| material.base_color)
73}
74
75pub fn get_metallic_roughness(world: &World, entity: Entity) -> Option<(f32, f32)> {
77 material_of(world, entity).map(|material| (material.metallic, material.roughness))
78}
79
80pub fn get_emissive(world: &World, entity: Entity) -> Option<([f32; 3], f32)> {
82 material_of(world, entity)
83 .map(|material| (material.emissive_factor, material.emissive_strength))
84}
85
86pub fn get_unlit(world: &World, entity: Entity) -> Option<bool> {
88 material_of(world, entity).map(|material| material.unlit)
89}
90
91pub fn get_texture(world: &World, entity: Entity) -> Option<String> {
93 material_of(world, entity).and_then(|material| material.base_texture)
94}
95
96#[derive(Clone, PartialEq, Serialize, Deserialize)]
98pub struct AnimationView {
99 pub clips: Vec<String>,
100 pub current: Option<u32>,
101 pub playing: bool,
102 pub time: f32,
103 pub duration: f32,
104 pub speed: f32,
105 pub looping: bool,
106}
107
108pub fn animation_view(player: &AnimationPlayer) -> AnimationView {
110 let duration = player
111 .get_current_clip()
112 .map(|clip| clip.duration)
113 .unwrap_or(0.0);
114 AnimationView {
115 clips: player.clips.iter().map(|clip| clip.name.clone()).collect(),
116 current: player.current_clip.map(|index| index as u32),
117 playing: player.playing,
118 time: player.time,
119 duration,
120 speed: player.speed,
121 looping: player.looping,
122 }
123}
124
125#[derive(Clone, Serialize, Deserialize)]
130pub struct EntityView {
131 pub id: u32,
132 pub name: String,
133 pub translation: [f32; 3],
134 pub rotation: [f32; 3],
135 pub scale: [f32; 3],
136 pub mesh: Option<String>,
137 pub material_name: Option<String>,
138 pub tags: Vec<String>,
139 pub animation: Option<AnimationView>,
140 pub morph_weights: Option<Vec<f32>>,
141 pub visibility: Option<bool>,
142 pub casts_shadow: bool,
143 pub light: Option<Light>,
144 pub camera: Option<Camera>,
145 pub is_active_camera: bool,
146 pub rigid_body: Option<RigidBodyComponent>,
147 pub collider: Option<ColliderComponent>,
148 pub character_controller: Option<CharacterControllerComponent>,
149 pub navmesh_agent: Option<NavMeshAgent>,
150 pub particle_emitter: Option<Box<ParticleEmitter>>,
151 pub decal: Option<Decal>,
152 pub audio_source: Option<AudioSource>,
153 pub render_layer: Option<RenderLayer>,
154 pub culling_mask: Option<CullingMask>,
155 pub camera_culling_mask: Option<CameraCullingMask>,
156 pub ignore_parent_scale: bool,
157 pub text: Option<(String, TextProperties)>,
158 pub script: Option<String>,
159 pub present: Vec<ComponentKind>,
160 pub addable: Vec<ComponentKind>,
161}
162
163pub fn describe_entity(world: &World, entity: Entity) -> Option<EntityView> {
166 let transform = world.core.get_local_transform(entity).copied()?;
167 let (roll, pitch, yaw) = quat_to_euler_xyz(&transform.rotation);
168
169 let name = world
170 .core
171 .get_name(entity)
172 .map(|name| name.0.clone())
173 .filter(|name| !name.is_empty())
174 .unwrap_or_else(|| format!("Entity {}", entity.id));
175
176 let text = world.core.get_text(entity).map(|text| {
177 let content = world
178 .resources
179 .text
180 .cache
181 .get_text(text.text_index)
182 .map(str::to_string)
183 .unwrap_or_default();
184 (content, text.properties.clone())
185 });
186
187 Some(EntityView {
188 id: entity.id,
189 name,
190 translation: [
191 transform.translation.x,
192 transform.translation.y,
193 transform.translation.z,
194 ],
195 rotation: [roll.to_degrees(), pitch.to_degrees(), yaw.to_degrees()],
196 scale: [transform.scale.x, transform.scale.y, transform.scale.z],
197 mesh: world
198 .core
199 .get_render_mesh(entity)
200 .map(|mesh| mesh.name.clone()),
201 material_name: world
202 .core
203 .get_material_ref(entity)
204 .map(|reference| reference.name.clone()),
205 tags: world
206 .resources
207 .entities
208 .tags
209 .get(&entity)
210 .cloned()
211 .unwrap_or_default(),
212 animation: world.core.get_animation_player(entity).map(animation_view),
213 morph_weights: world
214 .core
215 .get_morph_weights(entity)
216 .map(|weights| weights.weights.clone()),
217 visibility: world
218 .core
219 .get_visibility(entity)
220 .map(|visibility| visibility.visible),
221 casts_shadow: world.core.entity_has_casts_shadow(entity),
222 light: world.core.get_light(entity).cloned(),
223 camera: world.core.get_camera(entity).copied(),
224 is_active_camera: world.resources.active_camera == Some(entity),
225 rigid_body: world.core.get_rigid_body(entity).cloned(),
226 collider: world.core.get_collider(entity).cloned(),
227 character_controller: world.core.get_character_controller(entity).cloned(),
228 navmesh_agent: world.core.get_navmesh_agent(entity).cloned(),
229 particle_emitter: world
230 .core
231 .get_particle_emitter(entity)
232 .cloned()
233 .map(Box::new),
234 decal: world.core.get_decal(entity).cloned(),
235 audio_source: world.core.get_audio_source(entity).cloned(),
236 render_layer: world.core.get_render_layer(entity).copied(),
237 culling_mask: world.core.get_culling_mask(entity).copied(),
238 camera_culling_mask: world.core.get_camera_culling_mask(entity).copied(),
239 ignore_parent_scale: world.core.entity_has_ignore_parent_scale(entity),
240 text,
241 script: world
242 .core
243 .get_script(entity)
244 .map(|script| script.source_text().to_string()),
245 present: present_components(world, entity),
246 addable: addable_components(world, entity),
247 })
248}