use super::*;
use crate::ecs::material::resources::material_registry_find_or_insert;
use crate::ecs::world::{Entity, Vec3, World};
pub fn create_textured_material(
base_color: Vec3,
roughness: f32,
metallic: f32,
) -> crate::ecs::material::components::Material {
crate::ecs::material::components::Material {
base_color: [base_color.x, base_color.y, base_color.z, 1.0],
emissive_factor: [0.0, 0.0, 0.0],
roughness,
metallic,
..Default::default()
}
}
pub fn create_emissive_material(
base_color: Vec3,
emissive_strength: f32,
) -> crate::ecs::material::components::Material {
crate::ecs::material::components::Material {
base_color: [base_color.x, base_color.y, base_color.z, 1.0],
emissive_factor: [
base_color.x * emissive_strength,
base_color.y * emissive_strength,
base_color.z * emissive_strength,
],
..Default::default()
}
}
pub fn spawn_static_physics_cube_with_material(
world: &mut World,
position: Vec3,
size: Vec3,
material: crate::ecs::material::components::Material,
) -> Entity {
let entity = world.spawn_entities(
crate::ecs::world::NAME
| crate::ecs::world::LOCAL_TRANSFORM
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::LOCAL_TRANSFORM_DIRTY
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::MATERIAL_REF
| crate::ecs::world::BOUNDING_VOLUME
| crate::ecs::world::CASTS_SHADOW
| crate::ecs::world::RIGID_BODY
| crate::ecs::world::COLLIDER
| crate::ecs::world::VISIBILITY,
1,
)[0];
if let Some(name) = world.core.get_name_mut(entity) {
name.0 = "Physics Cube".to_string();
}
if let Some(transform) = world.core.get_local_transform_mut(entity) {
transform.translation = position;
transform.scale = size;
}
if let Some(mesh) = world.core.get_render_mesh_mut(entity) {
mesh.name = "Cube".to_string();
}
let fallback_name = format!("StaticPhysicsCube_{}", entity.id);
let material_name = material_registry_find_or_insert(
&mut world.resources.material_registry,
fallback_name,
material,
);
if let Some(&index) = world
.resources
.material_registry
.registry
.name_to_index
.get(&material_name)
{
world
.resources
.material_registry
.registry
.add_reference(index);
}
world.core.set_material_ref(
entity,
crate::ecs::material::components::MaterialRef::new(material_name),
);
if let Some(bv) = world.core.get_bounding_volume_mut(entity) {
*bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Cube");
}
if let Some(rigid_body) = world.core.get_rigid_body_mut(entity) {
*rigid_body =
RigidBodyComponent::new_static().with_translation(position.x, position.y, position.z);
}
if let Some(collider) = world.core.get_collider_mut(entity) {
*collider = ColliderComponent::new_cuboid(size.x / 2.0, size.y / 2.0, size.z / 2.0)
.with_friction(0.8)
.with_restitution(0.1);
}
entity
}
pub fn spawn_dynamic_physics_sphere_with_material(
world: &mut World,
position: Vec3,
radius: f32,
mass: f32,
material: crate::ecs::material::components::Material,
) -> Entity {
let scale_factor = radius;
let entity = world.spawn_entities(
crate::ecs::world::NAME
| crate::ecs::world::LOCAL_TRANSFORM
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::LOCAL_TRANSFORM_DIRTY
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::MATERIAL_REF
| crate::ecs::world::BOUNDING_VOLUME
| crate::ecs::world::CASTS_SHADOW
| crate::ecs::world::RIGID_BODY
| crate::ecs::world::COLLIDER
| crate::ecs::world::COLLISION_LISTENER
| crate::ecs::world::PHYSICS_INTERPOLATION
| crate::ecs::world::VISIBILITY,
1,
)[0];
if let Some(name) = world.core.get_name_mut(entity) {
name.0 = "Physics Sphere".to_string();
}
if let Some(transform) = world.core.get_local_transform_mut(entity) {
transform.translation = position;
transform.scale = nalgebra_glm::vec3(scale_factor, scale_factor, scale_factor);
}
if let Some(mesh) = world.core.get_render_mesh_mut(entity) {
mesh.name = "Sphere".to_string();
}
let fallback_name = format!("DynamicPhysicsSphere_{}", entity.id);
let material_name = material_registry_find_or_insert(
&mut world.resources.material_registry,
fallback_name,
material,
);
if let Some(&index) = world
.resources
.material_registry
.registry
.name_to_index
.get(&material_name)
{
world
.resources
.material_registry
.registry
.add_reference(index);
}
world.core.set_material_ref(
entity,
crate::ecs::material::components::MaterialRef::new(material_name),
);
if let Some(bv) = world.core.get_bounding_volume_mut(entity) {
*bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Sphere");
}
if let Some(rigid_body) = world.core.get_rigid_body_mut(entity) {
*rigid_body = RigidBodyComponent::new_dynamic()
.with_translation(position.x, position.y, position.z)
.with_mass(mass);
}
if let Some(collider) = world.core.get_collider_mut(entity) {
*collider = ColliderComponent::new_ball(radius)
.with_friction(0.7)
.with_restitution(0.3);
}
world.resources.mesh_render_state.mark_entity_added(entity);
entity
}
pub fn spawn_dynamic_physics_cube_with_material(
world: &mut World,
position: Vec3,
size: Vec3,
mass: f32,
material: crate::ecs::material::components::Material,
) -> Entity {
let entity = world.spawn_entities(
crate::ecs::world::NAME
| crate::ecs::world::LOCAL_TRANSFORM
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::LOCAL_TRANSFORM_DIRTY
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::MATERIAL_REF
| crate::ecs::world::BOUNDING_VOLUME
| crate::ecs::world::CASTS_SHADOW
| crate::ecs::world::RIGID_BODY
| crate::ecs::world::COLLIDER
| crate::ecs::world::COLLISION_LISTENER
| crate::ecs::world::PHYSICS_INTERPOLATION
| crate::ecs::world::VISIBILITY,
1,
)[0];
if let Some(name) = world.core.get_name_mut(entity) {
name.0 = "Physics Cube".to_string();
}
if let Some(transform) = world.core.get_local_transform_mut(entity) {
transform.translation = position;
transform.scale = size;
}
if let Some(mesh) = world.core.get_render_mesh_mut(entity) {
mesh.name = "Cube".to_string();
}
let fallback_name = format!("DynamicPhysicsCube_{}", entity.id);
let material_name = material_registry_find_or_insert(
&mut world.resources.material_registry,
fallback_name,
material,
);
if let Some(&index) = world
.resources
.material_registry
.registry
.name_to_index
.get(&material_name)
{
world
.resources
.material_registry
.registry
.add_reference(index);
}
world.core.set_material_ref(
entity,
crate::ecs::material::components::MaterialRef::new(material_name),
);
if let Some(bv) = world.core.get_bounding_volume_mut(entity) {
*bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Cube");
}
if let Some(rigid_body) = world.core.get_rigid_body_mut(entity) {
*rigid_body = RigidBodyComponent::new_dynamic()
.with_translation(position.x, position.y, position.z)
.with_mass(mass);
}
if let Some(collider) = world.core.get_collider_mut(entity) {
*collider = ColliderComponent::new_cuboid(size.x / 2.0, size.y / 2.0, size.z / 2.0)
.with_friction(0.7)
.with_restitution(0.2);
}
world.resources.mesh_render_state.mark_entity_added(entity);
entity
}
pub fn spawn_dynamic_physics_cylinder_with_material(
world: &mut World,
position: Vec3,
half_height: f32,
radius: f32,
mass: f32,
material: crate::ecs::material::components::Material,
) -> Entity {
let entity = world.spawn_entities(
crate::ecs::world::NAME
| crate::ecs::world::LOCAL_TRANSFORM
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::LOCAL_TRANSFORM_DIRTY
| crate::ecs::world::RENDER_MESH
| crate::ecs::world::MATERIAL_REF
| crate::ecs::world::BOUNDING_VOLUME
| crate::ecs::world::CASTS_SHADOW
| crate::ecs::world::RIGID_BODY
| crate::ecs::world::COLLIDER
| crate::ecs::world::COLLISION_LISTENER
| crate::ecs::world::PHYSICS_INTERPOLATION
| crate::ecs::world::VISIBILITY,
1,
)[0];
if let Some(name) = world.core.get_name_mut(entity) {
name.0 = "Physics Cylinder".to_string();
}
if let Some(transform) = world.core.get_local_transform_mut(entity) {
transform.translation = position;
transform.scale = nalgebra_glm::vec3(radius * 2.0, half_height * 2.0, radius * 2.0);
}
if let Some(mesh) = world.core.get_render_mesh_mut(entity) {
mesh.name = "Cylinder".to_string();
}
let fallback_name = format!("DynamicPhysicsCylinder_{}", entity.id);
let material_name = material_registry_find_or_insert(
&mut world.resources.material_registry,
fallback_name,
material,
);
if let Some(&index) = world
.resources
.material_registry
.registry
.name_to_index
.get(&material_name)
{
world
.resources
.material_registry
.registry
.add_reference(index);
}
world.core.set_material_ref(
entity,
crate::ecs::material::components::MaterialRef::new(material_name),
);
if let Some(bv) = world.core.get_bounding_volume_mut(entity) {
*bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Cylinder");
}
if let Some(rigid_body) = world.core.get_rigid_body_mut(entity) {
*rigid_body = RigidBodyComponent::new_dynamic()
.with_translation(position.x, position.y, position.z)
.with_mass(mass);
}
if let Some(collider) = world.core.get_collider_mut(entity) {
*collider = ColliderComponent::new_cylinder(half_height, radius)
.with_friction(0.5)
.with_restitution(0.3);
}
world.resources.mesh_render_state.mark_entity_added(entity);
entity
}
pub fn spawn_first_person_player(world: &mut World, position: Vec3) -> (Entity, Entity) {
let player_entity = world.spawn_entities(
crate::ecs::world::NAME
| crate::ecs::world::LOCAL_TRANSFORM
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::LOCAL_TRANSFORM_DIRTY
| crate::ecs::world::CHARACTER_CONTROLLER
| crate::ecs::world::PHYSICS_INTERPOLATION,
1,
)[0];
if let Some(name) = world.core.get_name_mut(player_entity) {
name.0 = "Player".to_string();
}
if let Some(transform) = world.core.get_local_transform_mut(player_entity) {
transform.translation = position;
}
if let Some(interpolation) = world.core.get_physics_interpolation_mut(player_entity) {
interpolation.previous_translation = position;
interpolation.previous_rotation = nalgebra_glm::quat_identity();
interpolation.current_translation = position;
interpolation.current_rotation = nalgebra_glm::quat_identity();
interpolation.enabled = true;
}
if let Some(controller) = world.core.get_character_controller_mut(player_entity) {
*controller = CharacterControllerComponent::new_capsule(0.7, 0.3);
controller.max_speed = 5.0;
controller.acceleration = 50.0;
controller.jump_impulse = 5.0;
}
let camera_entity = world.spawn_entities(
crate::ecs::world::NAME
| crate::ecs::world::CAMERA
| crate::ecs::world::LOCAL_TRANSFORM
| crate::ecs::world::GLOBAL_TRANSFORM
| crate::ecs::world::LOCAL_TRANSFORM_DIRTY
| crate::ecs::world::PARENT,
1,
)[0];
if let Some(name) = world.core.get_name_mut(camera_entity) {
name.0 = "Player Camera".to_string();
}
if let Some(transform) = world.core.get_local_transform_mut(camera_entity) {
transform.translation = nalgebra_glm::vec3(0.0, 1.3, 0.0);
}
if let Some(parent) = world.core.get_parent_mut(camera_entity) {
*parent = crate::ecs::transform::components::Parent(Some(player_entity));
}
if let Some(camera) = world.core.get_camera_mut(camera_entity) {
camera.projection = crate::ecs::camera::components::Projection::Perspective(
crate::ecs::camera::components::PerspectiveCamera {
y_fov_rad: 85.0_f32.to_radians(),
z_near: 0.01,
z_far: Some(1000.0),
aspect_ratio: None,
},
);
camera.smoothing = Some(crate::ecs::camera::components::Smoothing {
mouse_sensitivity: 1.5,
..Default::default()
});
}
world.resources.active_camera = Some(camera_entity);
(player_entity, camera_entity)
}
pub fn look_camera_system(world: &mut World) {
let Some(camera_entity) = world.resources.active_camera else {
return;
};
let delta_time = world.resources.window.timing.delta_time;
let right_clicked = world
.resources
.input
.mouse
.state
.contains(crate::ecs::input::resources::MouseState::RIGHT_CLICKED);
let raw_mouse_delta = world.resources.input.mouse.raw_mouse_delta;
#[cfg(feature = "gamepad")]
let gamepad_input =
if let Some(gamepad) = crate::ecs::input::queries::query_active_gamepad(world) {
let right_stick_x = gamepad.value(gilrs::Axis::RightStickX);
let right_stick_y = gamepad.value(gilrs::Axis::RightStickY);
Some((right_stick_x, right_stick_y))
} else {
None
};
#[cfg(not(feature = "gamepad"))]
let gamepad_input: Option<(f32, f32)> = None;
let Some(camera) = world.core.get_camera_mut(camera_entity) else {
return;
};
let Some(smoothing) = camera.smoothing.as_mut() else {
return;
};
let mouse_delta = if right_clicked {
let smoothing_factor = if smoothing.mouse_smoothness > 0.0 {
1.0 - smoothing.mouse_smoothness.powi(7).powf(delta_time)
} else {
1.0
};
smoothing.smoothed_mouse_delta = smoothing.smoothed_mouse_delta * (1.0 - smoothing_factor)
+ raw_mouse_delta * smoothing_factor;
let pixels_to_radians = (std::f32::consts::PI / 1000.0) * smoothing.mouse_dpi_scale;
let mut delta =
smoothing.smoothed_mouse_delta * smoothing.mouse_sensitivity * pixels_to_radians;
delta.x *= -1.0;
delta.y *= -1.0;
Some(delta)
} else {
let decay_smoothness = (smoothing.mouse_smoothness * 0.5).max(0.01);
let smoothing_factor = 1.0 - decay_smoothness.powi(7).powf(delta_time);
smoothing.smoothed_mouse_delta *= 1.0 - smoothing_factor;
None
};
let gamepad_delta = if let Some((right_stick_x, right_stick_y)) = gamepad_input {
let deadzone = smoothing.gamepad_deadzone;
let right_stick_magnitude =
(right_stick_x * right_stick_x + right_stick_y * right_stick_y).sqrt();
if right_stick_magnitude > deadzone {
let target_input = nalgebra_glm::vec2(
apply_deadzone_physics(right_stick_x, deadzone),
apply_deadzone_physics(right_stick_y, deadzone),
);
let smoothing_factor = if smoothing.gamepad_smoothness > 0.0 {
1.0 - smoothing.gamepad_smoothness.powi(7).powf(delta_time)
} else {
1.0
};
smoothing.smoothed_gamepad_input = smoothing.smoothed_gamepad_input
* (1.0 - smoothing_factor)
+ target_input * smoothing_factor;
let delta = nalgebra_glm::vec2(
-smoothing.smoothed_gamepad_input.x,
smoothing.smoothed_gamepad_input.y,
) * smoothing.gamepad_sensitivity
* delta_time;
Some(delta)
} else {
let smoothing_factor = if smoothing.gamepad_smoothness > 0.0 {
1.0 - smoothing.gamepad_smoothness.powi(7).powf(delta_time)
} else {
1.0
};
smoothing.smoothed_gamepad_input *= 1.0 - smoothing_factor;
None
}
} else {
None
};
let final_delta = match (mouse_delta, gamepad_delta) {
(Some(mouse), Some(gamepad)) => mouse + gamepad,
(Some(mouse), None) => mouse,
(None, Some(gamepad)) => gamepad,
(None, None) => return,
};
let Some(local_transform) = world.core.get_local_transform_mut(camera_entity) else {
return;
};
let yaw = nalgebra_glm::quat_angle_axis(final_delta.x, &nalgebra_glm::vec3(0.0, 1.0, 0.0));
local_transform.rotation = yaw * local_transform.rotation;
let forward_vector = local_transform.forward_vector();
let current_pitch = forward_vector.y.asin();
let new_pitch = current_pitch + final_delta.y;
if new_pitch.abs() <= 89_f32.to_radians() {
let pitch =
nalgebra_glm::quat_angle_axis(final_delta.y, &nalgebra_glm::vec3(1.0, 0.0, 0.0));
local_transform.rotation *= pitch;
}
crate::ecs::transform::commands::mark_local_transform_dirty(world, camera_entity);
}
fn apply_deadzone_physics(value: f32, deadzone: f32) -> f32 {
if value.abs() > deadzone {
let sign = value.signum();
let compensated = (value.abs() - deadzone) / (1.0 - deadzone);
sign * compensated
} else {
0.0
}
}