1use crate::reflect::{ComponentKind, addable_components, present_components};
6use nightshade::ecs::animation::components::AnimationPlayer;
7use nightshade::ecs::camera::components::Camera;
8use nightshade::ecs::decal::components::Decal;
9use nightshade::ecs::primitives::{CameraCullingMask, CullingMask};
10use nightshade::ecs::text::components::TextProperties;
11use nightshade::plugins::audio::components::AudioSource;
12use nightshade::prelude::*;
13use nightshade::render::material::Material;
14use nightshade::render::particles::ParticleEmitter;
15use nightshade::render::render_layer::RenderLayer;
16use serde::{Deserialize, Serialize};
17
18pub fn quat_to_euler_xyz(quat: &Quat) -> (f32, f32, f32) {
21 let sinr = 2.0 * (quat.w * quat.i + quat.j * quat.k);
22 let cosr = 1.0 - 2.0 * (quat.i * quat.i + quat.j * quat.j);
23 let roll = sinr.atan2(cosr);
24
25 let sinp = 2.0 * (quat.w * quat.j - quat.k * quat.i);
26 let pitch = if sinp.abs() >= 1.0 {
27 std::f32::consts::FRAC_PI_2.copysign(sinp)
28 } else {
29 sinp.asin()
30 };
31
32 let siny = 2.0 * (quat.w * quat.k + quat.i * quat.j);
33 let cosy = 1.0 - 2.0 * (quat.j * quat.j + quat.k * quat.k);
34 let yaw = siny.atan2(cosy);
35
36 (roll, pitch, yaw)
37}
38
39pub fn euler_xyz_to_quat(roll: f32, pitch: f32, yaw: f32) -> Quat {
42 let cr = (roll * 0.5).cos();
43 let sr = (roll * 0.5).sin();
44 let cp = (pitch * 0.5).cos();
45 let sp = (pitch * 0.5).sin();
46 let cy = (yaw * 0.5).cos();
47 let sy = (yaw * 0.5).sin();
48
49 Quat::from_parts(
50 cr * cp * cy + sr * sp * sy,
51 nalgebra_glm::Vec3::new(
52 sr * cp * cy - cr * sp * sy,
53 cr * sp * cy + sr * cp * sy,
54 cr * cp * sy - sr * sp * cy,
55 ),
56 )
57}
58
59pub fn material_of(world: &World, entity: Entity) -> Option<Material> {
62 let material_ref = world.get::<nightshade::ecs::material::components::MaterialRef>(entity)?;
63 registry_entry_by_name(
64 &world
65 .res::<nightshade::ecs::asset_state::AssetState>()
66 .material_registry
67 .registry,
68 &material_ref.name,
69 )
70 .cloned()
71}
72
73pub fn color(world: &World, entity: Entity) -> Option<[f32; 4]> {
76 material_of(world, entity).map(|material| material.base_color)
77}
78
79pub fn metallic_roughness(world: &World, entity: Entity) -> Option<(f32, f32)> {
81 material_of(world, entity).map(|material| (material.metallic, material.roughness))
82}
83
84pub fn emissive(world: &World, entity: Entity) -> Option<([f32; 3], f32)> {
86 material_of(world, entity)
87 .map(|material| (material.emissive_factor, material.emissive_strength))
88}
89
90pub fn unlit(world: &World, entity: Entity) -> Option<bool> {
92 material_of(world, entity).map(|material| material.unlit)
93}
94
95pub fn texture(world: &World, entity: Entity) -> Option<String> {
97 material_of(world, entity).and_then(|material| material.base_texture)
98}
99
100#[derive(Clone, PartialEq, Serialize, Deserialize)]
102pub struct AnimationView {
103 pub clips: Vec<String>,
104 pub current: Option<u32>,
105 pub playing: bool,
106 pub time: f32,
107 pub duration: f32,
108 pub speed: f32,
109 pub looping: bool,
110}
111
112pub fn animation_view(player: &AnimationPlayer) -> AnimationView {
114 let duration = player
115 .get_current_clip()
116 .map(|clip| clip.duration)
117 .unwrap_or(0.0);
118 AnimationView {
119 clips: player.clips.iter().map(|clip| clip.name.clone()).collect(),
120 current: player.current_clip.map(|index| index as u32),
121 playing: player.playing,
122 time: player.time,
123 duration,
124 speed: player.speed,
125 looping: player.looping,
126 }
127}
128
129#[derive(Clone, Serialize, Deserialize)]
134pub struct EntityView {
135 pub id: u32,
136 pub name: String,
137 pub translation: [f32; 3],
138 pub rotation: [f32; 3],
139 pub scale: [f32; 3],
140 pub mesh: Option<String>,
141 pub material_name: Option<String>,
142 pub tags: Vec<String>,
143 pub animation: Option<AnimationView>,
144 pub morph_weights: Option<Vec<f32>>,
145 pub visibility: Option<bool>,
146 pub casts_shadow: bool,
147 pub light: Option<Light>,
148 pub camera: Option<Camera>,
149 pub is_active_camera: bool,
150 pub rigid_body: Option<RigidBodyComponent>,
151 pub collider: Option<ColliderComponent>,
152 pub character_controller: Option<CharacterControllerComponent>,
153 pub navmesh_agent: Option<NavMeshAgent>,
154 pub particle_emitter: Option<Box<ParticleEmitter>>,
155 pub decal: Option<Decal>,
156 pub audio_source: Option<AudioSource>,
157 pub render_layer: Option<RenderLayer>,
158 pub culling_mask: Option<CullingMask>,
159 pub camera_culling_mask: Option<CameraCullingMask>,
160 pub ignore_parent_scale: bool,
161 pub text: Option<(String, TextProperties)>,
162 pub script: Option<String>,
163 pub present: Vec<ComponentKind>,
164 pub addable: Vec<ComponentKind>,
165}
166
167pub fn describe_entity(world: &World, entity: Entity) -> Option<EntityView> {
170 let transform = world
171 .get::<nightshade::ecs::transform::components::LocalTransform>(entity)
172 .copied()?;
173 let (roll, pitch, yaw) = quat_to_euler_xyz(&transform.rotation);
174
175 let name = world
176 .get::<nightshade::ecs::primitives::Name>(entity)
177 .map(|name| name.0.clone())
178 .filter(|name| !name.is_empty())
179 .unwrap_or_else(|| format!("Entity {}", entity.id));
180
181 let text = world
182 .get::<nightshade::ecs::text::components::Text>(entity)
183 .map(|text| {
184 let content = world
185 .res::<nightshade::ecs::text::resources::TextState>()
186 .cache
187 .get_text(text.text_index)
188 .map(str::to_string)
189 .unwrap_or_default();
190 (content, text.properties.clone())
191 });
192
193 Some(EntityView {
194 id: entity.id,
195 name,
196 translation: [
197 transform.translation.x,
198 transform.translation.y,
199 transform.translation.z,
200 ],
201 rotation: [roll.to_degrees(), pitch.to_degrees(), yaw.to_degrees()],
202 scale: [transform.scale.x, transform.scale.y, transform.scale.z],
203 mesh: world
204 .get::<nightshade::ecs::mesh::components::RenderMesh>(entity)
205 .map(|mesh| mesh.name.clone()),
206 material_name: world
207 .get::<nightshade::ecs::material::components::MaterialRef>(entity)
208 .map(|reference| reference.name.clone()),
209 tags: world
210 .res::<nightshade::ecs::entity_registry::EntityRegistry>()
211 .tags
212 .get(&entity)
213 .cloned()
214 .unwrap_or_default(),
215 animation: world
216 .get::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
217 .map(animation_view),
218 morph_weights: world
219 .get::<nightshade::ecs::morph::components::MorphWeights>(entity)
220 .map(|weights| weights.weights.clone()),
221 visibility: world
222 .get::<nightshade::ecs::primitives::Visibility>(entity)
223 .map(|visibility| visibility.visible),
224 casts_shadow: world.has::<nightshade::ecs::primitives::CastsShadow>(entity),
225 light: world
226 .get::<nightshade::ecs::light::components::Light>(entity)
227 .cloned(),
228 camera: world
229 .get::<nightshade::ecs::camera::components::Camera>(entity)
230 .copied(),
231 is_active_camera: world
232 .res::<nightshade::ecs::camera::resources::ActiveCamera>()
233 .0
234 == Some(entity),
235 rigid_body: world
236 .get::<nightshade::plugins::physics::components::RigidBodyComponent>(entity)
237 .cloned(),
238 collider: world
239 .get::<nightshade::plugins::physics::components::ColliderComponent>(entity)
240 .cloned(),
241 character_controller: world
242 .get::<nightshade::plugins::physics::components::CharacterControllerComponent>(entity)
243 .cloned(),
244 navmesh_agent: world
245 .get::<nightshade::plugins::navmesh::components::NavMeshAgent>(entity)
246 .cloned(),
247 particle_emitter: world
248 .get::<nightshade::render::particles::ParticleEmitter>(entity)
249 .cloned()
250 .map(Box::new),
251 decal: world
252 .get::<nightshade::ecs::decal::components::Decal>(entity)
253 .cloned(),
254 audio_source: world
255 .get::<nightshade::plugins::audio::components::AudioSource>(entity)
256 .cloned(),
257 render_layer: world
258 .get::<nightshade::render::render_layer::RenderLayer>(entity)
259 .copied(),
260 culling_mask: world
261 .get::<nightshade::ecs::primitives::CullingMask>(entity)
262 .copied(),
263 camera_culling_mask: world
264 .get::<nightshade::ecs::primitives::CameraCullingMask>(entity)
265 .copied(),
266 ignore_parent_scale: world
267 .has::<nightshade::ecs::transform::components::IgnoreParentScale>(entity),
268 text,
269 script: world
270 .get::<nightshade::ecs::script::components::Script>(entity)
271 .map(|script| script.source_text().to_string()),
272 present: present_components(world, entity),
273 addable: addable_components(world, entity),
274 })
275}