]> git.localhorst.tv Git - gong.git/blob - src/geometry/geometry.cpp
code, assets, and other stuff stolen from blank
[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 std::ostream &operator <<(std::ostream &out, const Frustum &frustum) {
221         return out << "Frustum(" << std::endl
222                 << "\tleft:   " << frustum.plane[0] << std::endl
223                 << "\tright:  " << frustum.plane[1] << std::endl
224                 << "\tbottom: " << frustum.plane[2] << std::endl
225                 << "\ttop:    " << frustum.plane[3] << std::endl
226                 << "\tnear:   " << frustum.plane[4] << std::endl
227                 << "\tfar:    " << frustum.plane[5] << std::endl
228                 << ')';
229 }
230
231 bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept {
232         // transform corners into clip space
233         glm::vec4 corners[8] = {
234                 { box.min.x, box.min.y, box.min.z, 1.0f },
235                 { box.min.x, box.min.y, box.max.z, 1.0f },
236                 { box.min.x, box.max.y, box.min.z, 1.0f },
237                 { box.min.x, box.max.y, box.max.z, 1.0f },
238                 { box.max.x, box.min.y, box.min.z, 1.0f },
239                 { box.max.x, box.min.y, box.max.z, 1.0f },
240                 { box.max.x, box.max.y, box.min.z, 1.0f },
241                 { box.max.x, box.max.y, box.max.z, 1.0f },
242         };
243
244         // check how many corners lie outside
245         int hits[6] = { 0, 0, 0, 0, 0, 0 };
246         for (glm::vec4 &corner : corners) {
247                 corner = MVP * corner;
248                 // replacing this with *= 1/w is effectively more expensive
249                 corner /= corner.w;
250                 hits[0] += (corner.x >  1.0f);
251                 hits[1] += (corner.x < -1.0f);
252                 hits[2] += (corner.y >  1.0f);
253                 hits[3] += (corner.y < -1.0f);
254                 hits[4] += (corner.z >  1.0f);
255                 hits[5] += (corner.z < -1.0f);
256         }
257
258         // if all corners are outside any given clip plane, the test is true
259         for (int hit : hits) {
260                 if (hit == 8) return true;
261         }
262
263         // otherwise the box might still get culled completely, but can't say for sure ;)
264         return false;
265 }
266
267 bool CullTest(const AABB &box, const Frustum &frustum) noexcept {
268         for (const Plane &plane : frustum.plane) {
269                 const glm::vec3 np(
270                         ((plane.normal.x > 0.0f) ? box.max.x : box.min.x),
271                         ((plane.normal.y > 0.0f) ? box.max.y : box.min.y),
272                         ((plane.normal.z > 0.0f) ? box.max.z : box.min.z)
273                 );
274                 const float dp = glm::dot(plane.normal, np);
275                 // cull if nearest point is on the "outside" side of the plane
276                 if (dp < -plane.dist) return true;
277         }
278         return false;
279 }
280 }
281
282 }