use crate::palette::WHITE;
use crate::runner::MATERIAL_PREFIX;
use nightshade::prelude::nalgebra_glm::Mat4;
use nightshade::prelude::*;
pub use nightshade::prelude::despawn_recursive_immediate as despawn;
pub use nightshade::prelude::spawn_cone_at as spawn_cone;
pub use nightshade::prelude::spawn_cube_at as spawn_cube;
pub use nightshade::prelude::spawn_cylinder_at as spawn_cylinder;
pub use nightshade::prelude::spawn_plane_at as spawn_plane;
pub use nightshade::prelude::spawn_sphere_at as spawn_sphere;
pub use nightshade::prelude::spawn_torus_at as spawn_torus;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Shape {
#[default]
Cube,
Sphere,
Cylinder,
Cone,
Torus,
Plane,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Body {
#[default]
None,
Static,
Dynamic {
mass: f32,
},
}
pub struct Object {
pub shape: Shape,
pub position: Vec3,
pub scale: Vec3,
pub color: [f32; 4],
pub body: Body,
}
impl Default for Object {
fn default() -> Self {
Self {
shape: Shape::Cube,
position: Vec3::zeros(),
scale: Vec3::new(1.0, 1.0, 1.0),
color: WHITE,
body: Body::None,
}
}
}
pub fn spawn_object(world: &mut World, object: Object) -> Entity {
let entity = spawn_mesh_at(
world,
mesh_name(object.shape),
object.position,
object.scale,
);
crate::appearance::set_color(world, entity, object.color);
match object.body {
Body::None => {}
#[cfg(feature = "physics")]
Body::Static => {
let collider = static_collider(world, object.shape, object.scale)
.with_friction(0.8)
.with_restitution(0.1);
attach_body(
world,
entity,
RigidBodyComponent::new_static().with_translation(
object.position.x,
object.position.y,
object.position.z,
),
collider,
false,
);
}
#[cfg(feature = "physics")]
Body::Dynamic { mass } => {
let collider = dynamic_collider(world, object.shape, object.scale)
.with_friction(0.7)
.with_restitution(0.2);
attach_body(
world,
entity,
RigidBodyComponent::new_dynamic()
.with_translation(object.position.x, object.position.y, object.position.z)
.with_mass(mass),
collider,
true,
);
}
#[cfg(not(feature = "physics"))]
Body::Static | Body::Dynamic { .. } => {}
}
entity
}
pub fn spawn_floor(world: &mut World, half_extent: f32) -> Entity {
let entity = spawn_mesh_at(
world,
"Plane",
Vec3::zeros(),
Vec3::new(half_extent, 1.0, half_extent),
);
#[cfg(feature = "physics")]
attach_body(
world,
entity,
RigidBodyComponent::new_static().with_translation(0.0, -0.05, 0.0),
ColliderComponent::new_cuboid(half_extent, 0.05, half_extent)
.with_friction(0.8)
.with_restitution(0.1),
false,
);
entity
}
pub fn spawn_model(world: &mut World, glb_bytes: &[u8], position: Vec3) -> Entity {
let mut result =
import_gltf_from_bytes(glb_bytes).expect("failed to import the glb model bytes");
nightshade::ecs::loading::queue_gltf_load(world, &mut result);
let prefab = &result.prefabs[0];
nightshade::ecs::prefab::commands::spawn_prefab_with_skins(
world,
prefab,
&result.animations,
&result.skins,
position,
)
}
pub fn play_animation(world: &mut World, entity: Entity, clip_index: usize) {
if let Some(player) = world.core.get_animation_player_mut(entity) {
player.play(clip_index);
}
}
pub fn set_animation_looping(world: &mut World, entity: Entity, looping: bool) {
if let Some(player) = world.core.get_animation_player_mut(entity) {
player.looping = looping;
}
}
pub fn set_parent(world: &mut World, child: Entity, parent: Option<Entity>) {
let child_world = crate::placement::world_matrix(world, child);
let parent_world = parent
.map(|parent_entity| crate::placement::world_matrix(world, parent_entity))
.unwrap_or_else(Mat4::identity);
let local = nalgebra_glm::inverse(&parent_world) * child_world;
let translation = nalgebra_glm::vec3(local[(0, 3)], local[(1, 3)], local[(2, 3)]);
let basis_x = nalgebra_glm::vec3(local[(0, 0)], local[(1, 0)], local[(2, 0)]);
let basis_y = nalgebra_glm::vec3(local[(0, 1)], local[(1, 1)], local[(2, 1)]);
let basis_z = nalgebra_glm::vec3(local[(0, 2)], local[(1, 2)], local[(2, 2)]);
let scale = nalgebra_glm::vec3(
basis_x.magnitude(),
basis_y.magnitude(),
basis_z.magnitude(),
);
let rotation_matrix = nalgebra_glm::Mat3::from_columns(&[
basis_x / scale.x.max(f32::EPSILON),
basis_y / scale.y.max(f32::EPSILON),
basis_z / scale.z.max(f32::EPSILON),
]);
let rotation = nalgebra_glm::mat3_to_quat(&rotation_matrix);
if parent.is_some() {
world.core.add_components(child, PARENT);
}
update_parent(
world,
child,
parent.map(|parent_entity| Parent(Some(parent_entity))),
);
assign_local_transform(
world,
child,
LocalTransform {
translation,
rotation,
scale,
},
);
}
#[cfg(feature = "picking")]
pub(crate) fn is_reserved(world: &World, entity: Entity) -> bool {
world
.core
.get_name(entity)
.is_some_and(|name| name.0.starts_with(crate::runner::RESERVED_PREFIX))
}
pub(crate) fn api_material_name(entity: Entity) -> String {
format!("{MATERIAL_PREFIX}{}", entity.id)
}
fn mesh_name(shape: Shape) -> &'static str {
match shape {
Shape::Cube => "Cube",
Shape::Sphere => "Sphere",
Shape::Cylinder => "Cylinder",
Shape::Cone => "Cone",
Shape::Torus => "Torus",
Shape::Plane => "Plane",
}
}
#[cfg(feature = "physics")]
fn dynamic_collider(world: &World, shape: Shape, scale: Vec3) -> ColliderComponent {
match shape {
Shape::Cube => ColliderComponent::new_cuboid(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5),
Shape::Sphere => ColliderComponent::new_ball(scale.x),
Shape::Cylinder => ColliderComponent::new_cylinder(scale.y * 0.5, scale.x * 0.5),
Shape::Cone => ColliderComponent::new_cone(scale.y * 0.5, scale.x * 0.5),
Shape::Torus => ColliderComponent {
shape: ColliderShape::ConvexMesh {
vertices: scaled_mesh_points(world, "Torus", scale),
},
..Default::default()
},
Shape::Plane => ColliderComponent::new_cuboid(scale.x, 0.05, scale.z),
}
}
#[cfg(feature = "physics")]
fn static_collider(world: &World, shape: Shape, scale: Vec3) -> ColliderComponent {
match shape {
Shape::Torus | Shape::Plane => {
let shape_mesh_name = mesh_name(shape);
ColliderComponent {
shape: ColliderShape::TriMesh {
vertices: scaled_mesh_points(world, shape_mesh_name, scale),
indices: mesh_triangles(world, shape_mesh_name),
},
..Default::default()
}
}
_ => dynamic_collider(world, shape, scale),
}
}
#[cfg(feature = "physics")]
fn scaled_mesh_points(world: &World, mesh_name: &str, scale: Vec3) -> Vec<[f32; 3]> {
registry_entry_by_name(&world.resources.assets.mesh_cache.registry, mesh_name)
.map(|mesh| {
mesh.vertices
.iter()
.map(|vertex| {
[
vertex.position[0] * scale.x,
vertex.position[1] * scale.y,
vertex.position[2] * scale.z,
]
})
.collect()
})
.unwrap_or_default()
}
#[cfg(feature = "physics")]
fn mesh_triangles(world: &World, mesh_name: &str) -> Vec<[u32; 3]> {
registry_entry_by_name(&world.resources.assets.mesh_cache.registry, mesh_name)
.map(|mesh| {
mesh.indices
.chunks_exact(3)
.map(|triangle| [triangle[0], triangle[1], triangle[2]])
.collect()
})
.unwrap_or_default()
}
#[cfg(feature = "physics")]
fn attach_body(
world: &mut World,
entity: Entity,
body: RigidBodyComponent,
collider: ColliderComponent,
dynamic: bool,
) {
let mut flags = RIGID_BODY | COLLIDER;
if dynamic {
flags |= COLLISION_LISTENER | PHYSICS_INTERPOLATION;
}
world.core.add_components(entity, flags);
world.core.set_rigid_body(entity, body);
world.core.set_collider(entity, collider);
if dynamic {
reset_physics_interpolation(world, entity);
if let Some(interpolation) = world.core.get_physics_interpolation_mut(entity) {
interpolation.enabled = true;
}
}
}