mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'juliancoffee/capsule_prism' into 'master'
New CapsulePrism collider See merge request veloren/veloren!2843
This commit is contained in:
commit
9cc70e6d22
@ -9,6 +9,7 @@ layout (std140, set = 1, binding = 0)
|
|||||||
uniform u_locals {
|
uniform u_locals {
|
||||||
vec4 w_pos;
|
vec4 w_pos;
|
||||||
vec4 w_color;
|
vec4 w_color;
|
||||||
|
vec4 w_ori;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout (location = 0)
|
layout (location = 0)
|
||||||
@ -16,5 +17,29 @@ out vec4 f_color;
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
f_color = w_color;
|
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,14 @@ impl<
|
|||||||
const EXTENSION: &'static str = "ron";
|
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
|
||||||
|
|
||||||
impl Body {
|
impl Body {
|
||||||
pub fn is_humanoid(&self) -> bool { matches!(self, Body::Humanoid(_)) }
|
pub fn is_humanoid(&self) -> bool { matches!(self, Body::Humanoid(_)) }
|
||||||
|
|
||||||
@ -292,11 +300,12 @@ impl Body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The width (shoulder to shoulder), length (nose to tail) and height
|
/// 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> {
|
pub fn dimensions(&self) -> Vec3<f32> {
|
||||||
match self {
|
match self {
|
||||||
Body::BipedLarge(body) => match body.species {
|
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::Dullahan => Vec3::new(4.6, 3.0, 5.5),
|
||||||
biped_large::Species::Mightysaurok => Vec3::new(4.0, 3.0, 3.4),
|
biped_large::Species::Mightysaurok => Vec3::new(4.0, 3.0, 3.4),
|
||||||
biped_large::Species::Mindflayer => Vec3::new(4.4, 3.0, 8.0),
|
biped_large::Species::Mindflayer => Vec3::new(4.4, 3.0, 8.0),
|
||||||
@ -350,6 +359,8 @@ impl Body {
|
|||||||
quadruped_medium::Species::Ngoubou => Vec3::new(2.0, 3.2, 2.4),
|
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::Llama => Vec3::new(2.0, 2.5, 2.6),
|
||||||
quadruped_medium::Species::Alpaca => Vec3::new(2.0, 2.0, 2.0),
|
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),
|
_ => Vec3::new(2.0, 3.0, 2.0),
|
||||||
},
|
},
|
||||||
Body::QuadrupedSmall(body) => match body.species {
|
Body::QuadrupedSmall(body) => match body.species {
|
||||||
@ -389,9 +400,60 @@ impl Body {
|
|||||||
// Note: This is used for collisions, but it's not very accurate for shapes that
|
// 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
|
// are very much not cylindrical. Eventually this ought to be replaced by more
|
||||||
// accurate collision shapes.
|
// accurate collision shapes.
|
||||||
pub fn radius(&self) -> f32 {
|
pub fn max_radius(&self) -> f32 {
|
||||||
let dim = self.dimensions();
|
let dim = self.dimensions();
|
||||||
dim.x.max(dim.y) / 2.0
|
let (x, y) = (dim.x, dim.y);
|
||||||
|
|
||||||
|
x.max(y) / 2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_radius(&self) -> f32 {
|
||||||
|
let (_p0, _p1, radius) = self.sausage();
|
||||||
|
|
||||||
|
radius
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
//
|
||||||
|
// _ ----------_
|
||||||
|
// y -* r *-
|
||||||
|
// y * r *
|
||||||
|
// y * rrr aaaaaaaaa rrr *
|
||||||
|
// y * r *
|
||||||
|
// y * r *
|
||||||
|
// *____________ ^
|
||||||
|
let dim = self.dimensions();
|
||||||
|
// The width (shoulder to shoulder) and length (nose to tail)
|
||||||
|
let (width, length) = (dim.x, dim.y);
|
||||||
|
|
||||||
|
if length > width {
|
||||||
|
// Dachshund-like
|
||||||
|
let radius = width / 2.0;
|
||||||
|
|
||||||
|
let a = length - 2.0 * radius;
|
||||||
|
|
||||||
|
let p0 = Vec2::new(0.0, -a / 2.0);
|
||||||
|
let p1 = Vec2::new(0.0, a / 2.0);
|
||||||
|
|
||||||
|
(p0, p1, radius)
|
||||||
|
} else {
|
||||||
|
// Cyclops-like
|
||||||
|
let radius = length / 2.0;
|
||||||
|
|
||||||
|
let a = width - 2.0 * radius;
|
||||||
|
|
||||||
|
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
|
// How far away other entities should try to be. Will be added uppon the other
|
||||||
@ -399,7 +461,7 @@ impl Body {
|
|||||||
// lead to that both entities will try to keep 5.0 units away from each
|
// lead to that both entities will try to keep 5.0 units away from each
|
||||||
// other.
|
// other.
|
||||||
pub fn spacing_radius(&self) -> f32 {
|
pub fn spacing_radius(&self) -> f32 {
|
||||||
self.radius()
|
self.max_radius()
|
||||||
+ match self {
|
+ match self {
|
||||||
Body::QuadrupedSmall(body) => match body.species {
|
Body::QuadrupedSmall(body) => match body.species {
|
||||||
quadruped_small::Species::Rat => 0.0,
|
quadruped_small::Species::Rat => 0.0,
|
||||||
@ -417,6 +479,7 @@ impl Body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Height from the bottom to the top (in metres)
|
||||||
pub fn height(&self) -> f32 { self.dimensions().z }
|
pub fn height(&self) -> f32 { self.dimensions().z }
|
||||||
|
|
||||||
pub fn base_energy(&self) -> u32 {
|
pub fn base_energy(&self) -> u32 {
|
||||||
|
@ -54,7 +54,12 @@ pub struct PreviousPhysCache {
|
|||||||
/// Calculates a Sphere over the Entity for quick boundary checking
|
/// Calculates a Sphere over the Entity for quick boundary checking
|
||||||
pub collision_boundary: f32,
|
pub collision_boundary: f32,
|
||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
|
/// Approximate radius of cylinder of collider.
|
||||||
pub scaled_radius: f32,
|
pub scaled_radius: f32,
|
||||||
|
/// Radius of stadium of collider.
|
||||||
|
pub neighborhood_radius: f32,
|
||||||
|
/// relative p0 and p1 of collider's statium, None if cylinder.
|
||||||
|
pub origins: Option<(Vec2<f32>, Vec2<f32>)>,
|
||||||
pub ori: Quaternion<f32>,
|
pub ori: Quaternion<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,18 +104,30 @@ impl Component for Density {
|
|||||||
// Collider
|
// Collider
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Collider {
|
pub enum Collider {
|
||||||
// TODO: pass the map from ids -> voxel data to get_radius and get_z_limits to compute a
|
// TODO: pass the map from ids -> voxel data to get_radius
|
||||||
// bounding cylinder
|
// and get_z_limits to compute a bounding cylinder.
|
||||||
Voxel { id: String },
|
Voxel {
|
||||||
Box { radius: f32, z_min: f32, z_max: f32 },
|
id: String,
|
||||||
|
},
|
||||||
|
/// 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,
|
Point,
|
||||||
}
|
}
|
||||||
|
|
||||||
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, .. } => {
|
||||||
|
let a = p0.distance(*p1);
|
||||||
|
a / 2.0 + *radius
|
||||||
|
},
|
||||||
Collider::Point => 0.0,
|
Collider::Point => 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +140,7 @@ 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::Point => (0.0, 0.0),
|
Collider::Point => (0.0, 0.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,8 +232,9 @@ fn height_offset(body: &Body, look_dir: Dir) -> f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn beam_offsets(body: &Body, look_dir: Dir, ori: Vec3<f32>) -> Vec3<f32> {
|
pub fn beam_offsets(body: &Body, look_dir: Dir, ori: Vec3<f32>) -> Vec3<f32> {
|
||||||
let body_radius = body.radius();
|
let body_radius = body.min_radius();
|
||||||
let body_offsets_z = height_offset(body, look_dir);
|
let body_offsets_z = height_offset(body, look_dir);
|
||||||
|
|
||||||
Vec3::new(
|
Vec3::new(
|
||||||
body_radius * ori.x * 1.1,
|
body_radius * ori.x * 1.1,
|
||||||
body_radius * ori.y * 1.1,
|
body_radius * ori.y * 1.1,
|
||||||
|
@ -196,7 +196,7 @@ impl CharacterBehavior for Data {
|
|||||||
Outcome::GroundSlam {
|
Outcome::GroundSlam {
|
||||||
pos: data.pos.0
|
pos: data.pos.0
|
||||||
+ *data.ori.look_dir()
|
+ *data.ori.look_dir()
|
||||||
* (data.body.radius() + self.static_data.range),
|
* (data.body.max_radius() + self.static_data.range),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -641,7 +641,7 @@ pub fn handle_manipulate_loadout(
|
|||||||
// MAX_PICKUP_RANGE and the radius of the body
|
// MAX_PICKUP_RANGE and the radius of the body
|
||||||
let sprite_range_check = |pos: Vec3<f32>| {
|
let sprite_range_check = |pos: Vec3<f32>| {
|
||||||
(sprite_pos_f32 - pos).magnitude_squared()
|
(sprite_pos_f32 - pos).magnitude_squared()
|
||||||
< (MAX_PICKUP_RANGE + data.body.radius()).powi(2)
|
< (MAX_PICKUP_RANGE + data.body.max_radius()).powi(2)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Checks if player's feet or head is near to sprite
|
// Checks if player's feet or head is near to sprite
|
||||||
|
@ -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)
|
||||||
|
@ -82,174 +82,193 @@ impl<'a> System<'a> for Sys {
|
|||||||
&beam_segments,
|
&beam_segments,
|
||||||
)
|
)
|
||||||
.par_join()
|
.par_join()
|
||||||
.fold(|| (Vec::new(), Vec::new(), Vec::new()),
|
.fold(
|
||||||
|(mut server_events, mut add_hit_entities, mut outcomes),
|
|| (Vec::new(), Vec::new(), Vec::new()),
|
||||||
(entity, pos, ori, beam_segment)|
|
|(mut server_events, mut add_hit_entities, mut outcomes),
|
||||||
{
|
(entity, pos, ori, beam_segment)| {
|
||||||
let creation_time = match beam_segment.creation {
|
let creation_time = match beam_segment.creation {
|
||||||
Some(time) => time,
|
Some(time) => time,
|
||||||
// Skip newly created beam segments
|
// Skip newly created beam segments
|
||||||
None => return (server_events, add_hit_entities, outcomes),
|
None => return (server_events, add_hit_entities, outcomes),
|
||||||
};
|
|
||||||
let end_time = creation_time + beam_segment.duration.as_secs_f64();
|
|
||||||
|
|
||||||
let beam_owner = beam_segment
|
|
||||||
.owner
|
|
||||||
.and_then(|uid| read_data.uid_allocator.retrieve_entity_internal(uid.into()));
|
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
if rng.gen_bool(0.005) {
|
|
||||||
server_events.push(ServerEvent::Sound {
|
|
||||||
sound: Sound::new(SoundKind::Beam, pos.0, 7.0, time),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If beam segment is out of time emit destroy event but still continue since it
|
|
||||||
// may have traveled and produced effects a bit before reaching its
|
|
||||||
// end point
|
|
||||||
if end_time < time {
|
|
||||||
server_events.push(ServerEvent::Delete(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine area that was covered by the beam in the last tick
|
|
||||||
let frame_time = dt.min((end_time - time) as f32);
|
|
||||||
if frame_time <= 0.0 {
|
|
||||||
return (server_events, add_hit_entities, outcomes);
|
|
||||||
}
|
|
||||||
// Note: min() probably uneeded
|
|
||||||
let time_since_creation = (time - creation_time) as f32;
|
|
||||||
let frame_start_dist =
|
|
||||||
(beam_segment.speed * (time_since_creation - frame_time)).max(0.0);
|
|
||||||
let frame_end_dist = (beam_segment.speed * time_since_creation).max(frame_start_dist);
|
|
||||||
|
|
||||||
// Group to ignore collisions with
|
|
||||||
// Might make this more nuanced if beams are used for non damage effects
|
|
||||||
let group = beam_owner.and_then(|e| read_data.groups.get(e));
|
|
||||||
|
|
||||||
let hit_entities = if let Some(beam) = beam_owner.and_then(|e| beams.get(e)) {
|
|
||||||
&beam.hit_entities
|
|
||||||
} else {
|
|
||||||
return (server_events, add_hit_entities, outcomes);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Go through all affectable entities by querying the spatial grid
|
|
||||||
let target_iter = read_data
|
|
||||||
.cached_spatial_grid
|
|
||||||
.0
|
|
||||||
.in_circle_aabr(pos.0.xy(), frame_end_dist - frame_start_dist)
|
|
||||||
.filter_map(|target|{
|
|
||||||
read_data
|
|
||||||
.positions
|
|
||||||
.get(target)
|
|
||||||
.and_then(|l| read_data.healths.get(target).map(|r| (l,r)))
|
|
||||||
.and_then(|l| read_data.uids.get(target).map(|r| (l,r)))
|
|
||||||
.and_then(|l| read_data.bodies.get(target).map(|r| (l,r)))
|
|
||||||
.map(|(((pos_b, health_b), uid_b), body_b)| {
|
|
||||||
(target, uid_b, pos_b, health_b, body_b)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
target_iter.for_each(|(target, uid_b, pos_b, health_b, body_b)| {
|
|
||||||
// Check to see if entity has already been hit recently
|
|
||||||
if hit_entities.iter().any(|&uid| uid == *uid_b) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scales
|
|
||||||
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
|
|
||||||
let rad_b = body_b.radius() * scale_b;
|
|
||||||
let height_b = body_b.height() * scale_b;
|
|
||||||
|
|
||||||
// Check if it is a hit
|
|
||||||
let hit = entity != target && !health_b.is_dead
|
|
||||||
// Collision shapes
|
|
||||||
&& sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.look_dir(), beam_segment.angle, pos_b.0, rad_b, height_b);
|
|
||||||
|
|
||||||
// Finally, ensure that a hit has actually occurred by performing a raycast. We do this last because
|
|
||||||
// it's likely to be the most expensive operation.
|
|
||||||
let tgt_dist = pos.0.distance(pos_b.0);
|
|
||||||
let hit = hit && read_data.terrain
|
|
||||||
.ray(pos.0, pos.0 + *ori.look_dir() * (tgt_dist + 1.0))
|
|
||||||
.until(|b| b.is_filled())
|
|
||||||
.cast().0 >= tgt_dist;
|
|
||||||
|
|
||||||
if hit {
|
|
||||||
// See if entities are in the same group
|
|
||||||
let same_group = group
|
|
||||||
.map(|group_a| Some(group_a) == read_data.groups.get(target))
|
|
||||||
.unwrap_or(Some(*uid_b) == beam_segment.owner);
|
|
||||||
|
|
||||||
let target_group = if same_group {
|
|
||||||
GroupTarget::InGroup
|
|
||||||
} else {
|
|
||||||
GroupTarget::OutOfGroup
|
|
||||||
};
|
};
|
||||||
|
let end_time = creation_time + beam_segment.duration.as_secs_f64();
|
||||||
|
|
||||||
// If owner, shouldn't heal or damage
|
let beam_owner = beam_segment.owner.and_then(|uid| {
|
||||||
if Some(*uid_b) == beam_segment.owner {
|
read_data.uid_allocator.retrieve_entity_internal(uid.into())
|
||||||
return;
|
});
|
||||||
|
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
if rng.gen_bool(0.005) {
|
||||||
|
server_events.push(ServerEvent::Sound {
|
||||||
|
sound: Sound::new(SoundKind::Beam, pos.0, 7.0, time),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let attacker_info =
|
// If beam segment is out of time emit destroy event but still continue since it
|
||||||
beam_owner
|
// may have traveled and produced effects a bit before reaching its
|
||||||
.zip(beam_segment.owner)
|
// end point
|
||||||
.map(|(entity, uid)| AttackerInfo {
|
if end_time < time {
|
||||||
entity,
|
server_events.push(ServerEvent::Delete(entity));
|
||||||
uid,
|
}
|
||||||
energy: read_data.energies.get(entity),
|
|
||||||
combo: read_data.combos.get(entity),
|
|
||||||
inventory: read_data.inventories.get(entity),
|
|
||||||
});
|
|
||||||
|
|
||||||
let target_info = TargetInfo {
|
// Determine area that was covered by the beam in the last tick
|
||||||
entity: target,
|
let frame_time = dt.min((end_time - time) as f32);
|
||||||
uid: *uid_b,
|
if frame_time <= 0.0 {
|
||||||
inventory: read_data.inventories.get(target),
|
return (server_events, add_hit_entities, outcomes);
|
||||||
stats: read_data.stats.get(target),
|
}
|
||||||
health: read_data.healths.get(target),
|
// Note: min() probably uneeded
|
||||||
pos: pos_b.0,
|
let time_since_creation = (time - creation_time) as f32;
|
||||||
ori: read_data.orientations.get(target),
|
let frame_start_dist =
|
||||||
char_state: read_data.character_states.get(target),
|
(beam_segment.speed * (time_since_creation - frame_time)).max(0.0);
|
||||||
|
let frame_end_dist =
|
||||||
|
(beam_segment.speed * time_since_creation).max(frame_start_dist);
|
||||||
|
|
||||||
|
// Group to ignore collisions with
|
||||||
|
// Might make this more nuanced if beams are used for non damage effects
|
||||||
|
let group = beam_owner.and_then(|e| read_data.groups.get(e));
|
||||||
|
|
||||||
|
let hit_entities = if let Some(beam) = beam_owner.and_then(|e| beams.get(e)) {
|
||||||
|
&beam.hit_entities
|
||||||
|
} else {
|
||||||
|
return (server_events, add_hit_entities, outcomes);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Go through all affectable entities by querying the spatial grid
|
||||||
|
let target_iter = read_data
|
||||||
|
.cached_spatial_grid
|
||||||
|
.0
|
||||||
|
.in_circle_aabr(pos.0.xy(), frame_end_dist - frame_start_dist)
|
||||||
|
.filter_map(|target| {
|
||||||
|
read_data
|
||||||
|
.positions
|
||||||
|
.get(target)
|
||||||
|
.and_then(|l| read_data.healths.get(target).map(|r| (l, r)))
|
||||||
|
.and_then(|l| read_data.uids.get(target).map(|r| (l, r)))
|
||||||
|
.and_then(|l| read_data.bodies.get(target).map(|r| (l, r)))
|
||||||
|
.map(|(((pos_b, health_b), uid_b), body_b)| {
|
||||||
|
(target, uid_b, pos_b, health_b, body_b)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
target_iter.for_each(|(target, uid_b, pos_b, health_b, body_b)| {
|
||||||
|
// Check to see if entity has already been hit recently
|
||||||
|
if hit_entities.iter().any(|&uid| uid == *uid_b) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// PvP check
|
// Scales
|
||||||
let may_harm = combat::may_harm(
|
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
|
||||||
&read_data.alignments,
|
let rad_b = body_b.max_radius() * scale_b;
|
||||||
&read_data.players,
|
let height_b = body_b.height() * scale_b;
|
||||||
&read_data.uid_allocator,
|
|
||||||
beam_owner,
|
|
||||||
target,
|
|
||||||
);
|
|
||||||
let attack_options = AttackOptions {
|
|
||||||
// No luck with dodging beams
|
|
||||||
target_dodging: false,
|
|
||||||
may_harm,
|
|
||||||
target_group,
|
|
||||||
};
|
|
||||||
|
|
||||||
beam_segment.properties.attack.apply_attack(
|
// Check if it is a hit
|
||||||
attacker_info,
|
// TODO: use Capsule Prism instead of cylinder
|
||||||
target_info,
|
let hit = entity != target
|
||||||
ori.look_dir(),
|
&& !health_b.is_dead
|
||||||
attack_options,
|
&& sphere_wedge_cylinder_collision(
|
||||||
1.0,
|
pos.0,
|
||||||
AttackSource::Beam,
|
frame_start_dist,
|
||||||
|e| server_events.push(e),
|
frame_end_dist,
|
||||||
|o| outcomes.push(o),
|
*ori.look_dir(),
|
||||||
);
|
beam_segment.angle,
|
||||||
|
pos_b.0,
|
||||||
|
rad_b,
|
||||||
|
height_b,
|
||||||
|
);
|
||||||
|
|
||||||
add_hit_entities.push((beam_owner, *uid_b));
|
// Finally, ensure that a hit has actually occurred by performing a raycast.
|
||||||
}
|
// We do this last because it's likely to be the
|
||||||
});
|
// most expensive operation.
|
||||||
(server_events, add_hit_entities, outcomes)
|
let tgt_dist = pos.0.distance(pos_b.0);
|
||||||
}).reduce(|| (Vec::new(), Vec::new(), Vec::new()),
|
let hit = hit
|
||||||
|(mut events_a, mut hit_entities_a, mut outcomes_a),
|
&& read_data
|
||||||
(mut events_b, mut hit_entities_b, mut outcomes_b)| {
|
.terrain
|
||||||
events_a.append(&mut events_b);
|
.ray(pos.0, pos.0 + *ori.look_dir() * (tgt_dist + 1.0))
|
||||||
hit_entities_a.append(&mut hit_entities_b);
|
.until(|b| b.is_filled())
|
||||||
outcomes_a.append(&mut outcomes_b);
|
.cast()
|
||||||
(events_a, hit_entities_a, outcomes_a)
|
.0
|
||||||
});
|
>= tgt_dist;
|
||||||
|
|
||||||
|
if hit {
|
||||||
|
// See if entities are in the same group
|
||||||
|
let same_group = group
|
||||||
|
.map(|group_a| Some(group_a) == read_data.groups.get(target))
|
||||||
|
.unwrap_or(Some(*uid_b) == beam_segment.owner);
|
||||||
|
|
||||||
|
let target_group = if same_group {
|
||||||
|
GroupTarget::InGroup
|
||||||
|
} else {
|
||||||
|
GroupTarget::OutOfGroup
|
||||||
|
};
|
||||||
|
|
||||||
|
// If owner, shouldn't heal or damage
|
||||||
|
if Some(*uid_b) == beam_segment.owner {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attacker_info =
|
||||||
|
beam_owner.zip(beam_segment.owner).map(|(entity, uid)| {
|
||||||
|
AttackerInfo {
|
||||||
|
entity,
|
||||||
|
uid,
|
||||||
|
energy: read_data.energies.get(entity),
|
||||||
|
combo: read_data.combos.get(entity),
|
||||||
|
inventory: read_data.inventories.get(entity),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let target_info = TargetInfo {
|
||||||
|
entity: target,
|
||||||
|
uid: *uid_b,
|
||||||
|
inventory: read_data.inventories.get(target),
|
||||||
|
stats: read_data.stats.get(target),
|
||||||
|
health: read_data.healths.get(target),
|
||||||
|
pos: pos_b.0,
|
||||||
|
ori: read_data.orientations.get(target),
|
||||||
|
char_state: read_data.character_states.get(target),
|
||||||
|
};
|
||||||
|
|
||||||
|
// PvP check
|
||||||
|
let may_harm = combat::may_harm(
|
||||||
|
&read_data.alignments,
|
||||||
|
&read_data.players,
|
||||||
|
&read_data.uid_allocator,
|
||||||
|
beam_owner,
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
let attack_options = AttackOptions {
|
||||||
|
// No luck with dodging beams
|
||||||
|
target_dodging: false,
|
||||||
|
may_harm,
|
||||||
|
target_group,
|
||||||
|
};
|
||||||
|
|
||||||
|
beam_segment.properties.attack.apply_attack(
|
||||||
|
attacker_info,
|
||||||
|
target_info,
|
||||||
|
ori.look_dir(),
|
||||||
|
attack_options,
|
||||||
|
1.0,
|
||||||
|
AttackSource::Beam,
|
||||||
|
|e| server_events.push(e),
|
||||||
|
|o| outcomes.push(o),
|
||||||
|
);
|
||||||
|
|
||||||
|
add_hit_entities.push((beam_owner, *uid_b));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(server_events, add_hit_entities, outcomes)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
|| (Vec::new(), Vec::new(), Vec::new()),
|
||||||
|
|(mut events_a, mut hit_entities_a, mut outcomes_a),
|
||||||
|
(mut events_b, mut hit_entities_b, mut outcomes_b)| {
|
||||||
|
events_a.append(&mut events_b);
|
||||||
|
hit_entities_a.append(&mut hit_entities_b);
|
||||||
|
outcomes_a.append(&mut outcomes_b);
|
||||||
|
(events_a, hit_entities_a, outcomes_a)
|
||||||
|
},
|
||||||
|
);
|
||||||
job.cpu_stats.measure(ParMode::Single);
|
job.cpu_stats.measure(ParMode::Single);
|
||||||
outcomes.append(&mut new_outcomes);
|
outcomes.append(&mut new_outcomes);
|
||||||
|
|
||||||
|
@ -80,7 +80,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Scales
|
// Scales
|
||||||
let eye_pos = pos.0 + Vec3::unit_z() * body.eye_height();
|
let eye_pos = pos.0 + Vec3::unit_z() * body.eye_height();
|
||||||
let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0);
|
let scale = read_data.scales.get(attacker).map_or(1.0, |s| s.0);
|
||||||
let rad = body.radius() * scale;
|
// TODO: use Capsule Prisms instead of Cylinders
|
||||||
|
let rad = body.max_radius() * scale;
|
||||||
|
|
||||||
// Mine blocks broken by the attack
|
// Mine blocks broken by the attack
|
||||||
if let Some((block_pos, tool)) = melee_attack.break_block {
|
if let Some((block_pos, tool)) = melee_attack.break_block {
|
||||||
@ -115,7 +116,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
// Scales
|
// Scales
|
||||||
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
|
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
|
||||||
let rad_b = body_b.radius() * scale_b;
|
let rad_b = body_b.max_radius() * scale_b;
|
||||||
|
|
||||||
// Check if entity is dodging
|
// Check if entity is dodging
|
||||||
let target_dodging = read_data
|
let target_dodging = read_data
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -152,7 +152,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
// Scales
|
// Scales
|
||||||
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
|
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
|
||||||
let rad_b = body_b.radius() * scale_b;
|
// TODO: use Capsule Prism instead of Cylinder
|
||||||
|
let rad_b = body_b.max_radius() * scale_b;
|
||||||
|
|
||||||
// Angle checks
|
// Angle checks
|
||||||
let pos_b_ground = Vec3::new(pos_b.0.x, pos_b.0.y, pos.0.z);
|
let pos_b_ground = Vec3::new(pos_b.0.x, pos_b.0.y, pos.0.z);
|
||||||
|
@ -715,8 +715,8 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
|
|||||||
cyl_body: Body,
|
cyl_body: Body,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
// 2d check
|
// 2d check
|
||||||
let horiz_dist =
|
let horiz_dist = Vec2::<f32>::from(sphere_pos - cyl_pos).distance(Vec2::default())
|
||||||
Vec2::<f32>::from(sphere_pos - cyl_pos).distance(Vec2::default()) - cyl_body.radius();
|
- cyl_body.max_radius();
|
||||||
// z check
|
// z check
|
||||||
let half_body_height = cyl_body.height() / 2.0;
|
let half_body_height = cyl_body.height() / 2.0;
|
||||||
let vert_distance =
|
let vert_distance =
|
||||||
|
@ -203,11 +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(),
|
||||||
},
|
},
|
||||||
_ => comp::Collider::Box {
|
_ => capsule(&body),
|
||||||
radius: body.radius(),
|
|
||||||
z_min: 0.0,
|
|
||||||
z_max: body.height(),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.with(comp::Controller::default())
|
.with(comp::Controller::default())
|
||||||
.with(body)
|
.with(body)
|
||||||
@ -239,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.radius(),
|
|
||||||
z_min: 0.0,
|
|
||||||
z_max: body.height(),
|
|
||||||
})
|
|
||||||
.with(body)
|
.with(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,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.radius(),
|
|
||||||
z_min: 0.0,
|
|
||||||
z_max: body.height(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
projectile_base.with(projectile).with(body)
|
projectile_base.with(projectile).with(body)
|
||||||
@ -382,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).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)
|
||||||
@ -458,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,
|
||||||
@ -500,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.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());
|
||||||
@ -887,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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1783,9 +1783,9 @@ impl<'a> AgentData<'a> {
|
|||||||
// Wield the weapon as running towards the target
|
// Wield the weapon as running towards the target
|
||||||
controller.actions.push(ControlAction::Wield);
|
controller.actions.push(ControlAction::Wield);
|
||||||
|
|
||||||
let min_attack_dist = (self.body.map_or(0.5, |b| b.radius()) + DEFAULT_ATTACK_RANGE)
|
let min_attack_dist = (self.body.map_or(0.5, |b| b.max_radius()) + DEFAULT_ATTACK_RANGE)
|
||||||
* self.scale
|
* self.scale
|
||||||
+ tgt_data.body.map_or(0.5, |b| b.radius()) * tgt_data.scale.map_or(1.0, |s| s.0);
|
+ tgt_data.body.map_or(0.5, |b| b.max_radius()) * tgt_data.scale.map_or(1.0, |s| s.0);
|
||||||
let dist_sqrd = self.pos.0.distance_squared(tgt_data.pos.0);
|
let dist_sqrd = self.pos.0.distance_squared(tgt_data.pos.0);
|
||||||
let angle = self
|
let angle = self
|
||||||
.ori
|
.ori
|
||||||
@ -3676,7 +3676,7 @@ impl<'a> AgentData<'a> {
|
|||||||
) {
|
) {
|
||||||
const BIRD_ATTACK_RANGE: f32 = 4.0;
|
const BIRD_ATTACK_RANGE: f32 = 4.0;
|
||||||
const BIRD_CHARGE_DISTANCE: f32 = 15.0;
|
const BIRD_CHARGE_DISTANCE: f32 = 15.0;
|
||||||
let bird_attack_distance = self.body.map_or(0.0, |b| b.radius()) + BIRD_ATTACK_RANGE;
|
let bird_attack_distance = self.body.map_or(0.0, |b| b.max_radius()) + BIRD_ATTACK_RANGE;
|
||||||
// Increase action timer
|
// Increase action timer
|
||||||
agent.action_state.timer += read_data.dt.0;
|
agent.action_state.timer += read_data.dt.0;
|
||||||
// If higher than 2 blocks
|
// If higher than 2 blocks
|
||||||
@ -3748,7 +3748,7 @@ impl<'a> AgentData<'a> {
|
|||||||
const MINOTAUR_ATTACK_RANGE: f32 = 5.0;
|
const MINOTAUR_ATTACK_RANGE: f32 = 5.0;
|
||||||
const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0;
|
const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0;
|
||||||
let minotaur_attack_distance =
|
let minotaur_attack_distance =
|
||||||
self.body.map_or(0.0, |b| b.radius()) + MINOTAUR_ATTACK_RANGE;
|
self.body.map_or(0.0, |b| b.max_radius()) + MINOTAUR_ATTACK_RANGE;
|
||||||
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
|
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
|
||||||
// Sets action counter at start of combat
|
// Sets action counter at start of combat
|
||||||
if agent.action_state.counter < MINOTAUR_FRENZY_THRESHOLD
|
if agent.action_state.counter < MINOTAUR_FRENZY_THRESHOLD
|
||||||
@ -3815,7 +3815,7 @@ impl<'a> AgentData<'a> {
|
|||||||
const GOLEM_LASER_RANGE: f32 = 30.0;
|
const GOLEM_LASER_RANGE: f32 = 30.0;
|
||||||
const GOLEM_LONG_RANGE: f32 = 50.0;
|
const GOLEM_LONG_RANGE: f32 = 50.0;
|
||||||
const GOLEM_TARGET_SPEED: f32 = 8.0;
|
const GOLEM_TARGET_SPEED: f32 = 8.0;
|
||||||
let golem_melee_range = self.body.map_or(0.0, |b| b.radius()) + GOLEM_MELEE_RANGE;
|
let golem_melee_range = self.body.map_or(0.0, |b| b.max_radius()) + GOLEM_MELEE_RANGE;
|
||||||
// Fraction of health, used for activation of shockwave
|
// Fraction of health, used for activation of shockwave
|
||||||
// If golem don't have health for some reason, assume it's full
|
// If golem don't have health for some reason, assume it's full
|
||||||
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
|
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
|
||||||
|
@ -36,6 +36,8 @@ pub struct Locals {
|
|||||||
/// by the shader
|
/// by the shader
|
||||||
pub pos: [f32; 4],
|
pub pos: [f32; 4],
|
||||||
pub color: [f32; 4],
|
pub color: [f32; 4],
|
||||||
|
/// quaternion as [x, y, z, w]
|
||||||
|
pub ori: [f32; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BoundLocals = Bound<Consts<Locals>>;
|
pub type BoundLocals = Bound<Consts<Locals>>;
|
||||||
|
@ -9,12 +9,21 @@ use vek::*;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DebugShape {
|
pub enum DebugShape {
|
||||||
Line([Vec3<f32>; 2]),
|
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 {
|
impl DebugShape {
|
||||||
pub fn mesh(&self) -> Mesh<DebugVertex> {
|
pub fn mesh(&self) -> Mesh<DebugVertex> {
|
||||||
use core::f32::consts::PI;
|
use core::f32::consts::{PI, TAU};
|
||||||
let mut mesh = Mesh::new();
|
let mut mesh = Mesh::new();
|
||||||
let tri = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>| {
|
let tri = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>| {
|
||||||
Tri::<DebugVertex>::new(x.into(), y.into(), z.into())
|
Tri::<DebugVertex>::new(x.into(), y.into(), z.into())
|
||||||
@ -22,28 +31,115 @@ impl DebugShape {
|
|||||||
let quad = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>, w: Vec3<f32>| {
|
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())
|
Quad::<DebugVertex>::new(x.into(), y.into(), z.into(), w.into())
|
||||||
};
|
};
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
DebugShape::Line([a, b]) => {
|
DebugShape::Line([a, b]) => {
|
||||||
let h = Vec3::new(0.0, 1.0, 0.0);
|
let h = Vec3::new(0.0, 1.0, 0.0);
|
||||||
mesh.push_quad(quad(*a, a + h, b + h, *b));
|
mesh.push_quad(quad(*a, a + h, b + h, *b));
|
||||||
},
|
},
|
||||||
DebugShape::Cylinder { radius, height } => {
|
DebugShape::Cylinder { radius, height } => {
|
||||||
const SUBDIVISIONS: usize = 16;
|
const SUBDIVISIONS: u8 = 16;
|
||||||
for i in 0..SUBDIVISIONS {
|
for i in 0..SUBDIVISIONS {
|
||||||
let angle = |j: usize| (j as f32 / SUBDIVISIONS as f32) * 2.0 * PI;
|
// dot on circle edge
|
||||||
let a = Vec3::zero();
|
let to = |n: u8| {
|
||||||
let b = Vec3::new(radius * angle(i).cos(), radius * angle(i).sin(), 0.0);
|
let angle = TAU * f32::from(n) / f32::from(SUBDIVISIONS);
|
||||||
let c = Vec3::new(
|
|
||||||
radius * angle(i + 1).cos(),
|
Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0)
|
||||||
radius * angle(i + 1).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);
|
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));
|
// Draw bottom sector
|
||||||
mesh.push_tri(tri(a + h, b + h, c + h));
|
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| {
|
||||||
|
let angle = offset + TAU * 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
|
mesh
|
||||||
}
|
}
|
||||||
@ -55,7 +151,8 @@ pub struct DebugShapeId(pub u64);
|
|||||||
pub struct Debug {
|
pub struct Debug {
|
||||||
next_shape_id: DebugShapeId,
|
next_shape_id: DebugShapeId,
|
||||||
pending_shapes: HashMap<DebugShapeId, DebugShape>,
|
pending_shapes: HashMap<DebugShapeId, DebugShape>,
|
||||||
pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4])>,
|
#[allow(clippy::type_complexity)]
|
||||||
|
pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4], [f32; 4])>,
|
||||||
pending_deletes: HashSet<DebugShapeId>,
|
pending_deletes: HashSet<DebugShapeId>,
|
||||||
models: HashMap<DebugShapeId, (Model<DebugVertex>, Bound<Consts<DebugLocals>>)>,
|
models: HashMap<DebugShapeId, (Model<DebugVertex>, Bound<Consts<DebugLocals>>)>,
|
||||||
}
|
}
|
||||||
@ -78,8 +175,8 @@ impl Debug {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pos_and_color(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4]) {
|
pub fn set_context(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4], ori: [f32; 4]) {
|
||||||
self.pending_locals.insert(id, (pos, color));
|
self.pending_locals.insert(id, (pos, color, ori));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); }
|
pub fn remove_shape(&mut self, id: DebugShapeId) { self.pending_deletes.insert(id); }
|
||||||
@ -90,6 +187,7 @@ impl Debug {
|
|||||||
let locals = renderer.create_debug_bound_locals(&[DebugLocals {
|
let locals = renderer.create_debug_bound_locals(&[DebugLocals {
|
||||||
pos: [0.0; 4],
|
pos: [0.0; 4],
|
||||||
color: [1.0, 0.0, 0.0, 1.0],
|
color: [1.0, 0.0, 0.0, 1.0],
|
||||||
|
ori: [0.0, 0.0, 0.0, 1.0],
|
||||||
}]);
|
}]);
|
||||||
self.models.insert(id, (model, locals));
|
self.models.insert(id, (model, locals));
|
||||||
} else {
|
} else {
|
||||||
@ -99,12 +197,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) {
|
if let Some((_, locals)) = self.models.get_mut(&id) {
|
||||||
let lc = srgba_to_linear(color.into());
|
let lc = srgba_to_linear(color.into());
|
||||||
let new_locals = [DebugLocals {
|
let new_locals = [DebugLocals {
|
||||||
pos,
|
pos,
|
||||||
color: [lc.r, lc.g, lc.b, lc.a],
|
color: [lc.r, lc.g, lc.b, lc.a],
|
||||||
|
ori,
|
||||||
}];
|
}];
|
||||||
renderer.update_consts(locals, &new_locals);
|
renderer.update_consts(locals, &new_locals);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1152,32 +1152,49 @@ impl Scene {
|
|||||||
if settings.interface.toggle_hitboxes {
|
if settings.interface.toggle_hitboxes {
|
||||||
let positions = ecs.read_component::<comp::Pos>();
|
let positions = ecs.read_component::<comp::Pos>();
|
||||||
let colliders = ecs.read_component::<comp::Collider>();
|
let colliders = ecs.read_component::<comp::Collider>();
|
||||||
|
let orientations = ecs.read_component::<comp::Ori>();
|
||||||
let groups = ecs.read_component::<comp::Group>();
|
let groups = ecs.read_component::<comp::Group>();
|
||||||
for (entity, pos, collider, group) in
|
for (entity, pos, collider, ori, group) in (
|
||||||
(&ecs.entities(), &positions, &colliders, groups.maybe()).join()
|
&ecs.entities(),
|
||||||
|
&positions,
|
||||||
|
&colliders,
|
||||||
|
&orientations,
|
||||||
|
groups.maybe(),
|
||||||
|
)
|
||||||
|
.join()
|
||||||
{
|
{
|
||||||
if let comp::Collider::Box {
|
match collider {
|
||||||
radius,
|
comp::Collider::CapsulePrism {
|
||||||
z_min,
|
p0,
|
||||||
z_max,
|
p1,
|
||||||
} = collider
|
radius,
|
||||||
{
|
z_min,
|
||||||
current_entities.insert(entity);
|
z_max,
|
||||||
let shape_id = hitboxes.entry(entity).or_insert_with(|| {
|
} => {
|
||||||
self.debug.add_shape(DebugShape::Cylinder {
|
current_entities.insert(entity);
|
||||||
radius: *radius,
|
let shape_id = hitboxes.entry(entity).or_insert_with(|| {
|
||||||
height: *z_max - *z_min,
|
self.debug.add_shape(DebugShape::CapsulePrism {
|
||||||
})
|
p0: *p0,
|
||||||
});
|
p1: *p1,
|
||||||
let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0];
|
radius: *radius,
|
||||||
let color = if group == Some(&comp::group::ENEMY) {
|
height: *z_max - *z_min,
|
||||||
[1.0, 0.0, 0.0, 0.5]
|
})
|
||||||
} else if group == Some(&comp::group::NPC) {
|
});
|
||||||
[0.0, 0.0, 1.0, 0.5]
|
let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min, 0.0];
|
||||||
} else {
|
let color = if group == Some(&comp::group::ENEMY) {
|
||||||
[0.0, 1.0, 0.0, 0.5]
|
[1.0, 0.0, 0.0, 0.5]
|
||||||
};
|
} else if group == Some(&comp::group::NPC) {
|
||||||
self.debug.set_pos_and_color(*shape_id, hb_pos, color);
|
[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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,7 @@ impl ParticleMgr {
|
|||||||
0.0,
|
0.0,
|
||||||
)
|
)
|
||||||
.normalized()
|
.normalized()
|
||||||
* (body.radius() + 4.0)
|
* (body.max_radius() + 4.0)
|
||||||
+ Vec3::unit_z() * (body.height() + 2.0) * rng.gen::<f32>();
|
+ Vec3::unit_z() * (body.height() + 2.0) * rng.gen::<f32>();
|
||||||
|
|
||||||
Particle::new_directed(
|
Particle::new_directed(
|
||||||
@ -688,7 +688,7 @@ impl ParticleMgr {
|
|||||||
0.0,
|
0.0,
|
||||||
)
|
)
|
||||||
.normalized()
|
.normalized()
|
||||||
* (body.radius() + 2.0)
|
* (body.max_radius() + 2.0)
|
||||||
+ Vec3::unit_z() * body.height() * rng.gen::<f32>();
|
+ Vec3::unit_z() * body.height() * rng.gen::<f32>();
|
||||||
|
|
||||||
let (start_pos, end_pos) =
|
let (start_pos, end_pos) =
|
||||||
@ -720,8 +720,8 @@ impl ParticleMgr {
|
|||||||
|| {
|
|| {
|
||||||
let start_pos = pos.0
|
let start_pos = pos.0
|
||||||
+ Vec3::new(
|
+ Vec3::new(
|
||||||
body.radius(),
|
body.max_radius(),
|
||||||
body.radius(),
|
body.max_radius(),
|
||||||
body.height() / 2.0,
|
body.height() / 2.0,
|
||||||
)
|
)
|
||||||
.map(|d| d * rng.gen_range(-1.0..1.0));
|
.map(|d| d * rng.gen_range(-1.0..1.0));
|
||||||
@ -1021,8 +1021,12 @@ impl ParticleMgr {
|
|||||||
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(15))),
|
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(15))),
|
||||||
|| {
|
|| {
|
||||||
let start_pos = pos.0
|
let start_pos = pos.0
|
||||||
+ Vec3::new(body.radius(), body.radius(), body.height() / 2.0)
|
+ Vec3::new(
|
||||||
.map(|d| d * rng.gen_range(-1.0..1.0));
|
body.max_radius(),
|
||||||
|
body.max_radius(),
|
||||||
|
body.height() / 2.0,
|
||||||
|
)
|
||||||
|
.map(|d| d * rng.gen_range(-1.0..1.0));
|
||||||
let end_pos = start_pos
|
let end_pos = start_pos
|
||||||
+ Vec3::unit_z() * body.height()
|
+ Vec3::unit_z() * body.height()
|
||||||
+ Vec3::<f32>::zero()
|
+ Vec3::<f32>::zero()
|
||||||
|
@ -1612,12 +1612,13 @@ fn under_cursor(
|
|||||||
.filter_map(|(e, p, s, b, i)| {
|
.filter_map(|(e, p, s, b, i)| {
|
||||||
const RADIUS_SCALE: f32 = 3.0;
|
const RADIUS_SCALE: f32 = 3.0;
|
||||||
// TODO: use collider radius instead of body radius?
|
// TODO: use collider radius instead of body radius?
|
||||||
let radius = s.map_or(1.0, |s| s.0) * b.radius() * RADIUS_SCALE;
|
let radius = s.map_or(1.0, |s| s.0) * b.max_radius() * RADIUS_SCALE;
|
||||||
// Move position up from the feet
|
// Move position up from the feet
|
||||||
let pos = Vec3::new(p.0.x, p.0.y, p.0.z + radius);
|
let pos = Vec3::new(p.0.x, p.0.y, p.0.z + radius);
|
||||||
// Distance squared from camera to the entity
|
// Distance squared from camera to the entity
|
||||||
let dist_sqr = pos.distance_squared(cam_pos);
|
let dist_sqr = pos.distance_squared(cam_pos);
|
||||||
// We only care about interacting with entities that contain items, or are not inanimate (to trade with)
|
// We only care about interacting with entities that contain items,
|
||||||
|
// or are not inanimate (to trade with)
|
||||||
if i.is_some() || !matches!(b, comp::Body::Object(_)) {
|
if i.is_some() || !matches!(b, comp::Body::Object(_)) {
|
||||||
Some((e, pos, radius, dist_sqr))
|
Some((e, pos, radius, dist_sqr))
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,9 +59,10 @@ impl EguiState {
|
|||||||
scene.debug.remove_shape(DebugShapeId(*debug_shape_id));
|
scene.debug.remove_shape(DebugShapeId(*debug_shape_id));
|
||||||
},
|
},
|
||||||
DebugShapeAction::SetPosAndColor { id, pos, color } => {
|
DebugShapeAction::SetPosAndColor { id, pos, color } => {
|
||||||
|
let identity_ori = [0.0, 0.0, 0.0, 1.0];
|
||||||
scene
|
scene
|
||||||
.debug
|
.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