417 lines
12 KiB
C++
417 lines
12 KiB
C++
#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()),
|
|
|
|
bt_col_dispatcher_(&bt_col_cfg_),
|
|
bt_world_(&bt_col_dispatcher_, &bt_broad_phase_, &bt_col_cfg_)
|
|
{
|
|
// Create Bullet collision mesh
|
|
bt_mesh_col_obj_.setCollisionShape(mesh_->GetCollisionMesh()->GetShape());
|
|
bt_world_.addCollisionObject(&bt_mesh_col_obj_);
|
|
|
|
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 = &assets::PortalDef::portal_defs[def_portal.def_name];
|
|
|
|
glm::vec3 angles_rad = glm::radians(def_portal.angles);
|
|
|
|
glm::mat4 rotation = glm::mat4((glm::quat(angles_rad)));
|
|
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.scale = def_portal.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;
|
|
|
|
// create portal collision object
|
|
portal.bt_col_shape = std::make_unique<btBoxShape>(
|
|
btVector3(portal.def->size.x * portal.scale * 0.5f, 0.1f, portal.def->size.y * portal.scale * 0.5f)
|
|
);
|
|
|
|
portal.bt_col_obj = std::make_unique<btCollisionObject>();
|
|
portal.bt_col_obj->setCollisionShape(portal.bt_col_shape.get());
|
|
|
|
btQuaternion bt_rotation(angles_rad.x, angles_rad.y, angles_rad.z);
|
|
btVector3 bt_position(def_portal.origin.x, def_portal.origin.y, def_portal.origin.z);
|
|
btTransform bt_transform(bt_rotation, bt_position);
|
|
portal.bt_col_obj->setWorldTransform(bt_transform);
|
|
|
|
portal.col_data.type = CO_PORTAL;
|
|
portal.col_data.portal = &portal;
|
|
portal.bt_col_obj->setUserPointer(&portal.col_data);
|
|
|
|
bt_world_.addCollisionObject(portal.bt_col_obj.get());
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
void game::Sector::AddLight(const glm::vec3& position, const glm::vec3& color, float radius)
|
|
{
|
|
Light light;
|
|
light.position = position;
|
|
light.color = color;
|
|
light.radius = radius;
|
|
lights_.push_back(light);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void LinkPortal(game::Portal& p1, game::Portal& p2, int flags) {
|
|
// Calculate the traverse transformation
|
|
glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0, 0, 1));
|
|
|
|
if (flags & game::LINK_ROTATE180)
|
|
{
|
|
rotation = glm::rotate(rotation, glm::radians(180.0f), glm::vec3(0, 1, 0));
|
|
}
|
|
|
|
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_basis[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, int flags)
|
|
{
|
|
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");
|
|
}
|
|
|
|
LinkPortal(p1, p2, flags);
|
|
|
|
if (&p1 != &p2)
|
|
{
|
|
LinkPortal(p2, p1, flags);
|
|
}
|
|
|
|
}
|
|
|
|
namespace game
|
|
{
|
|
struct PortalIgnoringClosestConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
|
{
|
|
using Super = btCollisionWorld::ClosestConvexResultCallback;
|
|
|
|
PortalIgnoringClosestConvexResultCallback(const btVector3& convexFromWorld, const btVector3& convexToWorld) :
|
|
Super(convexFromWorld, convexToWorld)
|
|
{
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Ignore portal hits
|
|
return m_closestHitFraction;
|
|
}
|
|
|
|
return Super::addSingleResult(convexResult, normalInWorldSpace);
|
|
}
|
|
};
|
|
}
|
|
|
|
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 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);
|
|
|
|
PortalIgnoringClosestConvexResultCallback result_callback(bt_start, bt_end);
|
|
|
|
bt_world_.convexSweepTest(&capsule, start_transform, end_transform, result_callback, 0.0f);
|
|
|
|
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;
|
|
}
|
|
|
|
namespace game
|
|
{
|
|
struct PortalContactResultCallback : public btCollisionWorld::ContactResultCallback
|
|
{
|
|
const game::Portal* portal = nullptr;
|
|
|
|
PortalContactResultCallback()
|
|
{
|
|
}
|
|
|
|
virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0,
|
|
const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override
|
|
{
|
|
void* ptr0 = colObj0Wrap->getCollisionObject()->getUserPointer();
|
|
void* ptr1 = colObj1Wrap->getCollisionObject()->getUserPointer();
|
|
void* ptr = ptr0 ? ptr0 : ptr1;
|
|
CollisionObjectData* data = static_cast<CollisionObjectData*>(ptr);
|
|
|
|
if (data && data->type == CO_PORTAL)
|
|
{
|
|
// We hit a portal, store it
|
|
portal = data->portal;
|
|
}
|
|
|
|
return 0.0f; // No specific action needed for contact results
|
|
}
|
|
};
|
|
}
|
|
|
|
const game::Portal* game::Sector::TestPortalContact(btCapsuleShapeZ& capsule, const glm::mat3& basis, const glm::vec3& pos)
|
|
{
|
|
btCollisionObject capsule_obj;
|
|
capsule_obj.setCollisionShape(&capsule);
|
|
btTransform capsule_transform;
|
|
|
|
btVector3 bt_pos(pos.x, pos.y, pos.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 bt_transform(bt_basis, bt_pos);
|
|
|
|
capsule_obj.setWorldTransform(bt_transform);
|
|
|
|
PortalContactResultCallback result_callback;
|
|
bt_world_.contactTest(&capsule_obj, result_callback);
|
|
|
|
return result_callback.portal;
|
|
}
|
|
|
|
static bool PointIsOnRight(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p)
|
|
{
|
|
// Check if point p is on the right side of the line segment p0 -> p1
|
|
return (p1.x - p0.x) * (p.y - p0.y) - (p1.y - p0.y) * (p.x - p0.x) < 0.0f;
|
|
}
|
|
|
|
static float SignedDistanceToLine(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p)
|
|
{
|
|
// Calculate the signed distance from point p to the line segment p0 -> p1
|
|
return (p1.x - p0.x) * (p.y - p0.y) - (p1.y - p0.y) * (p.x - p0.x);
|
|
}
|
|
|
|
void game::Sector::BakeLightmap()
|
|
{
|
|
const size_t lightmap_size = 256;
|
|
const glm::vec3 ambient_light(0.2f); // Ambient light color
|
|
const float margin = 2.0f;
|
|
|
|
std::span<const assets::MeshVertex> mesh_verts = mesh_->GetVertices();
|
|
std::span<const assets::MeshTriangle> mesh_tris = mesh_->GetTriangles();
|
|
|
|
struct LightmapTexel
|
|
{
|
|
bool used = false;
|
|
glm::vec3 color = glm::vec3(0.0f);
|
|
};
|
|
|
|
std::vector<LightmapTexel> lightmap(lightmap_size * lightmap_size);
|
|
|
|
for (const assets::MeshTriangle& tri : mesh_tris)
|
|
{
|
|
//const assets::MeshVertex* verts[3];
|
|
glm::vec2 verts[3];
|
|
glm::vec3 vert_pos[3];
|
|
glm::vec3 vert_norm[3];
|
|
|
|
collision::AABB2 tri_aabb;
|
|
|
|
for (size_t i = 0; i < 3; ++i)
|
|
{
|
|
const assets::MeshVertex& vert = mesh_verts[tri.vert[i]];
|
|
glm::vec2 pos_lightmap = vert.lightmap_uv * (float)lightmap_size;
|
|
verts[i] = pos_lightmap;
|
|
tri_aabb.AddPoint(pos_lightmap);
|
|
|
|
vert_pos[i] = vert.pos;
|
|
vert_norm[i] = vert.normal;
|
|
}
|
|
|
|
// Clamp to lightmap size
|
|
int y0 = glm::max(0, (int)(tri_aabb.min.y - margin));
|
|
int y1 = glm::min((int)lightmap_size, (int)(tri_aabb.max.y + 1.0f + margin));
|
|
int x0 = glm::max(0, (int)(tri_aabb.min.x - margin));
|
|
int x1 = glm::min((int)lightmap_size, (int)(tri_aabb.max.x + 1.0f + margin));
|
|
|
|
for (int y = y0; y < y1; ++y)
|
|
{
|
|
for (int x = x0; x < x1; ++x)
|
|
{
|
|
LightmapTexel& texel = lightmap[y * lightmap_size + x];
|
|
|
|
if (texel.used)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
glm::vec2 texel_pos((float)x + 0.5f, (float)y + 0.5f); // Center of the texel
|
|
|
|
//if (PointIsOnRight(verts[0], verts[1], texel_pos) ||
|
|
// PointIsOnRight(verts[1], verts[2], texel_pos) ||
|
|
// PointIsOnRight(verts[2], verts[0], texel_pos))
|
|
//{
|
|
// continue; // Texel is outside the triangle
|
|
//}
|
|
|
|
float sd = SignedDistanceToLine(verts[0], verts[1], texel_pos);
|
|
sd = glm::min(sd, SignedDistanceToLine(verts[1], verts[2], texel_pos));
|
|
sd = glm::min(sd, SignedDistanceToLine(verts[2], verts[0], texel_pos));
|
|
|
|
if (sd > margin)
|
|
{
|
|
continue; // Texel is outside the triangle
|
|
}
|
|
|
|
texel.used = sd < 0.0f;
|
|
|
|
// Compute barycentric coordinates
|
|
glm::vec2 v0 = verts[1] - verts[0];
|
|
glm::vec2 v1 = verts[2] - verts[0];
|
|
glm::vec2 v2 = texel_pos - verts[0];
|
|
float d00 = glm::dot(v0, v0);
|
|
float d01 = glm::dot(v0, v1);
|
|
float d11 = glm::dot(v1, v1);
|
|
float d20 = glm::dot(v2, v0);
|
|
float d21 = glm::dot(v2, v1);
|
|
float denom = d00 * d11 - d01 * d01;
|
|
if (denom == 0.0f)
|
|
{
|
|
continue; // Degenerate triangle, skip
|
|
}
|
|
|
|
float v = (d11 * d20 - d01 * d21) / denom;
|
|
float w = (d00 * d21 - d01 * d20) / denom;
|
|
float u = 1.0f - v - w;
|
|
|
|
glm::vec3 texel_pos_ws = u * vert_pos[0] + v * vert_pos[1] + w * vert_pos[2];
|
|
glm::vec3 texel_norm_ws = glm::normalize(u * vert_norm[0] + v * vert_norm[1] + w * vert_norm[2]);
|
|
|
|
glm::vec3 light_color = ambient_light;
|
|
|
|
for (const Light& light : lights_)
|
|
{
|
|
glm::vec3 light_dir = glm::normalize(light.position - texel_pos_ws);
|
|
float dot = glm::dot(texel_norm_ws, light_dir);
|
|
if (dot > 0.0f)
|
|
{
|
|
float dist = glm::length(light.position - texel_pos_ws);
|
|
float attenuation = glm::clamp(1.0f - (dist / light.radius), 0.0f, 1.0f);
|
|
light_color += light.color * dot * attenuation;
|
|
}
|
|
}
|
|
|
|
// Store the light color in the lightmap
|
|
texel.color = light_color;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> lightmap_data(lightmap_size * lightmap_size * 3);
|
|
for (size_t i = 0; i < lightmap.size(); ++i)
|
|
{
|
|
const LightmapTexel& texel = lightmap[i];
|
|
glm::vec3 color = glm::clamp(texel.color * 0.5f, glm::vec3(0.0f), glm::vec3(1.0f));
|
|
lightmap_data[i * 3 + 0] = static_cast<uint8_t>(color.r * 255.0f);
|
|
lightmap_data[i * 3 + 1] = static_cast<uint8_t>(color.g * 255.0f);
|
|
lightmap_data[i * 3 + 2] = static_cast<uint8_t>(color.b * 255.0f);
|
|
}
|
|
|
|
|
|
lightmap_ = std::make_shared<gfx::Texture>(
|
|
lightmap_size, lightmap_size,
|
|
lightmap_data.data(),
|
|
GL_RGB,
|
|
GL_RGB,
|
|
GL_UNSIGNED_BYTE,
|
|
GL_LINEAR
|
|
);
|
|
|
|
}
|