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: (
bone0: (
offset: (-5.5, -18.0, -4.5),
offset: (-5.5, -18.0, -1.0),
central: ("submarine.structure"),
),
bone1: (

View File

@ -75,7 +75,7 @@ impl Body {
Body::SailBoat => Vec3::new(12.0, 32.0, 6.0),
Body::Galleon => Vec3::new(14.0, 48.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::Submarine => Density(WATER_DENSITY), // Neutrally buoyant
_ => 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 {
Body::Object(_) => 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(_) => return None,
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();
// 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 =
Mat4::<f32>::translation_3d(
// Compute matrices that allow us to transform to and from the
// 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.pos.unwrap_or(Pos(wpos)).0,
) * Mat4::from(previous_cache_other.ori)
* Mat4::<f32>::scaling_3d(previous_cache_other.scale)
* Mat4::<f32>::translation_3d(
voxel_collider.translation,
);
let transform_last_to = transform_last_from.inverted();
- previous_cache.pos.unwrap_or(*pos).0,
previous_cache_other.ori,
);
let (transform_from, transform_to) =
from_to_matricies(pos_other.0 - pos.0, ori_other.to_quat());
let transform_from =
Mat4::<f32>::translation_3d(pos_other.0 - wpos)
* Mat4::from(ori_other.to_quat())
* Mat4::<f32>::scaling_3d(previous_cache_other.scale)
* Mat4::<f32>::translation_3d(
voxel_collider.translation,
);
let transform_to = transform_from.inverted();
// Compute the velocity of the collider, accounting for changes
// in orientation
// from the last tick. We then model this change as a change in
// surface velocity
// for the collider.
let vel_other = {
let pos_rel =
(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());
let ori_to = ori_from.inverted();
// Perform collision resolution
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
// orientation.
let pos_rel = (Mat4::<f32>::translation_3d(Vec3::zero())
* Mat4::from(ori_other.to_quat())
* Mat4::<f32>::translation_3d(voxel_collider.translation))
.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;
// Transform entity attributes back into world space now
// that we've performed
// collision resolution with them
tgt_pos = transform_from.mul_point(rpos.0) + pos.0;
vel.0 = previous_cache_other.ori * vel.0 + vel_other;
}
// Velocity added due to change in orientation, relative to the
// craft
let ori_vel = previous_cache_other.ori.inverse()
* (pos.0 - pos_other.0)
- ori_other.to_quat().inverse() * (pos.0 - pos_other.0);
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
// Collision resolution may also change the physics state. Since
// we may be interacting
// with multiple colliders at once (along with the regular
// terrain!) we keep track
// of a physics state 'delta' and try to sensibly resolve them
// against one-another at each step.
if physics_state_delta.on_ground.is_some() {
physics_state.ground_vel =
vel_other + ori_from.mul_direction(ori_vel);
// Rotate if on ground
// TODO: Do we need to do this? Perhaps just take the
// ground_vel regardless?
physics_state.ground_vel = vel_other;
}
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_other.to_quat()
* 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_delta
.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,

View File

@ -114,6 +114,7 @@ pub enum TrailSource {
Weapon,
GliderLeft,
GliderRight,
Propeller(f32),
}
impl TrailSource {
@ -147,6 +148,10 @@ impl TrailSource {
Vec4::new(-GLIDER_HORIZ, 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
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 core::convert::TryFrom;
@ -34,12 +34,16 @@ impl Skeleton for ShipSkeleton {
// Ships are normal scale
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 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()) = [
make_bone(bone0_mat),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone1)),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone2)),
make_bone(bone1_mat),
make_bone(bone2_mat),
make_bone(bone0_mat * Mat4::<f32>::from(self.bone3)),
];
Offsets {
@ -51,8 +55,12 @@ impl Skeleton for ShipSkeleton {
.mul_point(comp::Body::Ship(body).mount_offset().into_tuple().into()),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
primary_trail_mat: attr
.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),
bone2: (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 {
@ -82,6 +92,8 @@ impl Default for SkeletonAttr {
bone1: (0.0, 0.0, 0.0),
bone2: (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),
Galleon => (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),
},
bone2: match body {
@ -123,9 +135,18 @@ impl<'a> From<&'a Body> for SkeletonAttr {
SailBoat => (0.0, 0.0, 0.0),
Galleon => (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),
},
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,
},
}
}
}