Implement CapsulePrism collisions

This commit is contained in:
juliancoffee 2021-09-15 18:11:41 +03:00
parent 7712976b86
commit 6c3b61dc25
2 changed files with 193 additions and 99 deletions

View File

@ -55,6 +55,9 @@ pub struct PreviousPhysCache {
pub collision_boundary: f32,
pub scale: f32,
pub scaled_radius: f32,
pub neighborhood_radius: f32,
/// p0 and p1 in case of CapsulePrism collider, Vec2::zero() otherwise.
pub origins: (Vec2<f32>, Vec2<f32>),
pub ori: Quaternion<f32>,
}
@ -125,11 +128,10 @@ impl Collider {
match self {
Collider::Voxel { .. } => 1.0,
Collider::Box { radius, .. } => *radius,
// FIXME: I know that this is wrong for sure,
// because it's not a circle.
//
// CodeReviewers, please welp!
Collider::CapsulePrism { radius, .. } => *radius,
Collider::CapsulePrism { radius, p0, p1, .. } => {
let a = p0.distance(*p1);
a / 2.0 + *radius
},
Collider::Point => 0.0,
}
}

View File

@ -192,15 +192,18 @@ impl<'a> PhysicsData<'a> {
collision_boundary: 0.0,
scale: 0.0,
scaled_radius: 0.0,
neighborhood_radius: 0.0,
origins: (Vec2::zero(), Vec2::zero()),
ori: Quaternion::identity(),
});
}
// Update PreviousPhysCache
for (_, vel, position, mut phys_cache, collider, scale, cs, _, _, _) in (
for (_, vel, position, ori, mut phys_cache, collider, scale, cs, _, _, _) in (
&self.read.entities,
&self.write.velocities,
&self.write.positions,
&self.write.orientations,
&mut self.write.previous_phys_cache,
self.read.colliders.maybe(),
self.read.scales.maybe(),
@ -219,6 +222,10 @@ impl<'a> PhysicsData<'a> {
phys_cache.velocity_dt = vel.0 * self.read.dt.0;
let entity_center = position.0 + Vec3::new(0.0, z_limits.0 + half_height, 0.0);
let flat_radius = collider.map(|c| c.get_radius()).unwrap_or(0.5) * scale;
let neighborhood_radius = match collider {
Some(Collider::CapsulePrism { radius, .. }) => radius * scale,
_ => flat_radius,
};
let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt();
// Move center to the middle between OLD and OLD+VEL_DT so that we can reduce
@ -226,7 +233,36 @@ impl<'a> PhysicsData<'a> {
phys_cache.center = entity_center + phys_cache.velocity_dt / 2.0;
phys_cache.collision_boundary = radius + (phys_cache.velocity_dt / 2.0).magnitude();
phys_cache.scale = scale;
phys_cache.neighborhood_radius = neighborhood_radius;
phys_cache.scaled_radius = flat_radius;
let ori = ori.to_quat();
let (p0, p1) = match collider {
Some(Collider::CapsulePrism { p0, p1, .. }) => {
// Build the line between two origins
let a = p1 - p0;
let len = a.magnitude();
// Make it 3d
let a = Vec3::new(a.x, a.y, 0.0);
// Rotate it
let a = ori * a;
// Make it 2d again
let a = Vec2::new(a.x, a.y);
// Make sure we have the same length as before
// (and scale it)
let a = a * scale * len / a.magnitude();
// Splite the oriented line into two origins
let p0 = -a / 2.0;
let p1 = a / 2.0;
(p0, p1)
},
Some(Collider::Box { .. } | Collider::Voxel { .. } | Collider::Point) | None => {
(Vec2::zero(), Vec2::zero())
},
};
phys_cache.origins = (p0, p1);
phys_cache.ori = ori;
}
}
@ -349,13 +385,12 @@ impl<'a> PhysicsData<'a> {
spatial_grid
.in_circle_aabr(query_center, query_radius)
.filter_map(|entity| {
read.uids
.get(entity)
.and_then(|l| positions.get(entity).map(|r| (l, r)))
.and_then(|l| previous_phys_cache.get(entity).map(|r| (l, r)))
.and_then(|l| read.masses.get(entity).map(|r| (l, r)))
.map(|(((uid, pos), previous_cache), mass)| {
(
let uid = read.uids.get(entity)?;
let pos = positions.get(entity)?;
let previous_cache = previous_phys_cache.get(entity)?;
let mass = read.masses.get(entity)?;
Some((
entity,
uid,
pos,
@ -363,8 +398,7 @@ impl<'a> PhysicsData<'a> {
mass,
read.colliders.get(entity),
read.char_states.get(entity),
)
})
))
})
.for_each(
|(
@ -387,8 +421,6 @@ impl<'a> PhysicsData<'a> {
return;
}
let collision_dist = previous_cache.scaled_radius
+ previous_cache_other.scaled_radius;
let z_limits_other =
calc_z_limit(char_state_other_maybe, collider_other);
@ -403,24 +435,62 @@ impl<'a> PhysicsData<'a> {
.ceil()
as usize;
let step_delta = 1.0 / increments as f32;
let mut collision_registered = false;
let collision_dist = previous_cache.neighborhood_radius
+ previous_cache_other.neighborhood_radius;
for i in 0..increments {
let factor = i as f32 * step_delta;
// get positions
let pos = pos.0 + previous_cache.velocity_dt * factor;
let pos_other =
pos_other.0 + previous_cache_other.velocity_dt * factor;
let diff = pos.xy() - pos_other.xy();
// Compare Z ranges
let ceiling = pos.z + z_limits.1 * previous_cache.scale;
let floor = pos.z + z_limits.0 * previous_cache.scale;
let ceiling_other =
pos_other.z + z_limits_other.1 * previous_cache_other.scale;
let floor_other =
pos_other.z + z_limits_other.0 * previous_cache_other.scale;
let in_z_range =
ceiling >= floor_other && floor <= ceiling_other;
// check horizontal distance
let (p0_offset, p1_offset) = previous_cache.origins;
let p0 = pos + p0_offset;
let p1 = pos + p1_offset;
let segment = LineSegment2 {
start: p0.xy(),
end: p1.xy(),
};
let (p0_offset_other, p1_offset_other) =
previous_cache_other.origins;
let p0_other = pos_other + p0_offset_other;
let p1_other = pos_other + p1_offset_other;
let segment_other = LineSegment2 {
start: p0_other.xy(),
end: p1_other.xy(),
};
let diff = projection_between(segment, segment_other);
let in_collision_range =
diff.magnitude_squared() <= collision_dist.powi(2);
let collides = in_collision_range && in_z_range;
if !collides {
continue;
}
if diff.magnitude_squared() <= collision_dist.powi(2)
&& pos.z + z_limits.1 * previous_cache.scale
>= pos_other.z
+ z_limits_other.0 * previous_cache_other.scale
&& pos.z + z_limits.0 * previous_cache.scale
<= pos_other.z
+ z_limits_other.1 * previous_cache_other.scale
{
// If entities have not yet collided
// this tick (but just did) and if entity
//
@ -467,10 +537,7 @@ impl<'a> PhysicsData<'a> {
&& (!is_sticky || is_mid_air)
&& diff.magnitude_squared() > 0.0
&& !is_projectile
&& !matches!(
collider_other,
Some(Collider::Voxel { .. })
)
&& !matches!(collider_other, Some(Collider::Voxel { .. }))
&& !matches!(collider, Some(Collider::Voxel { .. }))
{
let force = 400.0
@ -484,7 +551,6 @@ impl<'a> PhysicsData<'a> {
collision_registered = true;
}
}
},
);
@ -848,7 +914,7 @@ impl<'a> PhysicsData<'a> {
z_max,
} => {
// FIXME:
// !! DEPRECATED !!
// Deprecated, should remove?
//
// Scale collider
let radius = radius.min(0.45) * scale;
@ -882,17 +948,14 @@ impl<'a> PhysicsData<'a> {
tgt_pos = cpos.0;
},
Collider::CapsulePrism {
radius,
z_min,
z_max,
..
p0: _,
p1: _,
radius: _,
} => {
// FIXME: placeholder.
// copypasted from Box collider
//
// This shoudln't pass Code Review.
// Scale collider
let radius = radius.min(0.45) * scale;
let radius = collider.get_radius().min(0.45) * scale;
let z_min = *z_min * scale;
let z_max = z_max.clamped(1.2, 1.95) * scale;
@ -1798,3 +1861,32 @@ fn voxel_collider_bounding_sphere(
radius,
}
}
/// Get the shortest line segment between AB and CD, used for pushback
/// and collision checks.
fn projection_between(ab: LineSegment2<f32>, cd: LineSegment2<f32>) -> Vec2<f32> {
// On 2d we can just check projection of A to CD, B to CD, C to AB and D to AB.
//
// NOTE: We don't check if segments are intersecting, because
// even if they do, we still need to return pushback vector.
let a = ab.start;
let b = ab.end;
let c = cd.start;
let d = cd.end;
let projections = [
// A to CD
a - cd.projected_point(a),
// B to CD
b - cd.projected_point(b),
// C to AB
c - ab.projected_point(c),
// D to AB
d - ab.projected_point(d),
];
// min_by_key returns None only if iterator is empty, so unwrap is fine
IntoIterator::into_iter(projections)
.min_by_key(|p| ordered_float::OrderedFloat(p.magnitude_squared()))
.unwrap()
}