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