mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add CapsulePrism collider variant
+ Add placeholder physics collision implementation as copy of cylinder Box collider. + Display it with debug hitboxes.
This commit is contained in:
parent
ce29e99403
commit
3b308a3f6f
@ -9,6 +9,7 @@ layout (std140, set = 1, binding = 0)
|
||||
uniform u_locals {
|
||||
vec4 w_pos;
|
||||
vec4 w_color;
|
||||
vec4 w_ori;
|
||||
};
|
||||
|
||||
layout (location = 0)
|
||||
@ -16,5 +17,29 @@ out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
f_color = w_color;
|
||||
gl_Position = all_mat * vec4((v_pos + w_pos.xyz) - focus_off.xyz, 1);
|
||||
|
||||
// Build rotation matrix
|
||||
// https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Rotation_matrices
|
||||
mat3 rotation_matrix;
|
||||
float q0 = w_ori[3];
|
||||
float q1 = w_ori[0];
|
||||
float q2 = w_ori[1];
|
||||
float q3 = w_ori[2];
|
||||
|
||||
float r00 = 1 - 2 * (pow(q2, 2) + pow(q3, 2));
|
||||
float r01 = 2 * (q1 * q2 - q0 * q3);
|
||||
float r02 = 2 * (q0 * q2 + q1 * q3);
|
||||
rotation_matrix[0] = vec3(r00, r01, r02);
|
||||
|
||||
float r10 = 2 * (q1 * q2 + q0 * q3);
|
||||
float r11 = 1 - 2 * (pow(q1, 2) + pow(q3, 2));
|
||||
float r12 = 2 * (q2 * q3 - q0 * q1);
|
||||
rotation_matrix[1] = vec3(r10, r11, r12);
|
||||
|
||||
float r20 = 2 * (q1 * q3 - q0 * q2);
|
||||
float r21 = 2 * (q0 * q1 + q2 * q3);
|
||||
float r22 = 1 - 2 * (pow(q1, 2) + pow(q2, 2));
|
||||
rotation_matrix[2] = vec3(r20, r21, r22);
|
||||
|
||||
gl_Position = all_mat * vec4((v_pos * rotation_matrix + w_pos.xyz) - focus_off.xyz, 1);
|
||||
}
|
||||
|
@ -147,6 +147,20 @@ impl<
|
||||
const EXTENSION: &'static str = "ron";
|
||||
}
|
||||
|
||||
// Utility enum used to build Stadium points
|
||||
// Read doc for [Body::sausage] for more.
|
||||
//
|
||||
// Actually can be removed I guess?
|
||||
// We can just determine shape form dimensions.
|
||||
//
|
||||
// But I want Dachshund in Veloren at least somewhere XD
|
||||
enum Shape {
|
||||
// Dachshund-like
|
||||
Long,
|
||||
// Cyclops-like
|
||||
Wide,
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub fn is_humanoid(&self) -> bool { matches!(self, Body::Humanoid(_)) }
|
||||
|
||||
@ -292,11 +306,12 @@ impl Body {
|
||||
}
|
||||
|
||||
/// The width (shoulder to shoulder), length (nose to tail) and height
|
||||
/// respectively
|
||||
/// respectively (in metres)
|
||||
// Code reviewers: should we replace metres with 'block height'?
|
||||
pub fn dimensions(&self) -> Vec3<f32> {
|
||||
match self {
|
||||
Body::BipedLarge(body) => match body.species {
|
||||
biped_large::Species::Cyclops => Vec3::new(4.6, 3.0, 6.5),
|
||||
biped_large::Species::Cyclops => Vec3::new(5.6, 3.0, 6.5),
|
||||
biped_large::Species::Dullahan => Vec3::new(4.6, 3.0, 5.5),
|
||||
biped_large::Species::Mightysaurok => Vec3::new(4.0, 3.0, 3.4),
|
||||
biped_large::Species::Mindflayer => Vec3::new(4.4, 3.0, 8.0),
|
||||
@ -350,6 +365,8 @@ impl Body {
|
||||
quadruped_medium::Species::Ngoubou => Vec3::new(2.0, 3.2, 2.4),
|
||||
quadruped_medium::Species::Llama => Vec3::new(2.0, 2.5, 2.6),
|
||||
quadruped_medium::Species::Alpaca => Vec3::new(2.0, 2.0, 2.0),
|
||||
quadruped_medium::Species::Camel => Vec3::new(2.0, 4.0, 3.5),
|
||||
// FIXME: We really shouldn't be doing wildcards here
|
||||
_ => Vec3::new(2.0, 3.0, 2.0),
|
||||
},
|
||||
Body::QuadrupedSmall(body) => match body.species {
|
||||
@ -386,6 +403,26 @@ impl Body {
|
||||
}
|
||||
}
|
||||
|
||||
fn shape(&self) -> Shape {
|
||||
match self {
|
||||
Body::BipedLarge(_)
|
||||
| Body::BipedSmall(_)
|
||||
| Body::Golem(_)
|
||||
| Body::Humanoid(_)
|
||||
| Body::Object(_) => Shape::Wide,
|
||||
Body::BirdLarge(_)
|
||||
| Body::BirdMedium(_)
|
||||
| Body::Dragon(_)
|
||||
| Body::FishMedium(_)
|
||||
| Body::FishSmall(_)
|
||||
| Body::QuadrupedLow(_)
|
||||
| Body::QuadrupedMedium(_)
|
||||
| Body::QuadrupedSmall(_)
|
||||
| Body::Ship(_)
|
||||
| Body::Theropod(_) => Shape::Long,
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This is used for collisions, but it's not very accurate for shapes that
|
||||
// are very much not cylindrical. Eventually this ought to be replaced by more
|
||||
// accurate collision shapes.
|
||||
@ -394,6 +431,52 @@ impl Body {
|
||||
dim.x.max(dim.y) / 2.0
|
||||
}
|
||||
|
||||
/// Base of our Capsule Prism used for collisions.
|
||||
/// Returns line segment and radius. See [this wiki page][stadium_wiki].
|
||||
///
|
||||
/// [stadium_wiki]: <https://en.wikipedia.org/wiki/Stadium_(geometry)>
|
||||
pub fn sausage(&self) -> (Vec2<f32>, Vec2<f32>, f32) {
|
||||
// Consider this ascii-art stadium with radius `r` and line segment `a`
|
||||
//
|
||||
// xxxxxxxxxxxxxxxxx
|
||||
//
|
||||
// _ aaaaaaaaa
|
||||
// y -* r * -
|
||||
// y * r *
|
||||
// y * rrr --------- rrr *
|
||||
// y * r *
|
||||
// y * r *
|
||||
// *__aaaaaaaaa_ ^
|
||||
let dim = self.dimensions();
|
||||
// The width (shoulder to shoulder) and length (nose to tail)
|
||||
let (width, length) = (dim.x, dim.y);
|
||||
|
||||
match self.shape() {
|
||||
Shape::Long => {
|
||||
let radius = width / 2.0;
|
||||
|
||||
let a = length - 2.0 * radius;
|
||||
debug_assert!(a > 0.0);
|
||||
|
||||
let p0 = Vec2::new(0.0, -a / 2.0);
|
||||
let p1 = Vec2::new(0.0, a / 2.0);
|
||||
|
||||
(p0, p1, radius)
|
||||
},
|
||||
Shape::Wide => {
|
||||
let radius = length / 2.0;
|
||||
|
||||
let a = width - 2.0 * radius;
|
||||
debug_assert!(a > 0.0);
|
||||
|
||||
let p0 = Vec2::new(-a / 2.0, 0.0);
|
||||
let p1 = Vec2::new(a / 2.0, 0.0);
|
||||
|
||||
(p0, p1, radius)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// How far away other entities should try to be. Will be added uppon the other
|
||||
// entitys spacing_radius. So an entity with 2.0 and an entity with 3.0 will
|
||||
// lead to that both entities will try to keep 5.0 units away from each
|
||||
@ -417,6 +500,7 @@ impl Body {
|
||||
}
|
||||
}
|
||||
|
||||
/// Height from the bottom to the top (in metres)
|
||||
pub fn height(&self) -> f32 { self.dimensions().z }
|
||||
|
||||
pub fn base_energy(&self) -> u32 {
|
||||
|
@ -99,10 +99,24 @@ impl Component for Density {
|
||||
// Collider
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Collider {
|
||||
// TODO: pass the map from ids -> voxel data to get_radius and get_z_limits to compute a
|
||||
// bounding cylinder
|
||||
Voxel { id: String },
|
||||
Box { radius: f32, z_min: f32, z_max: f32 },
|
||||
// TODO: pass the map from ids -> voxel data to get_radius
|
||||
// and get_z_limits to compute a bounding cylinder.
|
||||
Voxel {
|
||||
id: String,
|
||||
},
|
||||
Box {
|
||||
radius: f32,
|
||||
z_min: f32,
|
||||
z_max: f32,
|
||||
},
|
||||
/// Capsule prism with line segment from p0 to p1
|
||||
CapsulePrism {
|
||||
p0: Vec2<f32>,
|
||||
p1: Vec2<f32>,
|
||||
radius: f32,
|
||||
z_min: f32,
|
||||
z_max: f32,
|
||||
},
|
||||
Point,
|
||||
}
|
||||
|
||||
@ -111,6 +125,11 @@ 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::Point => 0.0,
|
||||
}
|
||||
}
|
||||
@ -124,6 +143,7 @@ impl Collider {
|
||||
match self {
|
||||
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::Point => (0.0, 0.0),
|
||||
}
|
||||
}
|
||||
|
@ -290,8 +290,9 @@ impl<'a> PhysicsData<'a> {
|
||||
!&read.mountings,
|
||||
read.stickies.maybe(),
|
||||
&mut write.physics_states,
|
||||
// TODO: if we need to avoid collisions for other things consider moving whether it
|
||||
// should interact into the collider component or into a separate component
|
||||
// TODO: if we need to avoid collisions for other things consider
|
||||
// moving whether it should interact into the collider component
|
||||
// or into a separate component.
|
||||
read.projectiles.maybe(),
|
||||
read.char_states.maybe(),
|
||||
)
|
||||
@ -322,9 +323,10 @@ impl<'a> PhysicsData<'a> {
|
||||
let mut entity_entity_collision_checks = 0;
|
||||
let mut entity_entity_collisions = 0;
|
||||
|
||||
// TODO: quick fix for bad performance at extrememly high velocities
|
||||
// use oriented rectangles at some threshold of displacement/radius
|
||||
// to query the spatial grid and limit max displacement per tick somehow
|
||||
// TODO: quick fix for bad performance. At extrememly high
|
||||
// velocities use oriented rectangles at some threshold of
|
||||
// displacement/radius to query the spatial grid and limit
|
||||
// max displacement per tick somehow.
|
||||
if previous_cache.collision_boundary > 128.0 {
|
||||
return PhysicsMetrics {
|
||||
entity_entity_collision_checks,
|
||||
@ -419,9 +421,14 @@ impl<'a> PhysicsData<'a> {
|
||||
<= 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 is either in mid air or is not sticky,
|
||||
// then mark them as colliding with the other entity
|
||||
// If entities have not yet collided
|
||||
// this tick (but just did) and if entity
|
||||
//
|
||||
// is either in mid air
|
||||
// or is not sticky,
|
||||
//
|
||||
// then mark them as colliding with the
|
||||
// other entity.
|
||||
if !collision_registered && (is_mid_air || !is_sticky) {
|
||||
physics.touch_entities.insert(*other);
|
||||
entity_entity_collisions += 1;
|
||||
@ -627,8 +634,9 @@ impl<'a> PhysicsData<'a> {
|
||||
// And not already stuck on a block (e.g., for arrows)
|
||||
&& !(physics_state.on_surface().is_some() && sticky.is_some())
|
||||
{
|
||||
// Clamp dt to an effective 10 TPS, to prevent gravity from slamming the
|
||||
// players into the floor when stationary if other systems cause the server
|
||||
// Clamp dt to an effective 10 TPS, to prevent gravity
|
||||
// from slamming the players into the floor when
|
||||
// stationary if other systems cause the server
|
||||
// to lag (as observed in the 0.9 release party).
|
||||
let dt = DeltaTime(read.dt.0.min(0.1));
|
||||
|
||||
@ -750,8 +758,8 @@ impl<'a> PhysicsData<'a> {
|
||||
)| {
|
||||
let mut land_on_ground = None;
|
||||
let mut outcomes = Vec::new();
|
||||
// Defer the writes of positions, velocities and orientations to allow an inner
|
||||
// loop over terrain-like entities
|
||||
// Defer the writes of positions, velocities and orientations
|
||||
// to allow an inner loop over terrain-like entities.
|
||||
let old_vel = *vel;
|
||||
let mut vel = *vel;
|
||||
let old_ori = *ori;
|
||||
@ -777,17 +785,23 @@ impl<'a> PhysicsData<'a> {
|
||||
Vec3::zero()
|
||||
};
|
||||
|
||||
// What's going on here? Because collisions need to be resolved against multiple
|
||||
// What's going on here?
|
||||
// Because collisions need to be resolved against multiple
|
||||
// colliders, this code takes the current position and
|
||||
// propagates it forward according to velocity to find a
|
||||
// 'target' position. This is where we'd ideally end up at the end of the tick,
|
||||
// 'target' position.
|
||||
//
|
||||
// This is where we'd ideally end up at the end of the tick,
|
||||
// assuming no collisions. Then, we refine this target by
|
||||
// stepping from the original position to the target for
|
||||
// every obstacle, refining the target position as we go. It's not perfect, but
|
||||
// it works pretty well in practice. Oddities can occur on
|
||||
// the intersection between multiple colliders, but it's not
|
||||
// like any game physics system resolves these sort of things well anyway. At
|
||||
// the very least, we don't do things that result in glitchy
|
||||
// every obstacle, refining the target position as we go.
|
||||
//
|
||||
// It's not perfect, but it works pretty well in practice.
|
||||
// Oddities can occur on the intersection between multiple
|
||||
// colliders, but it's not like any game physics system
|
||||
// resolves these sort of things well anyway.
|
||||
//
|
||||
// At the very least, we don't do things that result in glitchy
|
||||
// velocities or entirely broken position snapping.
|
||||
let mut tgt_pos = pos.0 + pos_delta;
|
||||
|
||||
@ -799,11 +813,12 @@ impl<'a> PhysicsData<'a> {
|
||||
|
||||
match &collider {
|
||||
Collider::Voxel { .. } => {
|
||||
// for now, treat entities with voxel colliders as their bounding
|
||||
// cylinders for the purposes of colliding them with terrain
|
||||
|
||||
// Additionally, multiply radius by 0.1 to make the cylinder smaller to
|
||||
// avoid lag
|
||||
// For now, treat entities with voxel colliders
|
||||
// as their bounding cylinders for the purposes of
|
||||
// colliding them with terrain.
|
||||
//
|
||||
// Additionally, multiply radius by 0.1 to make
|
||||
// the cylinder smaller to avoid lag.
|
||||
let radius = collider.get_radius() * scale * 0.1;
|
||||
let (z_min, z_max) = collider.get_z_limits(scale);
|
||||
|
||||
@ -832,6 +847,50 @@ impl<'a> PhysicsData<'a> {
|
||||
z_min,
|
||||
z_max,
|
||||
} => {
|
||||
// FIXME:
|
||||
// !! DEPRECATED !!
|
||||
//
|
||||
// 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 {
|
||||
radius,
|
||||
z_min,
|
||||
z_max,
|
||||
..
|
||||
} => {
|
||||
// FIXME: placeholder.
|
||||
// copypasted from Box collider
|
||||
//
|
||||
// This shoudln't pass Code Review.
|
||||
// Scale collider
|
||||
let radius = radius.min(0.45) * scale;
|
||||
let z_min = *z_min * scale;
|
||||
@ -866,8 +925,10 @@ impl<'a> PhysicsData<'a> {
|
||||
Collider::Point => {
|
||||
let mut pos = *pos;
|
||||
|
||||
// If the velocity is exactly 0, a raycast may not pick up the current
|
||||
// block. Handle this.
|
||||
// TODO: If the velocity is exactly 0,
|
||||
// a raycast may not pick up the current block.
|
||||
//
|
||||
// Handle this.
|
||||
let (dist, block) = if let Some(block) = read
|
||||
.terrain
|
||||
.get(pos.0.map(|e| e.floor() as i32))
|
||||
@ -882,7 +943,8 @@ impl<'a> PhysicsData<'a> {
|
||||
.until(|block: &Block| block.is_solid())
|
||||
.ignore_error()
|
||||
.cast();
|
||||
(dist, block.unwrap()) // Can't fail since we do ignore_error above
|
||||
// Can't fail since we do ignore_error above
|
||||
(dist, block.unwrap())
|
||||
};
|
||||
|
||||
pos.0 += pos_delta.try_normalized().unwrap_or_else(Vec3::zero) * dist;
|
||||
@ -987,6 +1049,7 @@ impl<'a> PhysicsData<'a> {
|
||||
// Collide with terrain-like entities
|
||||
let query_center = path_sphere.center.xy();
|
||||
let query_radius = path_sphere.radius;
|
||||
|
||||
voxel_collider_spatial_grid
|
||||
.in_circle_aabr(query_center, query_radius)
|
||||
.filter_map(|entity| {
|
||||
@ -1280,6 +1343,7 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
}
|
||||
|
||||
#[warn(clippy::pedantic)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
cylinder: (f32, f32, f32), // effective collision cylinder
|
||||
@ -1297,22 +1361,6 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
mut land_on_ground: impl FnMut(Entity, Vel),
|
||||
read: &PhysicsRead,
|
||||
) {
|
||||
let (radius, z_min, z_max) = cylinder;
|
||||
|
||||
// Probe distances
|
||||
let hdist = radius.ceil() as i32;
|
||||
// Neighbouring blocks iterator
|
||||
let near_iter = (-hdist..hdist + 1)
|
||||
.map(move |i| {
|
||||
(-hdist..hdist + 1).map(move |j| {
|
||||
(1 - Block::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32
|
||||
..z_max.ceil() as i32 + 1)
|
||||
.map(move |k| (i, j, k))
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.flatten();
|
||||
|
||||
// Function for iterating over the blocks the player at a specific position
|
||||
// collides with
|
||||
fn collision_iter<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
@ -1357,7 +1405,6 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
})
|
||||
}
|
||||
|
||||
let z_range = z_min..z_max;
|
||||
// Function for determining whether the player at a specific position collides
|
||||
// with blocks with the given criteria
|
||||
fn collision_with<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
@ -1381,6 +1428,25 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn always_hits(_: &Block) -> bool { true }
|
||||
let (radius, z_min, z_max) = cylinder;
|
||||
|
||||
// Probe distances
|
||||
let hdist = radius.ceil() as i32;
|
||||
// Neighbouring blocks iterator
|
||||
let near_iter = (-hdist..hdist + 1)
|
||||
.map(move |i| {
|
||||
(-hdist..hdist + 1).map(move |j| {
|
||||
(1 - Block::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32
|
||||
..z_max.ceil() as i32 + 1)
|
||||
.map(move |k| (i, j, k))
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.flatten();
|
||||
|
||||
let z_range = z_min..z_max;
|
||||
|
||||
physics_state.on_ground = None;
|
||||
physics_state.on_ceiling = false;
|
||||
|
||||
@ -1395,7 +1461,6 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
.ceil()
|
||||
.max(1.0);
|
||||
let old_pos = pos.0;
|
||||
fn block_true(_: &Block) -> bool { true }
|
||||
for _ in 0..increments as usize {
|
||||
pos.0 += pos_delta / increments;
|
||||
|
||||
@ -1485,7 +1550,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
// ...and the vertical resolution direction is sufficiently great...
|
||||
&& dir.z < -0.1
|
||||
// ...and the space above is free...
|
||||
&& !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &terrain, block_true, near_iter.clone(), radius, z_range.clone())
|
||||
&& !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &terrain, always_hits, near_iter.clone(), radius, z_range.clone())
|
||||
// ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)...
|
||||
// && terrain
|
||||
// .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))
|
||||
@ -1495,7 +1560,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
&& collision_with(
|
||||
pos.0 + resolve_dir - Vec3::unit_z() * 1.25,
|
||||
&terrain,
|
||||
block_true,
|
||||
always_hits,
|
||||
near_iter.clone(),
|
||||
radius,
|
||||
z_range.clone(),
|
||||
@ -1545,7 +1610,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
||||
} else if collision_with(
|
||||
pos.0 - Vec3::unit_z() * 1.1,
|
||||
&terrain,
|
||||
block_true,
|
||||
always_hits,
|
||||
near_iter.clone(),
|
||||
radius,
|
||||
z_range.clone(),
|
||||
|
@ -185,6 +185,7 @@ impl StateExt for State {
|
||||
inventory: comp::Inventory,
|
||||
body: comp::Body,
|
||||
) -> EcsEntityBuilder {
|
||||
use comp::body::{biped_large, quadruped_medium};
|
||||
self.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
@ -203,6 +204,31 @@ impl StateExt for State {
|
||||
comp::Body::Ship(ship) => comp::Collider::Voxel {
|
||||
id: ship.manifest_entry().to_string(),
|
||||
},
|
||||
body
|
||||
@
|
||||
(comp::Body::QuadrupedMedium(quadruped_medium::Body {
|
||||
species: quadruped_medium::Species::Camel,
|
||||
..
|
||||
})
|
||||
| comp::Body::BipedLarge(biped_large::Body {
|
||||
species: biped_large::Species::Cyclops,
|
||||
..
|
||||
})) => {
|
||||
// no Camel was hurt while doing this sausage
|
||||
// can't say the same about Cyclops
|
||||
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(),
|
||||
}
|
||||
},
|
||||
_ => comp::Collider::Box {
|
||||
radius: body.radius(),
|
||||
z_min: 0.0,
|
||||
|
@ -36,6 +36,8 @@ pub struct Locals {
|
||||
/// by the shader
|
||||
pub pos: [f32; 4],
|
||||
pub color: [f32; 4],
|
||||
/// quaternion as [x, y, z, w]
|
||||
pub ori: [f32; 4],
|
||||
}
|
||||
|
||||
pub type BoundLocals = Bound<Consts<Locals>>;
|
||||
|
@ -9,7 +9,16 @@ use vek::*;
|
||||
#[derive(Debug)]
|
||||
pub enum DebugShape {
|
||||
Line([Vec3<f32>; 2]),
|
||||
Cylinder { radius: f32, height: f32 },
|
||||
Cylinder {
|
||||
radius: f32,
|
||||
height: f32,
|
||||
},
|
||||
CapsulePrism {
|
||||
p0: Vec2<f32>,
|
||||
p1: Vec2<f32>,
|
||||
radius: f32,
|
||||
height: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl DebugShape {
|
||||
@ -22,28 +31,117 @@ impl DebugShape {
|
||||
let quad = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>| {
|
||||
Quad::<DebugVertex>::new(x.into(), y.into(), z.into(), w.into())
|
||||
};
|
||||
|
||||
match self {
|
||||
DebugShape::Line([a, b]) => {
|
||||
let h = Vec3::new(0.0, 1.0, 0.0);
|
||||
mesh.push_quad(quad(*a, a + h, b + h, *b));
|
||||
},
|
||||
DebugShape::Cylinder { radius, height } => {
|
||||
const SUBDIVISIONS: usize = 16;
|
||||
const SUBDIVISIONS: u8 = 16;
|
||||
for i in 0..SUBDIVISIONS {
|
||||
let angle = |j: usize| (j as f32 / SUBDIVISIONS as f32) * 2.0 * PI;
|
||||
let a = Vec3::zero();
|
||||
let b = Vec3::new(radius * angle(i).cos(), radius * angle(i).sin(), 0.0);
|
||||
let c = Vec3::new(
|
||||
radius * angle(i + 1).cos(),
|
||||
radius * angle(i + 1).sin(),
|
||||
0.0,
|
||||
);
|
||||
// dot on circle edge
|
||||
let to = |n: u8| {
|
||||
const FULL: f32 = 2.0 * PI;
|
||||
let angle = FULL * f32::from(n) / f32::from(SUBDIVISIONS);
|
||||
|
||||
Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0)
|
||||
};
|
||||
|
||||
let origin = Vec3::zero();
|
||||
let r0 = to(i);
|
||||
let r1 = to(i + 1);
|
||||
|
||||
let h = Vec3::new(0.0, 0.0, *height);
|
||||
mesh.push_tri(tri(c, b, a));
|
||||
mesh.push_quad(quad(b, c, c + h, b + h));
|
||||
mesh.push_tri(tri(a + h, b + h, c + h));
|
||||
|
||||
// Draw bottom sector
|
||||
mesh.push_tri(tri(r1, r0, origin));
|
||||
// Draw face
|
||||
mesh.push_quad(quad(r0, r1, r1 + h, r0 + h));
|
||||
// Draw top sector
|
||||
mesh.push_tri(tri(origin + h, r0 + h, r1 + h));
|
||||
}
|
||||
},
|
||||
DebugShape::CapsulePrism {
|
||||
p0,
|
||||
p1,
|
||||
radius,
|
||||
height,
|
||||
} => {
|
||||
// We split circle in two parts
|
||||
const HALF_SECTORS: u8 = 8;
|
||||
const TOTAL: u8 = HALF_SECTORS * 2;
|
||||
|
||||
let offset = (p0 - p1).angle_between(Vec2::new(0.0, 1.0));
|
||||
let h = Vec3::new(0.0, 0.0, *height);
|
||||
|
||||
let draw_cylinder_sector =
|
||||
|mesh: &mut Mesh<DebugVertex>, origin: Vec3<f32>, from: u8, to: u8| {
|
||||
for i in from..to {
|
||||
// dot on circle edge
|
||||
let to = |n: u8| {
|
||||
const FULL: f32 = 2.0 * PI;
|
||||
let angle = offset + FULL * f32::from(n) / f32::from(TOTAL);
|
||||
let (x, y) = (radius * angle.cos(), radius * angle.sin());
|
||||
let to_edge = Vec3::new(x, y, 0.0);
|
||||
|
||||
origin + to_edge
|
||||
};
|
||||
|
||||
let r0 = to(i);
|
||||
let r1 = to(i + 1);
|
||||
|
||||
// Draw bottom sector
|
||||
mesh.push_tri(tri(r1, r0, origin));
|
||||
// Draw face
|
||||
mesh.push_quad(quad(r0, r1, r1 + h, r0 + h));
|
||||
// Draw top sector
|
||||
mesh.push_tri(tri(origin + h, r0 + h, r1 + h));
|
||||
}
|
||||
};
|
||||
|
||||
let p0 = Vec3::new(p0.x, p0.y, 0.0);
|
||||
let p1 = Vec3::new(p1.x, p1.y, 0.0);
|
||||
// 1) Draw first half-cylinder
|
||||
draw_cylinder_sector(&mut mesh, p0, 0, HALF_SECTORS);
|
||||
|
||||
// 2) Draw cuboid in-between
|
||||
// get main line segment
|
||||
let a = p1 - p0;
|
||||
// normalize
|
||||
let a = a / a.magnitude();
|
||||
// stretch to radius
|
||||
let a = a * *radius;
|
||||
// rotate to 90 degrees to get needed shift
|
||||
let ortoghonal = Quaternion::rotation_z(PI / 2.0);
|
||||
let shift = ortoghonal * a;
|
||||
|
||||
// bottom points
|
||||
let a0 = p0 + shift;
|
||||
let b0 = p0 - shift;
|
||||
let c0 = p1 - shift;
|
||||
let d0 = p1 + shift;
|
||||
|
||||
// top points
|
||||
let a1 = a0 + h;
|
||||
let b1 = b0 + h;
|
||||
let c1 = c0 + h;
|
||||
let d1 = d0 + h;
|
||||
|
||||
// Bottom
|
||||
mesh.push_quad(quad(d0, c0, b0, a0));
|
||||
|
||||
// Faces
|
||||
// (we need only two of them, because other two are inside)
|
||||
mesh.push_quad(quad(d0, a0, a1, d1));
|
||||
mesh.push_quad(quad(b0, c0, c1, b1));
|
||||
|
||||
// Top
|
||||
mesh.push_quad(quad(a1, b1, c1, d1));
|
||||
|
||||
// 3) Draw second half-cylinder
|
||||
draw_cylinder_sector(&mut mesh, p1, HALF_SECTORS, TOTAL);
|
||||
},
|
||||
}
|
||||
mesh
|
||||
}
|
||||
@ -55,7 +153,7 @@ pub struct DebugShapeId(pub u64);
|
||||
pub struct Debug {
|
||||
next_shape_id: DebugShapeId,
|
||||
pending_shapes: HashMap<DebugShapeId, DebugShape>,
|
||||
pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4])>,
|
||||
pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4], [f32; 4])>,
|
||||
pending_deletes: HashSet<DebugShapeId>,
|
||||
models: HashMap<DebugShapeId, (Model<DebugVertex>, Bound<Consts<DebugLocals>>)>,
|
||||
}
|
||||
@ -78,8 +176,8 @@ impl Debug {
|
||||
id
|
||||
}
|
||||
|
||||
pub fn set_pos_and_color(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4]) {
|
||||
self.pending_locals.insert(id, (pos, color));
|
||||
pub fn set_context(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4], ori: [f32; 4]) {
|
||||
self.pending_locals.insert(id, (pos, color, ori));
|
||||
}
|
||||
|
||||
pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); }
|
||||
@ -90,6 +188,7 @@ impl Debug {
|
||||
let locals = renderer.create_debug_bound_locals(&[DebugLocals {
|
||||
pos: [0.0; 4],
|
||||
color: [1.0, 0.0, 0.0, 1.0],
|
||||
ori: [0.0, 0.0, 0.0, 1.0],
|
||||
}]);
|
||||
self.models.insert(id, (model, locals));
|
||||
} else {
|
||||
@ -99,12 +198,13 @@ impl Debug {
|
||||
);
|
||||
}
|
||||
}
|
||||
for (id, (pos, color)) in self.pending_locals.drain() {
|
||||
for (id, (pos, color, ori)) in self.pending_locals.drain() {
|
||||
if let Some((_, locals)) = self.models.get_mut(&id) {
|
||||
let lc = srgba_to_linear(color.into());
|
||||
let new_locals = [DebugLocals {
|
||||
pos,
|
||||
color: [lc.r, lc.g, lc.b, lc.a],
|
||||
ori,
|
||||
}];
|
||||
renderer.update_consts(locals, &new_locals);
|
||||
} else {
|
||||
|
@ -1152,32 +1152,73 @@ impl Scene {
|
||||
if settings.interface.toggle_hitboxes {
|
||||
let positions = ecs.read_component::<comp::Pos>();
|
||||
let colliders = ecs.read_component::<comp::Collider>();
|
||||
let orientations = ecs.read_component::<comp::Ori>();
|
||||
let groups = ecs.read_component::<comp::Group>();
|
||||
for (entity, pos, collider, group) in
|
||||
(&ecs.entities(), &positions, &colliders, groups.maybe()).join()
|
||||
for (entity, pos, collider, ori, group) in (
|
||||
&ecs.entities(),
|
||||
&positions,
|
||||
&colliders,
|
||||
&orientations,
|
||||
groups.maybe(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
if let comp::Collider::Box {
|
||||
radius,
|
||||
z_min,
|
||||
z_max,
|
||||
} = collider
|
||||
{
|
||||
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]
|
||||
};
|
||||
self.debug.set_pos_and_color(*shape_id, hb_pos, color);
|
||||
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 {
|
||||
p0,
|
||||
p1,
|
||||
radius,
|
||||
z_min,
|
||||
z_max,
|
||||
} => {
|
||||
current_entities.insert(entity);
|
||||
let shape_id = hitboxes.entry(entity).or_insert_with(|| {
|
||||
self.debug.add_shape(DebugShape::CapsulePrism {
|
||||
p0: *p0,
|
||||
p1: *p1,
|
||||
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]
|
||||
};
|
||||
let ori = ori.to_quat();
|
||||
let hb_ori = [ori.x, ori.y, ori.z, ori.w];
|
||||
self.debug.set_context(*shape_id, hb_pos, color, hb_ori);
|
||||
},
|
||||
comp::Collider::Voxel { .. } | comp::Collider::Point => {
|
||||
// ignore terrain-like or point-hitboxes
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,9 +59,10 @@ impl EguiState {
|
||||
scene.debug.remove_shape(DebugShapeId(*debug_shape_id));
|
||||
},
|
||||
DebugShapeAction::SetPosAndColor { id, pos, color } => {
|
||||
let identity_ori = [0.0, 0.0, 0.0, 1.0];
|
||||
scene
|
||||
.debug
|
||||
.set_pos_and_color(DebugShapeId(*id), *pos, *color);
|
||||
.set_context(DebugShapeId(*id), *pos, *color, identity_ori);
|
||||
},
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user