Adress review

- Rename Collider::get_radius to bounding_radius
- Check origin difference in CapsulePrism with EPSILON * 10.0 instead of
  magic 0.00001
- Add comments for closest_points, hardnened expression against NaNs
- Add comments to try_e2e_collision function, renamed to
  resolve_e2e_collision, make it return whether collision was triggered.
- Remove Collider::Box (it is Cylinder, which is subset of CapsulePrism
  with p0=p1=Vec2::zero())
This commit is contained in:
juliancoffee
2021-09-16 19:45:17 +03:00
parent 3dd6aa9dea
commit 4e3fb87526
5 changed files with 73 additions and 145 deletions

View File

@ -109,11 +109,6 @@ pub enum Collider {
Voxel { Voxel {
id: String, id: String,
}, },
Box {
radius: f32,
z_min: f32,
z_max: f32,
},
/// Capsule prism with line segment from p0 to p1 /// Capsule prism with line segment from p0 to p1
CapsulePrism { CapsulePrism {
p0: Vec2<f32>, p0: Vec2<f32>,
@ -126,10 +121,9 @@ pub enum Collider {
} }
impl Collider { impl Collider {
pub fn get_radius(&self) -> f32 { pub fn bounding_radius(&self) -> f32 {
match self { match self {
Collider::Voxel { .. } => 1.0, Collider::Voxel { .. } => 1.0,
Collider::Box { radius, .. } => *radius,
Collider::CapsulePrism { radius, p0, p1, .. } => { Collider::CapsulePrism { radius, p0, p1, .. } => {
let a = p0.distance(*p1); let a = p0.distance(*p1);
a / 2.0 + *radius a / 2.0 + *radius
@ -146,7 +140,6 @@ impl Collider {
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) { pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
match self { match self {
Collider::Voxel { .. } => (0.0, 1.0), Collider::Voxel { .. } => (0.0, 1.0),
Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier),
Collider::CapsulePrism { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier), Collider::CapsulePrism { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier),
Collider::Point => (0.0, 0.0), Collider::Point => (0.0, 0.0),
} }

View File

@ -39,7 +39,7 @@ impl Cylinder {
char_state: Option<&crate::comp::CharacterState>, char_state: Option<&crate::comp::CharacterState>,
) -> Self { ) -> Self {
let scale = scale.map_or(1.0, |s| s.0); let scale = scale.map_or(1.0, |s| s.0);
let radius = collider.as_ref().map_or(0.5, |c| c.get_radius()) * scale; let radius = collider.as_ref().map_or(0.5, |c| c.bounding_radius()) * scale;
let z_limit_modifier = char_state let z_limit_modifier = char_state
.filter(|char_state| char_state.is_dodge()) .filter(|char_state| char_state.is_dodge())
.map_or(1.0, |_| 0.5) .map_or(1.0, |_| 0.5)

View File

@ -23,7 +23,7 @@ use specs::{
Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect, Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect,
WriteStorage, WriteStorage,
}; };
use std::ops::{ControlFlow, Range}; use std::ops::Range;
use vek::*; use vek::*;
/// The density of the fluid as a function of submersion ratio in given fluid /// The density of the fluid as a function of submersion ratio in given fluid
@ -222,7 +222,7 @@ impl<'a> PhysicsData<'a> {
phys_cache.velocity_dt = vel.0 * self.read.dt.0; phys_cache.velocity_dt = vel.0 * self.read.dt.0;
let entity_center = position.0 + Vec3::new(0.0, 0.0, z_min + half_height); let entity_center = position.0 + Vec3::new(0.0, 0.0, z_min + half_height);
let flat_radius = collider.map_or(0.5, Collider::get_radius) * scale; let flat_radius = collider.map_or(0.5, Collider::bounding_radius) * scale;
let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt(); let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt();
// Move center to the middle between OLD and OLD+VEL_DT // Move center to the middle between OLD and OLD+VEL_DT
@ -234,9 +234,7 @@ impl<'a> PhysicsData<'a> {
let neighborhood_radius = match collider { let neighborhood_radius = match collider {
Some(Collider::CapsulePrism { radius, .. }) => radius * scale, Some(Collider::CapsulePrism { radius, .. }) => radius * scale,
Some(Collider::Box { .. } | Collider::Voxel { .. } | Collider::Point) | None => { Some(Collider::Voxel { .. } | Collider::Point) | None => flat_radius,
flat_radius
},
}; };
phys_cache.neighborhood_radius = neighborhood_radius; phys_cache.neighborhood_radius = neighborhood_radius;
@ -250,7 +248,7 @@ impl<'a> PhysicsData<'a> {
// //
// Other advantage of early-return is that we don't // Other advantage of early-return is that we don't
// later divide by zero and return NaN // later divide by zero and return NaN
if len < 0.000001 { if len < std::f32::EPSILON * 10.0 {
Some((*p0, *p0)) Some((*p0, *p0))
} else { } else {
// Apply orientation to origins of prism. // Apply orientation to origins of prism.
@ -276,9 +274,7 @@ impl<'a> PhysicsData<'a> {
Some((p0, p1)) Some((p0, p1))
} }
}, },
Some(Collider::Box { .. } | Collider::Voxel { .. } | Collider::Point) | None => { Some(Collider::Voxel { .. } | Collider::Point) | None => None,
None
},
}; };
phys_cache.origins = origins; phys_cache.origins = origins;
phys_cache.ori = ori; phys_cache.ori = ori;
@ -460,7 +456,10 @@ impl<'a> PhysicsData<'a> {
for i in 0..increments { for i in 0..increments {
let factor = i as f32 * step_delta; let factor = i as f32 * step_delta;
match try_e2e_collision( // We are not interested if collision succeed
// or no as of now.
// Collision reaction is done inside.
let _ = resolve_e2e_collision(
// utility variables for our entity // utility variables for our entity
&mut collision_registered, &mut collision_registered,
&mut entity_entity_collisions, &mut entity_entity_collisions,
@ -486,10 +485,7 @@ impl<'a> PhysicsData<'a> {
collider_other, collider_other,
*mass, *mass,
*mass_other, *mass_other,
) { );
ControlFlow::Continue(..) => continue,
ControlFlow::Break(..) => break,
}
} }
}, },
); );
@ -824,7 +820,7 @@ impl<'a> PhysicsData<'a> {
// //
// Additionally, multiply radius by 0.1 to make // Additionally, multiply radius by 0.1 to make
// the cylinder smaller to avoid lag. // the cylinder smaller to avoid lag.
let radius = collider.get_radius() * scale * 0.1; let radius = collider.bounding_radius() * scale * 0.1;
let (z_min, z_max) = collider.get_z_limits(scale); let (z_min, z_max) = collider.get_z_limits(scale);
let mut cpos = *pos; let mut cpos = *pos;
@ -847,45 +843,6 @@ impl<'a> PhysicsData<'a> {
); );
tgt_pos = cpos.0; tgt_pos = cpos.0;
}, },
Collider::Box {
radius,
z_min,
z_max,
} => {
// FIXME:
// Deprecated, should remove?
//
// Scale collider
let radius = radius.min(0.45) * scale;
let z_min = *z_min * scale;
let z_max = z_max.clamped(1.2, 1.95) * scale;
let cylinder = (radius, z_min, z_max);
let mut cpos = *pos;
box_voxel_collision(
cylinder,
&*read.terrain,
entity,
&mut cpos,
tgt_pos,
&mut vel,
&mut physics_state,
Vec3::zero(),
&read.dt,
was_on_ground,
block_snap,
climbing,
|entity, vel| land_on_ground = Some((entity, vel)),
read,
);
// Sticky things shouldn't move when on a surface
if physics_state.on_surface().is_some() && sticky.is_some() {
vel.0 = physics_state.ground_vel;
}
tgt_pos = cpos.0;
},
Collider::CapsulePrism { Collider::CapsulePrism {
z_min, z_min,
z_max, z_max,
@ -894,7 +851,7 @@ impl<'a> PhysicsData<'a> {
radius: _, radius: _,
} => { } => {
// Scale collider // Scale collider
let radius = collider.get_radius().min(0.45) * scale; let radius = collider.bounding_radius().min(0.45) * scale;
let z_min = *z_min * scale; let z_min = *z_min * scale;
let z_max = z_max.clamped(1.2, 1.95) * scale; let z_max = z_max.clamped(1.2, 1.95) * scale;
@ -1039,7 +996,7 @@ impl<'a> PhysicsData<'a> {
let entity_center = pos.0 + (z_limits.0 + half_height) * Vec3::unit_z(); let entity_center = pos.0 + (z_limits.0 + half_height) * Vec3::unit_z();
let path_center = entity_center + pos_delta / 2.0; let path_center = entity_center + pos_delta / 2.0;
let flat_radius = collider.get_radius() * scale; let flat_radius = collider.bounding_radius() * scale;
let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt(); let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt();
let path_bounding_radius = radius + (pos_delta / 2.0).magnitude(); let path_bounding_radius = radius + (pos_delta / 2.0).magnitude();
@ -1087,7 +1044,7 @@ impl<'a> PhysicsData<'a> {
// use bounding cylinder regardless of our collider // use bounding cylinder regardless of our collider
// TODO: extract point-terrain collision above to its own // TODO: extract point-terrain collision above to its own
// function // function
let radius = collider.get_radius(); let radius = collider.bounding_radius();
let (z_min, z_max) = collider.get_z_limits(1.0); let (z_min, z_max) = collider.get_z_limits(1.0);
let radius = radius.min(0.45) * scale; let radius = radius.min(0.45) * scale;
@ -1301,7 +1258,7 @@ impl<'a> PhysicsData<'a> {
.for_each(|(entity, pos, scale, collider)| { .for_each(|(entity, pos, scale, collider)| {
let scale = scale.map(|s| s.0).unwrap_or(1.0); let scale = scale.map(|s| s.0).unwrap_or(1.0);
let radius_2d = let radius_2d =
(collider.map(|c| c.get_radius()).unwrap_or(0.5) * scale).ceil() as u32; (collider.map(|c| c.bounding_radius()).unwrap_or(0.5) * scale).ceil() as u32;
let pos_2d = pos.0.xy().map(|e| e as i32); let pos_2d = pos.0.xy().map(|e| e as i32);
const POS_TRUNCATION_ERROR: u32 = 1; const POS_TRUNCATION_ERROR: u32 = 1;
spatial_grid.insert(pos_2d, radius_2d + POS_TRUNCATION_ERROR, entity); spatial_grid.insert(pos_2d, radius_2d + POS_TRUNCATION_ERROR, entity);
@ -1801,8 +1758,9 @@ fn voxel_collider_bounding_sphere(
} }
} }
/// Returns whether interesction between entities occured
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn try_e2e_collision( fn resolve_e2e_collision(
// utility variables for our entity // utility variables for our entity
collision_registered: &mut bool, collision_registered: &mut bool,
entity_entity_collisions: &mut u64, entity_entity_collisions: &mut u64,
@ -1828,7 +1786,7 @@ fn try_e2e_collision(
collider_other: Option<&Collider>, collider_other: Option<&Collider>,
mass: Mass, mass: Mass,
mass_other: Mass, mass_other: Mass,
) -> ControlFlow<()> { ) -> bool {
// Find the distance betwen our collider and // Find the distance betwen our collider and
// collider we collide with and get vector of pushback. // collider we collide with and get vector of pushback.
// //
@ -1850,7 +1808,7 @@ fn try_e2e_collision(
let in_z_range = ceiling >= floor_other && floor <= ceiling_other; let in_z_range = ceiling >= floor_other && floor <= ceiling_other;
if !in_z_range { if !in_z_range {
return ControlFlow::Continue(()); return false;
} }
let ours = ColliderContext { let ours = ColliderContext {
@ -1865,7 +1823,7 @@ fn try_e2e_collision(
let in_collision_range = diff.magnitude_squared() <= collision_dist.powi(2); let in_collision_range = diff.magnitude_squared() <= collision_dist.powi(2);
if !in_collision_range { if !in_collision_range {
return ControlFlow::Continue(()); return false;
} }
// If entities have not yet collided this tick (but just did) and if entity // If entities have not yet collided this tick (but just did) and if entity
@ -1900,16 +1858,17 @@ fn try_e2e_collision(
&& !matches!(collider_other, Some(Collider::Voxel { .. })) && !matches!(collider_other, Some(Collider::Voxel { .. }))
&& !matches!(collider, Some(Collider::Voxel { .. })) && !matches!(collider, Some(Collider::Voxel { .. }))
{ {
const ELASTIC_FORCE_COEFFICIENT: f32 = 400.0;
let mass_coefficient = mass_other.0 / (mass.0 + mass_other.0); let mass_coefficient = mass_other.0 / (mass.0 + mass_other.0);
let distance_coefficient = collision_dist - diff.magnitude(); let distance_coefficient = collision_dist - diff.magnitude();
let force = 400.0 * distance_coefficient * mass_coefficient; let force = ELASTIC_FORCE_COEFFICIENT * distance_coefficient * mass_coefficient;
*vel_delta += Vec3::from(diff.normalized()) * force * step_delta; *vel_delta += Vec3::from(diff.normalized()) * force * step_delta;
} }
*collision_registered = true; *collision_registered = true;
ControlFlow::Continue(()) true
} }
struct ColliderContext<'a> { struct ColliderContext<'a> {
@ -1958,17 +1917,32 @@ fn projection_between(c0: ColliderContext, c1: ColliderContext) -> (Vec2<f32>, f
} }
} }
/// Returns the points on line segments n and m respectively that are the
/// closest to one-another. If the lines are parallel, an arbitrary,
/// unspecified pair of points that sit on the line segments will be chosen.
fn closest_points(n: LineSegment2<f32>, m: LineSegment2<f32>) -> (Vec2<f32>, Vec2<f32>) { fn closest_points(n: LineSegment2<f32>, m: LineSegment2<f32>) -> (Vec2<f32>, Vec2<f32>) {
let p0 = n.start; // TODO: Rewrite this to something reasonable, if you have faith
let r1 = n.end - n.start; #![allow(clippy::many_single_char_names)]
let q0 = m.start;
let r2 = m.end - m.start;
// The solution for following equations let a = n.start;
// t = (q0.x + u * r2.x - p0.x) / r1.x let b = n.end - n.start;
// u = (p0.y + t * r1.y - q0.y) / r2.y let c = m.start;
let t = (r2.y / r2.x * (q0.x - p0.x) + p0.y - q0.y) / (r1.x * r2.y / r2.x - r1.y); let d = m.end - m.start;
let u = (p0.y + t * r1.y - q0.y) / r2.y;
// Check to prevent div by 0.0 (produces NaNs) and minimize precision
// loss from dividing by small values.
// If both d.x and d.y are 0.0 then the segment is a point and we are fine
// to fallback to the end point projection.
let t = if d.x > d.y {
(d.y / d.x * (c.x - a.x) + a.y - c.y) / (b.x * d.y / d.x - b.y)
} else {
(d.x / d.y * (c.y - a.y) + a.x - c.x) / (b.y * d.x / d.y - b.x)
};
let u = if d.y > d.x {
(a.y + t * b.y - c.y) / d.y
} else {
(a.x + t * b.x - c.x) / d.x
};
// Check to see whether the lines are parallel // Check to see whether the lines are parallel
if !t.is_finite() || !u.is_finite() { if !t.is_finite() || !u.is_finite() {
@ -1978,14 +1952,14 @@ fn closest_points(n: LineSegment2<f32>, m: LineSegment2<f32>) -> (Vec2<f32>, Vec
(n.start, m.projected_point(n.start)), (n.start, m.projected_point(n.start)),
(n.end, m.projected_point(n.end)), (n.end, m.projected_point(n.end)),
]) ])
.min_by_key(|(a, b)| ordered_float::OrderedFloat(a.distance_squared(*b))) .min_by_key(|(a, b)| ordered_float::OrderedFloat(a.distance_squared(*b)))
.expect("Lines had non-finite elements") .expect("Lines had non-finite elements")
} else { } else {
let t = t.clamped(0.0, 1.0); let t = t.clamped(0.0, 1.0);
let u = u.clamped(0.0, 1.0); let u = u.clamped(0.0, 1.0);
let close_n = p0 + r1 * t; let close_n = a + b * t;
let close_m = q0 + r2 * u; let close_m = c + d * u;
let proj_n = n.projected_point(close_m); let proj_n = n.projected_point(close_m);
let proj_m = m.projected_point(close_n); let proj_m = m.projected_point(close_n);

View File

@ -203,20 +203,7 @@ impl StateExt for State {
comp::Body::Ship(ship) => comp::Collider::Voxel { comp::Body::Ship(ship) => comp::Collider::Voxel {
id: ship.manifest_entry().to_string(), id: ship.manifest_entry().to_string(),
}, },
_ => { _ => capsule(&body),
let (p0, p1, radius) = body.sausage();
// TODO:
// It would be cool not have z_min as hardcoded 0.0
// but it needs to work nicer with terrain collisions.
comp::Collider::CapsulePrism {
p0,
p1,
radius,
z_min: 0.0,
z_max: body.height(),
}
},
}) })
.with(comp::Controller::default()) .with(comp::Controller::default())
.with(body) .with(body)
@ -248,11 +235,7 @@ impl StateExt for State {
.with(comp::Ori::default()) .with(comp::Ori::default())
.with(body.mass()) .with(body.mass())
.with(body.density()) .with(body.density())
.with(comp::Collider::Box { .with(capsule(&body))
radius: body.max_radius(),
z_min: 0.0,
z_max: body.height(),
})
.with(body) .with(body)
} }
@ -314,11 +297,7 @@ impl StateExt for State {
if projectile.is_point { if projectile.is_point {
projectile_base = projectile_base.with(comp::Collider::Point) projectile_base = projectile_base.with(comp::Collider::Point)
} else { } else {
projectile_base = projectile_base.with(comp::Collider::Box { projectile_base = projectile_base.with(capsule(&body))
radius: body.max_radius(),
z_min: 0.0,
z_max: body.height(),
})
} }
projectile_base.with(projectile).with(body) projectile_base.with(projectile).with(body)
@ -391,11 +370,7 @@ impl StateExt for State {
.with(pos) .with(pos)
.with(comp::Vel(Vec3::zero())) .with(comp::Vel(Vec3::zero()))
.with(comp::Ori::default()) .with(comp::Ori::default())
.with(comp::Collider::Box { .with(capsule(&object.into()))
radius: comp::Body::Object(object).max_radius(),
z_min: 0.0,
z_max: comp::Body::Object(object).height()
})
.with(comp::Body::Object(object)) .with(comp::Body::Object(object))
.with(comp::Mass(10.0)) .with(comp::Mass(10.0))
// .with(comp::Sticky) // .with(comp::Sticky)
@ -467,7 +442,9 @@ impl StateExt for State {
self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point)); self.write_component_ignore_entity_dead(entity, comp::Pos(spawn_point));
self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero())); self.write_component_ignore_entity_dead(entity, comp::Vel(Vec3::zero()));
self.write_component_ignore_entity_dead(entity, comp::Ori::default()); self.write_component_ignore_entity_dead(entity, comp::Ori::default());
self.write_component_ignore_entity_dead(entity, comp::Collider::Box { self.write_component_ignore_entity_dead(entity, comp::Collider::CapsulePrism {
p0: Vec2::zero(),
p1: Vec2::zero(),
radius: 0.4, radius: 0.4,
z_min: 0.0, z_min: 0.0,
z_max: 1.75, z_max: 1.75,
@ -509,11 +486,7 @@ impl StateExt for State {
// and we call nothing that can delete it in any of the subsequent // and we call nothing that can delete it in any of the subsequent
// commands, so we can assume that all of these calls succeed, // commands, so we can assume that all of these calls succeed,
// justifying ignoring the result of insertion. // justifying ignoring the result of insertion.
self.write_component_ignore_entity_dead(entity, comp::Collider::Box { self.write_component_ignore_entity_dead(entity, capsule(&body));
radius: body.max_radius(),
z_min: 0.0,
z_max: body.height(),
});
self.write_component_ignore_entity_dead(entity, body); self.write_component_ignore_entity_dead(entity, body);
self.write_component_ignore_entity_dead(entity, body.mass()); self.write_component_ignore_entity_dead(entity, body.mass());
self.write_component_ignore_entity_dead(entity, body.density()); self.write_component_ignore_entity_dead(entity, body.density());
@ -896,3 +869,15 @@ fn send_to_group(g: &comp::Group, ecs: &specs::World, msg: &comp::ChatMsg) {
} }
} }
} }
fn capsule(body: &comp::Body) -> comp::Collider {
let (p0, p1, radius) = body.sausage();
comp::Collider::CapsulePrism {
p0,
p1,
radius,
z_min: 0.0,
z_max: body.height(),
}
}

View File

@ -1164,30 +1164,6 @@ impl Scene {
.join() .join()
{ {
match collider { match collider {
comp::Collider::Box {
radius,
z_min,
z_max,
} => {
current_entities.insert(entity);
let shape_id = hitboxes.entry(entity).or_insert_with(|| {
self.debug.add_shape(DebugShape::Cylinder {
radius: *radius,
height: *z_max - *z_min,
})
});
let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0];
let color = if group == Some(&comp::group::ENEMY) {
[1.0, 0.0, 0.0, 0.5]
} else if group == Some(&comp::group::NPC) {
[0.0, 0.0, 1.0, 0.5]
} else {
[0.0, 1.0, 0.0, 0.5]
};
// cylinders don't need orientation anyway
let ori = [0.0, 0.0, 0.0, 1.0];
self.debug.set_context(*shape_id, hb_pos, color, ori);
},
comp::Collider::CapsulePrism { comp::Collider::CapsulePrism {
p0, p0,
p1, p1,