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 {
|
||||
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,14 @@ 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
|
||||
|
||||
impl Body {
|
||||
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
|
||||
/// 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 +359,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 {
|
||||
@ -389,9 +400,60 @@ impl Body {
|
||||
// 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.
|
||||
pub fn radius(&self) -> f32 {
|
||||
pub fn max_radius(&self) -> f32 {
|
||||
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
|
||||
@ -399,7 +461,7 @@ impl Body {
|
||||
// lead to that both entities will try to keep 5.0 units away from each
|
||||
// other.
|
||||
pub fn spacing_radius(&self) -> f32 {
|
||||
self.radius()
|
||||
self.max_radius()
|
||||
+ match self {
|
||||
Body::QuadrupedSmall(body) => match body.species {
|
||||
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 base_energy(&self) -> u32 {
|
||||
|
@ -54,7 +54,12 @@ pub struct PreviousPhysCache {
|
||||
/// Calculates a Sphere over the Entity for quick boundary checking
|
||||
pub collision_boundary: f32,
|
||||
pub scale: f32,
|
||||
/// Approximate radius of cylinder of collider.
|
||||
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>,
|
||||
}
|
||||
|
||||
@ -99,18 +104,30 @@ 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,
|
||||
},
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl Collider {
|
||||
pub fn get_radius(&self) -> f32 {
|
||||
pub fn bounding_radius(&self) -> f32 {
|
||||
match self {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -123,7 +140,7 @@ impl Collider {
|
||||
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
let body_radius = body.radius();
|
||||
let body_radius = body.min_radius();
|
||||
let body_offsets_z = height_offset(body, look_dir);
|
||||
|
||||
Vec3::new(
|
||||
body_radius * ori.x * 1.1,
|
||||
body_radius * ori.y * 1.1,
|
||||
|
@ -196,7 +196,7 @@ impl CharacterBehavior for Data {
|
||||
Outcome::GroundSlam {
|
||||
pos: data.pos.0
|
||||
+ *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
|
||||
let sprite_range_check = |pos: Vec3<f32>| {
|
||||
(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
|
||||
|
@ -39,7 +39,7 @@ impl Cylinder {
|
||||
char_state: Option<&crate::comp::CharacterState>,
|
||||
) -> Self {
|
||||
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
|
||||
.filter(|char_state| char_state.is_dodge())
|
||||
.map_or(1.0, |_| 0.5)
|
||||
|
@ -82,174 +82,193 @@ impl<'a> System<'a> for Sys {
|
||||
&beam_segments,
|
||||
)
|
||||
.par_join()
|
||||
.fold(|| (Vec::new(), Vec::new(), Vec::new()),
|
||||
|(mut server_events, mut add_hit_entities, mut outcomes),
|
||||
(entity, pos, ori, beam_segment)|
|
||||
{
|
||||
let creation_time = match beam_segment.creation {
|
||||
Some(time) => time,
|
||||
// Skip newly created beam segments
|
||||
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
|
||||
.fold(
|
||||
|| (Vec::new(), Vec::new(), Vec::new()),
|
||||
|(mut server_events, mut add_hit_entities, mut outcomes),
|
||||
(entity, pos, ori, beam_segment)| {
|
||||
let creation_time = match beam_segment.creation {
|
||||
Some(time) => time,
|
||||
// Skip newly created beam segments
|
||||
None => return (server_events, add_hit_entities, outcomes),
|
||||
};
|
||||
let end_time = creation_time + beam_segment.duration.as_secs_f64();
|
||||
|
||||
// If owner, shouldn't heal or damage
|
||||
if Some(*uid_b) == beam_segment.owner {
|
||||
return;
|
||||
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),
|
||||
});
|
||||
}
|
||||
|
||||
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),
|
||||
});
|
||||
// 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));
|
||||
}
|
||||
|
||||
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),
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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,
|
||||
};
|
||||
// Scales
|
||||
let scale_b = read_data.scales.get(target).map_or(1.0, |s| s.0);
|
||||
let rad_b = body_b.max_radius() * scale_b;
|
||||
let height_b = body_b.height() * scale_b;
|
||||
|
||||
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),
|
||||
);
|
||||
// Check if it is a hit
|
||||
// TODO: use Capsule Prism instead of cylinder
|
||||
let hit = entity != target
|
||||
&& !health_b.is_dead
|
||||
&& 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,
|
||||
);
|
||||
|
||||
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)
|
||||
});
|
||||
// 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
|
||||
};
|
||||
|
||||
// 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);
|
||||
outcomes.append(&mut new_outcomes);
|
||||
|
||||
|
@ -80,7 +80,8 @@ impl<'a> System<'a> for Sys {
|
||||
// Scales
|
||||
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 rad = body.radius() * scale;
|
||||
// TODO: use Capsule Prisms instead of Cylinders
|
||||
let rad = body.max_radius() * scale;
|
||||
|
||||
// Mine blocks broken by the attack
|
||||
if let Some((block_pos, tool)) = melee_attack.break_block {
|
||||
@ -115,7 +116,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// 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 rad_b = body_b.max_radius() * scale_b;
|
||||
|
||||
// Check if entity is dodging
|
||||
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
|
||||
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
|
||||
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,
|
||||
) -> f32 {
|
||||
// 2d check
|
||||
let horiz_dist =
|
||||
Vec2::<f32>::from(sphere_pos - cyl_pos).distance(Vec2::default()) - cyl_body.radius();
|
||||
let horiz_dist = Vec2::<f32>::from(sphere_pos - cyl_pos).distance(Vec2::default())
|
||||
- cyl_body.max_radius();
|
||||
// z check
|
||||
let half_body_height = cyl_body.height() / 2.0;
|
||||
let vert_distance =
|
||||
|
@ -203,11 +203,7 @@ impl StateExt for State {
|
||||
comp::Body::Ship(ship) => comp::Collider::Voxel {
|
||||
id: ship.manifest_entry().to_string(),
|
||||
},
|
||||
_ => comp::Collider::Box {
|
||||
radius: body.radius(),
|
||||
z_min: 0.0,
|
||||
z_max: body.height(),
|
||||
},
|
||||
_ => capsule(&body),
|
||||
})
|
||||
.with(comp::Controller::default())
|
||||
.with(body)
|
||||
@ -239,11 +235,7 @@ impl StateExt for State {
|
||||
.with(comp::Ori::default())
|
||||
.with(body.mass())
|
||||
.with(body.density())
|
||||
.with(comp::Collider::Box {
|
||||
radius: body.radius(),
|
||||
z_min: 0.0,
|
||||
z_max: body.height(),
|
||||
})
|
||||
.with(capsule(&body))
|
||||
.with(body)
|
||||
}
|
||||
|
||||
@ -305,11 +297,7 @@ impl StateExt for State {
|
||||
if projectile.is_point {
|
||||
projectile_base = projectile_base.with(comp::Collider::Point)
|
||||
} else {
|
||||
projectile_base = projectile_base.with(comp::Collider::Box {
|
||||
radius: body.radius(),
|
||||
z_min: 0.0,
|
||||
z_max: body.height(),
|
||||
})
|
||||
projectile_base = projectile_base.with(capsule(&body))
|
||||
}
|
||||
|
||||
projectile_base.with(projectile).with(body)
|
||||
@ -382,11 +370,7 @@ impl StateExt for State {
|
||||
.with(pos)
|
||||
.with(comp::Vel(Vec3::zero()))
|
||||
.with(comp::Ori::default())
|
||||
.with(comp::Collider::Box {
|
||||
radius: comp::Body::Object(object).radius(),
|
||||
z_min: 0.0,
|
||||
z_max: comp::Body::Object(object).height()
|
||||
})
|
||||
.with(capsule(&object.into()))
|
||||
.with(comp::Body::Object(object))
|
||||
.with(comp::Mass(10.0))
|
||||
// .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::Vel(Vec3::zero()));
|
||||
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,
|
||||
z_min: 0.0,
|
||||
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
|
||||
// commands, so we can assume that all of these calls succeed,
|
||||
// justifying ignoring the result of insertion.
|
||||
self.write_component_ignore_entity_dead(entity, comp::Collider::Box {
|
||||
radius: body.radius(),
|
||||
z_min: 0.0,
|
||||
z_max: body.height(),
|
||||
});
|
||||
self.write_component_ignore_entity_dead(entity, capsule(&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.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
|
||||
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
|
||||
+ 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 angle = self
|
||||
.ori
|
||||
@ -3676,7 +3676,7 @@ impl<'a> AgentData<'a> {
|
||||
) {
|
||||
const BIRD_ATTACK_RANGE: f32 = 4.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
|
||||
agent.action_state.timer += read_data.dt.0;
|
||||
// If higher than 2 blocks
|
||||
@ -3748,7 +3748,7 @@ impl<'a> AgentData<'a> {
|
||||
const MINOTAUR_ATTACK_RANGE: f32 = 5.0;
|
||||
const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0;
|
||||
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());
|
||||
// Sets action counter at start of combat
|
||||
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_LONG_RANGE: f32 = 50.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
|
||||
// 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());
|
||||
|
@ -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,12 +9,21 @@ 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 {
|
||||
pub fn mesh(&self) -> Mesh<DebugVertex> {
|
||||
use core::f32::consts::PI;
|
||||
use core::f32::consts::{PI, TAU};
|
||||
let mut mesh = Mesh::new();
|
||||
let tri = |x: Vec3<f32>, y: Vec3<f32>, z: Vec3<f32>| {
|
||||
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>| {
|
||||
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| {
|
||||
let angle = TAU * 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| {
|
||||
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
|
||||
}
|
||||
@ -55,7 +151,8 @@ 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])>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
pending_locals: HashMap<DebugShapeId, ([f32; 4], [f32; 4], [f32; 4])>,
|
||||
pending_deletes: HashSet<DebugShapeId>,
|
||||
models: HashMap<DebugShapeId, (Model<DebugVertex>, Bound<Consts<DebugLocals>>)>,
|
||||
}
|
||||
@ -78,8 +175,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 +187,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 +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) {
|
||||
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,49 @@ 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::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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ impl ParticleMgr {
|
||||
0.0,
|
||||
)
|
||||
.normalized()
|
||||
* (body.radius() + 4.0)
|
||||
* (body.max_radius() + 4.0)
|
||||
+ Vec3::unit_z() * (body.height() + 2.0) * rng.gen::<f32>();
|
||||
|
||||
Particle::new_directed(
|
||||
@ -688,7 +688,7 @@ impl ParticleMgr {
|
||||
0.0,
|
||||
)
|
||||
.normalized()
|
||||
* (body.radius() + 2.0)
|
||||
* (body.max_radius() + 2.0)
|
||||
+ Vec3::unit_z() * body.height() * rng.gen::<f32>();
|
||||
|
||||
let (start_pos, end_pos) =
|
||||
@ -720,8 +720,8 @@ impl ParticleMgr {
|
||||
|| {
|
||||
let start_pos = pos.0
|
||||
+ Vec3::new(
|
||||
body.radius(),
|
||||
body.radius(),
|
||||
body.max_radius(),
|
||||
body.max_radius(),
|
||||
body.height() / 2.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))),
|
||||
|| {
|
||||
let start_pos = pos.0
|
||||
+ Vec3::new(body.radius(), body.radius(), body.height() / 2.0)
|
||||
.map(|d| d * rng.gen_range(-1.0..1.0));
|
||||
+ Vec3::new(
|
||||
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
|
||||
+ Vec3::unit_z() * body.height()
|
||||
+ Vec3::<f32>::zero()
|
||||
|
@ -1612,12 +1612,13 @@ fn under_cursor(
|
||||
.filter_map(|(e, p, s, b, i)| {
|
||||
const RADIUS_SCALE: f32 = 3.0;
|
||||
// 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
|
||||
let pos = Vec3::new(p.0.x, p.0.y, p.0.z + radius);
|
||||
// Distance squared from camera to the entity
|
||||
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(_)) {
|
||||
Some((e, pos, radius, dist_sqr))
|
||||
} else {
|
||||
|
@ -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