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