|
|
@@ -0,0 +1,392 @@ |
|
|
|
// |
|
|
|
// Lol Engine |
|
|
|
// |
|
|
|
// Copyright © 2010—2020 Sam Hocevar <sam@hocevar.net> |
|
|
|
// © 2013—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com> |
|
|
|
// |
|
|
|
// Lol Engine is free software. It comes without any warranty, to |
|
|
|
// the extent permitted by applicable law. You can redistribute it |
|
|
|
// and/or modify it under the terms of the Do What the Fuck You Want |
|
|
|
// to Public License, Version 2, as published by the WTFPL Task Force. |
|
|
|
// See http://www.wtfpl.net/ for more details. |
|
|
|
// |
|
|
|
|
|
|
|
#pragma once |
|
|
|
|
|
|
|
#include <lol/base/array.h> |
|
|
|
#include <lol/math/vector.h> // vec_t |
|
|
|
#include <lol/math/transform.h> // mat_t |
|
|
|
#include <lol/debug/lines.h> |
|
|
|
#include <lol/image/color.h> |
|
|
|
|
|
|
|
#include <cfloat> /* for FLT_MAX */ |
|
|
|
|
|
|
|
namespace lol |
|
|
|
{ |
|
|
|
|
|
|
|
//------ PORTAL SYSTEM -------- |
|
|
|
template <typename TE> class PortalRoom; |
|
|
|
template <typename TE> class PortalDoor; |
|
|
|
template <typename TE> class PortalSet; |
|
|
|
|
|
|
|
//-- |
|
|
|
namespace Debug { |
|
|
|
template <typename TE, typename TV = void> |
|
|
|
void Draw(PortalDoor<TE>& port, vec4 color) |
|
|
|
{ |
|
|
|
vec3 points[4]; port.GetPoints(points); |
|
|
|
|
|
|
|
// Draw normal |
|
|
|
vec3 p = port.m_center + port.m_up * port.m_size.y * .5f; |
|
|
|
Debug::DrawLine(p, p + port.m_normal, color::red); |
|
|
|
Debug::DrawLine(p, p + port.m_up, color::green); |
|
|
|
// Draw door |
|
|
|
for (int l = 0; l < 4; l++) |
|
|
|
Debug::DrawLine(points[l], points[(l + 1) % 4], color); |
|
|
|
Debug::DrawLine(points[0], points[2], color); |
|
|
|
Debug::DrawLine(points[1], points[3], color); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//PortalDoor base class |
|
|
|
template <typename TE> |
|
|
|
class PortalDoor |
|
|
|
{ |
|
|
|
friend class PortalSet<TE>; |
|
|
|
friend class PortalRoom<TE>; |
|
|
|
friend void Debug::Draw<TE,void>(PortalDoor<TE>& port, vec4 color); |
|
|
|
|
|
|
|
private: |
|
|
|
void Init() |
|
|
|
{ |
|
|
|
m_center = vec3::zero; |
|
|
|
m_normal = vec3::zero; |
|
|
|
m_up = vec3::zero; |
|
|
|
m_size = vec2::zero; |
|
|
|
|
|
|
|
m_view = mat4::identity; |
|
|
|
m_proj = mat4::identity; |
|
|
|
|
|
|
|
m_rooms[0] = nullptr; |
|
|
|
m_rooms[1] = nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
public: |
|
|
|
//Normal portal |
|
|
|
PortalDoor(vec3 center, vec3 normal, vec3 up, vec2 size) |
|
|
|
{ |
|
|
|
Init(); |
|
|
|
|
|
|
|
m_center = center; |
|
|
|
m_normal = normal; |
|
|
|
m_up = up; |
|
|
|
m_size = size; |
|
|
|
} |
|
|
|
//Camera portal |
|
|
|
PortalDoor(mat4 view, mat4 proj) |
|
|
|
{ |
|
|
|
Init(); |
|
|
|
|
|
|
|
m_view = view; |
|
|
|
m_proj = proj; |
|
|
|
} |
|
|
|
//D.Tor |
|
|
|
~PortalDoor() |
|
|
|
{ |
|
|
|
ConnectRooms(nullptr, nullptr); |
|
|
|
} |
|
|
|
|
|
|
|
//Connect door to room |
|
|
|
void ConnectRooms(class PortalRoom<TE>* front_room, class PortalRoom<TE>* back_room) |
|
|
|
{ |
|
|
|
for (int i = 0; i < 2; i++) |
|
|
|
if (m_rooms[i] != nullptr) |
|
|
|
*m_rooms[i] >> this; |
|
|
|
|
|
|
|
m_rooms[0] = back_room; |
|
|
|
m_rooms[1] = front_room; |
|
|
|
|
|
|
|
for (int i = 0; i < 2; i++) |
|
|
|
if (m_rooms[i] != nullptr) |
|
|
|
*m_rooms[i] << this; |
|
|
|
} |
|
|
|
|
|
|
|
//-- |
|
|
|
void DisconnectRoom(class PortalRoom<TE>* room) |
|
|
|
{ |
|
|
|
for (int i = 0; i < 2; i++) |
|
|
|
if (m_rooms[i] != nullptr && m_rooms[i] == room) |
|
|
|
m_rooms[i] = nullptr; |
|
|
|
} |
|
|
|
|
|
|
|
//-- |
|
|
|
PortalRoom<TE>* GetRoom(bool front) { return m_rooms[(int)front]; } |
|
|
|
PortalRoom<TE>* GetRoom(PortalRoom<TE>* room) { return (m_rooms[0] == room) ? m_rooms[1] : m_rooms[0]; } |
|
|
|
|
|
|
|
//Get Four portal point |
|
|
|
void GetPoints(vec3 *points) const |
|
|
|
{ |
|
|
|
vec3 right = cross(m_normal, m_up); |
|
|
|
points[0] = m_center + right * m_size.x * .5f + m_up * m_size.y; |
|
|
|
points[1] = m_center + right * m_size.x * .5f; |
|
|
|
points[2] = m_center + right * m_size.x * -.5f; |
|
|
|
points[3] = m_center + right * m_size.x * -.5f + m_up * m_size.y; |
|
|
|
} |
|
|
|
//Builds the portal view proj. |
|
|
|
//Returns false if portal is out of the view or points are on each others. |
|
|
|
bool BuildViewProj(mat4 view, mat4 proj) |
|
|
|
{ |
|
|
|
mat4 cam_mx = proj * view; |
|
|
|
mat4 inv_proj_mx = inverse(proj); |
|
|
|
|
|
|
|
// First: Check normal dot |
|
|
|
if (lol::fabs(dot(mat3(inverse(view)) * vec3(0.f, 0.f, 1.f), m_normal)) < .00001f) |
|
|
|
return false; |
|
|
|
|
|
|
|
// Second: convert to screen coordinates |
|
|
|
vec3 port_2d[2] = { vec3(FLT_MAX), vec3(-FLT_MAX) }; |
|
|
|
vec3 door_points[4]; |
|
|
|
vec4 proj_p[4]; |
|
|
|
|
|
|
|
GetPoints(door_points); |
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
{ |
|
|
|
//W to S calculations |
|
|
|
proj_p[i] = cam_mx * vec4(door_points[i], 1.f); |
|
|
|
proj_p[i] /= proj_p[i].w; |
|
|
|
|
|
|
|
//Clamp points within screen |
|
|
|
port_2d[0] = lol::min(proj_p[i].xyz, port_2d[0]); |
|
|
|
port_2d[1] = lol::max(proj_p[i].xyz, port_2d[1]); |
|
|
|
port_2d[0] = vec3(lol::clamp(port_2d[0].xy, vec2(-1.f), vec2(1.f)), port_2d[0].z); |
|
|
|
port_2d[1] = vec3(lol::clamp(port_2d[1].xy, vec2(-1.f), vec2(1.f)), port_2d[1].z); |
|
|
|
} |
|
|
|
|
|
|
|
//Quit if door not within the screen |
|
|
|
for (int i = 0; i < 3; i++) |
|
|
|
if (port_2d[0][i] == port_2d[1][i]) |
|
|
|
return false; |
|
|
|
|
|
|
|
//Third: Convert back to view |
|
|
|
ivec2 j[4] = { ivec2(0), ivec2(0, 1), ivec2(1), ivec2(1, 0) }; |
|
|
|
vec3 frust[2] = { vec3(FLT_MAX), vec3(-FLT_MAX) }; |
|
|
|
for (int i = 0; i < 5; i++) |
|
|
|
{ |
|
|
|
int k = i % 4; |
|
|
|
//world calculations |
|
|
|
proj_p[k] = inv_proj_mx * vec4(port_2d[j[k].x].x, port_2d[j[k].y].y, (i<4)?(port_2d[0].z):(1.f), 1.f); |
|
|
|
proj_p[k] /= proj_p[k].w; |
|
|
|
proj_p[k].z = lol::fabs(proj_p[k].z); |
|
|
|
|
|
|
|
for (int h = 0; h < 3; h++) |
|
|
|
{ |
|
|
|
if (i < 4 || h > 1) |
|
|
|
{ |
|
|
|
frust[0][h] = lol::min(frust[0][h], proj_p[k][h]); |
|
|
|
frust[1][h] = lol::max(frust[1][h], proj_p[k][h]); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//Fourth: Create frustum |
|
|
|
m_proj = mat4::frustum(frust[0].x, frust[1].x, frust[0].y, frust[1].y, frust[0].z, frust[1].z); |
|
|
|
m_view = view; |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
//View proj getter (doesn't check matrix validity) |
|
|
|
mat4 GetViewProj() { return m_proj * m_view; } |
|
|
|
|
|
|
|
//-- |
|
|
|
bool TestCollide(const vec3& point) |
|
|
|
{ |
|
|
|
return TestPointVsFrustum(point, GetViewProj()); |
|
|
|
} |
|
|
|
|
|
|
|
//-- |
|
|
|
bool TestCollide(const PortalDoor& door) |
|
|
|
{ |
|
|
|
vec3 door_points[4]; |
|
|
|
vec3 res_points[4]; |
|
|
|
ivec3 pos_test = ivec3::zero; |
|
|
|
bool is_in = false; |
|
|
|
|
|
|
|
//Get points and test them on frustum |
|
|
|
door.GetPoints(door_points); |
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
{ |
|
|
|
is_in = is_in || TestPointVsFrustum(door_points[i], GetViewProj(), &res_points[i]); |
|
|
|
|
|
|
|
if (is_in) |
|
|
|
return true; |
|
|
|
|
|
|
|
//Add points on test stuff |
|
|
|
pos_test += ivec3(lol::clamp(res_points[i], vec3(-1.1f), vec3(1.1f))); |
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
//Check if at least one point is not on the same side as the others |
|
|
|
for (int i = 0; i < 3; i++) |
|
|
|
if (lol::fabs(pos_test[i]) == 4) |
|
|
|
return false; |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
private: |
|
|
|
mat4 m_view; |
|
|
|
mat4 m_proj; |
|
|
|
vec3 m_center; |
|
|
|
vec3 m_normal; |
|
|
|
vec3 m_up; |
|
|
|
vec2 m_size; |
|
|
|
PortalRoom<TE>* m_rooms[2]; //0: Back, 1: Front |
|
|
|
}; |
|
|
|
|
|
|
|
//-- |
|
|
|
template <typename TE> |
|
|
|
class PortalRoom |
|
|
|
{ |
|
|
|
friend class PortalSet<TE>; |
|
|
|
friend class PortalDoor<TE>; |
|
|
|
|
|
|
|
public: |
|
|
|
PortalRoom(TE* element = nullptr) |
|
|
|
{ |
|
|
|
m_element = element; |
|
|
|
} |
|
|
|
~PortalRoom() |
|
|
|
{ |
|
|
|
for (auto door : m_doors) |
|
|
|
door->DisconnectRoom(this); |
|
|
|
m_doors.clear(); |
|
|
|
} |
|
|
|
|
|
|
|
PortalRoom& operator<<(class PortalDoor<TE>* door) |
|
|
|
{ |
|
|
|
m_doors.push_unique(door); |
|
|
|
return *this; |
|
|
|
} |
|
|
|
PortalRoom& operator>>(class PortalDoor<TE>* door) |
|
|
|
{ |
|
|
|
m_doors.remove_swap_item(door); |
|
|
|
return *this; |
|
|
|
} |
|
|
|
|
|
|
|
int GetDoorCount() { return m_doors.count(); } |
|
|
|
PortalDoor<TE>* GetDoor(int i) { return m_doors[i]; } |
|
|
|
|
|
|
|
private: |
|
|
|
//Portals associated with this room. |
|
|
|
array<PortalDoor<TE>*> m_doors; |
|
|
|
TE* m_element; |
|
|
|
}; |
|
|
|
|
|
|
|
//-- |
|
|
|
template <typename TE> |
|
|
|
class PortalSet |
|
|
|
{ |
|
|
|
public: |
|
|
|
~PortalSet() |
|
|
|
{ |
|
|
|
for (auto door : m_doors) |
|
|
|
delete door; |
|
|
|
for (auto room : m_rooms) |
|
|
|
delete room; |
|
|
|
m_doors.clear(); |
|
|
|
m_rooms.clear(); |
|
|
|
} |
|
|
|
|
|
|
|
//Visible room getter |
|
|
|
void GetVisibleRooms(PortalDoor<TE>* see_through, PortalRoom<TE>* start_room, array<PortalRoom<TE>*>& visible_rooms) |
|
|
|
{ |
|
|
|
array<PortalDoor<TE>*> ignore_doors; |
|
|
|
GetVisibleRooms(see_through, start_room, visible_rooms, ignore_doors); |
|
|
|
#if LOL_BUILD_DEBUG |
|
|
|
for (auto room : visible_rooms) |
|
|
|
{ |
|
|
|
vec4 tmp = vec4::zero; |
|
|
|
for (auto port : room->m_doors) |
|
|
|
{ |
|
|
|
Debug::Draw(*port, color::cyan); |
|
|
|
tmp += vec4(port->m_center, 1.f); |
|
|
|
} |
|
|
|
tmp /= tmp.w; |
|
|
|
Debug::DrawBox(tmp.xyz - vec3(1.f), tmp.xyz + vec3(1.f), color::yellow); |
|
|
|
} |
|
|
|
for (auto port : ignore_doors) |
|
|
|
{ |
|
|
|
Debug::Draw(*port, color::magenta); |
|
|
|
Debug::DrawViewProj(port->m_view, port->m_proj, color::magenta); |
|
|
|
} |
|
|
|
#endif //LOL_BUILD_DEBUG |
|
|
|
} |
|
|
|
private: |
|
|
|
void GetVisibleRooms(PortalDoor<TE>* see_through, PortalRoom<TE>* start_room, array<PortalRoom<TE>*>& visible_rooms, array<PortalDoor<TE>*>& ignore_doors) |
|
|
|
{ |
|
|
|
for (auto door : start_room->m_doors) |
|
|
|
{ |
|
|
|
if (door == see_through || ignore_doors.Find(door) != INDEX_NONE) |
|
|
|
continue; |
|
|
|
|
|
|
|
if (see_through->TestCollide(*door)) |
|
|
|
{ |
|
|
|
PortalRoom<TE>* other_room = door->GetRoom(start_room); |
|
|
|
if (visible_rooms.Find(other_room) != INDEX_NONE) |
|
|
|
continue; |
|
|
|
|
|
|
|
ignore_doors.push_unique(door); |
|
|
|
visible_rooms.push_unique(other_room); |
|
|
|
door->BuildViewProj(see_through->m_view, see_through->m_proj); |
|
|
|
GetVisibleRooms(door, other_room, visible_rooms, ignore_doors); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
public: |
|
|
|
|
|
|
|
//Operator |
|
|
|
PortalSet<TE>& operator<<(class PortalRoom<TE>* room) |
|
|
|
{ |
|
|
|
m_rooms.push_unique(room); |
|
|
|
for (auto door : room->m_doors) |
|
|
|
m_doors.push_unique(door); |
|
|
|
return *this; |
|
|
|
} |
|
|
|
//-- |
|
|
|
PortalSet<TE>& operator>>(class PortalRoom<TE>* room) |
|
|
|
{ |
|
|
|
for (auto door : room->m_doors) |
|
|
|
*this >> door; |
|
|
|
m_rooms.remove_item(room); |
|
|
|
return *this; |
|
|
|
} |
|
|
|
//-- |
|
|
|
PortalSet<TE>& operator<<(class PortalDoor<TE>* door) |
|
|
|
{ |
|
|
|
m_doors.push_unique(door); |
|
|
|
return *this; |
|
|
|
} |
|
|
|
//-- |
|
|
|
PortalSet<TE>& operator>>(class PortalDoor<TE>* door) |
|
|
|
{ |
|
|
|
m_doors.remove_item(door); |
|
|
|
return *this; |
|
|
|
} |
|
|
|
|
|
|
|
//-- |
|
|
|
int GetDoorCount() { return m_doors.count(); } |
|
|
|
PortalDoor<TE>* GetDoor(int i) { return m_doors[i]; } |
|
|
|
int GetRoomCount() { return m_rooms.count(); } |
|
|
|
PortalRoom<TE>* GetRoom(int i) { return m_rooms[i]; } |
|
|
|
|
|
|
|
private: |
|
|
|
//Portals associated with this room. |
|
|
|
array<PortalRoom<TE>*> m_rooms; |
|
|
|
array<PortalDoor<TE>*> m_doors; |
|
|
|
}; |
|
|
|
|
|
|
|
} /* namespace lol */ |
|
|
|
|