ToyMaker Game Engine 0.0.2
ToyMaker is a game engine developed and maintained by Zoheb Shujauddin.
Loading...
Searching...
No Matches
math.hpp
Go to the documentation of this file.
1
12
13#ifndef TOYMAKERENGINE_SPATIALQUERYMATH_H
14#define TOYMAKERENGINE_SPATIALQUERYMATH_H
15
16#include <iostream>
17#include <glm/glm.hpp>
18
19#include "../util.hpp"
20#include "types.hpp"
21
22namespace ToyMaker {
23
24 static constexpr uint8_t kMaxIterations { 32 };
25 static constexpr float kSimplexEpsilon { 0.0001 };
26
27
34 glm::mat3 computeBarycentricSolver(const AreaTriangle& triangle);
35
42 std::pair<bool, glm::vec3> computeIntersection(const Ray& ray, const Plane& plane);
49 std::pair<bool, glm::vec3> computeIntersection(const Ray& ray, const AreaTriangle& triangle);
59 std::pair<uint8_t, std::pair<glm::vec3, glm::vec3>> computeIntersections(const Ray& ray, const AxisAlignedBounds& axisAlignedBounds);
67 std::pair<uint8_t, std::pair<glm::vec3, glm::vec3>> computeIntersections(const Ray& ray, const ObjectBounds& objectbounds);
68
73 bool overlaps(const glm::vec3& point, const ObjectBounds& bounds);
78 inline bool overlaps(const ObjectBounds& bounds, const glm::vec3& point) {
79 return overlaps(point, bounds);
80 }
81
85 bool overlaps(const Ray& ray, const ObjectBounds& bounds);
86
91 inline bool overlaps(const ObjectBounds& bounds, const Ray& ray) {
92 return overlaps(ray, bounds);
93 }
94
99 bool overlaps(const glm::vec3& point, const AxisAlignedBounds& bounds);
105 inline bool overlaps(const AxisAlignedBounds& bounds, const glm::vec3& point) {
106 return overlaps(point, bounds);
107 }
108
113 bool overlaps(const Ray& ray, const AxisAlignedBounds& bounds);
119 inline bool overlaps(const AxisAlignedBounds& bounds, const Ray& ray) {
120 return overlaps(ray, bounds);
121 }
122
127 bool overlaps(const AxisAlignedBounds& one, const AxisAlignedBounds& two);
133 bool overlaps(const ObjectBounds& one, const ObjectBounds& two);
139 bool overlaps(const AxisAlignedBounds& one, const ObjectBounds& two);
140
146 inline bool overlaps(const ObjectBounds& one, const AxisAlignedBounds& two) {
147 return overlaps(two, one);
148 }
149
159 Collision checkCollisionSphereSphere(const ObjectBounds& one, const ObjectBounds& two);
160
176 template <typename T, typename U>
177 std::pair<bool, Simplex> gjkOverlaps(const T& one, const U& two);
178
191 template <typename T, typename U>
192 Polytope buildPolytope(const T& one, const U& two, const Simplex& gjkSimplex);
193
210 template <typename T, typename U>
211 Collision checkCollision(const T& one, const U& two);
212
218 bool contains(const glm::vec3& point, const ObjectBounds& bounds);
224 bool contains(const glm::vec3& point, const AxisAlignedBounds& bounds);
230 bool contains(const Ray& ray, const AxisAlignedBounds& bounds);
236 bool contains(const Ray& ray, const ObjectBounds& bounds);
242 bool contains(const AxisAlignedBounds& one, const AxisAlignedBounds& two);
248 bool contains(const ObjectBounds& one, const AxisAlignedBounds& two);
249
250
260 template<typename T, typename U>
261 inline glm::vec3 minkowskiDifference(const T& one, const U& two, const glm::vec3& along) {
262 const glm::vec3 supportOne { one.getSupportAlong(along) };
263 const glm::vec3 supportTwo { two.getSupportAlong(-along) };
264 return supportOne - supportTwo;
265 }
266
278 template<typename T, typename U>
279 inline std::tuple<glm::vec3, glm::vec3, glm::vec3> minkowskiDifferenceFull(const T& one, const U& two, const glm::vec3& along) {
280 const glm::vec3 supportOne { one.getSupportAlong(along) };
281 const glm::vec3 supportTwo { two.getSupportAlong(-along) };
282 return { supportOne - supportTwo, supportOne, supportTwo };
283 }
284
285 template <typename T, typename U>
286 inline std::pair<bool, Simplex> gjkOverlaps(const T& one, const U& two) {
287 assert(one.isSensible() && two.isSensible() && "Invalid object bounds provided");
288
289 // Both need to be non-degenerate bounds to actually overlap
290 if(!one.isPositiveStrict() || !two.isPositiveStrict()) {
291 return { false, Simplex {} };
292 }
293
294 glm::vec3 searchDirection { two.getPositionWorld() - one.getPositionWorld() };
295
296 // avoid handling cases where 2 objects overlap at their origin
297 if(squareDistance(searchDirection) == 0.f) {
298 std::cerr << "Invalid overlap where 2 objects share same origin detected.\n";
299 return { false, Simplex {} };
300 }
301
302 Simplex simplex {};
303 auto fullDifference { minkowskiDifferenceFull(one, two, searchDirection) };
304 const auto& candidatePoint { std::get<0>(fullDifference) }; // updated with fullDifference
305 const auto& supportA { std::get<1>(fullDifference) }; // updated with fullDifference
306 const auto& supportB { std::get<2>(fullDifference) }; // updated with fullDifference
307 simplex.append(candidatePoint, supportA, supportB);
308
309 // we go towards the origin from the first point we found
310 glm::vec3 toOrigin { -simplex.mPoints[0] };
311 searchDirection = toOrigin;
312 for(uint8_t iteration { 0 }; squareDistance(toOrigin) && iteration < kMaxIterations; ++iteration) {
313 // find a new search direction
314 searchDirection = toOrigin;
315
316 // find a new candidate point
317 fullDifference = minkowskiDifferenceFull(one, two, searchDirection);
318
319 // we couldn't find a point far enough past the origin in this direction, so
320 // obviously there is no intersection
321 if(glm::dot(candidatePoint, searchDirection) <= 0.f) {
322 return { false, simplex };
323 }
324
325 simplex.append(candidatePoint, supportA, supportB);
326 const auto result { simplex.evaluate() };
327 toOrigin = result.second;
328 if(result.first) {
329 return { true, simplex };
330 }
331 }
332
333 // handle degenerate simplexes by forcing them to expand to a full simplex
334 while(simplex.mNPoints < 4) {
335 switch(simplex.mNPoints) {
336 case 1:
337 simplex.mPoints[simplex.mNPoints] = glm::vec3 { 2.f * kSimplexEpsilon, 0.f, 0.f };
338 simplex.mPointsSupportA[simplex.mNPoints] = glm::vec3 { kSimplexEpsilon, 0.f, 0.f };
339 simplex.mPointsSupportB[simplex.mNPoints] = -glm::vec3 { kSimplexEpsilon, 0.f, 0.f };
340 ++simplex.mNPoints;
341 break;
342 case 2:
343 {
344 const glm::vec3 dirAB { glm::normalize(simplex.mPoints[1] - simplex.mPoints[0]) };
345 assert(squareDistance(dirAB) > 0 && "A and B should never be the same point");
346 const glm::vec3 dirOffset { (dirAB.x != 0.f || dirAB.z != 0.f)?
347 glm::vec3{ 0.f, 1.f, 0.f } : glm::vec3{ 1.f, 0.f, 1.f }
348 };
349 searchDirection = glm::normalize(-dirAB + dirOffset);
350 simplex.mPoints[simplex.mNPoints] = 2.f * kSimplexEpsilon * dirOffset;
351 simplex.mPointsSupportA[simplex.mNPoints] = kSimplexEpsilon * dirOffset;
352 simplex.mPointsSupportB[simplex.mNPoints] = -kSimplexEpsilon * dirOffset;
353 ++simplex.mNPoints;
354 }
355 break;
356 case 3:
357 {
358 const glm::vec3 normABC { glm::normalize(glm::cross(simplex.mPoints[1] - simplex.mPoints[0], simplex.mPoints[2] - simplex.mPoints[0])) };
359 assert(isNumber(normABC) && isFinite(normABC) && "Invalid norm computed, degenerate triangle detected.");
360 simplex.mPoints[simplex.mNPoints] = 2.f * kSimplexEpsilon * normABC;
361 simplex.mPointsSupportA[simplex.mNPoints] = kSimplexEpsilon * normABC;
362 simplex.mPointsSupportB[simplex.mNPoints] = -kSimplexEpsilon * normABC;
363 ++simplex.mNPoints;
364 }
365 break;
366 default:
367 assert(false && "Simplex with invalid number of points for this section of the algorithm provided");
368 break;
369 };
370 }
371
372 return { true, simplex };
373 }
374
375 // TODO: This shouldn't be a template, since both parameters will _have_ to be ObjectBounds
376 template <typename T, typename U>
377 inline Collision checkCollision(const T& one, const U& two) {
378 // perform inexpensive AABB test first
380 return {
381 .mCollided { false }
382 };
383 }
384
385 // special case: sphere sphere collision, solvable in constant time
386 if(
387 one.mType == ObjectBounds::TrueVolumeType::SPHERE
388 && two.mType == ObjectBounds::TrueVolumeType::SPHERE
389 ) {
390 return checkCollisionSphereSphere(one, two);
391 }
392
393 // check via GJK whether there's any overlap at all
394 const std::pair<bool, Simplex> overlapResult { gjkOverlaps(one, two) };
395 if(!overlapResult.first) {
396 return {
397 .mCollided { false },
398 };
399 }
400
401
402 // allocate result storage
403 Collision collisionResult { Collision {
404 .mCollided { true },
405 } };
406
407 // build a polytope containing the closest surface to the origin in
408 // the Minkowski difference between the objects
409 const Polytope polytope { buildPolytope(one, two, overlapResult.second) };
410
411 // derive barycentric coordinates for the contact point relative to the
412 // triangle in the difference shape it lies on
413 const glm::vec3 closestPoint { polytope.getClosestPoint() };
414 const AreaTriangle closestTriangle { polytope.getClosestTriangle() };
415 const glm::mat3 barycentricSolver { computeBarycentricSolver(closestTriangle) };
416 // If we can't solve this, then there's nothing for us to do
417 if(!isSensible(barycentricSolver)) {
418 return { false };
419 }
420
421 const glm::vec3 barycentricCoordinates {
422 barycentricSolver * closestPoint
423 };
424
425 // Use the barycentric coordinates to derive contact point for both shapes
426 const AreaTriangle closestTriangleOne {
428 };
429 const AreaTriangle closestTriangleTwo {
431 };
432 const glm::mat3 barycentricOne {
433 closestTriangleOne.mPoints[0], closestTriangleOne.mPoints[1], closestTriangleOne.mPoints[2]
434 };
435 const glm::mat3 barycentricTwo {
436 closestTriangleTwo.mPoints[0], closestTriangleTwo.mPoints[1], closestTriangleTwo.mPoints[2]
437 };
438 collisionResult.mContactA.mPoint = barycentricOne * barycentricCoordinates;
439 assert(
440 isNumber(collisionResult.mContactA.mPoint) && isFinite(collisionResult.mContactA.mPoint)
441 && "Invalid contact point computed for object A"
442 );
443 collisionResult.mContactB.mPoint = barycentricTwo * barycentricCoordinates;
444 assert(
445 isNumber(collisionResult.mContactB.mPoint) && isFinite(collisionResult.mContactB.mPoint)
446 && "Invalid contact point computed for object B"
447 );
448
449 // build tangent vectors to the normal (used for friction calculations)
450 const auto contactNormal {
451 squareDistance(closestPoint)? closestPoint: one.getPositionWorld() - two.getPositionWorld()
452 };
453 const auto tangentPair { getTangents(contactNormal) };
454 collisionResult.mContactB.mNormal = glm::normalize(contactNormal);
455 collisionResult.mContactB.mTangent1 = glm::normalize(tangentPair.first);
456 collisionResult.mContactB.mTangent2 = glm::normalize(tangentPair.second);
457 collisionResult.mContactA.mNormal = -collisionResult.mContactB.mNormal;
458 collisionResult.mContactA.mTangent1 = -collisionResult.mContactB.mTangent1;
459 collisionResult.mContactA.mTangent2 = -collisionResult.mContactB.mTangent2;
460
461 // set penetration depth value for both contacts
462 collisionResult.mContactA.mPenetrationDepth
463 = collisionResult.mContactB.mPenetrationDepth
464 = glm::length(closestPoint);
465
466 return collisionResult;
467 }
468
469 template <typename T, typename U>
470 inline Polytope buildPolytope(const T& one, const U& two, const Simplex& simplex) {
471 Polytope polytope { simplex };
472 glm::vec3 searchDirection { polytope.getNextSearch() };
473 assert(squareDistance(searchDirection) > 0 && "Search direction should always be non-zero");
474 assert(isFinite(searchDirection) && "Search direction should always be finite");
475
476 // Compute first support point
477 std::tuple<glm::vec3, glm::vec3, glm::vec3> differenceFull { minkowskiDifferenceFull(one, two, searchDirection) };
478
479 // These reference types can be thought of as "views" into the
480 // differenceFull struct
481 const glm::vec3& supportA { std::get<1>(differenceFull) };
482 const glm::vec3& supportB { std::get<2>(differenceFull) };
483 const glm::vec3& difference { std::get<0>(differenceFull) };
484
485 // Keep building out the expanding polytope until either the minimum translation vector is found or we run out of iterations
486 uint8_t iterationsLeft { kMaxIterations };
487 while(polytope.append(difference, supportA, supportB) && iterationsLeft--) {
488 searchDirection = polytope.getNextSearch();
489 differenceFull = minkowskiDifferenceFull(one, two, searchDirection);
490 }
491
492 return polytope;
493 }
494}
495
496#endif
An object containing a coarse simplified representation, AABB, of spatially queryable objects.
Definition types.hpp:1322
Primitive for EPA algorithm.
Definition types.hpp:636
AreaTriangle getClosestTriangle() const
Returns the closest polytope triangle.
Definition types.cpp:105
AreaTriangle getClosestTriangleSupportB() const
Gets the points of shape B that were responsible for generating the closest triangle of the polytope.
Definition types.cpp:123
bool append(const glm::vec3 &newPoint, const glm::vec3 &supportA, const glm::vec3 &supportB)
Appends a new point, replacing the topmost triangle in the polygon with 3 more triangles including th...
Definition types.cpp:155
glm::vec3 getClosestPoint() const
Returns the closest point to the origin on the triangle face closest to the origin.
Definition types.cpp:91
AreaTriangle getClosestTriangleSupportA() const
Gets the points of shape A that were responsible for generating the closest triangle of the polytope.
Definition types.cpp:114
glm::vec3 getNextSearch() const
Returns the next search direction for a point to add to the polytope.
Definition types.cpp:80
bool isNumber(float number)
Tests whether a float is really a number (as opposed to a special error representation).
Definition util.hpp:92
float squareDistance(const glm::vec3 &vector)
Returns the square of the length of a 3 component vector.
Definition util.hpp:35
bool isFinite(float number)
Tests whether a given number is finite.
Definition util.hpp:47
bool isSensible(const glm::mat3 &matrix)
Determines whether a particular matrix is valid and finite.
Definition math.cpp:775
bool contains(const glm::vec3 &point, const ObjectBounds &bounds)
Returns whether point is contained by bounds.
Definition math.cpp:291
std::pair< bool, glm::vec3 > computeIntersection(const Ray &ray, const Plane &plane)
Returns a bool-vector pair, with bool indicating whether a point of intersection was found,...
Definition math.cpp:45
glm::vec3 minkowskiDifference(const T &one, const U &two, const glm::vec3 &along)
Closely associated with GJK; finds the Minkowski difference between two shapes situated somewhere in ...
Definition math.hpp:261
std::pair< glm::vec3, glm::vec3 > getTangents(const glm::vec3 &vector)
Returns a pair of normalized tangents to an input vector.
Definition util.cpp:121
std::pair< uint8_t, std::pair< glm::vec3, glm::vec3 > > computeIntersections(const Ray &ray, const AxisAlignedBounds &axisAlignedBounds)
Returns an unsigned int and vector-pair pair, with unsigned indicating whether any and how many point...
Definition math.cpp:128
bool overlaps(const glm::vec3 &point, const ObjectBounds &bounds)
Returns a bool value indicating whether point is contained by bounds.
Definition math.cpp:170
glm::mat3 computeBarycentricSolver(const AreaTriangle &triangle)
Computes a Barycentric solver matrix based on an input triangle.
Definition math.cpp:780
Collision checkCollisionSphereSphere(const ObjectBounds &one, const ObjectBounds &two)
Specialization of checkCollision() where both objects being tested are spheres.
Definition math.cpp:827
std::tuple< glm::vec3, glm::vec3, glm::vec3 > minkowskiDifferenceFull(const T &one, const U &two, const glm::vec3 &along)
Closely associated with GJK; finds the Minkowski difference between two shapes situated somewhere in ...
Definition math.hpp:279
Namespace containing all class definitions and functions related to the ToyMaker engine.
Definition application.hpp:25
std::pair< bool, Simplex > gjkOverlaps(const T &one, const U &two)
GJK implementation based on Casey Muratori's video lecture, which is available here.
Definition math.hpp:286
Collision checkCollision(const T &one, const U &two)
Checks whether a pair of convex 3D shapes are colliding, returning information about the collision if...
Definition math.hpp:377
Polytope buildPolytope(const T &one, const U &two, const Simplex &gjkSimplex)
EPA implementation, used to derive contact information for intersections between convex shapes.
Definition math.hpp:470
Classes and structs representing data related to the engine's spatial query system (the precursor to ...
A set of 3 points located in the world forming a (hopefully sensible) triangle.
Definition types.hpp:327
std::array< glm::vec3, 3 > mPoints
The points of the triangle, where each point has 3 components.
Definition types.hpp:332
Data representing everything about a collision.
Definition types.hpp:816
Contact mContactA
Contact information relative to the first collision shape.
Definition types.hpp:827
Contact mContactB
Contact information relative to the second collision shape.
Definition types.hpp:833
float mPenetrationDepth
The length by which to move this object in the direction of the contact normal to separate the collid...
Definition types.hpp:802
glm::vec3 mTangent1
Worldspace tangent orthogonal to the contact normal and the other contact tangent.
Definition types.hpp:789
glm::vec3 mNormal
Worldspace normal pointing inward from the surface of this shape such that moving in this direction b...
Definition types.hpp:783
glm::vec3 mPoint
The worldspace point on the surface of this shape that made contact with the other shape.
Definition types.hpp:776
glm::vec3 mTangent2
Worldspace tangent orthogonal to the contact normal and the other contact tangent.
Definition types.hpp:795
A component defining the true bounds of a spatially queryable object situated somewhere in the world.
Definition types.hpp:845
A set of numbers describing a plane situated somewhere in the world.
Definition types.hpp:500
A set of numbers describing a ray with its source at some finite point in the world,...
Definition types.hpp:447
Primitive for GJK algorithm.
Definition types.hpp:549
std::array< glm::vec3, 4 > mPointsSupportA
The point on the LHS of the Minkowski difference, where each index corresponds to a point on mPoints.
Definition types.hpp:561
std::array< glm::vec3, 4 > mPointsSupportB
The point on the RHS of the Minkowski difference, where each index corresponds to a point on mPoints.
Definition types.hpp:567
std::pair< bool, glm::vec3 > evaluate()
Tries to find a 3-simplex that encloses the origin.
Definition types.cpp:665
bool append(const glm::vec3 &candidatePoint, const glm::vec3 &supportA, const glm::vec3 &supportB)
Adds a new point to the simplex.
Definition types.hpp:581
uint8_t mNPoints
The number of points in this simplex.
Definition types.hpp:573
std::array< glm::vec3, 4 > mPoints
Points representing the simplex, derived by finding the Minksowski difference of support points on tw...
Definition types.hpp:555
Contains a couple of classes not tied to any part of the engine in particular, but useful to those pa...