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