Cleaned up and documented voxel collider code

This commit is contained in:
Joshua Barretto 2023-05-24 12:17:08 +01:00
parent 04687aa0b2
commit a09dbf0396
6 changed files with 148 additions and 93 deletions

View File

@ -163,7 +163,7 @@
), ),
Submarine: ( Submarine: (
bone0: ( bone0: (
offset: (-5.5, -18.0, -4.5), offset: (-5.5, -18.0, -1.0),
central: ("submarine.structure"), central: ("submarine.structure"),
), ),
bone1: ( bone1: (

View File

@ -75,7 +75,7 @@ impl Body {
Body::SailBoat => Vec3::new(12.0, 32.0, 6.0), Body::SailBoat => Vec3::new(12.0, 32.0, 6.0),
Body::Galleon => Vec3::new(14.0, 48.0, 10.0), Body::Galleon => Vec3::new(14.0, 48.0, 10.0),
Body::Skiff => Vec3::new(7.0, 15.0, 10.0), Body::Skiff => Vec3::new(7.0, 15.0, 10.0),
Body::Submarine => Vec3::new(2.0, 15.0, 2.0), Body::Submarine => Vec3::new(2.0, 15.0, 8.0),
} }
} }
@ -110,7 +110,7 @@ impl Body {
Body::DefaultAirship | Body::AirBalloon | Body::Volume => Density(AIR_DENSITY), Body::DefaultAirship | Body::AirBalloon | Body::Volume => Density(AIR_DENSITY),
Body::Submarine => Density(WATER_DENSITY), // Neutrally buoyant Body::Submarine => Density(WATER_DENSITY), // Neutrally buoyant
_ => Density(AIR_DENSITY * 0.95 + WATER_DENSITY * 0.05), /* Most boats should be very _ => Density(AIR_DENSITY * 0.95 + WATER_DENSITY * 0.05), /* Most boats should be very
* buoyant */ * buoyant */
} }
} }

View File

