feat(core): add AABB collision and rigid-body physics stepping
This commit is contained in:
1 parent
f3126d8bcf
commit
05b6651677
2 files changed
+147
No files matched your search
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "World.h"
|
||||
#include "Math.h"
|
||||
|
||||
namespace vf {
|
||||
|
||||
struct Aabb {
|
||||
Vec3 min;
|
||||
Vec3 max;
|
||||
};
|
||||
|
||||
struct Player {
|
||||
Vec3 position; // feet centre (bottom of the collision box)
|
||||
Vec3 velocity;
|
||||
float yaw = 0.0f;
|
||||
float pitch = 0.0f;
|
||||
float width = 0.6f;
|
||||
float height = 1.8f;
|
||||
float eyeHeight = 1.62f;
|
||||
bool onGround = false;
|
||||
bool flying = false;
|
||||
};
|
||||
|
||||
struct MoveInput {
|
||||
float forward = 0.0f; // -1..1
|
||||
float strafe = 0.0f; // -1..1
|
||||
bool jump = false;
|
||||
bool up = false; // fly ascend
|
||||
bool down = false; // fly descend
|
||||
};
|
||||
|
||||
struct PhysicsConfig {
|
||||
float walkSpeed = 4.6f;
|
||||
float flySpeed = 9.0f;
|
||||
float gravity = 26.0f;
|
||||
float jumpSpeed = 8.4f;
|
||||
float maxFallSpeed = 55.0f;
|
||||
};
|
||||
|
||||
Aabb playerBox(const Player& player);
|
||||
Vec3 eyePosition(const Player& player);
|
||||
|
||||
bool boxOverlapsSolid(const World& world, const Aabb& box);
|
||||
|
||||
// Advances the player one fixed step: applies input, gravity/fly, then resolves
|
||||
// AABB collisions against solid voxels axis by axis (move and slide).
|
||||
void stepPlayer(const World& world, Player& player, const MoveInput& input,
|
||||
const PhysicsConfig& config, float dt);
|
||||
|
||||
} // namespace vf
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "vf/Physics.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace vf {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float epsilon = 1e-4f;
|
||||
|
||||
float& axis(Vec3& v, int i) { return (&v.x)[i]; }
|
||||
|
||||
Aabb boxAt(const Player& player, const Vec3& position) {
|
||||
float half = player.width * 0.5f;
|
||||
return Aabb{
|
||||
{position.x - half, position.y, position.z - half},
|
||||
{position.x + half, position.y + player.height, position.z + half},
|
||||
};
|
||||
}
|
||||
|
||||
// Moves the player one axis by `disp`, snapping out of any solid voxel it
|
||||
// enters. Returns true when a collision stopped the motion on that axis.
|
||||
bool moveAxis(const World& world, Player& player, int a, float disp) {
|
||||
axis(player.position, a) += disp;
|
||||
Aabb box = boxAt(player, player.position);
|
||||
if (!boxOverlapsSolid(world, box)) return false;
|
||||
|
||||
if (disp > 0.0f) {
|
||||
float snapped = std::floor(axis(box.max, a));
|
||||
axis(player.position, a) -= (axis(box.max, a) - snapped) + epsilon;
|
||||
} else if (disp < 0.0f) {
|
||||
float snapped = std::ceil(axis(box.min, a));
|
||||
axis(player.position, a) += (snapped - axis(box.min, a)) + epsilon;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Aabb playerBox(const Player& player) { return boxAt(player, player.position); }
|
||||
|
||||
Vec3 eyePosition(const Player& player) {
|
||||
return {player.position.x, player.position.y + player.eyeHeight, player.position.z};
|
||||
}
|
||||
|
||||
bool boxOverlapsSolid(const World& world, const Aabb& box) {
|
||||
int x0 = static_cast<int>(std::floor(box.min.x));
|
||||
int y0 = static_cast<int>(std::floor(box.min.y));
|
||||
int z0 = static_cast<int>(std::floor(box.min.z));
|
||||
int x1 = static_cast<int>(std::ceil(box.max.x)) - 1;
|
||||
int y1 = static_cast<int>(std::ceil(box.max.y)) - 1;
|
||||
int z1 = static_cast<int>(std::ceil(box.max.z)) - 1;
|
||||
|
||||
for (int x = x0; x <= x1; ++x)
|
||||
for (int y = y0; y <= y1; ++y)
|
||||
for (int z = z0; z <= z1; ++z)
|
||||
if (world.solidAt(x, y, z)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void stepPlayer(const World& world, Player& player, const MoveInput& input,
|
||||
const PhysicsConfig& config, float dt) {
|
||||
Vec3 forward{std::sin(player.yaw), 0.0f, -std::cos(player.yaw)};
|
||||
Vec3 right{std::cos(player.yaw), 0.0f, std::sin(player.yaw)};
|
||||
|
||||
Vec3 wish = forward * input.forward + right * input.strafe;
|
||||
float wishLen = length(wish);
|
||||
if (wishLen > 1.0f) wish = wish * (1.0f / wishLen);
|
||||
|
||||
if (player.flying) {
|
||||
player.velocity.x = wish.x * config.flySpeed;
|
||||
player.velocity.z = wish.z * config.flySpeed;
|
||||
float vertical = (input.up ? 1.0f : 0.0f) - (input.down ? 1.0f : 0.0f);
|
||||
player.velocity.y = vertical * config.flySpeed;
|
||||
} else {
|
||||
player.velocity.x = wish.x * config.walkSpeed;
|
||||
player.velocity.z = wish.z * config.walkSpeed;
|
||||
player.velocity.y -= config.gravity * dt;
|
||||
if (player.velocity.y < -config.maxFallSpeed) player.velocity.y = -config.maxFallSpeed;
|
||||
if (input.jump && player.onGround) {
|
||||
player.velocity.y = config.jumpSpeed;
|
||||
player.onGround = false;
|
||||
}
|
||||
}
|
||||
|
||||
player.onGround = false;
|
||||
moveAxis(world, player, 0, player.velocity.x * dt);
|
||||
moveAxis(world, player, 2, player.velocity.z * dt);
|
||||
bool collidedY = moveAxis(world, player, 1, player.velocity.y * dt);
|
||||
if (collidedY) {
|
||||
if (player.velocity.y < 0.0f) player.onGround = true;
|
||||
player.velocity.y = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vf
|
||||
Reference in new issue
Block a user