use std::collections::HashMap;
use nightshade_ecs::Entity;
use crate::ecs::world::World;
use crate::plugins::physics::resources::{
JointType, PhysicsWorld, physics_world_add_collider, physics_world_add_rigid_body,
};
use crate::assets::asset::AssetUuid;
use crate::assets::scene::character_controller::{
SceneCharacterController, SceneCharacterControllerConfig, SceneCharacterShape,
};
use crate::assets::scene::physics::{
SceneBodyType, SceneCollider, SceneJoint, SceneJointConnection, ScenePhysics,
};
use crate::plugins::physics::components::{
CharacterControllerComponent, ColliderComponent, ColliderShape, RigidBodyComponent,
};
use crate::plugins::physics::types::{
CharacterControllerConfig, InteractionGroups, LockedAxes, RigidBodyType,
};
pub fn scene_joint_to_generic_joint(
joint: &SceneJoint,
) -> (rapier3d::prelude::GenericJoint, JointType) {
use rapier3d::prelude::*;
match joint {
SceneJoint::Fixed {
parent_anchor,
child_anchor,
} => (
FixedJointBuilder::new()
.local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
.local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
.build()
.into(),
JointType::Fixed,
),
SceneJoint::Revolute {
parent_anchor,
child_anchor,
axis,
limits,
} => {
let axis_unit = UnitVector::new_normalize(vector![axis[0], axis[1], axis[2]]);
let mut builder = RevoluteJointBuilder::new(axis_unit)
.local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
.local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]]);
if let Some([min, max]) = limits {
builder = builder.limits([*min, *max]);
}
(builder.build().into(), JointType::Revolute)
}
SceneJoint::Prismatic {
parent_anchor,
child_anchor,
axis,
limits,
} => {
let axis_unit = UnitVector::new_normalize(vector![axis[0], axis[1], axis[2]]);
let mut builder = PrismaticJointBuilder::new(axis_unit)
.local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
.local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]]);
if let Some([min, max]) = limits {
builder = builder.limits([*min, *max]);
}
(builder.build().into(), JointType::Prismatic)
}
SceneJoint::Spherical {
parent_anchor,
child_anchor,
} => (
SphericalJointBuilder::new()
.local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
.local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
.build()
.into(),
JointType::Spherical,
),
SceneJoint::Rope {
parent_anchor,
child_anchor,
max_distance,
} => (
RopeJointBuilder::new(*max_distance)
.local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
.local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
.build()
.into(),
JointType::Rope,
),
SceneJoint::Spring {
parent_anchor,
child_anchor,
rest_length,
stiffness,
damping,
} => (
SpringJointBuilder::new(*rest_length, *stiffness, *damping)
.local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
.local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
.build()
.into(),
JointType::Spring,
),
}
}
pub fn apply_physics_component(
world: &mut World,
entity: Entity,
physics: &crate::assets::scene::physics::ScenePhysics,
) {
if world.ecs.resource::<PhysicsWorld>().is_none() {
return;
}
let transform = world
.get::<crate::ecs::transform::components::LocalTransform>(entity)
.copied()
.unwrap_or_default();
let is_dynamic = matches!(physics.body_type, SceneBodyType::Dynamic);
crate::plugins::physics::schema::physics_component_world(world);
let mut rigid_body = rigid_body_from_scene(physics);
rigid_body = rigid_body.with_translation(
transform.translation.x,
transform.translation.y,
transform.translation.z,
);
world.set(entity, rigid_body);
let collider = collider_from_scene(physics);
world.set(entity, collider);
let has_listener = world
.get::<crate::plugins::physics::components::CollisionListener>(entity)
.is_some();
let rigid_body_comp = world
.get::<crate::plugins::physics::components::RigidBodyComponent>(entity)
.cloned()
.unwrap();
let collider_comp = world
.get::<crate::plugins::physics::components::ColliderComponent>(entity)
.cloned();
let rapier_body = rigid_body_comp.to_rapier_rigid_body();
let rapier_handle =
physics_world_add_rigid_body(world.plugin_resource_mut::<PhysicsWorld>(), rapier_body);
if let Some(collider_comp) = collider_comp {
let mut rapier_collider = collider_comp.to_rapier_collider();
if has_listener {
rapier_collider.set_active_events(
rapier3d::prelude::ActiveEvents::COLLISION_EVENTS
| rapier3d::prelude::ActiveEvents::CONTACT_FORCE_EVENTS,
);
}
physics_world_add_collider(
world.plugin_resource_mut::<PhysicsWorld>(),
rapier_collider,
rapier_handle,
);
}
if let Some(rigid_body_mut) =
world.get_mut::<crate::plugins::physics::components::RigidBodyComponent>(entity)
{
rigid_body_mut.handle =
Some(crate::plugins::physics::types::rigid_body_handle_from_rapier(rapier_handle));
}
world
.plugin_resource_mut::<PhysicsWorld>()
.handle_to_entity
.insert(rapier_handle, entity);
world
.plugin_resource_mut::<PhysicsWorld>()
.entity_to_handle
.insert(entity, rapier_handle);
if is_dynamic {
let interpolation = crate::plugins::physics::components::PhysicsInterpolation {
enabled: true,
previous_translation: transform.translation,
current_translation: transform.translation,
previous_rotation: transform.rotation,
current_rotation: transform.rotation,
};
world.set(entity, interpolation);
}
}
pub fn spawn_scene_joints(
world: &mut World,
joints: &[SceneJointConnection],
uuid_to_entity: &HashMap<AssetUuid, Entity>,
warnings: &mut Vec<String>,
) {
if world.ecs.resource::<PhysicsWorld>().is_none() {
return;
}
for joint_connection in joints {
if let (Some(&parent_entity), Some(&child_entity)) = (
uuid_to_entity.get(&joint_connection.parent_entity),
uuid_to_entity.get(&joint_connection.child_entity),
) {
let (generic_joint, joint_type) = scene_joint_to_generic_joint(&joint_connection.joint);
let (spring_rest_length, spring_stiffness, spring_damping) =
if let SceneJoint::Spring {
rest_length,
stiffness,
damping,
..
} = &joint_connection.joint
{
(Some(*rest_length), Some(*stiffness), Some(*damping))
} else {
(None, None, None)
};
world
.plugin_resource_mut::<PhysicsWorld>()
.pending_joints
.push(crate::plugins::physics::resources::PendingJoint {
parent_entity,
child_entity,
joint: generic_joint,
joint_type,
collisions_enabled: joint_connection.collisions_enabled,
spring_rest_length,
spring_stiffness,
spring_damping,
});
} else {
warnings.push(format!(
"Joint references unknown entities: parent={}, child={}",
joint_connection.parent_entity, joint_connection.child_entity
));
}
}
}
pub fn export_entity_physics(
world: &World,
entity: Entity,
components: &mut crate::assets::scene::components::SceneComponents,
) {
if let Some(rigid_body) =
world.get::<crate::plugins::physics::components::RigidBodyComponent>(entity)
&& let Some(collider) =
world.get::<crate::plugins::physics::components::ColliderComponent>(entity)
{
use crate::plugins::physics::types::RigidBodyType;
let body_type = match rigid_body.body_type {
RigidBodyType::Fixed => SceneBodyType::Static,
RigidBodyType::Dynamic => SceneBodyType::Dynamic,
RigidBodyType::KinematicPositionBased => SceneBodyType::KinematicPositionBased,
RigidBodyType::KinematicVelocityBased => SceneBodyType::KinematicVelocityBased,
};
let scene_collider = match &collider.shape {
crate::plugins::physics::ColliderShape::Ball { radius } => {
crate::assets::scene::physics::SceneCollider::Ball { radius: *radius }
}
crate::plugins::physics::ColliderShape::Cuboid { hx, hy, hz } => {
crate::assets::scene::physics::SceneCollider::Cuboid {
half_extents: [*hx, *hy, *hz],
}
}
crate::plugins::physics::ColliderShape::Cylinder {
half_height,
radius,
} => crate::assets::scene::physics::SceneCollider::Cylinder {
half_height: *half_height,
radius: *radius,
},
crate::plugins::physics::ColliderShape::Capsule {
half_height,
radius,
} => crate::assets::scene::physics::SceneCollider::Capsule {
half_height: *half_height,
radius: *radius,
},
crate::plugins::physics::ColliderShape::TriMesh { vertices, indices } => {
crate::assets::scene::physics::SceneCollider::TriMesh {
vertices: vertices.clone(),
indices: indices.clone(),
}
}
crate::plugins::physics::ColliderShape::ConvexMesh { vertices } => {
crate::assets::scene::physics::SceneCollider::ConvexHull {
points: vertices.clone(),
}
}
crate::plugins::physics::ColliderShape::Cone {
half_height,
radius,
} => crate::assets::scene::physics::SceneCollider::Cone {
half_height: *half_height,
radius: *radius,
},
crate::plugins::physics::ColliderShape::HeightField {
nrows,
ncols,
heights,
scale,
} => crate::assets::scene::physics::SceneCollider::HeightField {
nrows: *nrows,
ncols: *ncols,
heights: heights.clone(),
scale: *scale,
},
};
components.physics = Some(crate::assets::scene::physics::ScenePhysics {
body_type,
collider: scene_collider,
friction: collider.friction,
restitution: collider.restitution,
mass: Some(rigid_body.mass),
is_sensor: collider.is_sensor,
collision_membership: collider.collision_groups.memberships,
collision_filter: collider.collision_groups.filter,
solver_membership: collider.solver_groups.memberships,
solver_filter: collider.solver_groups.filter,
locked_axes: rigid_body.locked_axes.bits(),
linvel: rigid_body.linvel,
angvel: rigid_body.angvel,
});
}
if let Some(character_controller) =
world.get::<crate::plugins::physics::components::CharacterControllerComponent>(entity)
{
components.character_controller = Some(
crate::assets::scene::character_controller::SceneCharacterController::from(
character_controller,
),
);
}
}
pub fn export_scene_joints(
world: &World,
entity_to_uuid: &HashMap<Entity, AssetUuid>,
scene: &mut crate::assets::scene::components::Scene,
) {
if world.ecs.resource::<PhysicsWorld>().is_none() {
return;
}
for (handle, info) in &world.plugin_resource::<PhysicsWorld>().joint_registry {
let parent_uuid = entity_to_uuid.get(&info.parent_entity).copied();
let child_uuid = entity_to_uuid.get(&info.child_entity).copied();
if let (Some(parent_uuid), Some(child_uuid)) = (parent_uuid, child_uuid)
&& let Some(rapier_joint) = world
.plugin_resource::<PhysicsWorld>()
.impulse_joint_set
.get(*handle)
{
let anchor1 = rapier_joint.data.local_anchor1();
let anchor2 = rapier_joint.data.local_anchor2();
let parent_anchor = [anchor1.x, anchor1.y, anchor1.z];
let child_anchor = [anchor2.x, anchor2.y, anchor2.z];
let scene_joint = match info.joint_type {
JointType::Fixed => SceneJoint::Fixed {
parent_anchor,
child_anchor,
},
JointType::Revolute => {
let axis = rapier_joint.data.local_axis1();
SceneJoint::Revolute {
parent_anchor,
child_anchor,
axis: [axis.x, axis.y, axis.z],
limits: rapier_joint
.data
.limits(rapier3d::prelude::JointAxis::AngX)
.map(|l| [l.min, l.max]),
}
}
JointType::Prismatic => {
let axis = rapier_joint.data.local_axis1();
SceneJoint::Prismatic {
parent_anchor,
child_anchor,
axis: [axis.x, axis.y, axis.z],
limits: rapier_joint
.data
.limits(rapier3d::prelude::JointAxis::LinX)
.map(|l| [l.min, l.max]),
}
}
JointType::Spherical => SceneJoint::Spherical {
parent_anchor,
child_anchor,
},
JointType::Rope => SceneJoint::Rope {
parent_anchor,
child_anchor,
max_distance: rapier_joint
.data
.limits(rapier3d::prelude::JointAxis::LinX)
.map(|l| l.max)
.unwrap_or(1.0),
},
JointType::Spring => SceneJoint::Spring {
parent_anchor,
child_anchor,
rest_length: info.spring_rest_length.unwrap_or(1.0),
stiffness: info.spring_stiffness.unwrap_or(1.0),
damping: info.spring_damping.unwrap_or(0.1),
},
};
scene.joints.push(SceneJointConnection {
parent_entity: parent_uuid,
child_entity: child_uuid,
joint: scene_joint,
collisions_enabled: info.collisions_enabled,
});
}
}
}
pub fn apply_physics_settings(
world: &mut World,
settings: &crate::assets::scene::settings::SceneSettings,
) {
if world.ecs.resource::<PhysicsWorld>().is_none() {
return;
}
let physics = world.plugin_resource_mut::<PhysicsWorld>();
physics.gravity = rapier3d::prelude::vector![
settings.gravity[0],
settings.gravity[1],
settings.gravity[2]
];
physics.fixed_timestep = settings.physics_timestep;
physics.integration_parameters.dt = settings.physics_timestep;
physics.max_substeps = settings.physics_max_substeps;
}
pub fn capture_physics_settings(world: &World) -> ([f32; 3], f32, u32) {
let fallback;
let physics = match world.ecs.resource::<PhysicsWorld>() {
Some(physics) => physics,
None => {
fallback = PhysicsWorld::default();
&fallback
}
};
(
[physics.gravity.x, physics.gravity.y, physics.gravity.z],
physics.fixed_timestep,
physics.max_substeps,
)
}
pub fn apply_character_controller(
world: &mut World,
entity: Entity,
scene_cc: &crate::assets::scene::character_controller::SceneCharacterController,
) {
if world.ecs.resource::<PhysicsWorld>().is_none() {
return;
}
crate::plugins::physics::schema::physics_component_world(world);
world.set(entity, character_controller_from_scene(scene_cc));
}
pub fn spawn_from_scene(
world: &mut World,
entity: Entity,
components: &crate::assets::scene::components::SceneComponents,
_context: &mut crate::assets::scene::hooks::SceneSpawnContext,
) {
if components.collision_listener && world.ecs.resource::<PhysicsWorld>().is_some() {
crate::plugins::physics::schema::physics_component_world(world);
world.set(
entity,
crate::plugins::physics::components::CollisionListener,
);
}
if let Some(physics) = &components.physics {
apply_physics_component(world, entity, physics);
}
if let Some(controller) = &components.character_controller {
apply_character_controller(world, entity, controller);
}
}
pub fn capture_to_scene(
world: &World,
entity: Entity,
components: &mut crate::assets::scene::components::SceneComponents,
) {
export_entity_physics(world, entity, components);
components.collision_listener = world
.get::<crate::plugins::physics::components::CollisionListener>(entity)
.is_some();
}
pub fn apply_scene(
world: &mut World,
scene: &crate::assets::scene::components::Scene,
uuid_to_entity: &HashMap<AssetUuid, Entity>,
) {
let mut warnings = Vec::new();
spawn_scene_joints(world, &scene.joints, uuid_to_entity, &mut warnings);
}
pub fn capture_scene(
world: &World,
entity_to_uuid: &HashMap<Entity, AssetUuid>,
scene: &mut crate::assets::scene::components::Scene,
) {
export_scene_joints(world, entity_to_uuid, scene);
}
pub fn apply_settings(world: &mut World, settings: &crate::assets::scene::settings::SceneSettings) {
apply_physics_settings(world, settings);
}
pub fn capture_settings(
world: &World,
settings: &mut crate::assets::scene::settings::SceneSettings,
) {
let (gravity, timestep, max_substeps) = capture_physics_settings(world);
settings.gravity = gravity;
settings.physics_timestep = timestep;
settings.physics_max_substeps = max_substeps;
}
pub fn register_scene_hooks(world: &mut World) {
let hooks = world.res_mut::<crate::assets::scene::hooks::SceneCapabilityHooks>();
hooks.entity_spawn.push(spawn_from_scene);
hooks.entity_capture.push(capture_to_scene);
hooks.scene_apply.push(apply_scene);
hooks.scene_capture.push(capture_scene);
hooks.settings_apply.push(apply_settings);
hooks.settings_capture.push(capture_settings);
}
pub fn body_type_from_scene(body_type: SceneBodyType) -> RigidBodyType {
match body_type {
SceneBodyType::Static => RigidBodyType::Fixed,
SceneBodyType::Dynamic => RigidBodyType::Dynamic,
SceneBodyType::KinematicPositionBased => RigidBodyType::KinematicPositionBased,
SceneBodyType::KinematicVelocityBased => RigidBodyType::KinematicVelocityBased,
}
}
impl From<RigidBodyType> for SceneBodyType {
fn from(body_type: RigidBodyType) -> Self {
match body_type {
RigidBodyType::Fixed => SceneBodyType::Static,
RigidBodyType::Dynamic => SceneBodyType::Dynamic,
RigidBodyType::KinematicPositionBased => SceneBodyType::KinematicPositionBased,
RigidBodyType::KinematicVelocityBased => SceneBodyType::KinematicVelocityBased,
}
}
}
pub fn rigid_body_from_scene(physics: &ScenePhysics) -> RigidBodyComponent {
let mut body = match physics.body_type {
SceneBodyType::Static => RigidBodyComponent::new_static(),
SceneBodyType::Dynamic => RigidBodyComponent::new_dynamic(),
SceneBodyType::KinematicPositionBased => RigidBodyComponent::new_kinematic(),
SceneBodyType::KinematicVelocityBased => RigidBodyComponent {
body_type: RigidBodyType::KinematicVelocityBased,
..Default::default()
},
};
if let Some(mass) = physics.mass {
body = body.with_mass(mass);
}
body.locked_axes = LockedAxes::from_bits_truncate(physics.locked_axes);
body.linvel = physics.linvel;
body.angvel = physics.angvel;
body
}
pub fn collider_from_scene(physics: &ScenePhysics) -> ColliderComponent {
let mut collider = match &physics.collider {
SceneCollider::Cuboid { half_extents } => {
ColliderComponent::new_cuboid(half_extents[0], half_extents[1], half_extents[2])
}
SceneCollider::Ball { radius } => ColliderComponent::new_ball(*radius),
SceneCollider::Cylinder {
half_height,
radius,
} => ColliderComponent::new_cylinder(*half_height, *radius),
SceneCollider::Capsule {
half_height,
radius,
} => ColliderComponent::new_capsule(*half_height, *radius),
SceneCollider::TriMesh { vertices, indices } => ColliderComponent {
shape: ColliderShape::TriMesh {
vertices: vertices.clone(),
indices: indices.clone(),
},
..Default::default()
},
SceneCollider::ConvexHull { points } => ColliderComponent {
shape: ColliderShape::ConvexMesh {
vertices: points.clone(),
},
..Default::default()
},
SceneCollider::Cone {
half_height,
radius,
} => ColliderComponent {
shape: ColliderShape::Cone {
half_height: *half_height,
radius: *radius,
},
..Default::default()
},
SceneCollider::HeightField {
nrows,
ncols,
heights,
scale,
} => ColliderComponent {
shape: ColliderShape::HeightField {
nrows: *nrows,
ncols: *ncols,
heights: heights.clone(),
scale: *scale,
},
..Default::default()
},
};
collider = collider
.with_friction(physics.friction)
.with_restitution(physics.restitution);
collider.is_sensor = physics.is_sensor;
collider.collision_groups =
InteractionGroups::new(physics.collision_membership, physics.collision_filter);
collider.solver_groups =
InteractionGroups::new(physics.solver_membership, physics.solver_filter);
collider
}
impl From<&CharacterControllerComponent> for SceneCharacterController {
fn from(controller: &CharacterControllerComponent) -> Self {
Self {
shape: match &controller.shape {
ColliderShape::Capsule {
half_height,
radius,
} => SceneCharacterShape::Capsule {
half_height: *half_height,
radius: *radius,
},
ColliderShape::Ball { radius } => SceneCharacterShape::Ball { radius: *radius },
ColliderShape::Cuboid { hx, hy, hz } => SceneCharacterShape::Cuboid {
hx: *hx,
hy: *hy,
hz: *hz,
},
_ => SceneCharacterShape::default(),
},
max_speed: controller.max_speed,
acceleration: controller.acceleration,
jump_impulse: controller.jump_impulse,
crouch_enabled: controller.crouch_enabled,
crouch_speed_multiplier: controller.crouch_speed_multiplier,
sprint_speed_multiplier: controller.sprint_speed_multiplier,
standing_half_height: controller.standing_half_height,
crouching_half_height: controller.crouching_half_height,
scale: controller.scale,
config: SceneCharacterControllerConfig {
offset: controller.config.offset,
max_slope_climb_angle: controller.config.max_slope_climb_angle,
min_slope_slide_angle: controller.config.min_slope_slide_angle,
autostep_max_height: controller.config.autostep_max_height,
autostep_min_width: controller.config.autostep_min_width,
autostep_include_dynamic_bodies: controller.config.autostep_include_dynamic_bodies,
snap_to_ground: controller.config.snap_to_ground,
},
}
}
}
pub fn character_controller_from_scene(
scene_controller: &SceneCharacterController,
) -> CharacterControllerComponent {
CharacterControllerComponent {
config: CharacterControllerConfig {
offset: scene_controller.config.offset,
max_slope_climb_angle: scene_controller.config.max_slope_climb_angle,
min_slope_slide_angle: scene_controller.config.min_slope_slide_angle,
autostep_max_height: scene_controller.config.autostep_max_height,
autostep_min_width: scene_controller.config.autostep_min_width,
autostep_include_dynamic_bodies: scene_controller
.config
.autostep_include_dynamic_bodies,
snap_to_ground: scene_controller.config.snap_to_ground,
},
shape: match scene_controller.shape {
SceneCharacterShape::Capsule {
half_height,
radius,
} => ColliderShape::Capsule {
half_height,
radius,
},
SceneCharacterShape::Ball { radius } => ColliderShape::Ball { radius },
SceneCharacterShape::Cuboid { hx, hy, hz } => ColliderShape::Cuboid { hx, hy, hz },
},
max_speed: scene_controller.max_speed,
acceleration: scene_controller.acceleration,
jump_impulse: scene_controller.jump_impulse,
crouch_enabled: scene_controller.crouch_enabled,
crouch_speed_multiplier: scene_controller.crouch_speed_multiplier,
sprint_speed_multiplier: scene_controller.sprint_speed_multiplier,
standing_half_height: scene_controller.standing_half_height,
crouching_half_height: scene_controller.crouching_half_height,
scale: scene_controller.scale,
..Default::default()
}
}