use std::collections::HashMap;
use freecs::Entity;
use crate::ecs::physics::resources::JointType;
use crate::ecs::world::{CHARACTER_CONTROLLER, COLLIDER, PHYSICS_INTERPOLATION, RIGID_BODY, World};
use super::asset_uuid::AssetUuid;
use super::physics::{SceneBodyType, SceneJoint, SceneJointConnection};
pub(super) 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(super) fn apply_physics_component(
world: &mut World,
entity: Entity,
physics: &super::physics::ScenePhysics,
) {
let transform = world
.core
.get_local_transform(entity)
.copied()
.unwrap_or_default();
let is_dynamic = matches!(physics.body_type, SceneBodyType::Dynamic);
let mut components = RIGID_BODY | COLLIDER;
if is_dynamic {
components |= PHYSICS_INTERPOLATION;
}
world.core.add_components(entity, components);
let mut rigid_body = physics.to_rigid_body();
rigid_body = rigid_body.with_translation(
transform.translation.x,
transform.translation.y,
transform.translation.z,
);
world.core.set_rigid_body(entity, rigid_body);
let collider = physics.to_collider();
world.core.set_collider(entity, collider);
let rigid_body_comp = world.core.get_rigid_body(entity).cloned().unwrap();
let collider_comp = world.core.get_collider(entity).cloned();
let rapier_body = rigid_body_comp.to_rapier_rigid_body();
let rapier_handle = world.resources.physics.add_rigid_body(rapier_body);
if let Some(collider_comp) = collider_comp {
let rapier_collider = collider_comp.to_rapier_collider();
world
.resources
.physics
.add_collider(rapier_collider, rapier_handle);
}
if let Some(rigid_body_mut) = world.core.get_rigid_body_mut(entity) {
rigid_body_mut.handle = Some(rapier_handle.into());
}
world
.resources
.physics
.handle_to_entity
.insert(rapier_handle, entity);
if is_dynamic && let Some(interpolation) = world.core.get_physics_interpolation_mut(entity) {
interpolation.enabled = true;
interpolation.previous_translation = transform.translation;
interpolation.current_translation = transform.translation;
interpolation.previous_rotation = transform.rotation;
interpolation.current_rotation = transform.rotation;
}
}
pub(super) fn spawn_scene_joints(
world: &mut World,
joints: &[SceneJointConnection],
uuid_to_entity: &HashMap<AssetUuid, Entity>,
warnings: &mut Vec<String>,
) {
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.resources.physics.pending_joints.push(
crate::ecs::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(super) fn export_entity_physics(
world: &World,
entity: Entity,
components: &mut super::components::SceneComponents,
) {
if let Some(rigid_body) = world.core.get_rigid_body(entity)
&& let Some(collider) = world.core.get_collider(entity)
{
use crate::ecs::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::ecs::physics::ColliderShape::Ball { radius } => {
super::physics::SceneCollider::Ball { radius: *radius }
}
crate::ecs::physics::ColliderShape::Cuboid { hx, hy, hz } => {
super::physics::SceneCollider::Cuboid {
half_extents: [*hx, *hy, *hz],
}
}
crate::ecs::physics::ColliderShape::Cylinder {
half_height,
radius,
} => super::physics::SceneCollider::Cylinder {
half_height: *half_height,
radius: *radius,
},
crate::ecs::physics::ColliderShape::Capsule {
half_height,
radius,
} => super::physics::SceneCollider::Capsule {
half_height: *half_height,
radius: *radius,
},
crate::ecs::physics::ColliderShape::TriMesh { vertices, indices } => {
super::physics::SceneCollider::TriMesh {
vertices: vertices.clone(),
indices: indices.clone(),
}
}
crate::ecs::physics::ColliderShape::ConvexMesh { vertices } => {
super::physics::SceneCollider::ConvexHull {
points: vertices.clone(),
}
}
crate::ecs::physics::ColliderShape::Cone {
half_height,
radius,
} => super::physics::SceneCollider::Cone {
half_height: *half_height,
radius: *radius,
},
crate::ecs::physics::ColliderShape::HeightField {
nrows,
ncols,
heights,
scale,
} => super::physics::SceneCollider::HeightField {
nrows: *nrows,
ncols: *ncols,
heights: heights.clone(),
scale: *scale,
},
};
components.physics = Some(super::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,
});
}
if let Some(character_controller) = world.core.get_character_controller(entity) {
components.character_controller = Some(
super::character_controller::SceneCharacterController::from(character_controller),
);
}
}
pub(super) fn export_scene_joints(
world: &World,
entity_to_uuid: &HashMap<Entity, AssetUuid>,
scene: &mut super::components::Scene,
) {
for (handle, info) in &world.resources.physics.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.resources.physics.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(super) fn apply_physics_settings(world: &mut World, settings: &super::settings::SceneSettings) {
world.resources.physics.gravity = rapier3d::prelude::vector![
settings.gravity[0],
settings.gravity[1],
settings.gravity[2]
];
world.resources.physics.fixed_timestep = settings.physics_timestep;
world.resources.physics.integration_parameters.dt = settings.physics_timestep;
world.resources.physics.max_substeps = settings.physics_max_substeps;
}
pub(super) fn capture_physics_settings(world: &World) -> ([f32; 3], f32, u32) {
let physics = &world.resources.physics;
(
[physics.gravity.x, physics.gravity.y, physics.gravity.z],
physics.fixed_timestep,
physics.max_substeps,
)
}
pub(super) fn apply_character_controller(
world: &mut World,
entity: Entity,
scene_cc: &super::character_controller::SceneCharacterController,
) {
world.core.add_components(entity, CHARACTER_CONTROLLER);
world
.core
.set_character_controller(entity, scene_cc.to_character_controller());
}