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.
 
 
 
 
 
 

391 regels
11 KiB

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