Basic movement
This commit is contained in:
parent
30d70e178e
commit
e4ee01de2e
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -5,3 +5,6 @@
|
|||||||
[submodule "external/glm"]
|
[submodule "external/glm"]
|
||||||
path = external/glm
|
path = external/glm
|
||||||
url = https://github.com/g-truc/glm.git
|
url = https://github.com/g-truc/glm.git
|
||||||
|
[submodule "bullet3"]
|
||||||
|
path = bullet3
|
||||||
|
url = https://github.com/bulletphysics/bullet3.git
|
||||||
|
|||||||
@ -16,9 +16,9 @@ add_executable(PortalGame
|
|||||||
"src/assets/sectordef.cpp"
|
"src/assets/sectordef.cpp"
|
||||||
"src/assets/mesh.hpp"
|
"src/assets/mesh.hpp"
|
||||||
"src/assets/mesh.cpp"
|
"src/assets/mesh.cpp"
|
||||||
"src/collision/capsule_triangle_sweep.hpp"
|
|
||||||
"src/collision/capsule_triangle_sweep.cpp"
|
|
||||||
"src/collision/trianglemesh.cpp"
|
"src/collision/trianglemesh.cpp"
|
||||||
|
"src/game/entity.hpp"
|
||||||
|
"src/game/entity.cpp"
|
||||||
"src/game/sector.hpp"
|
"src/game/sector.hpp"
|
||||||
"src/game/sector.cpp"
|
"src/game/sector.cpp"
|
||||||
"src/game/world.hpp"
|
"src/game/world.hpp"
|
||||||
@ -82,4 +82,8 @@ endif()
|
|||||||
|
|
||||||
add_subdirectory(external/glm)
|
add_subdirectory(external/glm)
|
||||||
target_link_libraries(PortalGame PRIVATE glm)
|
target_link_libraries(PortalGame PRIVATE glm)
|
||||||
target_include_directories(PortalGame PRIVATE "external/stb")
|
target_include_directories(PortalGame PRIVATE "external/stb")
|
||||||
|
|
||||||
|
add_subdirectory(bullet3)
|
||||||
|
target_link_libraries(PortalGame PRIVATE BulletCollision LinearMath Bullet3Common)
|
||||||
|
target_include_directories(PortalGame PRIVATE "bullet3/src")
|
||||||
|
|||||||
107
src/app.cpp
107
src/app.cpp
@ -1,7 +1,6 @@
|
|||||||
#include "app.hpp"
|
#include "app.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "collision/capsule_triangle_sweep.hpp"
|
|
||||||
|
|
||||||
App::App()
|
App::App()
|
||||||
{
|
{
|
||||||
@ -23,7 +22,11 @@ App::App()
|
|||||||
world_.LinkPortals(s1, "NDoor", s2, "WDoor");
|
world_.LinkPortals(s1, "NDoor", s2, "WDoor");
|
||||||
world_.LinkPortals(s2, "NDoor", s2, "SDoor");
|
world_.LinkPortals(s2, "NDoor", s2, "SDoor");
|
||||||
|
|
||||||
player_ = world_.Spawn<game::Entity>(s1, glm::vec3(0.0f, 0.0f, 1.0f));
|
game::CapsuleShape capsule_shape;
|
||||||
|
capsule_shape.radius = 0.2f; // 20cm radius
|
||||||
|
capsule_shape.height = 0.3f; // 1.8m height
|
||||||
|
|
||||||
|
player_ = world_.Spawn<game::Entity>(s1, capsule_shape, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::Frame()
|
void App::Frame()
|
||||||
@ -58,9 +61,8 @@ void App::Frame()
|
|||||||
|
|
||||||
//renderer_.DrawWorld(world_, 1, center + cameraPos, -cameraPos, up, aspect, 60.0f);
|
//renderer_.DrawWorld(world_, 1, center + cameraPos, -cameraPos, up, aspect, 60.0f);
|
||||||
|
|
||||||
static glm::vec3 position(0.0f, 0.0f, 1.5f);
|
glm::vec3 velocity(0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
glm::vec3 velocity(0.0f);
|
|
||||||
if (input_ & game::PI_FORWARD)
|
if (input_ & game::PI_FORWARD)
|
||||||
velocity.y += 1.0f;
|
velocity.y += 1.0f;
|
||||||
|
|
||||||
@ -79,84 +81,51 @@ void App::Frame()
|
|||||||
if (input_ & game::PI_CROUCH)
|
if (input_ & game::PI_CROUCH)
|
||||||
velocity.z -= 1.0f;
|
velocity.z -= 1.0f;
|
||||||
|
|
||||||
|
glm::vec3 forward = glm::vec3(
|
||||||
|
cos(angles_.x) * cos(angles_.y),
|
||||||
|
sin(angles_.x) * cos(angles_.y),
|
||||||
|
sin(angles_.y)
|
||||||
|
);
|
||||||
|
|
||||||
//velocity.z = -1.0f;
|
velocity.z -= 0.1f * delta_time; // Apply gravity
|
||||||
|
|
||||||
|
glm::vec3 forward_xy = glm::normalize(glm::vec3(forward.x, forward.y, 0.0f));
|
||||||
glm::vec3 cameraDir = glm::vec3(1.0f, 0.0f, 0.0f);
|
glm::vec3 right = glm::normalize(glm::vec3(forward.y, -forward.x, 0.0f));
|
||||||
auto tris = world_.GetSector(0).GetMesh()->GetCollisionMesh()->GetTriangles();
|
velocity = glm::normalize(velocity.x * right + velocity.y * forward_xy + velocity.z * glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
|
||||||
glm::vec3 c0 = position;
|
//if (glm::length(velocity) > 0.1f)
|
||||||
c0.z -= 0.1f;
|
//{
|
||||||
glm::vec3 c1 = position;
|
// velocity = glm::normalize(velocity); // Normalize to prevent faster diagonal movement
|
||||||
c1.z += 0.1f;
|
|
||||||
|
|
||||||
|
// glm::vec3 u = velocity * delta_time * 2.0f;
|
||||||
|
// //bool hit = world_.GetSector(0).SweepCapsule(position, position + u);
|
||||||
|
|
||||||
bool found = false;
|
// //if (!hit)
|
||||||
|
// //{
|
||||||
|
// // position += u;
|
||||||
|
// //}
|
||||||
|
//}
|
||||||
|
|
||||||
glm::vec3 u = velocity * delta_time;
|
player_->SetVelocity(velocity);
|
||||||
|
player_->Update(delta_time);
|
||||||
|
|
||||||
if (glm::length(velocity) > 0.001f)
|
const auto& position = player_->GetOccurrence().GetPosition();
|
||||||
{
|
|
||||||
//velocity = glm::normalize(velocity);
|
|
||||||
|
|
||||||
//collision::ResolveCollisions(
|
renderer_.DrawWorld(world_, 0, position, forward, glm::vec3(0.0f, 0.0f, 1.0f), aspect, 60.0f);
|
||||||
// position,
|
|
||||||
// velocity,
|
|
||||||
// 0.5f,
|
|
||||||
// 0.2f,
|
|
||||||
// delta_time,
|
|
||||||
// tris
|
|
||||||
//);
|
|
||||||
|
|
||||||
Sweep sweep;
|
|
||||||
|
|
||||||
static std::vector<collision::Triangle> sweep_tris;
|
|
||||||
sweep_tris.clear();
|
|
||||||
|
|
||||||
for (const auto& tri : tris) {
|
|
||||||
float dot = glm::dot(tri.verts[1] - tri.verts[0], tri.verts[2] - tri.verts[0]);
|
|
||||||
if (glm::abs(dot) < 0.001f) {
|
|
||||||
printf("degen!!!!!!!!!\n");
|
|
||||||
continue; // Skip degenerate triangles
|
|
||||||
}
|
|
||||||
|
|
||||||
//found = found || collision::SweepCapsuleTriangle(
|
|
||||||
// sweep,
|
|
||||||
// c0,
|
|
||||||
// c1,
|
|
||||||
// 0.2f,
|
|
||||||
// u,
|
|
||||||
// tri.verts[0], tri.verts[1], tri.verts[2]
|
|
||||||
//);
|
|
||||||
|
|
||||||
sweep_tris.push_back(tri);
|
|
||||||
}
|
|
||||||
|
|
||||||
collision::ResolveCollisions(
|
|
||||||
position,
|
|
||||||
velocity,
|
|
||||||
0.5f, // Capsule radius
|
|
||||||
0.2f, // Capsule height
|
|
||||||
delta_time,
|
|
||||||
sweep_tris
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
position += velocity * delta_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("position: %.2f, %.2f, %.2f\n", position.x, position.y, position.z);
|
|
||||||
printf("found: %s\n", found ? "true" : "false");
|
|
||||||
|
|
||||||
renderer_.DrawWorld(world_, 0, position, cameraDir, glm::vec3(0.0f, 0.0f, 1.0f), aspect, 60.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::MouseMove(const glm::vec2& delta)
|
void App::MouseMove(const glm::vec2& delta)
|
||||||
{
|
{
|
||||||
|
float sensitivity = 0.002f; // Sensitivity factor for mouse movement
|
||||||
|
angles_.x -= delta.x * sensitivity; // Yaw
|
||||||
|
angles_.y -= delta.y * sensitivity; // Pitch
|
||||||
|
|
||||||
|
// Clamp pitch to avoid gimbal lock
|
||||||
|
if (angles_.y > glm::radians(89.0f)) {
|
||||||
|
angles_.y = glm::radians(89.0f);
|
||||||
|
} else if (angles_.y < glm::radians(-89.0f)) {
|
||||||
|
angles_.y = glm::radians(-89.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
App::~App()
|
App::~App()
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
#include "game/player_input.hpp"
|
#include "game/player_input.hpp"
|
||||||
#include "gfx/renderer.hpp"
|
#include "gfx/renderer.hpp"
|
||||||
|
|
||||||
class App
|
class App
|
||||||
{
|
{
|
||||||
float time_ = 0.0f;
|
float time_ = 0.0f;
|
||||||
glm::ivec2 viewport_size_ = { 800, 600 };
|
glm::ivec2 viewport_size_ = { 800, 600 };
|
||||||
@ -17,6 +17,8 @@ class App
|
|||||||
game::World world_;
|
game::World world_;
|
||||||
game::Entity* player_ = nullptr;
|
game::Entity* player_ = nullptr;
|
||||||
|
|
||||||
|
glm::vec2 angles_ = { 0.0f, 0.0f }; // Pitch and yaw angles in radians
|
||||||
|
|
||||||
gfx::Renderer renderer_;
|
gfx::Renderer renderer_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -68,10 +68,12 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
|
|||||||
{
|
{
|
||||||
MeshTriangle& tri = tris.emplace_back();
|
MeshTriangle& tri = tris.emplace_back();
|
||||||
glm::vec3 tri_verts[3];
|
glm::vec3 tri_verts[3];
|
||||||
for (size_t i = 0; i < 3; ++i) {
|
for (size_t i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
uint32_t vert_index;
|
uint32_t vert_index;
|
||||||
iss >> vert_index;
|
iss >> vert_index;
|
||||||
if (vert_index >= verts.size()) {
|
if (vert_index >= verts.size())
|
||||||
|
{
|
||||||
throw std::runtime_error("Vertex index out of bounds in mesh file: " + filename);
|
throw std::runtime_error("Vertex index out of bounds in mesh file: " + filename);
|
||||||
}
|
}
|
||||||
tri.vert[i] = vert_index;
|
tri.vert[i] = vert_index;
|
||||||
@ -114,6 +116,11 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
|
|||||||
// Create the mesh object
|
// Create the mesh object
|
||||||
|
|
||||||
std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>(verts, tris, materials);
|
std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>(verts, tris, materials);
|
||||||
mesh->SetCollisionMesh(std::move(collision_mesh));
|
|
||||||
|
if (load_collision)
|
||||||
|
{
|
||||||
|
collision_mesh->Build();
|
||||||
|
mesh->SetCollisionMesh(std::move(collision_mesh));
|
||||||
|
}
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
namespace collision
|
|
||||||
{
|
|
||||||
struct Capsule
|
|
||||||
{
|
|
||||||
glm::vec3 p0;
|
|
||||||
glm::vec3 p1;
|
|
||||||
float radius;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,439 +0,0 @@
|
|||||||
#include "capsule_triangle_sweep.hpp"
|
|
||||||
|
|
||||||
#define EPSILON 1e-5f // Used to test if float is close to 0. Tweak this if you get problems.
|
|
||||||
|
|
||||||
// Return whether point P is contained inside 3D region delimited by triangle T0,T1,T2 edges.
|
|
||||||
static bool PointInsideTriangle(const glm::vec3& p, const glm::vec3& t0, const glm::vec3& t1, const glm::vec3& t2)
|
|
||||||
{
|
|
||||||
// Real-Time Collision Detection: 3.4: Barycentric Coordinates (pages 46-52).
|
|
||||||
//
|
|
||||||
// The book also has a subsection dedicated to point inside triangle tests:
|
|
||||||
// Real-Time Collision Detection: 5.4.2: Testing Point in Triangle (pages 203-206).
|
|
||||||
// But those tests only work for CCW triangles. This seems to work for either orientation.
|
|
||||||
glm::vec3 t01 = t1 - t0;
|
|
||||||
glm::vec3 t02 = t2 - t0;
|
|
||||||
glm::vec3 t0p = p - t0;
|
|
||||||
float t01t01 = glm::dot(t01, t01);
|
|
||||||
float t01t02 = glm::dot(t01, t02);
|
|
||||||
float t02t02 = glm::dot(t02, t02);
|
|
||||||
float t0pt01 = glm::dot(t0p, t01);
|
|
||||||
float t0pt02 = glm::dot(t0p, t02);
|
|
||||||
float denom = t01t01 * t02t02 - t01t02 * t01t02;
|
|
||||||
|
|
||||||
// Normally I would have to divide vd,wd by denom to get v,w. But divisions are
|
|
||||||
// expensive and cause troubles around 0. If denom isn't negative then we don't
|
|
||||||
// ever need to divide. If in the future it does turn out denom can be negative
|
|
||||||
// then we can always multiply by denom instead of dividing to keep sign the same.
|
|
||||||
float vd = t02t02 * t0pt01 - t01t02 * t0pt02;
|
|
||||||
float wd = t01t01 * t0pt02 - t01t02 * t0pt01;
|
|
||||||
return vd >= 0 && wd >= 0 && vd + wd <= denom;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether point P is contained inside 3D region delimited by parallelogram P0,P1,P2 edges.
|
|
||||||
static bool PointInsideParallelogram(const glm::vec3& p, const glm::vec3& p0, const glm::vec3& p1, const glm::vec3& p2)
|
|
||||||
{
|
|
||||||
// There may be a better way.
|
|
||||||
// https://math.stackexchange.com/questions/4381852/point-in-parallelogram-in-3d-space
|
|
||||||
glm::vec3 p3 = p2 + (p1 - p0);
|
|
||||||
return PointInsideTriangle(p, p0, p1, p2) || PointInsideTriangle(p, p1, p3, p2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether point P is contained inside a triangular prism A0,A1,A2-B0,B1,B2.
|
|
||||||
static bool PointInsideTriangularPrism(
|
|
||||||
const glm::vec3& p,
|
|
||||||
const glm::vec3& a0,
|
|
||||||
const glm::vec3& a1,
|
|
||||||
const glm::vec3& a2,
|
|
||||||
const glm::vec3& b0,
|
|
||||||
const glm::vec3& b1,
|
|
||||||
const glm::vec3& b2)
|
|
||||||
{
|
|
||||||
glm::vec3 faces[5][3] = { { a0,a1,a2 }, { b0,b2,b1 }, { a0,b0,a1 }, { a1,b1,a2 }, { a2,b2,a0 } };
|
|
||||||
float sgn = 0;
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
const glm::vec3& p0 = faces[i][1];
|
|
||||||
const glm::vec3& p1 = faces[i][0];
|
|
||||||
const glm::vec3& p2 = faces[i][2];
|
|
||||||
|
|
||||||
// Check which side of plane point is in. If it's always on the same side, it's colliding.
|
|
||||||
glm::vec3 p01 = p1 - p0;
|
|
||||||
glm::vec3 p02 = p2 - p0;
|
|
||||||
glm::vec3 n = glm::cross(p01, p02);
|
|
||||||
float d = glm::dot(n, p - p0);
|
|
||||||
if (i == 0) sgn = d;
|
|
||||||
if (sgn * d <= 0)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sweep sphere C,r with velocity Sv against plane N of triangle T0,T1,T2, ignoring edges.
|
|
||||||
static bool SweepSphereTrianglePlane(
|
|
||||||
Sweep& sweep,
|
|
||||||
const glm::vec3& c,
|
|
||||||
float r,
|
|
||||||
const glm::vec3& v,
|
|
||||||
const glm::vec3& t0,
|
|
||||||
const glm::vec3& t1,
|
|
||||||
const glm::vec3& t2,
|
|
||||||
const glm::vec3& n)
|
|
||||||
{
|
|
||||||
// Real-Time Collision Detection 5.5.3: Intersecting Moving Sphere Against Plane (pages 219-223).
|
|
||||||
float t;
|
|
||||||
float d = glm::dot(n, c - t0);
|
|
||||||
float pen = r - d;
|
|
||||||
if (pen > 0)
|
|
||||||
t = 0; // Sphere already starts coliding with triangle plane.
|
|
||||||
else {
|
|
||||||
// Sphere isn't immediately colliding with the plane. Check if it's moving away.
|
|
||||||
float denom = glm::dot(n, v);
|
|
||||||
if (denom >= 0)
|
|
||||||
return false; // Sphere is moving away from plane.
|
|
||||||
|
|
||||||
// Sphere will collide with plane at some point.
|
|
||||||
t = (r - d) / denom;
|
|
||||||
pen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If sphere misses entire triangle plane, then it definitely misses the triangle too.
|
|
||||||
if (t >= sweep.time)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Is the plane collision point inside the triangle?
|
|
||||||
// Real-Time Collision Detection: 5.4.2: Testing Point in Triangle (pg 203-206).
|
|
||||||
glm::vec3 collision = c + t * v - r * n;
|
|
||||||
if (!PointInsideTriangle(collision, t0, t1, t2))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Plane collision point is inside the triangle. So the sphere collides with the triangle.
|
|
||||||
sweep.time = t;
|
|
||||||
sweep.depth = pen;
|
|
||||||
sweep.point = collision;
|
|
||||||
sweep.normal = n;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sweep sphere C,r with velocity V against plane N of parallelogram P0,P1,P2 ignoring edges.
|
|
||||||
static bool SweepSphereParallelogramPlane(
|
|
||||||
Sweep& sweep,
|
|
||||||
const glm::vec3& c,
|
|
||||||
float r,
|
|
||||||
const glm::vec3& v,
|
|
||||||
const glm::vec3& p0,
|
|
||||||
const glm::vec3& p1,
|
|
||||||
const glm::vec3& p2,
|
|
||||||
const glm::vec3& n)
|
|
||||||
{
|
|
||||||
// Real-Time Collision Detection 5.5.3: Intersecting Moving Sphere Against Plane (pages 219-223).
|
|
||||||
float t;
|
|
||||||
float d = glm::dot(c, n - p0);
|
|
||||||
float pen = r - d;
|
|
||||||
if (pen > 0)
|
|
||||||
t = 0; // Sphere already starts coliding with the quad plane.
|
|
||||||
else {
|
|
||||||
// Sphere isn't immediately colliding with the plane. Check if it's moving away.
|
|
||||||
float denom = glm::dot(n, v);
|
|
||||||
if (denom >= 0)
|
|
||||||
return false; // Sphere is moving away from plane.
|
|
||||||
|
|
||||||
// Sphere will collide with plane at some point.
|
|
||||||
t = (r - d) / denom;
|
|
||||||
pen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If sphere misses entire quad plane, then it definitely misses the quad too.
|
|
||||||
if (t >= sweep.time)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Is the plane collision point inside the quad?
|
|
||||||
// Real-Time Collision Detection: 5.4.2: Testing Point in Triangle (pages 203-206).
|
|
||||||
glm::vec3 collision = c + t * v - r * n;
|
|
||||||
if (!PointInsideParallelogram(collision, p0, p1, p2))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Plane collision point is inside the quad. So the sphere collides with the quad.
|
|
||||||
sweep.time = t;
|
|
||||||
sweep.depth = pen;
|
|
||||||
sweep.point = collision;
|
|
||||||
sweep.normal = n;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sweep point P with velocity V against sphere S,r.
|
|
||||||
static bool SweepPointSphere(
|
|
||||||
Sweep& sweep,
|
|
||||||
const glm::vec3& p,
|
|
||||||
const glm::vec3& v,
|
|
||||||
const glm::vec3& s,
|
|
||||||
float r,
|
|
||||||
const glm::vec3& fallbackNormal)
|
|
||||||
{
|
|
||||||
// Real-Time Collision Detection 5.3.2: Intersecting Ray or Segment Against Sphere (pages 177-179).
|
|
||||||
|
|
||||||
// Set up quadratic equation.
|
|
||||||
glm::vec3 d = p - s;
|
|
||||||
float b = glm::dot(d, v);
|
|
||||||
float c = glm::dot(d, d) - r * r;
|
|
||||||
if (c > 0 && b > 0)
|
|
||||||
return false; // Point starts outside (c > 0) and moves away from sphere (b > 0).
|
|
||||||
float a = glm::dot(v, v);
|
|
||||||
float discr = b * b - a * c;
|
|
||||||
if (discr < 0)
|
|
||||||
return false; // Point misses sphere.
|
|
||||||
|
|
||||||
// Point hits sphere. Compute time of first impact.
|
|
||||||
float t = (-b - glm::sqrt(discr)) / a;
|
|
||||||
if (t >= sweep.time)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// The sphere is the first thing the point hits so far.
|
|
||||||
t = glm::max(t, 0.0f);
|
|
||||||
glm::vec3 collision = p + t * v;
|
|
||||||
glm::vec3 vec = collision - s;
|
|
||||||
float len = glm::length(vec);
|
|
||||||
sweep.time = t;
|
|
||||||
sweep.depth = t > 0 ? 0 : r - len;
|
|
||||||
sweep.point = collision;
|
|
||||||
sweep.normal = len >= EPSILON ? vec / len : fallbackNormal;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sweep point P with velocity V against cylinder C0,C1,r, ignoring the endcaps.
|
|
||||||
static bool SweepPointUncappedCylinder(
|
|
||||||
Sweep& sweep,
|
|
||||||
const glm::vec3& p,
|
|
||||||
const glm::vec3& v,
|
|
||||||
const glm::vec3& c0,
|
|
||||||
const glm::vec3& c1,
|
|
||||||
float r,
|
|
||||||
const glm::vec3& fallbackNormal)
|
|
||||||
{
|
|
||||||
// Real-Time Collision Detection 5.3.7: Intersecting Ray or Segment Against Cylinder (pages 194-198).
|
|
||||||
|
|
||||||
// Test if swept point is fully outside of either endcap.
|
|
||||||
glm::vec3 n = c1 - c0;
|
|
||||||
glm::vec3 d = p - c0;
|
|
||||||
float dn = glm::dot(d, n);
|
|
||||||
float vn = glm::dot(v, n);
|
|
||||||
float nn = glm::dot(n, n);
|
|
||||||
if (dn < 0 && dn + vn < 0)
|
|
||||||
return false; // Fully outside c0 end of cylinder.
|
|
||||||
if (dn > nn && dn + vn > nn)
|
|
||||||
return false; // Fully outside c1 end of cylinder.
|
|
||||||
|
|
||||||
// Set up quadratic equations and check if sweep direction is parallel to cylinder.
|
|
||||||
float t;
|
|
||||||
float vv = glm::dot(v, v);
|
|
||||||
float dv = glm::dot(d, v);
|
|
||||||
float dd = glm::dot(d, d);
|
|
||||||
float a = nn * vv - vn * vn;
|
|
||||||
float c = nn * (dd - r * r) - dn * dn;
|
|
||||||
if (a < EPSILON) {
|
|
||||||
// Sweep direction is parallel to cylinder.
|
|
||||||
if (c > 0)
|
|
||||||
return false; // Point starts outside of cylinder, so it never collides.
|
|
||||||
if (dn < 0)
|
|
||||||
return false; // Point starts outside of c0 endcap.
|
|
||||||
if (dn > nn)
|
|
||||||
return false; // Point starts outside of c1 endcap.
|
|
||||||
t = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Sweep direction is not parallel to cylinder. Solve for time of first contact.
|
|
||||||
float b = nn * dv - vn * dn;
|
|
||||||
float discr = b * b - a * c;
|
|
||||||
if (discr < 0)
|
|
||||||
return false; // Sweep misses cylinder.
|
|
||||||
t = (-b - glm::sqrt(discr)) / a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the sweep missed, or if it hits but another collision happens sooner.
|
|
||||||
if (t < 0 || t >= sweep.time)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// This is the first collision. Find the closest point on the center of the cylinder.
|
|
||||||
glm::vec3 collision = p + t * v;
|
|
||||||
glm::vec3 center;
|
|
||||||
if (nn < EPSILON)
|
|
||||||
center = c0; // The cylinder is actually a circle.
|
|
||||||
else
|
|
||||||
center = c0 + (glm::dot(collision - c0, n) / nn) * n;
|
|
||||||
|
|
||||||
// Update collision time, depth, and normal.
|
|
||||||
glm::vec3 vec = collision - center;
|
|
||||||
float len = glm::length(vec);
|
|
||||||
float depth = r - len;
|
|
||||||
sweep.time = t;
|
|
||||||
sweep.depth = t > 0 ? 0 : depth;
|
|
||||||
sweep.point = collision;
|
|
||||||
sweep.normal = len >= EPSILON ? vec / len : fallbackNormal;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sweep a capsule C0,C1,Cr with velocity Cv against the triangle T0,T1,T2.
|
|
||||||
// c0,c1 capsule line segment endpoints
|
|
||||||
// r capsule radius
|
|
||||||
// v capsule velocity
|
|
||||||
// t0,t1,t2 3 triangle vertices
|
|
||||||
// returns whether the capsule and triangle intersect
|
|
||||||
bool collision::SweepCapsuleTriangle(
|
|
||||||
Sweep& s,
|
|
||||||
const glm::vec3& c0,
|
|
||||||
const glm::vec3& c1,
|
|
||||||
float r,
|
|
||||||
const glm::vec3& v,
|
|
||||||
const glm::vec3& t0,
|
|
||||||
const glm::vec3& t1,
|
|
||||||
const glm::vec3& t2)
|
|
||||||
{
|
|
||||||
// Compute triangle plane equation.
|
|
||||||
glm::vec3 t01 = t1 - t0;
|
|
||||||
glm::vec3 t02 = t2 - t0;
|
|
||||||
glm::vec3 normal = glm::normalize(glm::cross(t01, t02));
|
|
||||||
|
|
||||||
// Extrude triangle along capsule direction.
|
|
||||||
glm::vec3 c01 = c1 - c0;
|
|
||||||
glm::vec3 a0 = t0;
|
|
||||||
glm::vec3 a1 = t1;
|
|
||||||
glm::vec3 a2 = t2;
|
|
||||||
glm::vec3 b0 = t0 - c01;
|
|
||||||
glm::vec3 b1 = t1 - c01;
|
|
||||||
glm::vec3 b2 = t2 - c01;
|
|
||||||
|
|
||||||
// Test for initial collision with the extruded triangle prism.
|
|
||||||
if (PointInsideTriangularPrism(c0, a0, a1, a2, b0, b1, b2)) {
|
|
||||||
// Capsule starts off penetrating triangle. Push it out from the triangle plane.
|
|
||||||
float d0 = glm::dot(normal, c0 - t0);
|
|
||||||
float d1 = glm::dot(normal, c1 - t0);
|
|
||||||
float d = glm::abs(d0) <= glm::abs(d1) ? d0 : d1;
|
|
||||||
glm::vec3 n = d >= 0 ? normal : -normal;
|
|
||||||
s.time = 0;
|
|
||||||
s.depth = glm::abs(d) + r;
|
|
||||||
s.normal = n;
|
|
||||||
s.point = c0 + d0 * normal;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompose capsule triangle sweep into: 2 sphere-triangle + 3 sphere-parallelogram + 9 point-cylinder + 6 point-sphere sweeps.
|
|
||||||
bool hit = false;
|
|
||||||
glm::vec3 triangles[2][3] = { {a0,a1,a2}, {b0,b1,b2} };
|
|
||||||
glm::vec3 parallelograms[3][3] = { {a0,a1,b0}, {a1,a2,b1}, {a2,a0,b2} };
|
|
||||||
glm::vec3 cylinders[9][2] = { {a0,a1}, {a1,a2}, {a2,a0}, {b0,b1}, {b1,b2}, {b2,b0}, {a0,b0}, {a1,b1}, {a2,b2} };
|
|
||||||
glm::vec3 spheres[6] = { a0, a1, a2, b0, b1, b2 };
|
|
||||||
|
|
||||||
// Do sphere-triangle sweeps.
|
|
||||||
glm::vec3 triangleNormals[2];
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
glm::vec3 p0 = triangles[i][0];
|
|
||||||
glm::vec3 p1 = triangles[i][1];
|
|
||||||
glm::vec3 p2 = triangles[i][2];
|
|
||||||
|
|
||||||
// Compute triangle plane normal.
|
|
||||||
glm::vec3 n = normal;
|
|
||||||
if (glm::dot(n, c0 - p0) < 0) n = -n; // Orient towards sphere.
|
|
||||||
triangleNormals[i] = n;
|
|
||||||
|
|
||||||
// Test for triangle-plane sphere intersection.
|
|
||||||
hit = hit || SweepSphereTrianglePlane(s, c0, r, v, p0, p1, p2, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do sphere-parallelogram sweeps.
|
|
||||||
glm::vec3 parallelogramNormals[3];
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
glm::vec3 p0 = parallelograms[i][0];
|
|
||||||
glm::vec3 p1 = parallelograms[i][1];
|
|
||||||
glm::vec3 p2 = parallelograms[i][2];
|
|
||||||
|
|
||||||
// Check if quad is degenerate. Happens when triangle edge completely parallel to capsule.
|
|
||||||
glm::vec3 p01 = p1 - p0;
|
|
||||||
glm::vec3 p02 = p2 - p0;
|
|
||||||
glm::vec3 c = glm::cross(p01, p02);
|
|
||||||
float len = glm::length(c);
|
|
||||||
if (len > EPSILON) {
|
|
||||||
// Compute quad plane equation.
|
|
||||||
glm::vec3 n = c / len;
|
|
||||||
if (glm::dot(n, c0 - p0) < 0) n = -n; // Orient towards sphere.
|
|
||||||
parallelogramNormals[i] = n;
|
|
||||||
|
|
||||||
// Do the sweep test.
|
|
||||||
hit = hit || SweepSphereParallelogramPlane(s, c0, r, v, p0, p1, p2, n);
|
|
||||||
}
|
|
||||||
else parallelogramNormals[i] = triangleNormals[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do point-cylinder sweeps.
|
|
||||||
for (int i = 0; i < 9; i++) {
|
|
||||||
glm::vec3 p0 = cylinders[i][0];
|
|
||||||
glm::vec3 p1 = cylinders[i][1];
|
|
||||||
glm::vec3 n;
|
|
||||||
if (i < 6)
|
|
||||||
n = triangleNormals[i / 3];
|
|
||||||
else
|
|
||||||
n = parallelogramNormals[i - 6];
|
|
||||||
hit = hit || SweepPointUncappedCylinder(s, c0, v, p0, p1, r, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do point-sphere sweeps.
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
glm::vec3 c = spheres[i];
|
|
||||||
glm::vec3 n = triangleNormals[i / 3];
|
|
||||||
hit = hit || SweepPointSphere(s, c0, v, c, r, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move a capsule and resolve any triangle collisions encountered along the way.
|
|
||||||
// p - capsule base position
|
|
||||||
// v - capsule velocity
|
|
||||||
// h - capsule height
|
|
||||||
// r - capsule radius
|
|
||||||
// dt - time-step length
|
|
||||||
// triangles - list of triangles to collide with
|
|
||||||
void collision::ResolveCollisions(glm::vec3& p, glm::vec3& v, float h, float r, float dt, std::span<const collision::Triangle> triangles) {
|
|
||||||
// Store the leftover movement in this vector.
|
|
||||||
glm::vec3 u = dt * v;
|
|
||||||
|
|
||||||
// Move and resolve collisions while there is still motion. But cap max iterations to ensure simulation terminates.
|
|
||||||
const int MAX_ITER = 16;
|
|
||||||
for (int iter = 0; iter < MAX_ITER && glm::dot(u, u) > 0.0f; iter++) {
|
|
||||||
// Compute capsule endpoints.
|
|
||||||
glm::vec3 c0 = p;
|
|
||||||
glm::vec3 c1 = p;
|
|
||||||
c0.y += r;
|
|
||||||
c1.y += h - r;
|
|
||||||
|
|
||||||
// Perform the sweep test against all triangles.
|
|
||||||
Sweep s;
|
|
||||||
s.time = 1;
|
|
||||||
for (int i = 0; i < triangles.size(); i++) {
|
|
||||||
glm::vec3 t0 = triangles[i].verts[0];
|
|
||||||
glm::vec3 t1 = triangles[i].verts[1];
|
|
||||||
glm::vec3 t2 = triangles[i].verts[2];
|
|
||||||
SweepCapsuleTriangle(s, c0, c1, r, u, t0, t1, t2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop objects from intersecting.
|
|
||||||
if (s.depth > 0)
|
|
||||||
p += (s.depth + EPSILON) * s.normal;
|
|
||||||
|
|
||||||
// Advance the cylinder until the first contact time.
|
|
||||||
glm::vec3 dp = s.time * u;
|
|
||||||
p += dp;
|
|
||||||
|
|
||||||
// If there were no collisions, entire motion is complete and we can terminate early.
|
|
||||||
if (s.time >= 1)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Cancel out motion parallel to the normal. This causes capsule to slide along surface.
|
|
||||||
u -= dp;
|
|
||||||
u += glm::dot(u, s.normal) * s.normal;
|
|
||||||
v += glm::dot(v, s.normal) * s.normal;
|
|
||||||
|
|
||||||
// Nudge the position and velocity slightly away from surface to avoid another collision.
|
|
||||||
glm::vec3 offset = EPSILON * s.normal;
|
|
||||||
p += offset;
|
|
||||||
v += offset;
|
|
||||||
u += offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "capsule.hpp"
|
|
||||||
#include "trianglemesh.hpp"
|
|
||||||
|
|
||||||
struct Sweep
|
|
||||||
{
|
|
||||||
float time = 1.0f; // Non-negative time of first contact.
|
|
||||||
float depth = 0.0f; // Non-negative penetration depth if objects start initially colliding.
|
|
||||||
glm::vec3 point = glm::vec3(0.0f); // Point of first-contact. Only updated when contact occurs.
|
|
||||||
glm::vec3 normal = glm::vec3(0.0f); // Unit-length collision normal. Only updated when contact occurs.
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace collision
|
|
||||||
{
|
|
||||||
|
|
||||||
bool SweepCapsuleTriangle(
|
|
||||||
Sweep& s,
|
|
||||||
const glm::vec3& c0,
|
|
||||||
const glm::vec3& c1,
|
|
||||||
float r,
|
|
||||||
const glm::vec3& v,
|
|
||||||
const glm::vec3& t0,
|
|
||||||
const glm::vec3& t1,
|
|
||||||
const glm::vec3& t2);
|
|
||||||
|
|
||||||
void ResolveCollisions(glm::vec3& p, glm::vec3& v, float h, float r, float dt, std::span<const collision::Triangle> triangles);
|
|
||||||
}
|
|
||||||
@ -1,14 +1,20 @@
|
|||||||
#include "trianglemesh.hpp"
|
#include "trianglemesh.hpp"
|
||||||
|
|
||||||
|
collision::TriangleMesh::TriangleMesh()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2)
|
void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2)
|
||||||
{
|
{
|
||||||
Triangle& tri = tris_.emplace_back();
|
btVector3 bt_v0(v0.x, v0.y, v0.z);
|
||||||
tri.verts[0] = v0;
|
btVector3 bt_v1(v1.x, v1.y, v1.z);
|
||||||
tri.verts[1] = v1;
|
btVector3 bt_v2(v2.x, v2.y, v2.z);
|
||||||
tri.verts[2] = v2;
|
bt_mesh_.addTriangle(bt_v0, bt_v1, bt_v2, true);
|
||||||
|
}
|
||||||
for (size_t i = 0; i < 3; ++i)
|
|
||||||
{
|
void collision::TriangleMesh::Build()
|
||||||
tri.aabb.AddPoint(tri.verts[i]);
|
{
|
||||||
}
|
bt_shape_ = std::make_unique<btBvhTriangleMeshShape>(&bt_mesh_, true, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,25 +5,23 @@
|
|||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include "aabb.hpp"
|
#include "aabb.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <btBulletCollisionCommon.h>
|
||||||
|
|
||||||
namespace collision
|
namespace collision
|
||||||
{
|
{
|
||||||
struct Triangle
|
|
||||||
{
|
|
||||||
glm::vec3 verts[3];
|
|
||||||
AABB3 aabb;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TriangleMesh
|
class TriangleMesh
|
||||||
{
|
{
|
||||||
std::vector<Triangle> tris_;
|
btTriangleMesh bt_mesh_;
|
||||||
|
std::unique_ptr<btBvhTriangleMeshShape> bt_shape_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TriangleMesh() = default;
|
TriangleMesh();
|
||||||
|
|
||||||
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
||||||
|
void Build();
|
||||||
|
|
||||||
std::span<const Triangle> GetTriangles() const {
|
btBvhTriangleMeshShape* GetShape() { return bt_shape_.get(); }
|
||||||
return std::span(tris_);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1,6 +1,153 @@
|
|||||||
#include "entity.hpp"
|
#include "entity.hpp"
|
||||||
|
#include "world.hpp"
|
||||||
|
|
||||||
game::Entity::Entity(World* world, size_t sector_idx, const glm::vec3& position)
|
game::Entity::Entity(World* world, size_t sector_idx, const CapsuleShape& capsule_shape, const glm::vec3& position) :
|
||||||
|
world_(world),
|
||||||
|
capsule_(capsule_shape),
|
||||||
|
velocity_(0.0f)
|
||||||
|
{
|
||||||
|
CreateOccurrenceParams occu_params;
|
||||||
|
occu_params.sector = &world->GetSector(sector_idx);
|
||||||
|
occu_params.position = position;
|
||||||
|
occu_params.scale = 1.0f; // Default scale
|
||||||
|
occu_params.basis = glm::mat3(1.0f); // Identity basis
|
||||||
|
|
||||||
|
// Create initial occurence in given sector
|
||||||
|
occu_ = CreateOccurrence(occu_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Entity::Move(glm::vec3& velocity, float dt)
|
||||||
|
{
|
||||||
|
glm::vec3 u = velocity * dt; // Calculate the movement vector
|
||||||
|
|
||||||
|
const int MAX_ITERS = 4;
|
||||||
|
for (size_t i = 0; i < MAX_ITERS && glm::dot(u, u) > 0.0f; ++i)
|
||||||
|
{
|
||||||
|
printf("Entity::Move: Iteration %zu, u = (%f, %f, %f)\n", i, u.x, u.y, u.z);
|
||||||
|
|
||||||
|
// offset in occu's sector space
|
||||||
|
glm::vec3 occu_offset = occu_->basis_ * u;
|
||||||
|
glm::vec3 to = occu_->position_ + occu_offset;
|
||||||
|
|
||||||
|
float occu_hit_fraction = 1.0f;
|
||||||
|
glm::vec3 occu_hit_normal;
|
||||||
|
const Portal* hit_portal = nullptr;
|
||||||
|
|
||||||
|
bool hit = occu_->Sweep(to, occu_hit_fraction, occu_hit_normal, &hit_portal);
|
||||||
|
|
||||||
|
if (hit_portal != touching_portal_)
|
||||||
|
{
|
||||||
|
touching_portal_ = hit_portal;
|
||||||
|
|
||||||
|
if (hit_portal)
|
||||||
|
{
|
||||||
|
// Beginning to touch a portal, create other occurrence in the linked sector
|
||||||
|
CreateOtherOccurence(*hit_portal);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not touching the portal anymore, destroy the other occurrence
|
||||||
|
other_occu_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float other_hit_fraction = 1.0f;
|
||||||
|
glm::vec3 other_hit_normal;
|
||||||
|
|
||||||
|
bool other_hit = false;
|
||||||
|
|
||||||
|
if (other_occu_)
|
||||||
|
{
|
||||||
|
glm::vec3 other_offset = other_occu_->basis_ * u;
|
||||||
|
glm::vec3 other_to = other_occu_->position_ + other_offset;
|
||||||
|
|
||||||
|
// Sweep the other occurrence if it exists
|
||||||
|
other_hit = other_occu_->Sweep(other_to, other_hit_fraction, other_hit_normal, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool any_hit = hit || other_hit;
|
||||||
|
float hit_fraction = 1.0f; // Default to no hit
|
||||||
|
glm::vec3 hit_normal;
|
||||||
|
|
||||||
|
if (hit && (!other_hit || occu_hit_fraction <= other_hit_fraction))
|
||||||
|
{
|
||||||
|
// Primary occurrence hit firstly
|
||||||
|
hit_fraction = occu_hit_fraction;
|
||||||
|
hit_normal = occu_->inv_basis_ * occu_hit_normal;
|
||||||
|
}
|
||||||
|
else if (other_hit)
|
||||||
|
{
|
||||||
|
// Other occurrence hit firstly
|
||||||
|
hit_fraction = other_hit_fraction;
|
||||||
|
hit_normal = other_occu_->inv_basis_ * other_hit_normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the position based on the hit fraction
|
||||||
|
occu_->position_ += hit_fraction * occu_offset;
|
||||||
|
|
||||||
|
if (any_hit)
|
||||||
|
{
|
||||||
|
occu_->position_ += hit_normal * 0.00001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (other_occu_ && touching_portal_)
|
||||||
|
{
|
||||||
|
other_occu_->position_ = touching_portal_->tr_position * glm::vec4(occu_->position_, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!any_hit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//hit_normal *= -1.0f; // Invert the normal to point outwards
|
||||||
|
printf("Entity::Move: Hit detected, hit_fraction = %f, hit_normal = (%f, %f, %f)\n", hit_fraction, hit_normal.x, hit_normal.y, hit_normal.z);
|
||||||
|
|
||||||
|
u -= hit_fraction * u; // Reduce the movement vector by the hit fraction
|
||||||
|
u -= glm::dot(u, hit_normal) * hit_normal; // Reflect the velocity along the hit normal
|
||||||
|
velocity -= glm::dot(velocity, hit_normal) * hit_normal; // Adjust the velocity
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Entity::Update(float dt)
|
||||||
|
{
|
||||||
|
Move(velocity_, dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Entity::CreateOtherOccurence(const Portal& portal)
|
||||||
|
{
|
||||||
|
CreateOccurrenceParams other_occu_params;
|
||||||
|
other_occu_params.sector = portal.link->sector;
|
||||||
|
other_occu_params.position = portal.tr_position * glm::vec4(occu_->position_, 1.0f);
|
||||||
|
other_occu_params.scale = occu_->scale_ * portal.tr_scale;
|
||||||
|
other_occu_params.basis = portal.tr_basis * occu_->basis_;
|
||||||
|
|
||||||
|
other_occu_ = CreateOccurrence(other_occu_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
game::EntityOccurrence::EntityOccurrence(Entity* entity, const CreateOccurrenceParams& params) :
|
||||||
|
entity_(entity),
|
||||||
|
sector_(params.sector),
|
||||||
|
position_(params.position),
|
||||||
|
scale_(params.scale),
|
||||||
|
basis_(params.basis),
|
||||||
|
inv_basis_(glm::inverse(params.basis)),
|
||||||
|
bt_capsule_(entity->GetCapsuleShape().radius * params.scale, entity->GetCapsuleShape().height * params.scale)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game::EntityOccurrence::Sweep(const glm::vec3& target_position, float& hit_fraction, glm::vec3& hit_normal, const Portal** hit_portal)
|
||||||
|
{
|
||||||
|
return sector_->SweepCapsule(
|
||||||
|
bt_capsule_,
|
||||||
|
basis_,
|
||||||
|
position_,
|
||||||
|
target_position,
|
||||||
|
hit_fraction,
|
||||||
|
hit_normal,
|
||||||
|
hit_portal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -6,32 +6,81 @@
|
|||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
class World;
|
class World;
|
||||||
|
class Entity;
|
||||||
|
|
||||||
|
struct CapsuleShape
|
||||||
|
{
|
||||||
|
float radius;
|
||||||
|
float height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CreateOccurrenceParams
|
||||||
|
{
|
||||||
|
Sector* sector;
|
||||||
|
glm::vec3 position;
|
||||||
|
float scale;
|
||||||
|
glm::mat3 basis; // Orthonormal
|
||||||
|
};
|
||||||
|
|
||||||
class EntityOccurrence
|
class EntityOccurrence
|
||||||
{
|
{
|
||||||
const Sector* sector_;
|
Entity* entity_;
|
||||||
|
Sector* sector_;
|
||||||
|
|
||||||
float radius_;
|
|
||||||
glm::vec3 position_;
|
glm::vec3 position_;
|
||||||
glm::vec3 up_;
|
|
||||||
|
|
||||||
glm::mat3 trans_;
|
|
||||||
glm::mat3 inv_trans_;
|
|
||||||
|
|
||||||
|
float scale_;
|
||||||
|
glm::mat3 basis_; // Orthonormal basis in sector space
|
||||||
|
glm::mat3 inv_basis_;
|
||||||
|
|
||||||
|
btCapsuleShapeZ bt_capsule_;
|
||||||
|
|
||||||
|
friend class Entity;
|
||||||
|
|
||||||
|
public:
|
||||||
|
EntityOccurrence(Entity* entity, const CreateOccurrenceParams& params);
|
||||||
|
|
||||||
|
// pos in sector space
|
||||||
|
bool Sweep(const glm::vec3& target_position, float& hit_fraction, glm::vec3& hit_normal, const Portal** hit_portal);
|
||||||
|
|
||||||
|
const Sector& GetSector() const { return *sector_; }
|
||||||
|
const glm::vec3& GetPosition() const { return position_; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Entity
|
class Entity
|
||||||
{
|
{
|
||||||
World* world_;
|
World* world_;
|
||||||
|
|
||||||
|
CapsuleShape capsule_;
|
||||||
|
|
||||||
std::unique_ptr<EntityOccurrence> occu_;
|
std::unique_ptr<EntityOccurrence> occu_;
|
||||||
std::unique_ptr<EntityOccurrence> other_occu_;
|
std::unique_ptr<EntityOccurrence> other_occu_;
|
||||||
|
const Portal* touching_portal_;
|
||||||
|
|
||||||
glm::vec3 velocity_;
|
glm::vec3 velocity_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Entity(World* world, size_t sector_idx, const glm::vec3& position);
|
Entity(World* world, size_t sector_idx, const CapsuleShape& capsule_shape, const glm::vec3& position);
|
||||||
|
|
||||||
|
// offset in entity space
|
||||||
|
void Move(glm::vec3& velocity, float dt);
|
||||||
|
|
||||||
|
void Update(float dt);
|
||||||
|
|
||||||
|
void SetVelocity(const glm::vec3& velocity) { velocity_ = velocity; }
|
||||||
|
|
||||||
|
const glm::vec3& GetVelocity() const { return velocity_; }
|
||||||
|
const CapsuleShape& GetCapsuleShape() const { return capsule_; }
|
||||||
|
EntityOccurrence& GetOccurrence() { return *occu_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual std::unique_ptr<EntityOccurrence> CreateOccurrence(const CreateOccurrenceParams& params)
|
||||||
|
{
|
||||||
|
return std::make_unique<EntityOccurrence>(this, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void CreateOtherOccurence(const Portal& portal);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,10 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef
|
|||||||
world_(world),
|
world_(world),
|
||||||
idx_(idx),
|
idx_(idx),
|
||||||
def_(std::move(def)),
|
def_(std::move(def)),
|
||||||
mesh_(def_->GetMesh())
|
mesh_(def_->GetMesh()),
|
||||||
|
|
||||||
|
bt_col_dispatcher_(&bt_col_cfg_),
|
||||||
|
bt_world_(&bt_col_dispatcher_, &bt_broad_phase_, &bt_col_cfg_)
|
||||||
{
|
{
|
||||||
auto def_portals = def_->GetPortals();
|
auto def_portals = def_->GetPortals();
|
||||||
size_t num_portals = def_portals.size();
|
size_t num_portals = def_portals.size();
|
||||||
@ -21,7 +24,6 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef
|
|||||||
|
|
||||||
Portal& portal = portals_.emplace_back();
|
Portal& portal = portals_.emplace_back();
|
||||||
portal.sector = this;
|
portal.sector = this;
|
||||||
portal.def_sector = &def_portal;
|
|
||||||
portal.def = &assets::PortalDef::portal_defs[def_portal.def_name];
|
portal.def = &assets::PortalDef::portal_defs[def_portal.def_name];
|
||||||
|
|
||||||
glm::mat4 rotation(glm::quat(glm::radians(def_portal.angles)));
|
glm::mat4 rotation(glm::quat(glm::radians(def_portal.angles)));
|
||||||
@ -29,6 +31,7 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef
|
|||||||
glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(def_portal.scale));
|
glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(def_portal.scale));
|
||||||
|
|
||||||
portal.trans = translation * rotation * scale;
|
portal.trans = translation * rotation * scale;
|
||||||
|
portal.scale = def_portal.scale;
|
||||||
|
|
||||||
portal.plane = glm::transpose(glm::inverse(portal.trans)) * glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
|
portal.plane = glm::transpose(glm::inverse(portal.trans)) * glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
@ -41,6 +44,9 @@ game::Sector::Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef
|
|||||||
|
|
||||||
portal_map_[def_portal.name] = i;
|
portal_map_[def_portal.name] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bt_mesh_col_obj_.setCollisionShape(mesh_->GetCollisionMesh()->GetShape());
|
||||||
|
bt_world_.addCollisionObject(&bt_mesh_col_obj_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Sector::ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert)
|
void game::Sector::ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert)
|
||||||
@ -61,6 +67,26 @@ int game::Sector::GetPortalIndex(const std::string& name) const
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void LinkPortal(game::Portal& p1, game::Portal& p2) {
|
||||||
|
// Calculate the traverse transformation
|
||||||
|
glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0, 0, 1));
|
||||||
|
glm::mat4 p1_to_p2 = p2.trans * rotation * glm::inverse(p1.trans);
|
||||||
|
|
||||||
|
p1.link = &p2;
|
||||||
|
|
||||||
|
// Transforms points from p1's sector space to p2's sector space
|
||||||
|
p1.tr_position = p1_to_p2;
|
||||||
|
|
||||||
|
// Transforms directions from p1's sector space to p2's sector space
|
||||||
|
p1.tr_basis = glm::mat3(p1.tr_position);
|
||||||
|
|
||||||
|
// get rid of scale
|
||||||
|
for (size_t i = 0; i < 3 ; i++)
|
||||||
|
p1.tr_orientation[i] = glm::normalize(p1.tr_basis[i]);
|
||||||
|
|
||||||
|
p1.tr_scale = p2.scale / p1.scale;
|
||||||
|
}
|
||||||
|
|
||||||
void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2)
|
void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2)
|
||||||
{
|
{
|
||||||
Portal& p1 = s1.portals_[idx1];
|
Portal& p1 = s1.portals_[idx1];
|
||||||
@ -71,18 +97,97 @@ void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2)
|
|||||||
throw std::runtime_error("One of the portals is already linked");
|
throw std::runtime_error("One of the portals is already linked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LinkPortal(p1, p2);
|
||||||
|
|
||||||
// Calculate the link transformation
|
|
||||||
glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0, 0, 1));
|
|
||||||
glm::mat4 p1_to_p2 = p2.trans * rotation * glm::inverse(p1.trans);
|
|
||||||
|
|
||||||
p1.link = &p2;
|
|
||||||
p1.link_trans = p1_to_p2;
|
|
||||||
|
|
||||||
if (&p1 != &p2)
|
if (&p1 != &p2)
|
||||||
{
|
{
|
||||||
p2.link = &p1;
|
LinkPortal(p2, p1);
|
||||||
p2.link_trans = glm::inverse(p1_to_p2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
struct PortalDetectingClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||||
|
{
|
||||||
|
using Super = btCollisionWorld::ClosestConvexResultCallback;
|
||||||
|
|
||||||
|
PortalDetectingClosestConvexResultCallback(const btVector3& convexFromWorld, const btVector3& convexToWorld) :
|
||||||
|
Super(convexFromWorld, convexToWorld)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const Portal* hit_portal = nullptr;
|
||||||
|
float hit_portal_fraction = std::numeric_limits<float>::max();
|
||||||
|
|
||||||
|
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) override
|
||||||
|
{
|
||||||
|
void* ptr = convexResult.m_hitCollisionObject->getUserPointer();
|
||||||
|
CollisionObjectData* data = static_cast<CollisionObjectData*>(ptr);
|
||||||
|
|
||||||
|
if (data && data->type == CO_PORTAL)
|
||||||
|
{
|
||||||
|
if (convexResult.m_hitFraction < hit_portal_fraction)
|
||||||
|
{
|
||||||
|
hit_portal = data->portal;
|
||||||
|
hit_portal_fraction = convexResult.m_hitFraction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Super::addSingleResult(convexResult, normalInWorldSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f; // Continue processing other results
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game::Sector::SweepCapsule(
|
||||||
|
const btCapsuleShapeZ& capsule,
|
||||||
|
const glm::mat3& basis,
|
||||||
|
const glm::vec3& start,
|
||||||
|
const glm::vec3& end,
|
||||||
|
float& hit_fraction,
|
||||||
|
glm::vec3& hit_normal,
|
||||||
|
const Portal** hit_portal)
|
||||||
|
{
|
||||||
|
//const btVector3* bt_start = reinterpret_cast<const btVector3*>(&start);
|
||||||
|
//const btVector3* bt_end = reinterpret_cast<const btVector3*>(&end);
|
||||||
|
//const btMatrix3x3* bt_basis = reinterpret_cast<const btMatrix3x3*>(&basis);
|
||||||
|
|
||||||
|
btVector3 bt_start(start.x, start.y, start.z);
|
||||||
|
btVector3 bt_end(end.x, end.y, end.z);
|
||||||
|
btMatrix3x3 bt_basis(
|
||||||
|
basis[0][0], basis[0][1], basis[0][2],
|
||||||
|
basis[1][0], basis[1][1], basis[1][2],
|
||||||
|
basis[2][0], basis[2][1], basis[2][2]
|
||||||
|
);
|
||||||
|
|
||||||
|
btTransform start_transform(bt_basis, bt_start);
|
||||||
|
btTransform end_transform(bt_basis, bt_end);
|
||||||
|
|
||||||
|
PortalDetectingClosestConvexResultCallback result_callback(bt_start, bt_end);
|
||||||
|
|
||||||
|
bt_world_.convexSweepTest(&capsule, start_transform, end_transform, result_callback);
|
||||||
|
|
||||||
|
if (hit_portal)
|
||||||
|
{
|
||||||
|
*hit_portal = result_callback.hit_portal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result_callback.hasHit())
|
||||||
|
{
|
||||||
|
hit_fraction = result_callback.m_closestHitFraction;
|
||||||
|
hit_normal = glm::vec3(
|
||||||
|
result_callback.m_hitNormalWorld.x(),
|
||||||
|
result_callback.m_hitNormalWorld.y(),
|
||||||
|
result_callback.m_hitNormalWorld.z()
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,28 +3,47 @@
|
|||||||
#include "assets/sectordef.hpp"
|
#include "assets/sectordef.hpp"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <btBulletCollisionCommon.h>
|
||||||
|
|
||||||
namespace game
|
namespace game
|
||||||
{
|
{
|
||||||
class Sector;
|
class Sector;
|
||||||
|
class Portal;
|
||||||
|
class World;
|
||||||
|
|
||||||
|
enum CollisionObjectType
|
||||||
|
{
|
||||||
|
CO_DEFAULT,
|
||||||
|
CO_PORTAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CollisionObjectData
|
||||||
|
{
|
||||||
|
CollisionObjectType type = CO_DEFAULT;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
const Portal* portal; // CO_PORTAL
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
struct Portal
|
struct Portal
|
||||||
{
|
{
|
||||||
Sector* sector;
|
Sector* sector;
|
||||||
|
|
||||||
const assets::SectorPortalDef* def_sector;
|
|
||||||
const assets::PortalDef* def;
|
const assets::PortalDef* def;
|
||||||
|
|
||||||
glm::mat4 trans;
|
glm::mat4 trans;
|
||||||
|
float scale;
|
||||||
|
|
||||||
glm::vec4 plane;
|
glm::vec4 plane;
|
||||||
glm::vec3 verts[4]; // Portal vertices in sector space
|
glm::vec3 verts[4]; // Portal vertices in sector space
|
||||||
|
|
||||||
Portal* link;
|
Portal* link;
|
||||||
glm::mat4 link_trans;
|
|
||||||
};
|
|
||||||
|
|
||||||
class World;
|
glm::mat4 tr_position; // this sector space to other sector space
|
||||||
|
glm::mat3 tr_basis;
|
||||||
|
glm::mat3 tr_orientation;
|
||||||
|
float tr_scale;
|
||||||
|
};
|
||||||
|
|
||||||
class Sector
|
class Sector
|
||||||
{
|
{
|
||||||
@ -37,6 +56,13 @@ namespace game
|
|||||||
std::vector<Portal> portals_;
|
std::vector<Portal> portals_;
|
||||||
std::map<std::string, size_t> portal_map_; // Maps portal name to index in portals_
|
std::map<std::string, size_t> portal_map_; // Maps portal name to index in portals_
|
||||||
|
|
||||||
|
btDefaultCollisionConfiguration bt_col_cfg_;
|
||||||
|
btCollisionDispatcher bt_col_dispatcher_;
|
||||||
|
btDbvtBroadphase bt_broad_phase_;
|
||||||
|
btCollisionWorld bt_world_;
|
||||||
|
|
||||||
|
btCollisionObject bt_mesh_col_obj_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef> def);
|
Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef> def);
|
||||||
|
|
||||||
@ -52,6 +78,14 @@ namespace game
|
|||||||
|
|
||||||
static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2);
|
static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2);
|
||||||
|
|
||||||
|
bool SweepCapsule(
|
||||||
|
const btCapsuleShapeZ& capsule,
|
||||||
|
const glm::mat3& basis,
|
||||||
|
const glm::vec3& start,
|
||||||
|
const glm::vec3& end,
|
||||||
|
float& hit_fraction,
|
||||||
|
glm::vec3& hit_normal,
|
||||||
|
const Portal** hit_portal);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ void gfx::Renderer::DrawWorld(
|
|||||||
proj_ = glm::perspective(
|
proj_ = glm::perspective(
|
||||||
glm::radians(fov), // FOV
|
glm::radians(fov), // FOV
|
||||||
aspect, // Aspect ratio
|
aspect, // Aspect ratio
|
||||||
0.1f, // Near plane
|
0.01f, // Near plane
|
||||||
1000.0f // Far plane
|
1000.0f // Far plane
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -190,9 +190,9 @@ void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Porta
|
|||||||
new_params.screen_aabb = params.screen_aabb.Intersection(portal_aabb);
|
new_params.screen_aabb = params.screen_aabb.Intersection(portal_aabb);
|
||||||
|
|
||||||
new_params.recursion = params.recursion + 1;
|
new_params.recursion = params.recursion + 1;
|
||||||
new_params.view = params.view * other_portal.link_trans;
|
new_params.view = params.view * other_portal.tr_position;
|
||||||
new_params.view_proj = proj_ * new_params.view;
|
new_params.view_proj = proj_ * new_params.view;
|
||||||
new_params.eye = portal.link_trans * glm::vec4(params.eye, 1.0f);
|
new_params.eye = portal.tr_position * glm::vec4(params.eye, 1.0f);
|
||||||
|
|
||||||
// Draw the sector through the portal
|
// Draw the sector through the portal
|
||||||
DrawSector(new_params);
|
DrawSector(new_params);
|
||||||
|
|||||||
15
src/main.cpp
15
src/main.cpp
@ -125,11 +125,22 @@ static void PollEvents()
|
|||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_PollEvent(&event))
|
while (SDL_PollEvent(&event))
|
||||||
{
|
{
|
||||||
if (event.type == SDL_QUIT)
|
switch (event.type)
|
||||||
{
|
{
|
||||||
|
case SDL_QUIT:
|
||||||
s_quit = true;
|
s_quit = true;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case SDL_MOUSEMOTION:
|
||||||
|
int xrel = event.motion.xrel;
|
||||||
|
int yrel = event.motion.yrel;
|
||||||
|
if (xrel != 0 || yrel != 0)
|
||||||
|
{
|
||||||
|
s_app->MouseMove(glm::vec2(static_cast<float>(xrel), static_cast<float>(yrel)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +197,8 @@ static void Main() {
|
|||||||
throw std::runtime_error("Failed to initialize OpenGL");
|
throw std::runtime_error("Failed to initialize OpenGL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
|
|
||||||
s_app = std::make_unique<App>();
|
s_app = std::make_unique<App>();
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user