tri.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : WWMath *
  23. * *
  24. * $Archive:: /Commando/Code/wwmath/tri.h $*
  25. * *
  26. * Author:: Greg_h *
  27. * *
  28. * $Modtime:: 5/04/01 8:35p $*
  29. * *
  30. * $Revision:: 14 $*
  31. * *
  32. *---------------------------------------------------------------------------------------------*
  33. * Functions: *
  34. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  35. #if defined(_MSC_VER)
  36. #pragma once
  37. #endif
  38. #ifndef TRI_H
  39. #define TRI_H
  40. #include "always.h"
  41. #include "vector4.h"
  42. #include "vector3.h"
  43. #include "vector2.h"
  44. #include <assert.h>
  45. /**
  46. ** TriClass
  47. ** When the math library needs to deal with triangles, this will be the form used.
  48. ** The initial reason for this class is for Commando's collision detection code. Initially
  49. ** I wrote custom code inside the render object for the terrain to perform collision
  50. ** detection. Moving the low-level geometrical collision code into the math library makes it
  51. ** more re-useable and independent from changes in the rendering code.
  52. */
  53. class TriClass
  54. {
  55. public:
  56. const Vector3 * N;
  57. const Vector3 * V[3];
  58. void Compute_Normal()
  59. {
  60. assert(N);
  61. assert(V[0]);
  62. assert(V[1]);
  63. assert(V[2]);
  64. Vector3::Cross_Product(*(V[1])-*(V[0]),*(V[2])-*(V[0]),(Vector3 *)N);
  65. ((Vector3 *)N)->Normalize();
  66. }
  67. bool Contains_Point(const Vector3 & ipoint) const;
  68. void Find_Dominant_Plane(int * axis1,int * axis2) const;
  69. };
  70. /*
  71. ** Utility functions:
  72. ** Functions which have to do with triangles but not neccessarily with TriClass - usually used
  73. ** by TriClass but also available for use elsewhere.
  74. */
  75. // Enums for raycast flags (currently used only for semi-infinite axis-aligned rays)
  76. enum
  77. {
  78. TRI_RAYCAST_FLAG_NONE = 0x00,
  79. TRI_RAYCAST_FLAG_HIT_EDGE = 0x01,
  80. TRI_RAYCAST_FLAG_START_IN_TRI = 0x02
  81. };
  82. // This function does a pure 2D point-in-triangle test. We pass in 3D points both for the
  83. // triangle and test points, but we also pass in the two axes to use (the third axis is ignored,
  84. // so we are actually testing the projection of the four points onto one of the axis planes). The
  85. // triangle points are not assumed to be in any particular winding order (that is checked for
  86. // internally). It is used internally by TriClass::Contains_Point(), and may be used elsewhere.
  87. // If the ray intersects the camera at an edge we also count it as an intersection. We set a bit
  88. // in 'flags' to true in this case (some users of this function need this extra information and
  89. // it is very cheap to compute). We do not modify 'flags' if no edges are hit, so it must be
  90. // initialized outside this function if its value is to be used.
  91. inline bool Point_In_Triangle_2D(const Vector3 &tri_point0, const Vector3 &tri_point1,
  92. const Vector3 &tri_point2, const Vector3 &test_point, int axis_1, int axis_2,
  93. unsigned char &flags)
  94. {
  95. // The function is based on checking signs of determinants, or in a more visually intuitive
  96. // sense, checking on which side of a line a point lies. For example, if the points run in
  97. // counter-clockwise order, the interior of the triangle is the intersection of the three
  98. // half-planes to the left of the directed infinite lines along P0 to P1, P1 to P2, P2 to P0.
  99. // Therefore the test point is in the triangle iff it is to the left of all three lines (if
  100. // the points are in clockwise order, we check if it is to the right of the lines).
  101. Vector2 p0p1(tri_point1[axis_1] - tri_point0[axis_1], tri_point1[axis_2] - tri_point0[axis_2]);
  102. Vector2 p1p2(tri_point2[axis_1] - tri_point1[axis_1], tri_point2[axis_2] - tri_point1[axis_2]);
  103. Vector2 p2p0(tri_point0[axis_1] - tri_point2[axis_1], tri_point0[axis_2] - tri_point2[axis_2]);
  104. // Check which side P2 is relative to P0P1. The sign of this test must equal the sign of all
  105. // three tests between the lines and the test point for the test point to be inside the
  106. // triangle. (this test will also tell us if the three points are colinear - if the triangle
  107. // is degenerate).
  108. Vector2 p0p2(tri_point2[axis_1] - tri_point0[axis_1], tri_point2[axis_2] - tri_point0[axis_2]);
  109. float p0p1p2 = Vector2::Perp_Dot_Product(p0p1, p0p2);
  110. if (p0p1p2 != 0.0f) {
  111. // The triangle is not degenerate - test three sides
  112. float side_factor = p0p1p2 > 0.0f ? 1.0f : -1.0f;
  113. float factors[3];
  114. // Now perform tests
  115. Vector2 p0pT(test_point[axis_1] - tri_point0[axis_1], test_point[axis_2] - tri_point0[axis_2]);
  116. factors[0] = Vector2::Perp_Dot_Product(p0p1, p0pT);
  117. if (factors[0] * side_factor < 0.0f) {
  118. return false;
  119. }
  120. Vector2 p1pT(test_point[axis_1] - tri_point1[axis_1], test_point[axis_2] - tri_point1[axis_2]);
  121. factors[1] = Vector2::Perp_Dot_Product(p1p2, p1pT);
  122. if (factors[1] * side_factor < 0.0f) {
  123. return false;
  124. }
  125. Vector2 p2pT(test_point[axis_1] - tri_point2[axis_1], test_point[axis_2] - tri_point2[axis_2]);
  126. factors[2] = Vector2::Perp_Dot_Product(p2p0, p2pT);
  127. if (factors[2] * side_factor < 0.0f) {
  128. return false;
  129. }
  130. if ((factors[0] == 0.0f) || (factors[1] == 0.0f) || (factors[2] == 0.0f)) flags |= TRI_RAYCAST_FLAG_HIT_EDGE;
  131. return true;
  132. } else {
  133. // The triangle is degenerate. This should be a rare case, so it does not matter much if it
  134. // is a little slower than the non-colinear case.
  135. // Find the two outer points along the triangle's line ('start' and 'end' points)
  136. float p0p1dist2 = p0p1.Length2();
  137. float p1p2dist2 = p1p2.Length2();
  138. float p2p0dist2 = p1p2.Length2();
  139. float max_dist2;
  140. Vector2 pSpE, pSpT; // 'end' point, test point - both in 'start' points' frame
  141. if (p0p1dist2 > p1p2dist2) {
  142. if (p0p1dist2 > p2p0dist2) {
  143. // points 0 and 1 are the 'outer' points. 0 is 'start' and 1 is 'end'.
  144. pSpE = p0p1;
  145. pSpT.Set(test_point[axis_1] - tri_point0[axis_1], test_point[axis_2] - tri_point0[axis_2]);
  146. max_dist2 = p0p1dist2;
  147. } else {
  148. // points 0 and 2 are the 'outer' points. 2 is 'start' and 0 is 'end'.
  149. pSpE = p2p0;
  150. pSpT.Set(test_point[axis_1] - tri_point2[axis_1], test_point[axis_2] - tri_point2[axis_2]);
  151. max_dist2 = p2p0dist2;
  152. }
  153. } else {
  154. if (p1p2dist2 > p2p0dist2) {
  155. // points 1 and 2 are the 'outer' points. 1 is 'start' and 2 is 'end'.
  156. pSpE = p1p2;
  157. pSpT.Set(test_point[axis_1] - tri_point1[axis_1], test_point[axis_2] - tri_point1[axis_2]);
  158. max_dist2 = p1p2dist2;
  159. } else {
  160. // points 0 and 2 are the 'outer' points. 2 is 'start' and 0 is 'end'.
  161. pSpE = p2p0;
  162. pSpT.Set(test_point[axis_1] - tri_point2[axis_1], test_point[axis_2] - tri_point2[axis_2]);
  163. max_dist2 = p2p0dist2;
  164. }
  165. }
  166. if (max_dist2 != 0.0f) {
  167. // Triangle is line segment, check if test point is colinear with it
  168. if (Vector2::Perp_Dot_Product(pSpE, pSpT)) {
  169. // Not colinear
  170. return false;
  171. } else {
  172. // Colinear - is test point's distance from start and end <= segment length?
  173. Vector2 pEpT = pSpT - pSpE;
  174. if (pSpT.Length2() <= max_dist2 && pEpT.Length2() <= max_dist2) {
  175. flags |= TRI_RAYCAST_FLAG_HIT_EDGE;
  176. return true;
  177. } else {
  178. return false;
  179. }
  180. }
  181. } else {
  182. // All triangle points coincide, check if test point coincides with them
  183. if (pSpT.Length2() == 0.0f) {
  184. flags |= TRI_RAYCAST_FLAG_HIT_EDGE;
  185. return true;
  186. } else {
  187. return false;
  188. }
  189. }
  190. }
  191. }
  192. // This function tests a semi-infinite axis-aligned ray vs. a triangle.
  193. // The inputs are blah blah blah
  194. inline bool Cast_Semi_Infinite_Axis_Aligned_Ray_To_Triangle(const Vector3 &tri_point0,
  195. const Vector3 &tri_point1, const Vector3 &tri_point2, const Vector4 &tri_plane,
  196. const Vector3 &ray_start, int axis_r, int axis_1, int axis_2, int direction,
  197. unsigned char & flags)
  198. {
  199. bool retval = false;
  200. // First check infinite ray vs. triangle (2D check)
  201. unsigned char flags_2d = TRI_RAYCAST_FLAG_NONE;
  202. if (Point_In_Triangle_2D(tri_point0, tri_point1, tri_point2, ray_start, axis_1, axis_2, flags_2d)) {
  203. // NOTE: SR plane equations, unlike WWMath's PlaneClass, use the Ax+By+Cz+D = 0
  204. // representation. It can also be viewed as C0x+C1y+C2z+C3 = dist where dist is the
  205. // signed distance from the plane (and therefore the plane is defined as those points
  206. // where dist = 0). Now that we know that the projection along the ray is inside the
  207. // triangle, determining whether the ray hits the triangle is a matter of determining
  208. // whether the start point is on the triangle plane's "anti-rayward side" (the side
  209. // which the ray points away from).
  210. // To determine this, we will use C[axis_r] (the plane coefficient of the ray axis).
  211. // This coefficient is positive if the positive direction of the ray axis points into
  212. // the planes' positive halfspace and negative if it points into the planes' negative
  213. // halfspace (it is zero if the ray axis is parallel to the triangle plane). If we
  214. // multiply this by 'sign' which is defined to be -1.0 if 'direction' equals 0 and
  215. // 1.0 if 'direction' equals 1, we will get a number which is positive or negative
  216. // depending on which halfspace the ray itself (as opposed to the rays axis) points
  217. // towards. If we further multiply this by dist(start point) - the result of plugging
  218. // the start point into the plane equation - we will get a number which is positive
  219. // if the start point is on the 'rayward side' (ray does not intersect the triangle)
  220. // and is negative if the start point is on the 'anti-rayward side' (ray does
  221. // intersect triangle). In either of these two cases we are done.
  222. // (see below for what happens when the result is zero - more checks need to be made).
  223. static const float sign[2] = {-1.0f, 1.0f };
  224. float result = tri_plane[axis_r] * sign[direction] * (tri_plane.X * ray_start.X +
  225. tri_plane.Y * ray_start.Y + tri_plane.Z * ray_start.Z + tri_plane.W);
  226. if (result < 0.0f) {
  227. // Intersection!
  228. flags |= (flags_2d & TRI_RAYCAST_FLAG_HIT_EDGE);
  229. retval = true;
  230. } else {
  231. if (result == 0.0f) {
  232. // If the result is 0, this means either the ray is parallel to the triangle
  233. // plane or the start point is embedded in the triangle plane. Note that since
  234. // the start point passed the 2D check, then if the ray is parallel the start
  235. // point must also be embedded in the triangle plane. This leaves us with two
  236. // cases:
  237. // A) The ray is not parallel to the plane - in this case the start point is
  238. // embedded in the triangle. We report an intersection, bitwise OR the edge
  239. // result from the 2D check into the 'edge hit' flag and set the 'start in tri'
  240. // flag.
  241. // B) The ray is parallel to the plane. In this case the result of the 2D test
  242. // tells us that the infinite line intersects the triangle (actually is
  243. // embedded in the triangle along part of its length), but we do not know
  244. // whether the semi-infinite ray also does so. We simplify things by not
  245. // counting such an 'intersection'. There are four reasons behind this:
  246. // 1. It differs from a 'normal' intersection (which has one intersecting
  247. // point) - there are infinitely many intersecting points.
  248. // 2. Moving the plane by an infinitesimally small amount to either side will
  249. // cause the ray to no longer touch the plane.
  250. // 3. This will not affect results for the known uses of this function.
  251. // 4. By doing so we avoid having to code up a bunch of complex tests.
  252. // Therefore in case B) we just report no intersection. We still need to find
  253. // out whether the point is embedded in the triangle (for setting the flag) so
  254. // we do another simple 2D test on the dominant plane.
  255. if (tri_plane[axis_r]) {
  256. // Case A)
  257. flags |= (flags_2d & TRI_RAYCAST_FLAG_HIT_EDGE);
  258. flags |= TRI_RAYCAST_FLAG_START_IN_TRI;
  259. retval = true;
  260. } else {
  261. // Case B) - test if point in tri (we know it is in plane, so we can use
  262. // TriClass function)
  263. TriClass tri;
  264. tri.V[0] = &tri_point0;
  265. tri.V[1] = &tri_point1;
  266. tri.V[2] = &tri_point2;
  267. tri.N = (Vector3*)&tri_plane;
  268. if (tri.Contains_Point(ray_start)) {
  269. flags |= TRI_RAYCAST_FLAG_START_IN_TRI;
  270. }
  271. }
  272. } // if (result == 0.0f)
  273. } // else (result < 0.0f)
  274. } // if Point_In_Triangle_2D()
  275. return retval;
  276. }
  277. #endif