]> git.localhorst.tv Git - gong.git/blob - src/geometry/geometry.cpp
simple AABB intersection test
[gong.git] / src / geometry / geometry.cpp
1 #include "const.hpp"
2 #include "distance.hpp"
3 #include "primitive.hpp"
4 #include "rotation.hpp"
5
6 #include <limits>
7 #include <ostream>
8 #include <glm/gtx/io.hpp>
9 #include <glm/gtx/matrix_cross_product.hpp>
10 #include <glm/gtx/optimum_pow.hpp>
11 #include <glm/gtx/transform.hpp>
12
13
14 namespace gong {
15 namespace geometry {
16
17 glm::mat3 find_rotation(const glm::vec3 &a, const glm::vec3 &b) noexcept {
18         glm::vec3 v(glm::cross(a, b));
19         if (iszero(v)) {
20                 // a and b are parallel
21                 if (iszero(a - b)) {
22                         // a and b are identical
23                         return glm::mat3(1.0f);
24                 } else {
25                         // a and b are opposite
26                         // create arbitrary unit vector perpendicular to a and
27                         // rotate 180° around it
28                         glm::vec3 arb(a);
29                         if (std::abs(a.x - 1.0f) > std::numeric_limits<float>::epsilon()) {
30                                 arb.x += 1.0f;
31                         } else {
32                                 arb.y += 1.0f;
33                         }
34                         glm::vec3 axis(glm::normalize(glm::cross(a, arb)));
35                         return glm::mat3(glm::rotate(PI, axis));
36                 }
37         }
38         float mv = glm::length2(v);
39         float c = glm::dot(a, b);
40         float f = (1 - c) / mv;
41         glm::mat3 vx(glm::matrixCross3(v));
42         return glm::mat3(1.0f) + vx + (glm::pow2(vx) * f);
43 }
44
45 std::ostream &operator <<(std::ostream &out, const AABB &box) {
46         return out << "AABB(" << box.min << ", " << box.max << ')';
47 }
48
49 bool Intersection(const AABB &a, const AABB &b) noexcept {
50         if (a.max.x < b.min.x) return false;
51         if (b.max.x < a.min.x) return false;
52         if (a.max.y < b.min.y) return false;
53         if (b.max.y < a.min.y) return false;
54         if (a.max.z < b.min.z) return false;
55         if (b.max.z < a.min.z) return false;
56         return true;
57 }
58
59 std::ostream &operator <<(std::ostream &out, const Ray &ray) {
60         return out << "Ray(" << ray.orig << ", " << ray.dir << ')';
61 }
62
63 bool Intersection(
64         const Ray &ray,
65         const AABB &box,
66         float &dist
67 ) noexcept {
68         float t_min = 0.0f;
69         float t_max = std::numeric_limits<float>::infinity();
70         for (int i = 0; i < 3; ++i) {
71                 float t1 = (box.min[i] - ray.orig[i]) * ray.inv_dir[i];
72                 float t2 = (box.max[i] - ray.orig[i]) * ray.inv_dir[i];
73                 t_min = std::max(t_min, std::min(t1, t2));
74                 t_max = std::min(t_max, std::max(t1, t2));
75         }
76         dist = t_min;
77         return t_max >= t_min;
78 }
79
80 bool Intersection(
81         const Ray &ray,
82         const AABB &aabb,
83         const glm::mat4 &M,
84         float *dist,
85         glm::vec3 *normal
86 ) noexcept {
87         float t_min = 0.0f;
88         float t_max = std::numeric_limits<float>::infinity();
89         const glm::vec3 aabb_pos(M[3].x, M[3].y, M[3].z);
90         const glm::vec3 delta = aabb_pos - ray.orig;
91
92         glm::vec3 t1(t_min, t_min, t_min), t2(t_max, t_max, t_max);
93
94         for (int i = 0; i < 3; ++i) {
95                 const glm::vec3 axis(M[i].x, M[i].y, M[i].z);
96                 const float e = glm::dot(axis, delta);
97                 const float f = glm::dot(axis, ray.dir);
98
99                 if (std::abs(f) > std::numeric_limits<float>::epsilon()) {
100                         t1[i] = (e + aabb.min[i]) / f;
101                         t2[i] = (e + aabb.max[i]) / f;
102
103                         t_min = std::max(t_min, std::min(t1[i], t2[i]));
104                         t_max = std::min(t_max, std::max(t1[i], t2[i]));
105
106                         if (t_max < t_min) {
107                                 return false;
108                         }
109                 } else {
110                         if (aabb.min[i] - e > 0.0f || aabb.max[i] - e < 0.0f) {
111                                 return false;
112                         }
113                 }
114         }
115
116         if (dist) {
117                 *dist = t_min;
118         }
119         if (normal) {
120                 glm::vec3 min_all(glm::min(t1, t2));
121                 if (min_all.x > min_all.y) {
122                         if (min_all.x > min_all.z) {
123                                 *normal = glm::vec3(t2.x < t1.x ? 1 : -1, 0, 0);
124                         } else {
125                                 *normal = glm::vec3(0, 0, t2.z < t1.z ? 1 : -1);
126                         }
127                 } else if (min_all.y > min_all.z) {
128                         *normal = glm::vec3(0, t2.y < t1.y ? 1 : -1, 0);
129                 } else {
130                         *normal = glm::vec3(0, 0, t2.z < t1.z ? 1 : -1);
131                 }
132         }
133         return true;
134 }
135
136 bool Intersection(
137         const AABB &a_box,
138         const glm::mat4 &a_m,
139         const AABB &b_box,
140         const glm::mat4 &b_m,
141         float &depth,
142         glm::vec3 &normal
143 ) noexcept {
144         glm::vec3 a_corners[8] = {
145                 glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.min.y, a_box.min.z, 1)),
146                 glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.min.y, a_box.max.z, 1)),
147                 glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.max.y, a_box.min.z, 1)),
148                 glm::vec3(a_m * glm::vec4(a_box.min.x, a_box.max.y, a_box.max.z, 1)),
149                 glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.min.y, a_box.min.z, 1)),
150                 glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.min.y, a_box.max.z, 1)),
151                 glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.max.y, a_box.min.z, 1)),
152                 glm::vec3(a_m * glm::vec4(a_box.max.x, a_box.max.y, a_box.max.z, 1)),
153         };
154
155         glm::vec3 b_corners[8] = {
156                 glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.min.y, b_box.min.z, 1)),
157                 glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.min.y, b_box.max.z, 1)),
158                 glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.max.y, b_box.min.z, 1)),
159                 glm::vec3(b_m * glm::vec4(b_box.min.x, b_box.max.y, b_box.max.z, 1)),
160                 glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.min.y, b_box.min.z, 1)),
161                 glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.min.y, b_box.max.z, 1)),
162                 glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.max.y, b_box.min.z, 1)),
163                 glm::vec3(b_m * glm::vec4(b_box.max.x, b_box.max.y, b_box.max.z, 1)),
164         };
165
166         glm::vec3 axes[15] = {
167                 glm::vec3(a_m[0]),
168                 glm::vec3(a_m[1]),
169                 glm::vec3(a_m[2]),
170                 glm::vec3(b_m[0]),
171                 glm::vec3(b_m[1]),
172                 glm::vec3(b_m[2]),
173                 glm::normalize(glm::cross(glm::vec3(a_m[0]), glm::vec3(b_m[0]))),
174                 glm::normalize(glm::cross(glm::vec3(a_m[0]), glm::vec3(b_m[1]))),
175                 glm::normalize(glm::cross(glm::vec3(a_m[0]), glm::vec3(b_m[2]))),
176                 glm::normalize(glm::cross(glm::vec3(a_m[1]), glm::vec3(b_m[0]))),
177                 glm::normalize(glm::cross(glm::vec3(a_m[1]), glm::vec3(b_m[1]))),
178                 glm::normalize(glm::cross(glm::vec3(a_m[1]), glm::vec3(b_m[2]))),
179                 glm::normalize(glm::cross(glm::vec3(a_m[2]), glm::vec3(b_m[0]))),
180                 glm::normalize(glm::cross(glm::vec3(a_m[2]), glm::vec3(b_m[1]))),
181                 glm::normalize(glm::cross(glm::vec3(a_m[2]), glm::vec3(b_m[2]))),
182         };
183
184         depth = std::numeric_limits<float>::infinity();
185         int min_axis = 0;
186
187         int cur_axis = 0;
188         for (const glm::vec3 &axis : axes) {
189                 if (glm::any(glm::isnan(axis))) {
190                         // can result from the cross products if A and B have parallel axes
191                         ++cur_axis;
192                         continue;
193                 }
194                 float a_min = std::numeric_limits<float>::infinity();
195                 float a_max = -std::numeric_limits<float>::infinity();
196                 for (const glm::vec3 &corner : a_corners) {
197                         float val = glm::dot(corner, axis);
198                         a_min = std::min(a_min, val);
199                         a_max = std::max(a_max, val);
200                 }
201
202                 float b_min = std::numeric_limits<float>::infinity();
203                 float b_max = -std::numeric_limits<float>::infinity();
204                 for (const glm::vec3 &corner : b_corners) {
205                         float val = glm::dot(corner, axis);
206                         b_min = std::min(b_min, val);
207                         b_max = std::max(b_max, val);
208                 }
209
210                 if (a_max < b_min || b_max < a_min) return false;
211
212                 float overlap = std::min(a_max, b_max) - std::max(a_min, b_min);
213                 if (overlap < depth) {
214                         depth = overlap;
215                         min_axis = cur_axis;
216                 }
217
218                 ++cur_axis;
219         }
220
221         normal = axes[min_axis];
222         return true;
223 }
224
225
226 std::ostream &operator <<(std::ostream &out, const Plane &plane) {
227         return out << "Plane(" << plane.normal << ", " << plane.dist << ')';
228 }
229
230 float Distance(const glm::vec3 &point, const Plane &plane) {
231         return std::abs(SignedDistance(point, plane));
232 }
233
234 float SignedDistance(const glm::vec3 &point, const Plane &plane) {
235         return (
236                 plane.A() * point.x + plane.B() * point.y + plane.C() * point.z + plane.D()
237         ) / glm::length(plane.normal);
238 }
239
240 std::ostream &operator <<(std::ostream &out, const Frustum &frustum) {
241         return out << "Frustum(" << std::endl
242                 << "\tleft:   " << frustum.plane[0] << std::endl
243                 << "\tright:  " << frustum.plane[1] << std::endl
244                 << "\tbottom: " << frustum.plane[2] << std::endl
245                 << "\ttop:    " << frustum.plane[3] << std::endl
246                 << "\tnear:   " << frustum.plane[4] << std::endl
247                 << "\tfar:    " << frustum.plane[5] << std::endl
248                 << ')';
249 }
250
251 bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept {
252         // transform corners into clip space
253         glm::vec4 corners[8] = {
254                 { box.min.x, box.min.y, box.min.z, 1.0f },
255                 { box.min.x, box.min.y, box.max.z, 1.0f },
256                 { box.min.x, box.max.y, box.min.z, 1.0f },
257                 { box.min.x, box.max.y, box.max.z, 1.0f },
258                 { box.max.x, box.min.y, box.min.z, 1.0f },
259                 { box.max.x, box.min.y, box.max.z, 1.0f },
260                 { box.max.x, box.max.y, box.min.z, 1.0f },
261                 { box.max.x, box.max.y, box.max.z, 1.0f },
262         };
263
264         // check how many corners lie outside
265         int hits[6] = { 0, 0, 0, 0, 0, 0 };
266         for (glm::vec4 &corner : corners) {
267                 corner = MVP * corner;
268                 // replacing this with *= 1/w is effectively more expensive
269                 corner /= corner.w;
270                 hits[0] += (corner.x >  1.0f);
271                 hits[1] += (corner.x < -1.0f);
272                 hits[2] += (corner.y >  1.0f);
273                 hits[3] += (corner.y < -1.0f);
274                 hits[4] += (corner.z >  1.0f);
275                 hits[5] += (corner.z < -1.0f);
276         }
277
278         // if all corners are outside any given clip plane, the test is true
279         for (int hit : hits) {
280                 if (hit == 8) return true;
281         }
282
283         // otherwise the box might still get culled completely, but can't say for sure ;)
284         return false;
285 }
286
287 bool CullTest(const AABB &box, const Frustum &frustum) noexcept {
288         for (const Plane &plane : frustum.plane) {
289                 const glm::vec3 np(
290                         ((plane.normal.x > 0.0f) ? box.max.x : box.min.x),
291                         ((plane.normal.y > 0.0f) ? box.max.y : box.min.y),
292                         ((plane.normal.z > 0.0f) ? box.max.z : box.min.z)
293                 );
294                 const float dp = glm::dot(plane.normal, np);
295                 // cull if nearest point is on the "outside" side of the plane
296                 if (dp < -plane.dist) return true;
297         }
298         return false;
299 }
300
301 std::ostream &operator <<(std::ostream &out, const Sphere &s) {
302         return out << "Sphere(" << s.origin << ", " << s.radius << ')';
303 }
304
305 bool Intersection(
306         const Sphere &sphere,
307         const Plane &plane,
308         float &dist,
309         glm::vec3 &norm
310 ) noexcept {
311         float sig_dist = SignedDistance(sphere.origin, plane);
312         dist = sphere.radius - std::abs(sig_dist);
313         if (dist > 0.0f) {
314                 norm = sig_dist > 0.0f ? plane.normal : -plane.normal;
315                 return true;
316         } else {
317                 return false;
318         }
319 }
320
321 bool Intersection(
322         const Sphere &sphere,
323         const Plane &plane,
324         float &dist
325 ) noexcept {
326         dist = sphere.radius - SignedDistance(sphere.origin, plane);
327         return dist > 0.0f;
328 }
329
330 }
331 }