1 | // tGeometry.cpp  |
2 | //  |
3 | // Idealized geometric shapes. These include triangles, circles, boxes, spheres, rays, lines, planes, cylinders,  |
4 | // capsules, frustums, and ellipses. When the shape primitives have a number in their name, it refers to the space the  |
5 | // shape is in, not the dimensionality of the shape itself. eg. A tCircle3 is a (2D) circle in R3 while a tCircle2 is a  |
6 | // 2D circle in R2. For shapes that are in R3 we drop the 3 because the R3 primitives are more general.  |
7 | //  |
8 | // Copyright (c) 2006, 2016 Tristan Grimmer.  |
9 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
10 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
11 | //  |
12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
13 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
14 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
15 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
16 | // PERFORMANCE OF THIS SOFTWARE.  |
17 |   |
18 | #include "Math/tGeometry.h"  |
19 |   |
20 |   |
21 | namespace tMath  |
22 | {  |
23 | static tIntersectResult IntersectFindLineLineHelper(const tLine2& a, const tLine2& b, float& ua, float& ub);  |
24 | };  |
25 |   |
26 |   |
27 | bool tMath::tORect2::IsInside(const tVector2& point) const  |
28 | {  |
29 | tVector2 d = point - Center;  |
30 | for (int a = 0; a < 2; a++)  |
31 | {  |
32 | float dot = tAbs(d * Axes[a]);  |
33 | if (dot > Extents.E[a])  |
34 | return false;  |
35 | }  |
36 |   |
37 | return true;  |
38 | }  |
39 |   |
40 |   |
41 | void tMath::tABox::Transform(const tMatrix4& t)  |
42 | {  |
43 | // Transform the 8 box points to the new space and grow a new box around them.  |
44 | tABox box;  |
45 | box.AddPoint( (t * tVector4(Min.x, Min.y, Min.z, 1.0f)).GetCartesian() );  |
46 | box.AddPoint( (t * tVector4(Max.x, Min.y, Min.z, 1.0f)).GetCartesian() );  |
47 | box.AddPoint( (t * tVector4(Min.x, Max.y, Min.z, 1.0f)).GetCartesian() );  |
48 | box.AddPoint( (t * tVector4(Max.x, Max.y, Min.z, 1.0f)).GetCartesian() );  |
49 | box.AddPoint( (t * tVector4(Min.x, Min.y, Max.z, 1.0f)).GetCartesian() );  |
50 | box.AddPoint( (t * tVector4(Max.x, Min.y, Max.z, 1.0f)).GetCartesian() );  |
51 | box.AddPoint( (t * tVector4(Min.x, Max.y, Max.z, 1.0f)).GetCartesian() );  |
52 | box.AddPoint( (t * tVector4(Max.x, Max.y, Max.z, 1.0f)).GetCartesian() );  |
53 |   |
54 | *this = box;  |
55 | }  |
56 |   |
57 |   |
58 | bool tMath::tOBox::IsInside(const tVector3& point) const  |
59 | {  |
60 | tVector3 d = point - Center;  |
61 | for (int a = 0; a < 3; a++)  |
62 | {  |
63 | float dot = tAbs(d * Axes[a]);  |
64 | if (dot > Extents.E[a])  |
65 | return false;  |
66 | }  |
67 |   |
68 | return true;  |
69 | }  |
70 |   |
71 |   |
72 | void tMath::tPlane::ComputeOrthogonalBasisVectors(tVector3& u, tVector3& v) const  |
73 | {  |
74 | // The algorithm is 'householder orthogonalization' as described here:  |
75 | // http://math.stackexchange.com/questions/64430/find-extra-arbitrary-two-points-for-a-plane-given-the-normal-and-a-point-that-l  |
76 | tVector3 n = Normal;  |
77 | n.Normalize();  |
78 |   |
79 | // Compute the 'mirror' vector w = (nx+1,ny,nz).  |
80 | tVector3 w = n;  |
81 | w.x += 1.0f;  |
82 |   |
83 | // Compute the 3x3 Householder matrix H = I - 2(wwT/wTw) where T means transpose.  |
84 | tMatrix4 wwT = w.MulByTranspose(w);  |
85 | float wTw = w*w;  |
86 | tMatrix4 H = tMatrix4::identity - 2.0f*(wwT/wTw);  |
87 |   |
88 | // Row1 of H will be a unit vector parallel to n. Row 2 and 3 will be unit vectors orthogonal to n and each other.  |
89 | // We transpose to get the rows out easily.  |
90 | H.Transpose();  |
91 | u = H.C2;  |
92 | v = H.C3;  |
93 | }  |
94 |   |
95 |   |
96 | void tMath::tFrustum::Set(const tMatrix4& mat)  |
97 | {   |
98 | // We put the matrix in row major format so we can do the trick described  |
99 | // in Akenine-Moller&Haines, Real-Time Rendering, 2nd Ed., pp.613-614.  |
100 | tMatrix4 m(mat);  |
101 | m.Transpose();  |
102 | tVector4 t;  |
103 |   |
104 | tSub(t, m.C4, m.C1); Planes[Plane_Right].Set(t);  |
105 | tAdd(t, m.C4, m.C1); Planes[Plane_Left].Set(t);  |
106 | tSub(t, m.C4, m.C2); Planes[Plane_Top].Set(t);  |
107 | tAdd(t, m.C4, m.C2); Planes[Plane_Bottom].Set(t);  |
108 | tSub(t, m.C4, m.C3); Planes[Plane_Far].Set(t);  |
109 | tAdd(t, m.C4, m.C3); Planes[Plane_Near].Set(t);  |
110 |   |
111 | for (int p = 0; p < Plane_NumPlanes; p++)  |
112 | Planes[p].Normalize();  |
113 | }  |
114 |   |
115 |   |
116 | bool tMath::tComputeSmallEnclosingSphere(tSphere& sphere, const tVector3* points, int numPoints, float minRadius)  |
117 | {  |
118 | if (!points || (numPoints <= 0))  |
119 | return false;  |
120 |   |
121 | if (minRadius < Epsilon)  |
122 | minRadius = Epsilon;  |
123 |   |
124 | float& radius = sphere.Radius;  |
125 | tVector3& center = sphere.Center;  |
126 |   |
127 | radius = minRadius;  |
128 | center = points[0];  |
129 | if (numPoints == 1)  |
130 | return true;  |
131 |   |
132 | for (int t = 0; t < 2; t++)  |
133 | {  |
134 | for (int p = 0; p < numPoints; p++)  |
135 | {  |
136 | float distSq = (points[p] - center).LengthSq();  |
137 | float radSq = radius*radius;  |
138 | if (distSq > radSq)  |
139 | {  |
140 | float dist = tSqrt(distSq);  |
141 | float param = dist / radius;  |
142 | float paramInv = 1.0f/param;  |
143 | float paramInvSq = paramInv*paramInv;  |
144 | float onePlus = 1.0f + paramInvSq;  |
145 | float oneMinus = 1.0f - paramInvSq;  |
146 |   |
147 | radius = 0.5f * (param + paramInv) * radius;  |
148 | center = (onePlus*center + oneMinus*points[p]) / 2.0f;  |
149 | }  |
150 | }  |
151 | }  |
152 |   |
153 | for (int p = 0; p < numPoints; p++)  |
154 | {  |
155 | tVector3 diff = points[p] - center;  |
156 | float distSq = diff.LengthSq();  |
157 | float radSq = radius*radius;  |
158 | if (distSq > radSq)  |
159 | {  |
160 | float dist = tSqrt(distSq);  |
161 | radius = (radius + dist) / 2.0f;  |
162 | center += diff * (dist - radius) / dist;  |
163 | }  |
164 | }  |
165 |   |
166 | return true;  |
167 | }  |
168 |   |
169 |   |
170 | bool tMath::tComputeSmallEnclosingSphere_Unstable(tSphere& sphere, const tVector3* points, int numPoints)  |
171 | {  |
172 | if (!points || (numPoints <= 0))  |
173 | return false;  |
174 |   |
175 | float& radius = sphere.Radius;  |
176 | tVector3& center = sphere.Center;  |
177 | if (numPoints == 1)  |
178 | {  |
179 | radius = 0.0f;  |
180 | center = points[0];  |
181 | return true;  |
182 | }  |
183 |   |
184 | tVector3 xmin(PosInfinity, PosInfinity, PosInfinity);  |
185 | tVector3 xmax(NegInfinity, NegInfinity, NegInfinity);  |
186 | tVector3 ymin(PosInfinity, PosInfinity, PosInfinity);  |
187 | tVector3 ymax(NegInfinity, NegInfinity, NegInfinity);  |
188 | tVector3 zmin(PosInfinity, PosInfinity, PosInfinity);  |
189 | tVector3 zmax(NegInfinity, NegInfinity, NegInfinity);  |
190 | for (int p = 0; p < numPoints; p++)  |
191 | {  |
192 | const tVector3& point = points[p];  |
193 | if (xmin.x > point.x)  |
194 | xmin = point;  |
195 |   |
196 | if (xmax.x < point.x)  |
197 | xmax = point;  |
198 |   |
199 | if (ymin.y > point.y)  |
200 | ymin = point;  |
201 |   |
202 | if (ymax.y < point.y)  |
203 | ymax = point;  |
204 |   |
205 | if (zmin.z > point.z)  |
206 | zmin = point;  |
207 |   |
208 | if (zmax.z < point.z)  |
209 | zmax = point;  |
210 | }  |
211 |   |
212 | // Span the distance between min and max squared.  |
213 | float xspan = (xmax - xmin).LengthSq();  |
214 | float yspan = (ymax - ymin).LengthSq();  |
215 | float zspan = (zmax - zmin).LengthSq();  |
216 |   |
217 | // Find maximally separated pair.  |
218 | tVector3 diam1(xmin);  |
219 | tVector3 diam2(xmax);  |
220 | float maxspan = xspan;  |
221 |   |
222 | if (yspan > maxspan)  |
223 | {  |
224 | maxspan = yspan;  |
225 | diam1 = ymin;  |
226 | diam2 = ymax;  |
227 | }  |
228 |   |
229 | if (zspan > maxspan)  |
230 | {  |
231 | diam1 = zmin;  |
232 | diam2 = zmax;  |
233 | }  |
234 |   |
235 | // Calculate initial center.  |
236 | center.Set( (diam1.x+diam2.x)*0.5f, (diam1.y+diam2.y)*0.5f, (diam1.z+diam2.z)*0.5f );  |
237 |   |
238 | // Calculate initial radius.  |
239 | tVector3 radVec(diam2.x - center.x, diam2.y - center.y, diam2.z - center.z);  |
240 | float radSq = radVec.LengthSq();  |
241 | radius = tSqrt(radSq);  |
242 |   |
243 | // Second pass. Increment current sphere.  |
244 | for (int p = 0; p < numPoints; p++)  |
245 | {  |
246 | const tVector3& point = points[p];  |
247 | tVector3 diff = point - center;  |
248 | float distSq = diff.LengthSq();  |
249 |   |
250 | // DistSq is guaranteed to be > 0 so the divide is safe.  |
251 | if (distSq > radSq)  |
252 | {  |
253 | float dist = tSqrt(distSq);  |
254 | radius = (radius + dist) / 2.0f;  |
255 | float diff = dist - radius;  |
256 | center = (center*radius + point*diff) / dist;  |
257 | }  |
258 | }  |
259 |   |
260 | return true;  |
261 | }  |
262 |   |
263 |   |
264 | float tMath::tDistanceToLine(const tVector3& p, const tLine& l)  |
265 | {  |
266 | tVector3 u = l.B - l.A;  |
267 | u.Normalize();  |
268 | tVector3 w = p - l.A;  |
269 | u %= w; // Cross product.  |
270 |   |
271 | return u.Length();  |
272 | }  |
273 |   |
274 |   |
275 | tMath::tIntersectResult tMath::IntersectFindLineLineHelper(const tLine2& a, const tLine2& b, float& ua, float& ub)  |
276 | {  |
277 | // Based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/  |
278 | float x4mx3 = b.B.x - b.A.x;  |
279 | float y1my3 = a.A.y - b.A.y;  |
280 | float y4my3 = b.B.y - b.A.y;  |
281 | float x1mx3 = a.A.x - b.A.x;  |
282 | float x2mx1 = a.B.x - a.A.x;  |
283 | float y2my1 = a.B.y - a.A.y;  |
284 |   |
285 | float d = y4my3*x2mx1 - x4mx3*y2my1;  |
286 | float na = x4mx3*y1my3 - y4my3*x1mx3;  |
287 | float nb = x2mx1*y1my3 - y2my1*x1mx3;  |
288 | if (d == 0.0f)  |
289 | {  |
290 | // We're parallel, possibly coincident.  |
291 | if ((na == 0.0f) || (nb == 0.0f))  |
292 | return tIntersectResult::Coincident;  |
293 | else  |
294 | return tIntersectResult::Parallel;  |
295 | }  |
296 |   |
297 | ua = na/d;  |
298 | ub = nb/d;  |
299 | return tIntersectResult::Point;  |
300 | }  |
301 |   |
302 |   |
303 | tMath::tIntersectResult tMath::tIntersectFindLineLine(const tLine2& a, const tLine2& b, tVector2& i)  |
304 | {  |
305 | float ua, ub;  |
306 | tIntersectResult r = IntersectFindLineLineHelper(a, b, ua, ub);  |
307 | if (r != tIntersectResult::Point)  |
308 | return r;  |
309 |   |
310 | i = a.A + ua*(a.B - a.A);  |
311 | return tIntersectResult::Point;  |
312 | }  |
313 |   |
314 |   |
315 | tMath::tIntersectResult tMath::tIntersectFindSegSeg(const tLineSeg2& a, const tLineSeg2& b, tVector2& i)  |
316 | {  |
317 | float ua, ub;  |
318 | tIntersectResult r = IntersectFindLineLineHelper(a, b, ua, ub);  |
319 | if (r != tIntersectResult::Point)  |
320 | return r;  |
321 |   |
322 | if (tInRange(ua, 0.0f, 1.0f) && tInRange(ub, 0.0f, 1.0f))  |
323 | {  |
324 | i = a.A + ua*(a.B - a.A);  |
325 | return tIntersectResult::Point;  |
326 | }  |
327 |   |
328 | return tIntersectResult::None;  |
329 | }  |
330 |   |
331 |   |
332 | tMath::tIntersectResult tMath::tIntersectFindRaySeg(const tRay2& r, const tLineSeg2& b, tVector2& i)  |
333 | {  |
334 | tLine2 a(r.Start, r.Start+r.Dir);  |
335 |   |
336 | float ua, ub;  |
337 | tIntersectResult res = IntersectFindLineLineHelper(a, b, ua, ub);  |
338 | if (res != tIntersectResult::Point)  |
339 | return res;  |
340 |   |
341 | if ((ua >= 0.0f) && tInRange(ub, 0.0f, 1.0f))  |
342 | {  |
343 | i = a.A + ua*(a.B - a.A);  |
344 | return tIntersectResult::Point;  |
345 | }  |
346 |   |
347 | return tIntersectResult::None;  |
348 | }  |
349 |   |
350 |   |
351 | int tMath::tIntersectFindRayRect(const tRay2& r, const tARect2& b, tVector2 ia[2], uint32* sides)  |
352 | {  |
353 | int numIntersections = 0;  |
354 |   |
355 | // The box is made of 4 segments that we test in turn.  |
356 | tVector2 tl(b.Min.x, b.Max.y);  |
357 | tVector2 br(b.Max.x, b.Min.y);  |
358 | tLineSeg2 seg;  |
359 | tVector2 i;  |
360 | if (sides)  |
361 | *sides = 0;  |
362 |   |
363 | // Bottom segment.  |
364 | seg.A = b.Min;  |
365 | seg.B = br;  |
366 | if (tIntersectFindRaySeg(r, seg, i) == tIntersectResult::Point)  |
367 | {  |
368 | ia[numIntersections++] = i;  |
369 | if (sides)  |
370 | *sides |= tARect2::Side_Bottom;  |
371 | }  |
372 |   |
373 | // Right segment.  |
374 | seg.A = br;  |
375 | seg.B = b.Max;  |
376 | if (tIntersectFindRaySeg(r, seg, i) == tIntersectResult::Point)  |
377 | {  |
378 | ia[numIntersections++] = i;  |
379 | if (sides)  |
380 | *sides |= tARect2::Side_Right;  |
381 | }  |
382 | if (numIntersections == 2)  |
383 | return numIntersections;  |
384 |   |
385 | // Top segment.  |
386 | seg.A = b.Max;  |
387 | seg.B = tl;  |
388 | if (tIntersectFindRaySeg(r, seg, i) == tIntersectResult::Point)  |
389 | {  |
390 | ia[numIntersections++] = i;  |
391 | if (sides)  |
392 | *sides |= tARect2::Side_Top;  |
393 | }  |
394 | if (numIntersections == 2)  |
395 | return numIntersections;  |
396 |   |
397 | // Left segment.  |
398 | seg.A = tl;  |
399 | seg.B = b.Min;  |
400 | if (tIntersectFindRaySeg(r, seg, i) == tIntersectResult::Point)  |
401 | {  |
402 | ia[numIntersections++] = i;  |
403 | if (sides)  |
404 | *sides |= tARect2::Side_Left;  |
405 | }  |
406 |   |
407 | return numIntersections;  |
408 | }  |
409 |   |
410 |   |
411 | bool tMath::tIntersectFindRaySphere(float& t, const tRay& ray, const tSphere& sphere)  |
412 | {  |
413 | const tVector3& p = ray.Start;  |
414 | const tVector3& d = ray.Dir;  |
415 | const tVector3& c = sphere.Center;  |
416 | const float& r = sphere.Radius;  |
417 |   |
418 | // rayDir must have length 1. Solution is quadratic equation with A=1 and B, C as below.  |
419 | float B = 2.0f*( d.x*(p.x-c.x) + d.y*(p.y-c.y) + d.z*(p.z-c.z) );  |
420 | float C = (p.x-c.x)*(p.x-c.x) + (p.y-c.y)*(p.y-c.y) + (p.z-c.z)*(p.z-c.z) - r*r;  |
421 |   |
422 | float discriminant = B*B - 4.0f*C;  |
423 | if (discriminant < 0.0f)  |
424 | return false;  |
425 |   |
426 | float root = tSqrt(discriminant);  |
427 |   |
428 | t = (-B - root)/2.0f;  |
429 |   |
430 | // May need the other root.  |
431 | if (t < 0.0f)  |
432 | t = (-B + root)/2.0f;  |
433 |   |
434 | if (t < 0.0f)  |
435 | // The sphere is behind us.  |
436 | return false;  |
437 |   |
438 | // The intersection = p + d*t.  |
439 | // t0, t1 = (- B + (B^2 - 4*C)^1/2) / 2 where t0 is for (-) and t1 is for (+).  |
440 | // If the discriminant is < 0.0 then there is no real root and no intersection. If there is a real root  |
441 | // (discriminant > = 0.0) then the smaller positive root is the closest intersection point. We compute t0 and if it  |
442 | // is positive we are done. If it is negative we compute t1. The intersection point is given by:   |
443 | // Ri = [xi, yi, zi] = [x0 + xd * ti , y0 + yd * ti, z0 + zd * ti]  |
444 | return true;  |
445 | }  |
446 |   |
447 |   |
448 | bool tMath::tIntersectTestLineSegSphere(const tLineSeg& segmentAB, const tSphere& sphere)  |
449 | {  |
450 | tRay rayAB(segmentAB);  |
451 | tLineSeg segmentBA(segmentAB.B, segmentAB.A);  |
452 | tRay rayBA(segmentBA);  |
453 |   |
454 | float u, v;  |
455 | if (tIntersectFindRaySphere(u, rayAB, sphere) && tIntersectFindRaySphere(v, rayBA, sphere))  |
456 | return true;  |
457 | else  |
458 | return false;  |
459 | }  |
460 |   |
461 |   |
462 | bool tMath::tIntersectFindRayEllipse(float& t, const tRay2& ray, const tEllipse2& ellipse)  |
463 | {  |
464 | float r1 = ellipse.RadiusX;  |
465 | float r2 = ellipse.RadiusY;  |
466 | tAssert(r1 && r2);  |
467 |   |
468 | float dx = ray.Dir.x;  |
469 | float dy = ray.Dir.y;  |
470 | tAssert(dx || dy);  |
471 |   |
472 | float discriminant = 1.0f/( (dx*dx/(r1*r1)) + (dy*dy/(r2*r2)) );  |
473 | if (discriminant < 0.0f)  |
474 | return false;  |
475 |   |
476 | t = tSqrt(discriminant);  |
477 | return true;  |
478 | }  |
479 |   |
480 |   |
481 | bool tMath::tIntersectTestRayTriangle(const tRay& ray, const tTriangle& tri)  |
482 | {  |
483 | // A reconditioned version of the Segura-Feito method.  |
484 | tVector3 a = tri.A - ray.Start;  |
485 | tVector3 b = tri.B - ray.Start;  |
486 | tVector3 c = tri.C - ray.Start;  |
487 |   |
488 | tVector3 t;  |
489 | tCross(t, a, ray.Dir);  |
490 | if (c*t > 0.0f)  |
491 | return false;  |
492 |   |
493 | if (b*t < 0.0f)  |
494 | return false;  |
495 |   |
496 | tCross(t, b, ray.Dir);  |
497 | if (c*t < 0.0f)  |
498 | return false;  |
499 |   |
500 | return true;  |
501 | }  |
502 |   |
503 |   |
504 | bool tMath::tIntersectTestFrustumSphere(const tFrustum& f, const tSphere& s)  |
505 | {  |
506 | for (int p = 0; p < int(tFrustum::Plane_NumPlanes); p++)  |
507 | {  |
508 | // Use the sphere's center in the normalized plane equation to get the projected distance to plane.  |
509 | if (s.Center * f.Planes[p].Normal + f.Planes[p].Distance < -s.Radius)  |
510 | return false;  |
511 | }  |
512 |   |
513 | return true;  |
514 | }  |
515 | |