You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
11 KiB

  1. //
  2. // Lol Engine
  3. //
  4. // Copyright: (c) 2010-2014 Sam Hocevar <sam@hocevar.net>
  5. // (c) 2013-2014 Benjamin "Touky" Huet <huet.benjamin@gmail.com>
  6. // This program is free software; you can redistribute it and/or
  7. // modify it under the terms of the Do What The Fuck You Want To
  8. // Public License, Version 2, as published by Sam Hocevar. See
  9. // http://www.wtfpl.net/ for more details.
  10. //
  11. #pragma once
  12. #include <cfloat> /* for FLT_MAX */
  13. #include <lol/base/array.h>
  14. #include <lol/debug/lines.h>
  15. #include <lol/image/color.h>
  16. namespace lol
  17. {
  18. //------ PORTAL SYSTEM --------
  19. template <typename TE> class PortalRoom;
  20. template <typename TE> class PortalDoor;
  21. template <typename TE> class PortalSet;
  22. //--
  23. namespace Debug {
  24. template <typename TE>
  25. void Draw(PortalDoor<TE>& port, vec4 color)
  26. {
  27. vec3 points[4]; port.GetPoints(points);
  28. // Draw normal
  29. vec3 p = port.m_center + port.m_up * port.m_size.y * .5f;
  30. Debug::DrawLine(p, p + port.m_normal, Color::red);
  31. Debug::DrawLine(p, p + port.m_up, Color::green);
  32. // Draw door
  33. for (int l = 0; l < 4; l++)
  34. Debug::DrawLine(points[l], points[(l + 1) % 4], color);
  35. Debug::DrawLine(points[0], points[2], color);
  36. Debug::DrawLine(points[1], points[3], color);
  37. }
  38. }
  39. //PortalDoor base class
  40. template <typename TE>
  41. class PortalDoor
  42. {
  43. friend class PortalSet<TE>;
  44. friend class PortalRoom<TE>;
  45. friend void Debug::Draw<TE>(PortalDoor<TE>& port, vec4 color);
  46. private:
  47. void Init()
  48. {
  49. m_center = vec3::zero;
  50. m_normal = vec3::zero;
  51. m_up = vec3::zero;
  52. m_size = vec2::zero;
  53. m_view = mat4::identity;
  54. m_proj = mat4::identity;
  55. m_rooms[0] = nullptr;
  56. m_rooms[1] = nullptr;
  57. }
  58. public:
  59. //Normal portal
  60. PortalDoor(vec3 center, vec3 normal, vec3 up, vec2 size)
  61. {
  62. Init();
  63. m_center = center;
  64. m_normal = normal;
  65. m_up = up;
  66. m_size = size;
  67. }
  68. //Camera portal
  69. PortalDoor(mat4 view, mat4 proj)
  70. {
  71. Init();
  72. m_view = view;
  73. m_proj = proj;
  74. }
  75. //D.Tor
  76. ~PortalDoor()
  77. {
  78. ConnectRooms(nullptr, nullptr);
  79. }
  80. //Connect door to room
  81. void ConnectRooms(class PortalRoom<TE>* front_room, class PortalRoom<TE>* back_room)
  82. {
  83. for (int i = 0; i < 2; i++)
  84. if (m_rooms[i] != nullptr)
  85. *m_rooms[i] >> this;
  86. m_rooms[0] = back_room;
  87. m_rooms[1] = front_room;
  88. for (int i = 0; i < 2; i++)
  89. if (m_rooms[i] != nullptr)
  90. *m_rooms[i] << this;
  91. }
  92. //--
  93. void DisconnectRoom(class PortalRoom<TE>* room)
  94. {
  95. for (int i = 0; i < 2; i++)
  96. if (m_rooms[i] != nullptr && m_rooms[i] == room)
  97. m_rooms[i] = nullptr;
  98. }
  99. //--
  100. PortalRoom<TE>* GetRoom(bool front) { return m_rooms[(int)front]; }
  101. PortalRoom<TE>* GetRoom(PortalRoom<TE>* room) { return (m_rooms[0] == room) ? m_rooms[1] : m_rooms[0]; }
  102. //Get Four portal point
  103. void GetPoints(vec3 *points) const
  104. {
  105. vec3 right = cross(m_normal, m_up);
  106. points[0] = m_center + right * m_size.x * .5f + m_up * m_size.y;
  107. points[1] = m_center + right * m_size.x * .5f;
  108. points[2] = m_center + right * m_size.x * -.5f;
  109. points[3] = m_center + right * m_size.x * -.5f + m_up * m_size.y;
  110. }
  111. //Builds the portal view proj.
  112. //Returns false if portal is out of the view or points are on each others.
  113. bool BuildViewProj(mat4 view, mat4 proj)
  114. {
  115. mat4 cam_mx = proj * view;
  116. mat4 inv_proj_mx = inverse(proj);
  117. // First: Check normal dot
  118. if (lol::abs(dot(mat3(inverse(view)) * vec3(0.f, 0.f, 1.f), m_normal)) < .00001f)
  119. return false;
  120. // Second: convert to screen coordinates
  121. vec3 port_2d[2] = { vec3(FLT_MAX), vec3(-FLT_MAX) };
  122. vec3 door_points[4];
  123. vec4 proj_p[4];
  124. GetPoints(door_points);
  125. for (int i = 0; i < 4; i++)
  126. {
  127. //W to S calculations
  128. proj_p[i] = cam_mx * vec4(door_points[i], 1.f);
  129. proj_p[i] /= proj_p[i].w;
  130. //Clamp points within screen
  131. port_2d[0] = lol::min(proj_p[i].xyz, port_2d[0]);
  132. port_2d[1] = lol::max(proj_p[i].xyz, port_2d[1]);
  133. port_2d[0] = vec3(lol::clamp(port_2d[0].xy, vec2(-1.f), vec2(1.f)), port_2d[0].z);
  134. port_2d[1] = vec3(lol::clamp(port_2d[1].xy, vec2(-1.f), vec2(1.f)), port_2d[1].z);
  135. }
  136. //Quit if door not within the screen
  137. for (int i = 0; i < 3; i++)
  138. if (port_2d[0][i] == port_2d[1][i])
  139. return false;
  140. //Third: Convert back to view
  141. ivec2 j[4] = { ivec2(0), ivec2(0, 1), ivec2(1), ivec2(1, 0) };
  142. vec3 frust[2] = { vec3(FLT_MAX), vec3(-FLT_MAX) };
  143. for (int i = 0; i < 5; i++)
  144. {
  145. int k = i % 4;
  146. //world calculations
  147. 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);
  148. proj_p[k] /= proj_p[k].w;
  149. proj_p[k].z = lol::abs(proj_p[k].z);
  150. for (int h = 0; h < 3; h++)
  151. {
  152. if (i < 4 || h > 1)
  153. {
  154. frust[0][h] = lol::min(frust[0][h], proj_p[k][h]);
  155. frust[1][h] = lol::max(frust[1][h], proj_p[k][h]);
  156. }
  157. }
  158. }
  159. //Fourth: Create frustum
  160. m_proj = mat4::frustum(frust[0].x, frust[1].x, frust[0].y, frust[1].y, frust[0].z, frust[1].z);
  161. m_view = view;
  162. return true;
  163. }
  164. //View proj getter (doesn't check matrix validity)
  165. mat4 GetViewProj() { return m_proj * m_view; }
  166. //--
  167. bool TestCollide(const vec3& point)
  168. {
  169. return TestPointVsFrustum(point, GetViewProj());
  170. }
  171. //--
  172. bool TestCollide(const PortalDoor& door)
  173. {
  174. vec3 door_points[4];
  175. vec3 res_points[4];
  176. ivec3 pos_test = ivec3::zero;
  177. bool is_in = false;
  178. //Get points and test them on frustum
  179. door.GetPoints(door_points);
  180. for (int i = 0; i < 4; i++)
  181. {
  182. is_in = is_in || TestPointVsFrustum(door_points[i], GetViewProj(), &res_points[i]);
  183. if (is_in)
  184. return true;
  185. //Add points on test stuff
  186. pos_test += ivec3(lol::clamp(res_points[i], vec3(-1.1f), vec3(1.1f)));
  187. }
  188. return false;
  189. //Check if at least one point is not on the same side as the others
  190. for (int i = 0; i < 3; i++)
  191. if (lol::abs(pos_test[i]) == 4)
  192. return false;
  193. return true;
  194. }
  195. private:
  196. mat4 m_view;
  197. mat4 m_proj;
  198. vec3 m_center;
  199. vec3 m_normal;
  200. vec3 m_up;
  201. vec2 m_size;
  202. PortalRoom<TE>* m_rooms[2]; //0: Back, 1: Front
  203. };
  204. //--
  205. template <typename TE>
  206. class PortalRoom
  207. {
  208. friend class PortalSet<TE>;
  209. friend class PortalDoor<TE>;
  210. public:
  211. PortalRoom(TE* element=nullptr)
  212. {
  213. m_element = element;
  214. }
  215. ~PortalRoom()
  216. {
  217. for (auto door : m_doors)
  218. door->DisconnectRoom(this);
  219. m_doors.Empty();
  220. }
  221. PortalRoom& operator<<(class PortalDoor<TE>* door)
  222. {
  223. m_doors.PushUnique(door);
  224. return *this;
  225. }
  226. PortalRoom& operator>>(class PortalDoor<TE>* door)
  227. {
  228. m_doors.RemoveSwapItem(door);
  229. return *this;
  230. }
  231. int GetDoorCount() { return m_doors.Count(); }
  232. PortalDoor<TE>* GetDoor(int i) { return m_doors[i]; }
  233. private:
  234. //Portals associated with this room.
  235. array<PortalDoor<TE>*> m_doors;
  236. TE* m_element;
  237. };
  238. //--
  239. template <typename TE>
  240. class PortalSet
  241. {
  242. public:
  243. ~PortalSet()
  244. {
  245. for (auto door : m_doors)
  246. delete door;
  247. for (auto room : m_rooms)
  248. delete room;
  249. m_doors.Empty();
  250. m_rooms.Empty();
  251. }
  252. //Visible room getter
  253. void GetVisibleRooms(PortalDoor<TE>* see_through, PortalRoom<TE>* start_room, array<PortalRoom<TE>*>& visible_rooms)
  254. {
  255. array<PortalDoor<TE>*> ignore_doors;
  256. GetVisibleRooms(see_through, start_room, visible_rooms, ignore_doors);
  257. #if LOL_BUILD_DEBUG
  258. for (auto room : visible_rooms)
  259. {
  260. vec4 tmp = vec4::zero;
  261. for (auto port : room->m_doors)
  262. {
  263. Debug::Draw(*port, Color::cyan);
  264. tmp += vec4(port->m_center, 1.f);
  265. }
  266. tmp /= tmp.w;
  267. Debug::DrawBox(tmp.xyz - vec3(1.f), tmp.xyz + vec3(1.f), Color::yellow);
  268. }
  269. for (auto port : ignore_doors)
  270. {
  271. Debug::Draw(*port, Color::magenta);
  272. Debug::DrawViewProj(port->m_view, port->m_proj, Color::magenta);
  273. }
  274. #endif //LOL_BUILD_DEBUG
  275. }
  276. private:
  277. void GetVisibleRooms(PortalDoor<TE>* see_through, PortalRoom<TE>* start_room, array<PortalRoom<TE>*>& visible_rooms, array<PortalDoor<TE>*>& ignore_doors)
  278. {
  279. for (auto door : start_room->m_doors)
  280. {
  281. if (door == see_through || ignore_doors.Find(door) != INDEX_NONE)
  282. continue;
  283. if (see_through->TestCollide(*door))
  284. {
  285. PortalRoom<TE>* other_room = door->GetRoom(start_room);
  286. if (visible_rooms.Find(other_room) != INDEX_NONE)
  287. continue;
  288. ignore_doors.PushUnique(door);
  289. visible_rooms.PushUnique(other_room);
  290. door->BuildViewProj(see_through->m_view, see_through->m_proj);
  291. GetVisibleRooms(door, other_room, visible_rooms, ignore_doors);
  292. }
  293. }
  294. }
  295. public:
  296. //Operator
  297. PortalSet<TE>& operator<<(class PortalRoom<TE>* room)
  298. {
  299. m_rooms.PushUnique(room);
  300. for (auto door : room->m_doors)
  301. m_doors.PushUnique(door);
  302. return *this;
  303. }
  304. //--
  305. PortalSet<TE>& operator>>(class PortalRoom<TE>* room)
  306. {
  307. for (auto door : room->m_doors)
  308. *this >> door;
  309. m_rooms.RemoveItem(room);
  310. return *this;
  311. }
  312. //--
  313. PortalSet<TE>& operator<<(class PortalDoor<TE>* door)
  314. {
  315. m_doors.PushUnique(door);
  316. return *this;
  317. }
  318. //--
  319. PortalSet<TE>& operator>>(class PortalDoor<TE>* door)
  320. {
  321. m_doors.RemoveItem(door);
  322. return *this;
  323. }
  324. //--
  325. int GetDoorCount() { return m_doors.Count(); }
  326. PortalDoor<TE>* GetDoor(int i) { return m_doors[i]; }
  327. int GetRoomCount() { return m_rooms.Count(); }
  328. PortalRoom<TE>* GetRoom(int i) { return m_rooms[i]; }
  329. private:
  330. //Portals associated with this room.
  331. array<PortalRoom<TE>*> m_rooms;
  332. array<PortalDoor<TE>*> m_doors;
  333. };
  334. } /* namespace lol */