@ -236,7 +236,7 @@ impl Body {
match self { match self {
Body::Object(_) => return None, Body::Object(_) => return None,
Body::ItemDrop(_) => return None, Body::ItemDrop(_) => return None,
Body::Ship(ship::Body::Submarine) => 2000.0 * self.mass().0, Body::Ship(ship::Body::Submarine) => 1000.0 * self.mass().0,
Body::Ship(ship) if ship.has_water_thrust() => 500.0 * self.mass().0, Body::Ship(ship) if ship.has_water_thrust() => 500.0 * self.mass().0,
Body::Ship(_) => return None, Body::Ship(_) => return None,
Body::BipedLarge(_) => 120.0 * self.mass().0, Body::BipedLarge(_) => 120.0 * self.mass().0,

View File

@ -1119,96 +1119,125 @@ impl<'a> PhysicsData<'a> {
} }
let mut physics_state_delta = physics_state.clone(); let mut physics_state_delta = physics_state.clone();
// deliberately don't use scale yet here, because the
// 11.0/0.8 thing is
// in the comp::Scale for visual reasons
let mut cpos = *pos;
let wpos = cpos.0;
// TODO: Cache the matrices here to avoid recomputing // Helper function for computing a transformation matrix and its
// inverse. Should
// be much cheaper than using `Mat4::inverted`.
let from_to_matricies =
|entity_rpos: Vec3<f32>, collider_ori: Quaternion<f32>| {
(
Mat4::<f32>::translation_3d(entity_rpos)
* Mat4::from(collider_ori)
* Mat4::scaling_3d(previous_cache_other.scale)
* Mat4::translation_3d(
voxel_collider.translation,
),
Mat4::<f32>::translation_3d(
-voxel_collider.translation,
) * Mat4::scaling_3d(
1.0 / previous_cache_other.scale,
) * Mat4::from(collider_ori.inverse())
* Mat4::translation_3d(-entity_rpos),
)
};
let transform_last_from = // Compute matrices that allow us to transform to and from the
Mat4::<f32>::translation_3d( // coordinate space of
// the collider. We have two variants of each, one for the
// current state and one for
// the previous state. This allows us to 'perfectly' track
// change in position
// between ticks, which prevents entities falling through voxel
// colliders due to spurious
// issues like differences in ping/variable dt.
// TODO: Cache the matrices here to avoid recomputing for each
// entity on them
let (_transform_last_from, transform_last_to) =
from_to_matricies(
previous_cache_other.pos.unwrap_or(*pos_other).0 previous_cache_other.pos.unwrap_or(*pos_other).0
- previous_cache.pos.unwrap_or(Pos(wpos)).0, - previous_cache.pos.unwrap_or(*pos).0,
) * Mat4::from(previous_cache_other.ori) previous_cache_other.ori,
* Mat4::<f32>::scaling_3d(previous_cache_other.scale) );
* Mat4::<f32>::translation_3d( let (transform_from, transform_to) =
voxel_collider.translation, from_to_matricies(pos_other.0 - pos.0, ori_other.to_quat());
);
let transform_last_to = transform_last_from.inverted();
let transform_from = // Compute the velocity of the collider, accounting for changes
Mat4::<f32>::translation_3d(pos_other.0 - wpos) // in orientation
* Mat4::from(ori_other.to_quat()) // from the last tick. We then model this change as a change in
* Mat4::<f32>::scaling_3d(previous_cache_other.scale) // surface velocity
* Mat4::<f32>::translation_3d( // for the collider.
voxel_collider.translation, let vel_other = {
); let pos_rel =
let transform_to = transform_from.inverted(); (Mat4::<f32>::translation_3d(
-voxel_collider.translation,
) * Mat4::from(ori_other.to_quat().inverse()))
.mul_point(pos.0 - pos_other.0);
let rpos_last =
(Mat4::<f32>::from(previous_cache_other.ori)
* Mat4::translation_3d(voxel_collider.translation))
.mul_point(pos_rel);
vel_other.0
+ (pos.0 - (pos_other.0 + rpos_last)) / read.dt.0
};
let ori_last_from = Mat4::from(previous_cache_other.ori); {
let ori_last_to = ori_last_from.inverted(); // Transform the entity attributes into the coordinate space
// of the collider ready
// for collision resolution
let mut rpos =
Pos(transform_last_to.mul_point(Vec3::zero()));
vel.0 = previous_cache_other.ori.inverse()
* (vel.0 - vel_other);
let ori_from = Mat4::from(ori_other.to_quat()); // Perform collision resolution
let ori_to = ori_from.inverted(); box_voxel_collision(
(radius, z_min, z_max),
&voxel_collider.volume(),
entity,
&mut rpos,
transform_to.mul_point(tgt_pos - pos.0),
&mut vel,
&mut physics_state_delta,
previous_cache_other.ori.inverse() * vel_other,
&read.dt,
was_on_ground,
block_snap,
climbing,
|entity, vel| {
land_on_ground = Some((
entity,
Vel(ori_other.to_quat() * vel.0),
));
},
read,
&ori,
);
// The velocity of the collider, taking into account // Transform entity attributes back into world space now
// orientation. // that we've performed
let pos_rel = (Mat4::<f32>::translation_3d(Vec3::zero()) // collision resolution with them
* Mat4::from(ori_other.to_quat()) tgt_pos = transform_from.mul_point(rpos.0) + pos.0;
* Mat4::<f32>::translation_3d(voxel_collider.translation)) vel.0 = previous_cache_other.ori * vel.0 + vel_other;
.inverted() }
.mul_point(wpos - pos_other.0);
let rpos_last = (Mat4::<f32>::translation_3d(Vec3::zero())
* Mat4::from(previous_cache_other.ori)
* Mat4::<f32>::translation_3d(voxel_collider.translation))
.mul_point(pos_rel);
let vel_other = vel_other.0
+ (wpos - (pos_other.0 + rpos_last)) / read.dt.0;
// Velocity added due to change in orientation, relative to the // Collision resolution may also change the physics state. Since
// craft // we may be interacting
let ori_vel = previous_cache_other.ori.inverse() // with multiple colliders at once (along with the regular
* (pos.0 - pos_other.0) // terrain!) we keep track
- ori_other.to_quat().inverse() * (pos.0 - pos_other.0); // of a physics state 'delta' and try to sensibly resolve them
// against one-another at each step.
cpos.0 = transform_last_to.mul_point(Vec3::zero());
vel.0 = ori_last_to.mul_direction(vel.0 - vel_other) - ori_vel;
let cylinder = (radius, z_min, z_max);
box_voxel_collision(
cylinder,
&voxel_collider.volume(),
entity,
&mut cpos,
transform_to.mul_point(tgt_pos - wpos),
&mut vel,
&mut physics_state_delta,
ori_last_to.mul_direction(vel_other),
&read.dt,
was_on_ground,
block_snap,
climbing,
|entity, vel| {
land_on_ground =
Some((entity, Vel(ori_from.mul_direction(vel.0))));
},
read,
&ori,
);
cpos.0 = transform_from.mul_point(cpos.0) + wpos;
vel.0 = ori_from.mul_direction(vel.0 + ori_vel) + vel_other;
tgt_pos = cpos.0;
// union in the state updates, so that the state isn't just
// based on the most
// recent terrain that collision was attempted with
if physics_state_delta.on_ground.is_some() { if physics_state_delta.on_ground.is_some() {
physics_state.ground_vel = // TODO: Do we need to do this? Perhaps just take the
vel_other + ori_from.mul_direction(ori_vel); // ground_vel regardless?
physics_state.ground_vel = vel_other;
// Rotate if on ground }
if physics_state_delta.on_surface().is_some() {
// If the collision resulted in us being on a surface,
// rotate us with the
// collider. Really this should be modelled via friction or
// something, but
// our physics model doesn't really take orientation into
// consideration.
ori = ori.rotated( ori = ori.rotated(
ori_other.to_quat() ori_other.to_quat()
* previous_cache_other.ori.inverse(), * previous_cache_other.ori.inverse(),
@ -1220,7 +1249,7 @@ impl<'a> PhysicsData<'a> {
physics_state.on_wall = physics_state.on_wall.or_else(|| { physics_state.on_wall = physics_state.on_wall.or_else(|| {
physics_state_delta physics_state_delta
.on_wall .on_wall
.map(|dir| ori_from.mul_direction(dir)) .map(|dir| previous_cache_other.ori * dir)
}); });
physics_state.in_fluid = match ( physics_state.in_fluid = match (
physics_state.in_fluid, physics_state.in_fluid,

View File

@ -114,6 +114,7 @@ pub enum TrailSource {
Weapon, Weapon,
GliderLeft, GliderLeft,
GliderRight, GliderRight,
Propeller(f32),
} }
impl TrailSource { impl TrailSource {
@ -147,6 +148,10 @@ impl TrailSource {
Vec4::new(-GLIDER_HORIZ, 0.0, GLIDER_VERT, 1.0), Vec4::new(-GLIDER_HORIZ, 0.0, GLIDER_VERT, 1.0),
Vec4::new(-(GLIDER_HORIZ + GLIDER_WIDTH), 0.0, GLIDER_VERT, 1.0), Vec4::new(-(GLIDER_HORIZ + GLIDER_WIDTH), 0.0, GLIDER_VERT, 1.0),
), ),
Self::Propeller(length) => (
Vec4::new(0.0, 0.0, *length * 0.5, 1.0),
Vec4::new(0.0, 0.0, *length, 1.0),
),
} }
} }
} }

View File

@ -3,7 +3,7 @@ pub mod idle;
// Reexports // Reexports
pub use self::idle::IdleAnimation; pub use self::idle::IdleAnimation;
use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton}; use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton, TrailSource};
use common::comp::{self}; use common::comp::{self};
use core::convert::TryFrom; use core::convert::TryFrom;
@ -34,12 +34,16 @@ impl Skeleton for ShipSkeleton {
// Ships are normal scale // Ships are normal scale
let scale_mat = Mat4::scaling_3d(1.0); let scale_mat = Mat4::scaling_3d(1.0);
let attr = SkeletonAttr::from(&body);
let bone0_mat = base_mat * scale_mat * Mat4::<f32>::from(self.bone0); let bone0_mat = base_mat * scale_mat * Mat4::<f32>::from(self.bone0);
let bone1_mat = bone0_mat * Mat4::<f32>::from(self.bone1);
let bone2_mat = bone0_mat * Mat4::<f32>::from(self.bone2);
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [ *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
make_bone(bone0_mat), make_bone(bone0_mat),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone1)), make_bone(bone1_mat),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone2)), make_bone(bone2_mat),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone3)), make_bone(bone0_mat * Mat4::<f32>::from(self.bone3)),
]; ];
Offsets { Offsets {
@ -51,8 +55,12 @@ impl Skeleton for ShipSkeleton {
.mul_point(comp::Body::Ship(body).mount_offset().into_tuple().into()), .mul_point(comp::Body::Ship(body).mount_offset().into_tuple().into()),
..Default::default() ..Default::default()
}, },
primary_trail_mat: None, primary_trail_mat: attr
secondary_trail_mat: None, .bone1_prop_trail_offset
.map(|offset| (bone1_mat, TrailSource::Propeller(offset))),
secondary_trail_mat: attr
.bone2_prop_trail_offset
.map(|offset| (bone2_mat, TrailSource::Propeller(offset))),
} }
} }
} }
@ -62,6 +70,8 @@ pub struct SkeletonAttr {
bone1: (f32, f32, f32), bone1: (f32, f32, f32),
bone2: (f32, f32, f32), bone2: (f32, f32, f32),
bone3: (f32, f32, f32), bone3: (f32, f32, f32),
bone1_prop_trail_offset: Option<f32>,
bone2_prop_trail_offset: Option<f32>,
} }
impl<'a> TryFrom<&'a comp::Body> for SkeletonAttr { impl<'a> TryFrom<&'a comp::Body> for SkeletonAttr {
@ -82,6 +92,8 @@ impl Default for SkeletonAttr {
bone1: (0.0, 0.0, 0.0), bone1: (0.0, 0.0, 0.0),
bone2: (0.0, 0.0, 0.0), bone2: (0.0, 0.0, 0.0),
bone3: (0.0, 0.0, 0.0), bone3: (0.0, 0.0, 0.0),
bone1_prop_trail_offset: None,
bone2_prop_trail_offset: None,
} }
} }
} }
@ -105,7 +117,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
SailBoat => (0.0, 0.0, 0.0), SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0), Galleon => (0.0, 0.0, 0.0),
Skiff => (0.0, 0.0, 0.0), Skiff => (0.0, 0.0, 0.0),
Submarine => (0.0, -15.0, 0.0), Submarine => (0.0, -15.0, 3.5),
Volume => (0.0, 0.0, 0.0), Volume => (0.0, 0.0, 0.0),
}, },
bone2: match body { bone2: match body {
@ -123,9 +135,18 @@ impl<'a> From<&'a Body> for SkeletonAttr {
SailBoat => (0.0, 0.0, 0.0), SailBoat => (0.0, 0.0, 0.0),
Galleon => (0.0, 0.0, 0.0), Galleon => (0.0, 0.0, 0.0),
Skiff => (0.0, 0.0, 0.0), Skiff => (0.0, 0.0, 0.0),
Submarine => (0.0, -18.0, 0.0), Submarine => (0.0, -18.0, 3.5),
Volume => (0.0, 0.0, 0.0), Volume => (0.0, 0.0, 0.0),
}, },
bone1_prop_trail_offset: match body {
DefaultAirship => Some(8.5),
Submarine => Some(3.5),
_ => None,
},
bone2_prop_trail_offset: match body {
DefaultAirship => Some(8.5),
_ => None,
},
} }
} }
} }