Stuff 9.8.2025
This commit is contained in:
parent
21fa4c6d6f
commit
d10ffb17f0
@ -12,9 +12,25 @@ add_executable(PortalGame
|
|||||||
"src/app.cpp"
|
"src/app.cpp"
|
||||||
"src/gl.hpp"
|
"src/gl.hpp"
|
||||||
"src/utils.hpp"
|
"src/utils.hpp"
|
||||||
|
"src/assets/sectordef.hpp"
|
||||||
|
"src/assets/sectordef.cpp"
|
||||||
|
"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/game/sector.hpp"
|
||||||
|
"src/game/sector.cpp"
|
||||||
|
"src/game/world.hpp"
|
||||||
|
"src/game/world.cpp"
|
||||||
"src/gfx/buffer_object.cpp"
|
"src/gfx/buffer_object.cpp"
|
||||||
"src/gfx/buffer_object.hpp"
|
"src/gfx/buffer_object.hpp"
|
||||||
|
"src/gfx/renderer.hpp"
|
||||||
|
"src/gfx/renderer.cpp"
|
||||||
|
"src/gfx/shader.hpp"
|
||||||
|
"src/gfx/shader.cpp"
|
||||||
|
"src/gfx/shader_sources.hpp"
|
||||||
|
"src/gfx/shader_sources.cpp"
|
||||||
"src/gfx/texture.cpp"
|
"src/gfx/texture.cpp"
|
||||||
"src/gfx/texture.hpp"
|
"src/gfx/texture.hpp"
|
||||||
"src/gfx/vertex_array.cpp"
|
"src/gfx/vertex_array.cpp"
|
||||||
@ -35,14 +51,16 @@ if (CMAKE_SYSTEM_NAME STREQUAL Emscripten)
|
|||||||
|
|
||||||
target_compile_options(PortalGame PRIVATE
|
target_compile_options(PortalGame PRIVATE
|
||||||
"-sUSE_SDL=2"
|
"-sUSE_SDL=2"
|
||||||
|
"-sNO_DISABLE_EXCEPTION_CATCHING=1"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_options(PortalGame PRIVATE
|
target_link_options(PortalGame PRIVATE
|
||||||
"-sUSE_SDL=2"
|
"-sUSE_SDL=2"
|
||||||
"-sASYNCIFY"
|
"-sASYNCIFY"
|
||||||
"-sUSE_WEBGL2=1"
|
"-sUSE_WEBGL2=1"
|
||||||
|
"-sNO_DISABLE_EXCEPTION_CATCHING=1"
|
||||||
"--shell-file" "${CMAKE_SOURCE_DIR}/shell.html"
|
"--shell-file" "${CMAKE_SOURCE_DIR}/shell.html"
|
||||||
"--preload-file" "${CMAKE_SOURCE_DIR}/assets"
|
"--preload-file" "${CMAKE_SOURCE_DIR}/assets/@/"
|
||||||
)
|
)
|
||||||
|
|
||||||
else()
|
else()
|
||||||
@ -63,5 +81,5 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(external/glm)
|
add_subdirectory(external/glm)
|
||||||
# target_include_directories(PortalGame PRIVATE "external/glm")
|
target_link_libraries(PortalGame PRIVATE glm)
|
||||||
target_link_libraries(PortalGame PRIVATE glm)
|
target_include_directories(PortalGame PRIVATE "external/stb")
|
||||||
@ -27,7 +27,7 @@
|
|||||||
"description": "Build with Emscripten toolchain using Multi-Config generator",
|
"description": "Build with Emscripten toolchain using Multi-Config generator",
|
||||||
"generator": "Ninja Multi-Config",
|
"generator": "Ninja Multi-Config",
|
||||||
"binaryDir": "${sourceDir}/build/wasm",
|
"binaryDir": "${sourceDir}/build/wasm",
|
||||||
"toolchainFile": "c:/dev/emsdk/cmake/Modules/Platform/Emscripten.cmake",
|
"toolchainFile": "c:/dev/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
Test
|
|
||||||
1955
external/glad/include/glad/glad.h
vendored
1955
external/glad/include/glad/glad.h
vendored
File diff suppressed because it is too large
Load Diff
892
external/glad/src/glad.c
vendored
892
external/glad/src/glad.c
vendored
File diff suppressed because it is too large
Load Diff
7988
external/stb/stb_image.h
vendored
Normal file
7988
external/stb/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
47
src/app.cpp
47
src/app.cpp
@ -1,14 +1,51 @@
|
|||||||
#include "app.hpp"
|
#include "app.hpp"
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include "gl.hpp"
|
#include <iostream>
|
||||||
|
|
||||||
App::App()
|
App::App()
|
||||||
{
|
{
|
||||||
|
std::cout << "Initializing App..." << std::endl;
|
||||||
|
std::cout << "Loading shader..." << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Loading sector defs..." << std::endl;
|
||||||
|
|
||||||
|
// Door frame portal def
|
||||||
|
assets::PortalDef& portal100x200 = assets::PortalDef::portal_defs["100x200"];
|
||||||
|
portal100x200.size = glm::vec2(1.0f, 2.0f); // 1m x 2m portal
|
||||||
|
portal100x200.half_frame_thickness = 0.1f; // 10cm offset towards the second sector
|
||||||
|
|
||||||
|
auto room001 = assets::SectorDef::LoadFromFile("data/room_001");
|
||||||
|
|
||||||
|
size_t s1 = world_.AddSector(room001);
|
||||||
|
size_t s2 = world_.AddSector(room001);
|
||||||
|
|
||||||
|
world_.LinkPortals(s1, "NDoor", s2, "WDoor");
|
||||||
|
world_.LinkPortals(s2, "NDoor", s2, "SDoor");
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::Frame()
|
void App::Frame()
|
||||||
{
|
{
|
||||||
glm::vec3 clear_color(glm::abs(glm::sin(m_time)), 0.2f, 0.3f);
|
float aspect = static_cast<float>(m_viewport_size.x) / static_cast<float>(m_viewport_size.y);
|
||||||
glClearColor(clear_color.r, clear_color.g, clear_color.b, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
renderer_.Begin(m_viewport_size.x, m_viewport_size.y);
|
||||||
|
|
||||||
|
float cameraDistance = 2.0f;
|
||||||
|
float angle = m_time * 0.1f; // Rotate over time
|
||||||
|
|
||||||
|
glm::vec3 cameraPos = glm::vec3(
|
||||||
|
cameraDistance * cos(angle),
|
||||||
|
cameraDistance * sin(angle),
|
||||||
|
cameraDistance * 0.13f
|
||||||
|
);
|
||||||
|
|
||||||
|
glm::vec3 center(0.0f, 0.0f, 1.5f);
|
||||||
|
glm::vec3 up(0.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
renderer_.DrawWorld(world_, 1, center + cameraPos, -cameraPos, up, aspect, 60.0f);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
App::~App()
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/app.hpp
12
src/app.hpp
@ -1,8 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "assets/sectordef.hpp"
|
||||||
|
|
||||||
|
#include "game/world.hpp"
|
||||||
|
#include "gfx/renderer.hpp"
|
||||||
|
|
||||||
class App
|
class App
|
||||||
{
|
{
|
||||||
float m_time = 0.0f;
|
float m_time = 0.0f;
|
||||||
|
glm::ivec2 m_viewport_size = { 800, 600 };
|
||||||
|
|
||||||
|
game::World world_;
|
||||||
|
|
||||||
|
gfx::Renderer renderer_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
App();
|
App();
|
||||||
@ -10,6 +20,8 @@ public:
|
|||||||
void Frame();
|
void Frame();
|
||||||
|
|
||||||
void SetTime(float time) { m_time = time; }
|
void SetTime(float time) { m_time = time; }
|
||||||
|
void SetViewportSize(int width, int height) { m_viewport_size = { width, height }; }
|
||||||
|
|
||||||
|
~App();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,21 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& filename)
|
assets::Mesh::Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials) :
|
||||||
|
va_(gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV | gfx::VA_LIGHTMAP_UV, gfx::VF_CREATE_EBO)
|
||||||
{
|
{
|
||||||
|
va_.SetVBOData(verts.data(), verts.size_bytes());
|
||||||
|
va_.SetIndices(reinterpret_cast<const GLuint*>(tris.data()), tris.size() * 3);
|
||||||
|
materials_.assign(materials.begin(), materials.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision)
|
||||||
|
{
|
||||||
|
std::shared_ptr<collision::TriangleMesh> collision_mesh;
|
||||||
|
|
||||||
|
if (load_collision)
|
||||||
|
collision_mesh = std::make_shared<collision::TriangleMesh>();
|
||||||
|
|
||||||
std::ifstream file(filename, std::ios::binary);
|
std::ifstream file(filename, std::ios::binary);
|
||||||
|
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
@ -16,7 +29,7 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
|
|||||||
|
|
||||||
std::vector<MeshVertex> verts;
|
std::vector<MeshVertex> verts;
|
||||||
std::vector<MeshTriangle> tris;
|
std::vector<MeshTriangle> tris;
|
||||||
std::vector<MeshMaterialSlot> materials;
|
std::vector<MeshMaterial> materials;
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
@ -42,11 +55,11 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
|
|||||||
{
|
{
|
||||||
if (materials.size() > 0)
|
if (materials.size() > 0)
|
||||||
{
|
{
|
||||||
MeshMaterialSlot& last_material = materials.back();
|
MeshMaterial& last_material = materials.back();
|
||||||
last_material.num_tris = tris.size() - last_material.first_tri;
|
last_material.num_tris = tris.size() - last_material.first_tri;
|
||||||
}
|
}
|
||||||
|
|
||||||
MeshMaterialSlot& material = materials.emplace_back();
|
MeshMaterial& material = materials.emplace_back();
|
||||||
iss >> material.name;
|
iss >> material.name;
|
||||||
material.first_tri = tris.size();
|
material.first_tri = tris.size();
|
||||||
|
|
||||||
@ -54,20 +67,53 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
|
|||||||
else if (command == "f") // Face
|
else if (command == "f") // Face
|
||||||
{
|
{
|
||||||
MeshTriangle& tri = tris.emplace_back();
|
MeshTriangle& tri = tris.emplace_back();
|
||||||
iss >> tri.vert[0] >> tri.vert[1] >> tri.vert[2];
|
glm::vec3 tri_verts[3];
|
||||||
|
for (size_t i = 0; i < 3; ++i) {
|
||||||
|
uint32_t vert_index;
|
||||||
|
iss >> vert_index;
|
||||||
|
if (vert_index >= verts.size()) {
|
||||||
|
throw std::runtime_error("Vertex index out of bounds in mesh file: " + filename);
|
||||||
|
}
|
||||||
|
tri.vert[i] = vert_index;
|
||||||
|
tri_verts[i] = verts[vert_index].pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_collision)
|
||||||
|
{
|
||||||
|
// Add triangle to collision mesh
|
||||||
|
collision_mesh->AddTriangle(tri_verts[0], tri_verts[1], tri_verts[2]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (materials.size() > 0)
|
if (materials.size() > 0)
|
||||||
{
|
{
|
||||||
MeshMaterialSlot& last_material = materials.back();
|
MeshMaterial& last_material = materials.back();
|
||||||
last_material.num_tris = tris.size() - last_material.first_tri;
|
last_material.num_tris = tris.size() - last_material.first_tri;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
if (verts.empty() || tris.empty())
|
if (verts.empty() || tris.empty())
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Mesh file is empty or malformed: " + filename);
|
throw std::runtime_error("Mesh file is empty or malformed: " + filename);
|
||||||
}
|
}
|
||||||
return std::make_shared<Mesh>(verts, tris, materials);
|
|
||||||
|
// Load textures for materials
|
||||||
|
for (MeshMaterial& material : materials)
|
||||||
|
{
|
||||||
|
if (!material.name.empty())
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
material.texture = gfx::Texture::LoadFromFile("data/" + material.name + ".png");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throw std::runtime_error("Failed to load texture for material '" + material.name + "': " + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create the mesh object
|
||||||
|
|
||||||
|
std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>(verts, tris, materials);
|
||||||
|
mesh->SetCollisionMesh(std::move(collision_mesh));
|
||||||
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "gfx/vertex_array.hpp"
|
#include "gfx/vertex_array.hpp"
|
||||||
#include "gfx/texture.hpp"
|
#include "gfx/texture.hpp"
|
||||||
|
#include "collision/trianglemesh.hpp"
|
||||||
|
|
||||||
namespace assets
|
namespace assets
|
||||||
{
|
{
|
||||||
@ -23,7 +24,7 @@ namespace assets
|
|||||||
uint32_t vert[3];
|
uint32_t vert[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MeshMaterialSlot
|
struct MeshMaterial
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
std::shared_ptr<gfx::Texture> texture;
|
std::shared_ptr<gfx::Texture> texture;
|
||||||
@ -34,11 +35,23 @@ namespace assets
|
|||||||
class Mesh
|
class Mesh
|
||||||
{
|
{
|
||||||
gfx::VertexArray va_;
|
gfx::VertexArray va_;
|
||||||
std::vector<MeshMaterialSlot> materials_;
|
std::vector<MeshMaterial> materials_;
|
||||||
|
std::shared_ptr<collision::TriangleMesh> collision_mesh_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterialSlot> materials);
|
Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials);
|
||||||
|
|
||||||
static std::shared_ptr<Mesh> LoadFromFile(const std::string& filename);
|
void SetCollisionMesh(std::shared_ptr<collision::TriangleMesh> mesh) {
|
||||||
|
collision_mesh_ = std::move(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<collision::TriangleMesh>& GetCollisionMesh() const {
|
||||||
|
return collision_mesh_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gfx::VertexArray& GetVA() { return va_; }
|
||||||
|
const std::span<MeshMaterial> GetMaterials() { return materials_; }
|
||||||
|
|
||||||
|
static std::shared_ptr<Mesh> LoadFromFile(const std::string& filename, bool load_collision);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
class RoomDef
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
||||||
65
src/assets/sectordef.cpp
Normal file
65
src/assets/sectordef.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include "sectordef.hpp"
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
std::map<std::string, assets::PortalDef> assets::PortalDef::portal_defs;
|
||||||
|
|
||||||
|
assets::SectorDef::SectorDef(std::shared_ptr<Mesh> mesh, std::span<SectorPortalDef> portals)
|
||||||
|
{
|
||||||
|
mesh_ = std::move(mesh);
|
||||||
|
portals_.assign(portals.begin(), portals.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<assets::SectorDef> assets::SectorDef::LoadFromFile(const std::string& base_name)
|
||||||
|
{
|
||||||
|
std::string mesh_file = base_name + ".mesh";
|
||||||
|
auto mesh = Mesh::LoadFromFile(mesh_file, true);
|
||||||
|
if (!mesh)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to load mesh from file: " + mesh_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string info_file = base_name + ".info";
|
||||||
|
std::vector<SectorPortalDef> portals;
|
||||||
|
|
||||||
|
std::ifstream info_stream(info_file);
|
||||||
|
|
||||||
|
if (!info_stream.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open sector info file: " + info_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(info_stream, line))
|
||||||
|
{
|
||||||
|
if (line.empty() || line[0] == '#') // Skip empty lines and comments
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::istringstream iss(line);
|
||||||
|
|
||||||
|
std::string command;
|
||||||
|
iss >> command;
|
||||||
|
|
||||||
|
if (command == "portal")
|
||||||
|
{
|
||||||
|
SectorPortalDef portal;
|
||||||
|
iss >> portal.def_name >> portal.name;
|
||||||
|
iss >> portal.origin.x >> portal.origin.y >> portal.origin.z;
|
||||||
|
iss >> portal.angles.x >> portal.angles.y >> portal.angles.z;
|
||||||
|
iss >> portal.scale;
|
||||||
|
if (iss.fail())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to parse portal definition in file: " + info_file);
|
||||||
|
}
|
||||||
|
portals.push_back(std::move(portal));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unknown command in sector info file: " + command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<SectorDef>(std::move(mesh), portals);
|
||||||
|
}
|
||||||
44
src/assets/sectordef.hpp
Normal file
44
src/assets/sectordef.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <map>
|
||||||
|
#include "mesh.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
struct PortalDef
|
||||||
|
{
|
||||||
|
glm::vec2 size = glm::vec2(1.0f, 1.0f); // Size of the portal in meters
|
||||||
|
float half_frame_thickness = 0.1f; // Offset of the portal towards second sector
|
||||||
|
|
||||||
|
// Portal def map
|
||||||
|
static std::map<std::string, PortalDef> portal_defs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SectorPortalDef
|
||||||
|
{
|
||||||
|
std::string def_name;
|
||||||
|
std::string name;
|
||||||
|
glm::vec3 origin;
|
||||||
|
glm::vec3 angles; // Euler angles in degrees
|
||||||
|
float scale = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SectorDef
|
||||||
|
{
|
||||||
|
std::shared_ptr<Mesh> mesh_;
|
||||||
|
std::vector<SectorPortalDef> portals_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SectorDef(std::shared_ptr<Mesh> mesh, std::span<SectorPortalDef> portals);
|
||||||
|
|
||||||
|
const std::shared_ptr<Mesh>& GetMesh() const { return mesh_; }
|
||||||
|
std::span<const SectorPortalDef> GetPortals() const { return portals_; }
|
||||||
|
|
||||||
|
static std::shared_ptr<SectorDef> LoadFromFile(const std::string& base_name);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
55
src/collision/aabb.hpp
Normal file
55
src/collision/aabb.hpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
template <size_t dim>
|
||||||
|
struct AABB
|
||||||
|
{
|
||||||
|
static constexpr float C_INFINITY = std::numeric_limits<float>::infinity();
|
||||||
|
static constexpr float C_DEFAULT_MARGIN = 0.01f; // Margin for AABBs
|
||||||
|
|
||||||
|
using Vec = glm::vec<dim, float, glm::defaultp>;
|
||||||
|
|
||||||
|
Vec min = Vec(C_INFINITY);
|
||||||
|
Vec max = Vec(-C_INFINITY);
|
||||||
|
|
||||||
|
AABB() = default;
|
||||||
|
AABB(const Vec& min, const Vec& max) : min(min), max(max) {}
|
||||||
|
|
||||||
|
void AddPoint(const Vec& point, float margin = C_DEFAULT_MARGIN)
|
||||||
|
{
|
||||||
|
min = glm::min(min, point - margin);
|
||||||
|
max = glm::max(max, point + margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollidesWith(const AABB<dim>& other) const;
|
||||||
|
|
||||||
|
AABB<dim> Intersection(const AABB<dim>& other) const
|
||||||
|
{
|
||||||
|
Vec new_min = glm::max(min, other.min);
|
||||||
|
Vec new_max = glm::min(max, other.max);
|
||||||
|
return AABB<dim>(new_min, new_max);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AABB<2>::CollidesWith(const AABB<2>& other) const
|
||||||
|
{
|
||||||
|
return (min.x <= other.max.x && max.x >= other.min.x) &&
|
||||||
|
(min.y <= other.max.y && max.y >= other.min.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AABB<3>::CollidesWith(const AABB<3>& other) const
|
||||||
|
{
|
||||||
|
return (min.x <= other.max.x && max.x >= other.min.x) &&
|
||||||
|
(min.y <= other.max.y && max.y >= other.min.y) &&
|
||||||
|
(min.z <= other.max.z && max.z >= other.min.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
using AABB2 = AABB<2>;
|
||||||
|
using AABB3 = AABB<3>;
|
||||||
|
}
|
||||||
13
src/collision/capsule.hpp
Normal file
13
src/collision/capsule.hpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
struct Capsule
|
||||||
|
{
|
||||||
|
glm::vec3 p0;
|
||||||
|
glm::vec3 p1;
|
||||||
|
float radius;
|
||||||
|
};
|
||||||
|
}
|
||||||
447
src/collision/capsule_triangle_sweep.cpp
Normal file
447
src/collision/capsule_triangle_sweep.cpp
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
#include "capsule_triangle_sweep.hpp"
|
||||||
|
|
||||||
|
#define EPSILON 1e-5f // Used to test if float is close to 0. Tweak this if you get problems.
|
||||||
|
|
||||||
|
struct Sweep
|
||||||
|
{
|
||||||
|
float time; // Non-negative time of first contact.
|
||||||
|
float depth; // Non-negative penetration depth if objects start initially colliding.
|
||||||
|
glm::vec3 point; // Point of first-contact. Only updated when contact occurs.
|
||||||
|
glm::vec3 normal; // Unit-length collision normal. Only updated when contact occurs.
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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][0];
|
||||||
|
const glm::vec3& p1 = faces[i][1];
|
||||||
|
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
|
||||||
|
static 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)
|
||||||
|
{
|
||||||
|
// 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 ResolveCollisions(glm::vec3& p, glm::vec3& v, float h, float r, float dt, std::span<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/collision/capsule_triangle_sweep.hpp
Normal file
9
src/collision/capsule_triangle_sweep.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "capsule.hpp"
|
||||||
|
#include "trianglemesh.hpp"
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
14
src/collision/trianglemesh.cpp
Normal file
14
src/collision/trianglemesh.cpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "trianglemesh.hpp"
|
||||||
|
|
||||||
|
void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2)
|
||||||
|
{
|
||||||
|
Triangle& tri = tris_.emplace_back();
|
||||||
|
tri.verts[0] = v0;
|
||||||
|
tri.verts[1] = v1;
|
||||||
|
tri.verts[2] = v2;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
tri.aabb.AddPoint(tri.verts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/collision/trianglemesh.hpp
Normal file
29
src/collision/trianglemesh.hpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <span>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include "aabb.hpp"
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
struct Triangle
|
||||||
|
{
|
||||||
|
glm::vec3 verts[3];
|
||||||
|
AABB3 aabb;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TriangleMesh
|
||||||
|
{
|
||||||
|
std::vector<Triangle> tris_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TriangleMesh() = default;
|
||||||
|
|
||||||
|
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
||||||
|
|
||||||
|
std::span<const Triangle> GetTriangles() const {
|
||||||
|
return std::span(tris_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
11
src/game/entity.hpp
Normal file
11
src/game/entity.hpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
class Entity
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
88
src/game/sector.cpp
Normal file
88
src/game/sector.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#include "sector.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
game::Sector::Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef> def) :
|
||||||
|
world_(world),
|
||||||
|
idx_(idx),
|
||||||
|
def_(std::move(def)),
|
||||||
|
mesh_(def_->GetMesh())
|
||||||
|
{
|
||||||
|
auto def_portals = def_->GetPortals();
|
||||||
|
size_t num_portals = def_portals.size();
|
||||||
|
|
||||||
|
portals_.reserve(num_portals);
|
||||||
|
for (size_t i = 0; i < num_portals; ++i)
|
||||||
|
{
|
||||||
|
const assets::SectorPortalDef& def_portal = def_portals[i];
|
||||||
|
|
||||||
|
Portal& portal = portals_.emplace_back();
|
||||||
|
portal.sector = this;
|
||||||
|
portal.def_sector = &def_portal;
|
||||||
|
portal.def = &assets::PortalDef::portal_defs[def_portal.def_name];
|
||||||
|
|
||||||
|
glm::mat4 rotation(glm::quat(glm::radians(def_portal.angles)));
|
||||||
|
glm::mat4 translation = glm::translate(glm::mat4(1.0f), def_portal.origin);
|
||||||
|
glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(def_portal.scale));
|
||||||
|
|
||||||
|
portal.trans = translation * rotation * scale;
|
||||||
|
|
||||||
|
portal.plane = glm::transpose(glm::inverse(portal.trans)) * glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
ComputePortalVertex(portal, 0, glm::vec2(-1.0f, -1.0f)); // Bottom-left
|
||||||
|
ComputePortalVertex(portal, 1, glm::vec2(-1.0f, 1.0f)); // Top-left
|
||||||
|
ComputePortalVertex(portal, 3, glm::vec2(1.0f, -1.0f)); // Bottom-right
|
||||||
|
ComputePortalVertex(portal, 2, glm::vec2(1.0f, 1.0f)); // Top-right
|
||||||
|
|
||||||
|
portal.link = nullptr;
|
||||||
|
|
||||||
|
portal_map_[def_portal.name] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Sector::ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert)
|
||||||
|
{
|
||||||
|
glm::vec2 vert_xz = base_vert * portal.def->size * 0.5f; // Scale to portal size
|
||||||
|
glm::vec3 vert(vert_xz.x, 0.0f, vert_xz.y); // Convert to 3D vector
|
||||||
|
portal.verts[idx] = glm::vec3(portal.trans * glm::vec4(vert, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
int game::Sector::GetPortalIndex(const std::string& name) const
|
||||||
|
{
|
||||||
|
auto it = portal_map_.find(name);
|
||||||
|
if (it != portal_map_.end())
|
||||||
|
{
|
||||||
|
return static_cast<int>(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2)
|
||||||
|
{
|
||||||
|
Portal& p1 = s1.portals_[idx1];
|
||||||
|
Portal& p2 = s2.portals_[idx2];
|
||||||
|
|
||||||
|
if (p1.link || p2.link)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("One of the portals is already linked");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
p2.link = &p1;
|
||||||
|
p2.link_trans = glm::inverse(p1_to_p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
58
src/game/sector.hpp
Normal file
58
src/game/sector.hpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "assets/sectordef.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
class Sector;
|
||||||
|
|
||||||
|
struct Portal
|
||||||
|
{
|
||||||
|
Sector* sector;
|
||||||
|
|
||||||
|
const assets::SectorPortalDef* def_sector;
|
||||||
|
const assets::PortalDef* def;
|
||||||
|
|
||||||
|
glm::mat4 trans;
|
||||||
|
|
||||||
|
glm::vec4 plane;
|
||||||
|
glm::vec3 verts[4]; // Portal vertices in sector space
|
||||||
|
|
||||||
|
Portal* link;
|
||||||
|
glm::mat4 link_trans;
|
||||||
|
};
|
||||||
|
|
||||||
|
class World;
|
||||||
|
|
||||||
|
class Sector
|
||||||
|
{
|
||||||
|
World* world_;
|
||||||
|
size_t idx_;
|
||||||
|
std::shared_ptr<assets::SectorDef> def_;
|
||||||
|
|
||||||
|
std::shared_ptr<assets::Mesh> mesh_;
|
||||||
|
|
||||||
|
std::vector<Portal> portals_;
|
||||||
|
std::map<std::string, size_t> portal_map_; // Maps portal name to index in portals_
|
||||||
|
|
||||||
|
public:
|
||||||
|
Sector(World* world, size_t idx, std::shared_ptr<assets::SectorDef> def);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert);
|
||||||
|
|
||||||
|
public:
|
||||||
|
const std::shared_ptr<assets::Mesh>& GetMesh() const { return mesh_; }
|
||||||
|
|
||||||
|
int GetPortalIndex(const std::string& name) const;
|
||||||
|
const Portal& GetPortal(size_t idx) const { return portals_[idx]; }
|
||||||
|
const size_t GetNumPortals() const { return portals_.size(); }
|
||||||
|
|
||||||
|
static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
29
src/game/world.cpp
Normal file
29
src/game/world.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include "world.hpp"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
game::World::World()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t game::World::AddSector(std::shared_ptr<assets::SectorDef> def)
|
||||||
|
{
|
||||||
|
size_t idx = sectors_.size();
|
||||||
|
sectors_.emplace_back(std::make_unique<Sector>(this, idx, std::move(def)));
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, size_t sector2, const std::string& portal_name2)
|
||||||
|
{
|
||||||
|
Sector& s1 = *sectors_[sector1];
|
||||||
|
Sector& s2 = *sectors_[sector2];
|
||||||
|
int p1 = s1.GetPortalIndex(portal_name1);
|
||||||
|
int p2 = s2.GetPortalIndex(portal_name2);
|
||||||
|
|
||||||
|
if (p1 < 0 || p2 < 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Invalid portal names for linking");
|
||||||
|
}
|
||||||
|
|
||||||
|
Sector::LinkPortals(s1, p1, s2, p2);
|
||||||
|
}
|
||||||
37
src/game/world.hpp
Normal file
37
src/game/world.hpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include "assets/sectordef.hpp"
|
||||||
|
#include "sector.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
class World
|
||||||
|
{
|
||||||
|
std::vector<std::unique_ptr<Sector>> sectors_;
|
||||||
|
std::vector<std::unique_ptr<Entity>> entities_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
World();
|
||||||
|
|
||||||
|
size_t AddSector(std::shared_ptr<assets::SectorDef> def);
|
||||||
|
|
||||||
|
void LinkPortals(size_t sector1, const std::string& portal_name1,
|
||||||
|
size_t sector2, const std::string& portal_name2);
|
||||||
|
|
||||||
|
template<class T, class... TArgs>
|
||||||
|
T* Spawn(TArgs&&... args)
|
||||||
|
{
|
||||||
|
auto& entity = entities_.emplace_back(std::make_unique<T>(this, std::forward<TArgs>(args)...));
|
||||||
|
return entity.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Sector& GetSector(size_t idx) { return *sectors_[idx]; }
|
||||||
|
const Sector& GetSector(size_t idx) const { return *sectors_[idx]; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
262
src/gfx/renderer.cpp
Normal file
262
src/gfx/renderer.cpp
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
#include "gl.hpp"
|
||||||
|
|
||||||
|
#include "gfx/shader_sources.hpp"
|
||||||
|
|
||||||
|
gfx::Renderer::Renderer()
|
||||||
|
{
|
||||||
|
ShaderSources::MakeShader(sector_shader_, SS_SECTOR_MESH_VERT, SS_SECTOR_MESH_FRAG);
|
||||||
|
ShaderSources::MakeShader(portal_shader_, SS_PORTAL_VERT, SS_PORTAL_FRAG);
|
||||||
|
proj_ = glm::mat4(1.0f); // Initialize projection matrix to identity
|
||||||
|
|
||||||
|
SetupPortalVAO();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::SetupPortalVAO()
|
||||||
|
{
|
||||||
|
const glm::vec3 portal_verts[4] = {
|
||||||
|
{ -0.5f, 0.0f, 0.5f }, // Top-left
|
||||||
|
{ -0.5f, 0.0f, -0.5f }, // Bottom-left
|
||||||
|
{ 0.5f, 0.0f, -0.5f }, // Bottom-right
|
||||||
|
{ 0.5f, 0.0f, 0.5f }, // Top-right
|
||||||
|
};
|
||||||
|
|
||||||
|
portal_vao_ = std::make_shared<VertexArray>(VA_POSITION, 0);
|
||||||
|
portal_vao_->SetVBOData(portal_verts, sizeof(portal_verts));
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::Begin(size_t width, size_t height)
|
||||||
|
{
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
glClearStencil(0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::DrawWorld(
|
||||||
|
const game::World& world,
|
||||||
|
size_t sector_idx,
|
||||||
|
const glm::vec3& eye,
|
||||||
|
const glm::vec3& dir,
|
||||||
|
const glm::vec3& up,
|
||||||
|
float aspect,
|
||||||
|
float fov)
|
||||||
|
{
|
||||||
|
proj_ = glm::perspective(
|
||||||
|
glm::radians(fov), // FOV
|
||||||
|
aspect, // Aspect ratio
|
||||||
|
0.1f, // Near plane
|
||||||
|
1000.0f // Far plane
|
||||||
|
);
|
||||||
|
|
||||||
|
glm::mat4 view = glm::lookAt(
|
||||||
|
eye, // Camera position
|
||||||
|
eye + dir, // Look at point (camera position + look direction)
|
||||||
|
up // Up vector
|
||||||
|
);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(GL_BACK);
|
||||||
|
|
||||||
|
glEnable(GL_STENCIL_TEST);
|
||||||
|
glStencilFunc(GL_ALWAYS, 0, 0xFF); // Always pass stencil test
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||||
|
glStencilMask(0xFF);
|
||||||
|
|
||||||
|
const game::Sector& sector = world.GetSector(sector_idx);
|
||||||
|
|
||||||
|
DrawSectorParams params;
|
||||||
|
params.sector = §or;
|
||||||
|
params.entry_portal = nullptr; // No entry portal for the first sector
|
||||||
|
params.clip_plane = glm::vec4(0.0f, 0.0f, 1.0f, 1000.0f); // Default clip plane
|
||||||
|
params.screen_aabb = collision::AABB2(glm::vec2(-1.0f), glm::vec2(1.0f)); // Full screen
|
||||||
|
params.recursion = 0; // Start with recursion level 0
|
||||||
|
params.view = view;
|
||||||
|
params.view_proj = proj_ * view;
|
||||||
|
params.eye = eye;
|
||||||
|
|
||||||
|
DrawSector(params);
|
||||||
|
|
||||||
|
//glDisable(GL_STENCIL_TEST);
|
||||||
|
//glDisable(GL_CULL_FACE);
|
||||||
|
//glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::DrawSector(const DrawSectorParams& params)
|
||||||
|
{
|
||||||
|
assets::Mesh& mesh = *params.sector->GetMesh();
|
||||||
|
|
||||||
|
glm::mat4 model = glm::mat4(1.0f); // Identity matrix for model
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// #ifndef PG_GLES
|
||||||
|
// glEnable(GL_CLIP_DISTANCE0);
|
||||||
|
// #else
|
||||||
|
// glEnable(GL_CLIP_DISTANCE0_EXT);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
glUseProgram(sector_shader_->GetId());
|
||||||
|
glUniformMatrix4fv(sector_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]);
|
||||||
|
glUniformMatrix4fv(sector_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &model[0][0]);
|
||||||
|
glUniform4fv(sector_shader_->U(gfx::SU_CLIP_PLANE), 1, ¶ms.clip_plane[0]);
|
||||||
|
|
||||||
|
glBindVertexArray(mesh.GetVA().GetVAOId());
|
||||||
|
for (auto mesh_materials = mesh.GetMaterials(); const auto& mat : mesh_materials) {
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
if (mat.texture) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, mat.texture->GetId());
|
||||||
|
glUniform1i(sector_shader_->U(gfx::SU_TEX), 0); // Bind texture to sampler
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0); // No texture
|
||||||
|
}
|
||||||
|
glDrawElements(GL_TRIANGLES, mat.num_tris * 3, GL_UNSIGNED_INT, (void*)(mat.first_tri * 3 * sizeof(uint32_t)));
|
||||||
|
}
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < params.sector->GetNumPortals(); ++i)
|
||||||
|
{
|
||||||
|
const game::Portal& portal = params.sector->GetPortal(i);
|
||||||
|
DrawPortal(params, portal);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Portal& portal)
|
||||||
|
{
|
||||||
|
if (params.recursion > 10)
|
||||||
|
{
|
||||||
|
return; // Recursion limit reached
|
||||||
|
}
|
||||||
|
|
||||||
|
if (&portal == params.entry_portal)
|
||||||
|
{
|
||||||
|
return; // Skip the portal this sector was rendered through
|
||||||
|
// This should NEVER happen due to portal plane check, but would cause cyclic traversal if it did.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!portal.link)
|
||||||
|
{
|
||||||
|
return; // Portal not linked, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signed distance of eye to the portal plane
|
||||||
|
float eye_plane_sd = glm::dot(glm::vec3(portal.plane), params.eye) + portal.plane.w;
|
||||||
|
if (eye_plane_sd < 0.0f)
|
||||||
|
{
|
||||||
|
return; // Eye is behind the portal plane, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
collision::AABB2 portal_aabb;
|
||||||
|
if (!ComputePortalScreenAABB(portal, params.view_proj, portal_aabb))
|
||||||
|
{
|
||||||
|
return; // Portal is fully behind the camera, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!portal_aabb.CollidesWith(params.screen_aabb))
|
||||||
|
{
|
||||||
|
return; // Portal AABB does not intersect with the screen AABB, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open portal
|
||||||
|
glStencilFunc(GL_EQUAL, params.recursion, 0xFF);
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
|
||||||
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
||||||
|
DrawPortalPlane(params, portal, false); // Mark the portal plane in stencil buffer
|
||||||
|
glStencilFunc(GL_EQUAL, params.recursion + 1, 0xFF);
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
||||||
|
glDepthFunc(GL_ALWAYS);
|
||||||
|
DrawPortalPlane(params, portal, true); // Clear the depth buffer for nested sector
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||||
|
|
||||||
|
const game::Portal& other_portal = *portal.link;
|
||||||
|
const game::Sector& other_sector = *other_portal.sector;
|
||||||
|
|
||||||
|
DrawSectorParams new_params;
|
||||||
|
new_params.sector = &other_sector;
|
||||||
|
new_params.entry_portal = &other_portal;
|
||||||
|
new_params.clip_plane = other_portal.plane; // Use the portal plane as the clip plane
|
||||||
|
|
||||||
|
// Compute the new screen AABB based on the portal AABB and the current screen AABB
|
||||||
|
new_params.screen_aabb = params.screen_aabb.Intersection(portal_aabb);
|
||||||
|
|
||||||
|
new_params.recursion = params.recursion + 1;
|
||||||
|
new_params.view = params.view * other_portal.link_trans;
|
||||||
|
new_params.view_proj = proj_ * new_params.view;
|
||||||
|
new_params.eye = portal.link_trans * glm::vec4(params.eye, 1.0f);
|
||||||
|
|
||||||
|
// Draw the sector through the portal
|
||||||
|
DrawSector(new_params);
|
||||||
|
|
||||||
|
// Close portal
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
|
||||||
|
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
||||||
|
glStencilFunc(GL_EQUAL, params.recursion + 1, 0xFF);
|
||||||
|
DrawPortalPlane(params, portal, false);
|
||||||
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gfx::Renderer::ComputePortalScreenAABB(const game::Portal& portal, const glm::mat4 view_proj, collision::AABB2& aabb)
|
||||||
|
{
|
||||||
|
size_t num_behind = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
glm::vec4 vert = glm::vec4(portal.verts[i], 1.0f);
|
||||||
|
glm::vec4 clip = view_proj * vert;
|
||||||
|
|
||||||
|
if (clip.w <= 0.0f)
|
||||||
|
{
|
||||||
|
num_behind++;
|
||||||
|
continue; // Skip vertices behind the camera
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 ndc = clip;
|
||||||
|
ndc /= clip.w;
|
||||||
|
|
||||||
|
aabb.AddPoint(ndc, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_behind == 4)
|
||||||
|
{
|
||||||
|
return false; // All vertices are behind the camera, no visible portal
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_behind > 0)
|
||||||
|
{
|
||||||
|
// Some vertices are behind the camera, cannot use the AABB
|
||||||
|
aabb = collision::AABB2(glm::vec2(-1.0f), glm::vec2(1.0f)); // Full screen AABB
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, bool clear_depth)
|
||||||
|
{
|
||||||
|
if (!clear_depth) // first draw
|
||||||
|
{
|
||||||
|
glUseProgram(portal_shader_->GetId());
|
||||||
|
glUniformMatrix4fv(portal_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]);
|
||||||
|
glUniformMatrix4fv(portal_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &portal.trans[0][0]);
|
||||||
|
glUniform4fv(portal_shader_->U(gfx::SU_CLIP_PLANE), 1, ¶ms.clip_plane[0]);
|
||||||
|
glUniform2fv(portal_shader_->U(gfx::SU_PORTAL_SIZE), 1, &portal.def->size[0]);
|
||||||
|
glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 0);
|
||||||
|
|
||||||
|
glBindVertexArray(portal_vao_->GetVAOId());
|
||||||
|
}
|
||||||
|
else // assume uniforms are already set from previous draw
|
||||||
|
{
|
||||||
|
glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 1);
|
||||||
|
}
|
||||||
|
glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Draw the portal as a quad
|
||||||
|
}
|
||||||
|
|
||||||
54
src/gfx/renderer.hpp
Normal file
54
src/gfx/renderer.hpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "gfx/shader.hpp"
|
||||||
|
#include "game/world.hpp"
|
||||||
|
#include "game/sector.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
struct DrawSectorParams
|
||||||
|
{
|
||||||
|
const game::Sector* sector;
|
||||||
|
const game::Portal* entry_portal;
|
||||||
|
glm::vec4 clip_plane;
|
||||||
|
collision::AABB2 screen_aabb;
|
||||||
|
size_t recursion;
|
||||||
|
glm::mat4 view;
|
||||||
|
glm::mat4 view_proj;
|
||||||
|
glm::vec3 eye;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Renderer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Renderer();
|
||||||
|
|
||||||
|
void Begin(size_t width, size_t height);
|
||||||
|
|
||||||
|
void DrawWorld(
|
||||||
|
const game::World& world,
|
||||||
|
size_t sector_idx,
|
||||||
|
const glm::vec3& eye,
|
||||||
|
const glm::vec3& dir,
|
||||||
|
const glm::vec3& up,
|
||||||
|
float aspect,
|
||||||
|
float fov);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Shader> sector_shader_;
|
||||||
|
std::unique_ptr<Shader> portal_shader_;
|
||||||
|
|
||||||
|
std::shared_ptr<VertexArray> portal_vao_;
|
||||||
|
void SetupPortalVAO();
|
||||||
|
|
||||||
|
glm::mat4 proj_;
|
||||||
|
|
||||||
|
void DrawSector(const DrawSectorParams& params);
|
||||||
|
void DrawPortal(const DrawSectorParams& params, const game::Portal& portal);
|
||||||
|
|
||||||
|
static bool ComputePortalScreenAABB(const game::Portal& portal, const glm::mat4 view_proj, collision::AABB2& aabb);
|
||||||
|
void DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, bool clear_depth);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
87
src/gfx/shader.cpp
Normal file
87
src/gfx/shader.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include "shader.hpp"
|
||||||
|
#define LOG_LENGTH 1024
|
||||||
|
|
||||||
|
// Nazvy uniformnich promennych v shaderech
|
||||||
|
static const char* const s_uni_names[] = {
|
||||||
|
"u_model", // SU_MODEL
|
||||||
|
"u_view_proj", // SU_VIEW_PROJ
|
||||||
|
"u_tex", // SU_TEX
|
||||||
|
"u_clip_plane", // SU_CLIP_PLANE
|
||||||
|
"u_portal_size", // SU_PORTAL_SIZE
|
||||||
|
"u_clear_depth", // SU_CLEAR_DEPTH
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vytvori shader z daneho zdroje
|
||||||
|
static GLuint CreateShader(const char* src, GLenum type) {
|
||||||
|
GLuint id = glCreateShader(type);
|
||||||
|
|
||||||
|
glShaderSource(id, 1, &src, NULL);
|
||||||
|
glCompileShader(id);
|
||||||
|
|
||||||
|
GLint is_compiled = 0;
|
||||||
|
glGetShaderiv(id, GL_COMPILE_STATUS, &is_compiled);
|
||||||
|
if (is_compiled == GL_FALSE) {
|
||||||
|
GLchar log[LOG_LENGTH];
|
||||||
|
glGetShaderInfoLog(id, LOG_LENGTH, NULL, log);
|
||||||
|
|
||||||
|
glDeleteShader(id);
|
||||||
|
|
||||||
|
throw std::runtime_error(std::string("Nelze zkompilovat shader: ") + log);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Shader::Shader(const char* vert_src, const char* frag_src) {
|
||||||
|
m_id = glCreateProgram();
|
||||||
|
|
||||||
|
if (!m_id)
|
||||||
|
throw std::runtime_error("Nelze vytvorit program!");
|
||||||
|
|
||||||
|
GLuint vert, frag;
|
||||||
|
|
||||||
|
// Vytvoreni vertex shaderu
|
||||||
|
vert = CreateShader(vert_src, GL_VERTEX_SHADER);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vyvoreni fragment shaderu
|
||||||
|
frag = CreateShader(frag_src, GL_FRAGMENT_SHADER);
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
glDeleteShader(vert);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pripojeni shaderu k programu
|
||||||
|
glAttachShader(m_id, vert);
|
||||||
|
glAttachShader(m_id, frag);
|
||||||
|
|
||||||
|
// Linknuti programu
|
||||||
|
glLinkProgram(m_id);
|
||||||
|
|
||||||
|
// Smazani shaderu
|
||||||
|
glDeleteShader(vert);
|
||||||
|
glDeleteShader(frag);
|
||||||
|
|
||||||
|
GLint is_linked = 0;
|
||||||
|
glGetProgramiv(m_id, GL_LINK_STATUS, &is_linked);
|
||||||
|
if (is_linked == GL_FALSE) {
|
||||||
|
GLchar log[LOG_LENGTH];
|
||||||
|
glGetProgramInfoLog(m_id, LOG_LENGTH, NULL, log);
|
||||||
|
|
||||||
|
glDeleteProgram(m_id);
|
||||||
|
|
||||||
|
throw std::runtime_error(std::string("Nelze linknout shader: ") + log);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ziskani lokaci uniformnich promennych
|
||||||
|
for (size_t i = 0; i < SU_COUNT; i++)
|
||||||
|
m_uni[i] = glGetUniformLocation(m_id, s_uni_names[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Shader::~Shader() {
|
||||||
|
// Smazani programu
|
||||||
|
glDeleteProgram(m_id);
|
||||||
|
}
|
||||||
51
src/gfx/shader.hpp
Normal file
51
src/gfx/shader.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "gl.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* \brief Uniformy shaderu
|
||||||
|
*/
|
||||||
|
enum ShaderUniform
|
||||||
|
{
|
||||||
|
SU_MODEL,
|
||||||
|
SU_VIEW_PROJ,
|
||||||
|
SU_TEX,
|
||||||
|
SU_CLIP_PLANE,
|
||||||
|
SU_PORTAL_SIZE,
|
||||||
|
SU_CLEAR_DEPTH,
|
||||||
|
|
||||||
|
SU_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Wrapper pro OpenGL shader program
|
||||||
|
*/
|
||||||
|
class Shader : public NonCopyableNonMovable
|
||||||
|
{
|
||||||
|
GLuint m_id;
|
||||||
|
GLint m_uni[SU_COUNT];
|
||||||
|
|
||||||
|
public:
|
||||||
|
Shader(const char* vert_src, const char* frag_src);
|
||||||
|
~Shader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Vrátí lokaci uniformy
|
||||||
|
*
|
||||||
|
* \param u Uniforma
|
||||||
|
* \return Lokace
|
||||||
|
*/
|
||||||
|
GLint U(ShaderUniform u) const { return m_uni[u]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Vrátí OpenGL název shaderu
|
||||||
|
*
|
||||||
|
* \return Název shaderu
|
||||||
|
*/
|
||||||
|
GLuint GetId() const { return m_id; }
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
115
src/gfx/shader_sources.cpp
Normal file
115
src/gfx/shader_sources.cpp
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#include "shader_sources.hpp"
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
#define GLSL_VERSION \
|
||||||
|
"#version 330 core\n" \
|
||||||
|
"\n"
|
||||||
|
#else
|
||||||
|
#define GLSL_VERSION \
|
||||||
|
"#version 300 es\n" \
|
||||||
|
"precision mediump float;\n" \
|
||||||
|
"\n"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Zdrojove kody shaderu
|
||||||
|
static const char* const s_srcs[] = {
|
||||||
|
|
||||||
|
// SS_SECTOR_MESH_VERT
|
||||||
|
GLSL_VERSION
|
||||||
|
R"GLSL(
|
||||||
|
layout (location = 0) in vec3 a_pos;
|
||||||
|
layout (location = 1) in vec3 a_normal;
|
||||||
|
layout (location = 3) in vec2 a_uv;
|
||||||
|
|
||||||
|
uniform mat4 u_model;
|
||||||
|
uniform mat4 u_view_proj;
|
||||||
|
uniform vec4 u_clip_plane;
|
||||||
|
|
||||||
|
out vec2 v_uv;
|
||||||
|
out float v_clip_distance;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 sector_pos = u_model * vec4(a_pos, 1.0);
|
||||||
|
gl_Position = u_view_proj * sector_pos;
|
||||||
|
|
||||||
|
// Clip against the plane
|
||||||
|
v_clip_distance = dot(sector_pos, u_clip_plane);
|
||||||
|
|
||||||
|
//v_normal = mat3(u_model) * a_normal;
|
||||||
|
v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
|
||||||
|
}
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_SECTOR_MESH_FRAG
|
||||||
|
GLSL_VERSION
|
||||||
|
R"GLSL(
|
||||||
|
in vec2 v_uv;
|
||||||
|
in float v_clip_distance;
|
||||||
|
|
||||||
|
uniform sampler2D u_tex;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 o_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (v_clip_distance < 0.0) {
|
||||||
|
discard; // Discard fragment if it is outside the clip plane
|
||||||
|
}
|
||||||
|
|
||||||
|
o_color = vec4(texture(u_tex, v_uv));
|
||||||
|
//o_color = vec4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_PORTAL_VERT
|
||||||
|
GLSL_VERSION
|
||||||
|
R"GLSL(
|
||||||
|
layout (location = 0) in vec3 a_pos;
|
||||||
|
|
||||||
|
uniform mat4 u_model; // Portal transform
|
||||||
|
uniform mat4 u_view_proj;
|
||||||
|
uniform vec4 u_clip_plane;
|
||||||
|
|
||||||
|
uniform vec2 u_portal_size; // Size of the portal in meters
|
||||||
|
|
||||||
|
out float v_clip_distance;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 pos = vec4(a_pos.x * u_portal_size.x, 0.0, a_pos.z * u_portal_size.y, 1.0);
|
||||||
|
vec4 sector_pos = u_model * pos;
|
||||||
|
gl_Position = u_view_proj * sector_pos;
|
||||||
|
|
||||||
|
// Clip against the plane
|
||||||
|
v_clip_distance = dot(sector_pos, u_clip_plane);
|
||||||
|
}
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_PORTAL_FRAG
|
||||||
|
GLSL_VERSION
|
||||||
|
R"GLSL(
|
||||||
|
in float v_clip_distance;
|
||||||
|
|
||||||
|
uniform bool u_clear_depth;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (v_clip_distance < 0.0) {
|
||||||
|
discard; // Discard fragment if it is outside the clip plane
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u_clear_depth) {
|
||||||
|
gl_FragDepth = 1.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gl_FragDepth = gl_FragCoord.z; // Use the default depth value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vrati zdrojovy kod shaderu
|
||||||
|
const char* gfx::ShaderSources::Get(ShaderSource ss) {
|
||||||
|
return s_srcs[ss];
|
||||||
|
}
|
||||||
33
src/gfx/shader_sources.hpp
Normal file
33
src/gfx/shader_sources.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "shader.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
enum ShaderSource
|
||||||
|
{
|
||||||
|
SS_SECTOR_MESH_VERT,
|
||||||
|
SS_SECTOR_MESH_FRAG,
|
||||||
|
|
||||||
|
SS_PORTAL_VERT,
|
||||||
|
SS_PORTAL_FRAG,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShaderSources
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Vrati zdrojovy kod shaderu
|
||||||
|
static const char* Get(ShaderSource ss);
|
||||||
|
|
||||||
|
static void MakeShader(std::unique_ptr<Shader>& shader, ShaderSource ss_vert, ShaderSource ss_frag) {
|
||||||
|
shader = std::make_unique<Shader>(
|
||||||
|
Get(ss_vert),
|
||||||
|
Get(ss_frag)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,7 +1,10 @@
|
|||||||
#include "texture.hpp"
|
#include "texture.hpp"
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
gfx::Texture::Texture(GLuint width, GLuint height, GLint internalformat, GLenum format, GLenum type, GLenum filter) {
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
gfx::Texture::Texture(GLuint width, GLuint height, const void* data, GLint internalformat, GLenum format, GLenum type, GLenum filter) {
|
||||||
glGenTextures(1, &m_id);
|
glGenTextures(1, &m_id);
|
||||||
|
|
||||||
if (!m_id)
|
if (!m_id)
|
||||||
@ -9,7 +12,7 @@ gfx::Texture::Texture(GLuint width, GLuint height, GLint internalformat, GLenum
|
|||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_id);
|
glBindTexture(GL_TEXTURE_2D, m_id);
|
||||||
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, type, NULL);
|
glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, type, data);
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||||
@ -22,3 +25,24 @@ gfx::Texture::Texture(GLuint width, GLuint height, GLint internalformat, GLenum
|
|||||||
gfx::Texture::~Texture() {
|
gfx::Texture::~Texture() {
|
||||||
glDeleteTextures(1, &m_id);
|
glDeleteTextures(1, &m_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<gfx::Texture> gfx::Texture::LoadFromFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
int width, height, channels;
|
||||||
|
unsigned char* data = stbi_load(filename.c_str(), &width, &height, &channels, 4);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw std::runtime_error("Failed to load texture from file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> texture;
|
||||||
|
try {
|
||||||
|
texture = std::make_shared<Texture>(width, height, data, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
stbi_image_free(data);
|
||||||
|
throw; // Rethrow the exception after freeing the data
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_image_free(data);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include "gl.hpp"
|
#include "gl.hpp"
|
||||||
|
|
||||||
@ -12,11 +14,13 @@ class Texture : public NonCopyableNonMovable
|
|||||||
GLuint m_id;
|
GLuint m_id;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Texture(GLuint width, GLuint height, GLint internalformat, GLenum format, GLenum type, GLenum filter = GL_NEAREST);
|
Texture(GLuint width, GLuint height, const void* data, GLint internalformat, GLenum format, GLenum type, GLenum filter = GL_NEAREST);
|
||||||
~Texture();
|
~Texture();
|
||||||
|
|
||||||
GLuint GetId() const { return m_id; }
|
GLuint GetId() const { return m_id; }
|
||||||
|
|
||||||
|
static std::shared_ptr<Texture> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -14,10 +14,12 @@ static const VertexAttribInfo s_ATTR_INFO[] = {
|
|||||||
{ 0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3 }, // VA_POSITION
|
{ 0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3 }, // VA_POSITION
|
||||||
{ 1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3 }, // VA_NORMAL
|
{ 1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3 }, // VA_NORMAL
|
||||||
{ 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 }, // VA_COLOR
|
{ 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 }, // VA_COLOR
|
||||||
|
{ 3, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2 }, // VA_UV
|
||||||
|
{ 4, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2 } // VA_LIGHTMAP_UV
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pocet typu vertex atributu
|
// Pocet typu vertex atributu
|
||||||
static const size_t s_ATTR_COUNT = 3;
|
static const size_t s_ATTR_COUNT = 5;
|
||||||
|
|
||||||
gfx::VertexArray::VertexArray(int attrs, int flags) : m_usage(GL_STATIC_DRAW), m_num_indices(0) {
|
gfx::VertexArray::VertexArray(int attrs, int flags) : m_usage(GL_STATIC_DRAW), m_num_indices(0) {
|
||||||
glGenVertexArrays(1, &m_vao);
|
glGenVertexArrays(1, &m_vao);
|
||||||
|
|||||||
@ -10,49 +10,51 @@
|
|||||||
|
|
||||||
namespace gfx
|
namespace gfx
|
||||||
{
|
{
|
||||||
// Vycet jednotlivych atributu vrcholu
|
// Vycet jednotlivych atributu vrcholu
|
||||||
enum VertexAttr
|
enum VertexAttr
|
||||||
{
|
{
|
||||||
VA_POSITION = 1 << 0,
|
VA_POSITION = 1 << 0,
|
||||||
VA_NORMAL = 1 << 1,
|
VA_NORMAL = 1 << 1,
|
||||||
VA_COLOR = 1 << 2,
|
VA_COLOR = 1 << 2,
|
||||||
};
|
VA_UV = 1 << 3,
|
||||||
|
VA_LIGHTMAP_UV = 1 << 4,
|
||||||
|
};
|
||||||
|
|
||||||
// Informace pro vytvoreni VertexArraye
|
// Informace pro vytvoreni VertexArraye
|
||||||
enum VertexArrayFlags
|
enum VertexArrayFlags
|
||||||
{
|
{
|
||||||
VF_CREATE_EBO = 1 << 0,
|
VF_CREATE_EBO = 1 << 0,
|
||||||
VF_DYNAMIC = 1 << 1,
|
VF_DYNAMIC = 1 << 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// VertexArray - trida pro praci s modely
|
// VertexArray - trida pro praci s modely
|
||||||
class VertexArray : public NonCopyableNonMovable
|
class VertexArray : public NonCopyableNonMovable
|
||||||
{
|
{
|
||||||
GLuint m_vao;// , m_vbo, m_ebo;
|
GLuint m_vao;// , m_vbo, m_ebo;
|
||||||
std::unique_ptr<BufferObject> m_vbo;
|
std::unique_ptr<BufferObject> m_vbo;
|
||||||
std::unique_ptr<BufferObject> m_ebo;
|
std::unique_ptr<BufferObject> m_ebo;
|
||||||
size_t m_num_indices;
|
size_t m_num_indices;
|
||||||
GLenum m_usage;
|
GLenum m_usage;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VertexArray(int attrs, int flags);
|
VertexArray(int attrs, int flags);
|
||||||
~VertexArray();
|
~VertexArray();
|
||||||
|
|
||||||
// Nastavi data do VBO
|
// Nastavi data do VBO
|
||||||
void SetVBOData(const void* data, size_t size);
|
void SetVBOData(const void* data, size_t size);
|
||||||
|
|
||||||
// Nastavi indexy do EBO
|
// Nastavi indexy do EBO
|
||||||
void SetIndices(const GLuint* data, size_t size);
|
void SetIndices(const GLuint* data, size_t size);
|
||||||
|
|
||||||
GLuint GetVAOId() const { return m_vao; }
|
GLuint GetVAOId() const { return m_vao; }
|
||||||
GLuint GetVBOId() const { return m_vbo->GetId(); }
|
GLuint GetVBOId() const { return m_vbo->GetId(); }
|
||||||
GLuint GetEBOId() const {
|
GLuint GetEBOId() const {
|
||||||
assert(m_ebo && "GetEBOId() ale !ebo");
|
assert(m_ebo && "GetEBOId() ale !ebo");
|
||||||
return m_ebo->GetId();
|
return m_ebo->GetId();
|
||||||
}
|
}
|
||||||
|
|
||||||
//size_t GetVBOSize() const { return m_vbo->; }
|
//size_t GetVBOSize() const { return m_vbo->; }
|
||||||
size_t GetNumIndices() const { return m_num_indices; }
|
size_t GetNumIndices() const { return m_num_indices; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
#include <GLES2/gl2.h>
|
#define PG_GLES
|
||||||
|
#include <GLES3/gl3.h>
|
||||||
|
#include <GLES3/gl2ext.h>
|
||||||
#else
|
#else
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
75
src/main.cpp
75
src/main.cpp
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/html5_webgl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "gl.hpp"
|
#include "gl.hpp"
|
||||||
@ -16,12 +17,28 @@ static std::unique_ptr<App> s_app;
|
|||||||
|
|
||||||
static bool InitSDL()
|
static bool InitSDL()
|
||||||
{
|
{
|
||||||
|
std::cout << "Initializing SDL..." << std::endl;
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) != 0)
|
if (SDL_Init(SDL_INIT_VIDEO) != 0)
|
||||||
{
|
{
|
||||||
std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
|
std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||||
|
#else
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
|
||||||
|
std::cout << "Creating SDL window..." << std::endl;
|
||||||
s_window = SDL_CreateWindow("PortalGame", 100, 100, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
s_window = SDL_CreateWindow("PortalGame", 100, 100, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||||
if (!s_window)
|
if (!s_window)
|
||||||
{
|
{
|
||||||
@ -33,18 +50,23 @@ static bool InitSDL()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
|
||||||
|
//if (severity == 0x826b)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
////std::cout << message << std::endl;
|
||||||
|
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
|
||||||
|
(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
|
||||||
|
type, severity, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // EMSCRIPTEN
|
||||||
|
|
||||||
|
|
||||||
static bool InitGL()
|
static bool InitGL()
|
||||||
{
|
{
|
||||||
#ifdef EMSCRIPTEN
|
std::cout << "Creating OpenGL context..." << std::endl;
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
|
||||||
#else
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
s_context = SDL_GL_CreateContext(s_window);
|
s_context = SDL_GL_CreateContext(s_window);
|
||||||
if (!s_context)
|
if (!s_context)
|
||||||
{
|
{
|
||||||
@ -62,14 +84,20 @@ static bool InitGL()
|
|||||||
|
|
||||||
#ifndef EMSCRIPTEN
|
#ifndef EMSCRIPTEN
|
||||||
// Initialize GLAD
|
// Initialize GLAD
|
||||||
|
std::cout << "Initializing GLAD..." << std::endl;
|
||||||
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
|
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
|
||||||
{
|
{
|
||||||
std::cerr << "Failed to initialize GLAD" << std::endl;
|
std::cerr << "Failed to initialize GLAD" << std::endl;
|
||||||
SDL_GL_DeleteContext(s_context);
|
SDL_GL_DeleteContext(s_context);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
glEnable(GL_DEBUG_OUTPUT);
|
||||||
|
glDebugMessageCallback(GLDebugCallback, 0);
|
||||||
|
|
||||||
|
SDL_GL_SetSwapInterval(0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,22 +137,27 @@ static void Frame()
|
|||||||
{
|
{
|
||||||
Uint32 current_time = SDL_GetTicks();
|
Uint32 current_time = SDL_GetTicks();
|
||||||
s_app->SetTime(current_time / 1000.0f); // Set time in seconds
|
s_app->SetTime(current_time / 1000.0f); // Set time in seconds
|
||||||
|
|
||||||
PollEvents();
|
PollEvents();
|
||||||
|
|
||||||
|
int width, height;
|
||||||
|
SDL_GetWindowSize(s_window, &width, &height);
|
||||||
|
s_app->SetViewportSize(width, height);
|
||||||
|
|
||||||
s_app->Frame();
|
s_app->Frame();
|
||||||
SDL_GL_SwapWindow(s_window);
|
SDL_GL_SwapWindow(s_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
static void Main() {
|
||||||
{
|
|
||||||
if (!InitSDL())
|
if (!InitSDL())
|
||||||
{
|
{
|
||||||
return 1;
|
throw std::runtime_error("Failed to initialize SDL");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!InitGL())
|
if (!InitGL())
|
||||||
{
|
{
|
||||||
ShutdownSDL();
|
ShutdownSDL();
|
||||||
return 1;
|
throw std::runtime_error("Failed to initialize OpenGL");
|
||||||
}
|
}
|
||||||
|
|
||||||
s_app = std::make_unique<App>();
|
s_app = std::make_unique<App>();
|
||||||
@ -142,5 +175,17 @@ int main(int argc, char *argv[])
|
|||||||
ShutdownGL();
|
ShutdownGL();
|
||||||
ShutdownSDL();
|
ShutdownSDL();
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Main();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
std::cerr << "[ERROR] " << e.what() << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user