initial commit, first gear
[antikythera] / src / geom.cc
1 #include <assert.h>
2 #include <float.h>
3 #include <algorithm>
4 #include "geom.h"
5
6 GeomObject::~GeomObject()
7 {
8 }
9
10
11 Sphere::Sphere()
12 {
13         radius = 1.0;
14 }
15
16 Sphere::Sphere(const Vec3 &cent, float radius)
17         : center(cent)
18 {
19         this->radius = radius;
20 }
21
22 void Sphere::set_union(const GeomObject *obj1, const GeomObject *obj2)
23 {
24         const Sphere *sph1 = dynamic_cast<const Sphere*>(obj1);
25         const Sphere *sph2 = dynamic_cast<const Sphere*>(obj2);
26
27         if(!sph1 || !sph2) {
28                 fprintf(stderr, "Sphere::set_union: arguments must be spheres");
29                 return;
30         }
31
32         float dist = length(sph1->center - sph2->center);
33         float surf_dist = dist - (sph1->radius + sph2->radius);
34         float d1 = sph1->radius + surf_dist / 2.0;
35         float d2 = sph2->radius + surf_dist / 2.0;
36         float t = d1 / (d1 + d2);
37
38         if(t < 0.0) t = 0.0;
39         if(t > 1.0) t = 1.0;
40
41         center = sph1->center * t + sph2->center * (1.0 - t);
42         radius = std::max(dist * t + sph2->radius, dist * (1.0f - t) + sph1->radius);
43 }
44
45 void Sphere::set_intersection(const GeomObject *obj1, const GeomObject *obj2)
46 {
47         fprintf(stderr, "Sphere::intersection undefined\n");
48 }
49
50 bool Sphere::intersect(const Ray &ray, HitPoint *hit) const
51 {
52         float a = dot(ray.dir, ray.dir);
53         float b = 2.0 * ray.dir.x * (ray.origin.x - center.x) +
54                 2.0 * ray.dir.y * (ray.origin.y - center.y) +
55                 2.0 * ray.dir.z * (ray.origin.z - center.z);
56         float c = dot(ray.origin, ray.origin) + dot(center, center) -
57                 2.0 * dot(ray.origin, center) - radius * radius;
58
59         float discr = b * b - 4.0 * a * c;
60         if(discr < 1e-4) {
61                 return false;
62         }
63
64         float sqrt_discr = sqrt(discr);
65         float t0 = (-b + sqrt_discr) / (2.0 * a);
66         float t1 = (-b - sqrt_discr) / (2.0 * a);
67
68         if(t0 < 1e-4)
69                 t0 = t1;
70         if(t1 < 1e-4)
71                 t1 = t0;
72
73         float t = t0 < t1 ? t0 : t1;
74         if(t < 1e-4) {
75                 return false;
76         }
77
78         // fill the HitPoint structure
79         if(hit) {
80                 hit->obj = this;
81                 hit->dist = t;
82                 hit->pos = ray.origin + ray.dir * t;
83                 hit->normal = (hit->pos - center) / radius;
84         }
85         return true;
86 }
87
88
89 AABox::AABox()
90 {
91 }
92
93 AABox::AABox(const Vec3 &vmin, const Vec3 &vmax)
94         : min(vmin), max(vmax)
95 {
96 }
97
98 void AABox::set_union(const GeomObject *obj1, const GeomObject *obj2)
99 {
100         const AABox *box1 = dynamic_cast<const AABox*>(obj1);
101         const AABox *box2 = dynamic_cast<const AABox*>(obj2);
102
103         if(!box1 || !box2) {
104                 fprintf(stderr, "AABox::set_union: arguments must be AABoxes too\n");
105                 return;
106         }
107
108         min.x = std::min(box1->min.x, box2->min.x);
109         min.y = std::min(box1->min.y, box2->min.y);
110         min.z = std::min(box1->min.z, box2->min.z);
111
112         max.x = std::max(box1->max.x, box2->max.x);
113         max.y = std::max(box1->max.y, box2->max.y);
114         max.z = std::max(box1->max.z, box2->max.z);
115 }
116
117 void AABox::set_intersection(const GeomObject *obj1, const GeomObject *obj2)
118 {
119         const AABox *box1 = dynamic_cast<const AABox*>(obj1);
120         const AABox *box2 = dynamic_cast<const AABox*>(obj2);
121
122         if(!box1 || !box2) {
123                 fprintf(stderr, "AABox::set_intersection: arguments must be AABoxes too\n");
124                 return;
125         }
126
127         for(int i=0; i<3; i++) {
128                 min[i] = std::max(box1->min[i], box2->min[i]);
129                 max[i] = std::min(box1->max[i], box2->max[i]);
130
131                 if(max[i] < min[i]) {
132                         max[i] = min[i];
133                 }
134         }
135 }
136
137 bool AABox::intersect(const Ray &ray, HitPoint *hit) const
138 {
139         Vec3 param[2] = {min, max};
140         Vec3 inv_dir(1.0 / ray.dir.x, 1.0 / ray.dir.y, 1.0 / ray.dir.z);
141         int sign[3] = {inv_dir.x < 0, inv_dir.y < 0, inv_dir.z < 0};
142
143         float tmin = (param[sign[0]].x - ray.origin.x) * inv_dir.x;
144         float tmax = (param[1 - sign[0]].x - ray.origin.x) * inv_dir.x;
145         float tymin = (param[sign[1]].y - ray.origin.y) * inv_dir.y;
146         float tymax = (param[1 - sign[1]].y - ray.origin.y) * inv_dir.y;
147
148         if(tmin > tymax || tymin > tmax) {
149                 return false;
150         }
151         if(tymin > tmin) {
152                 tmin = tymin;
153         }
154         if(tymax < tmax) {
155                 tmax = tymax;
156         }
157
158         float tzmin = (param[sign[2]].z - ray.origin.z) * inv_dir.z;
159         float tzmax = (param[1 - sign[2]].z - ray.origin.z) * inv_dir.z;
160
161         if(tmin > tzmax || tzmin > tmax) {
162                 return false;
163         }
164         if(tzmin > tmin) {
165                 tmin = tzmin;
166         }
167         if(tzmax < tmax) {
168                 tmax = tzmax;
169         }
170
171         float t = tmin < 1e-4 ? tmax : tmin;
172         if(t >= 1e-4) {
173
174                 if(hit) {
175                         hit->obj = this;
176                         hit->dist = t;
177                         hit->pos = ray.origin + ray.dir * t;
178
179                         float min_dist = FLT_MAX;
180                         Vec3 offs = min + (max - min) / 2.0;
181                         Vec3 local_hit = hit->pos - offs;
182
183                         static const Vec3 axis[] = {
184                                 Vec3(1, 0, 0), Vec3(0, 1, 0), Vec3(0, 0, 1)
185                         };
186                         //int tcidx[][2] = {{2, 1}, {0, 2}, {0, 1}};
187
188                         for(int i=0; i<3; i++) {
189                                 float dist = fabs((max[i] - offs[i]) - fabs(local_hit[i]));
190                                 if(dist < min_dist) {
191                                         min_dist = dist;
192                                         hit->normal = axis[i] * (local_hit[i] < 0.0 ? 1.0 : -1.0);
193                                         //hit->texcoord = Vec2(hit->pos[tcidx[i][0]], hit->pos[tcidx[i][1]]);
194                                 }
195                         }
196                 }
197                 return true;
198         }
199         return false;
200
201 }
202
203 Plane::Plane()
204         : normal(0.0, 1.0, 0.0)
205 {
206 }
207
208 Plane::Plane(const Vec3 &p, const Vec3 &norm)
209         : pt(p)
210 {
211         normal = normalize(norm);
212 }
213
214 Plane::Plane(const Vec3 &p1, const Vec3 &p2, const Vec3 &p3)
215         : pt(p1)
216 {
217         normal = normalize(cross(p2 - p1, p3 - p1));
218 }
219
220 Plane::Plane(const Vec3 &normal, float dist)
221 {
222         this->normal = normalize(normal);
223         pt = this->normal * dist;
224 }
225
226 void Plane::set_union(const GeomObject *obj1, const GeomObject *obj2)
227 {
228         fprintf(stderr, "Plane::set_union undefined\n");
229 }
230
231 void Plane::set_intersection(const GeomObject *obj1, const GeomObject *obj2)
232 {
233         fprintf(stderr, "Plane::set_intersection undefined\n");
234 }
235
236 bool Plane::intersect(const Ray &ray, HitPoint *hit) const
237 {
238         float ndotdir = dot(normal, ray.dir);
239         if(fabs(ndotdir) < 1e-4) {
240                 return false;
241         }
242
243         if(hit) {
244                 Vec3 ptdir = pt - ray.origin;
245                 float t = dot(normal, ptdir) / ndotdir;
246
247                 hit->pos = ray.origin + ray.dir * t;
248                 hit->normal = normal;
249                 hit->obj = this;
250         }
251         return true;
252 }
253
254 float sphere_distance(const Vec3 &cent, float rad, const Vec3 &pt)
255 {
256         return length(pt - cent) - rad;
257 }
258
259 // TODO version which takes both radii into account
260 float capsule_distance(const Vec3 &a, float ra, const Vec3 &b, float rb, const Vec3 &pt)
261 {
262         Vec3 ab_dir = b - a;
263         float ab_len_sq = length_sq(ab_dir);
264
265         if(fabs(ab_len_sq) < 1e-5) {
266                 // if a == b, the capsule is a sphere with radius the maximum of the capsule radii
267                 return sphere_distance(a, std::max(ra, rb), pt);
268         }
269         float ab_len = sqrt(ab_len_sq);
270
271         Vec3 ap_dir = pt - a;
272
273         float t = dot(ap_dir, ab_dir / ab_len) / ab_len;
274         if(t < 0.0) {
275                 return sphere_distance(a, ra, pt);
276         }
277         if(t >= 1.0) {
278                 return sphere_distance(b, rb, pt);
279         }
280
281         Vec3 pproj = a + ab_dir * t;
282         return length(pproj - pt) - ra;
283 }
284
285 #if 0
286 float capsule_distance(const Vec3 &a, float ra, const Vec3 &b, float rb, const Vec3 &pt)
287 {
288         Vec3 ab_dir = b - a;
289
290         if(fabs(length_sq(ab_dir)) < 1e-5) {
291                 // if a == b, the capsule is a sphere with radius the maximum of the capsule radii
292                 return sphere_distance(a, std::max(ra, rb), pt);
293         }
294         float ab_len = length(ab_dir);
295
296         Vec3 ap_dir = pt - a;
297         Vec3 rotaxis = normalize(cross(ab_dir, ap_dir));
298
299         Mat4 rmat;
300         rmat.set_rotation(rotaxis, M_PI / 2.0);
301         Vec3 right = rmat * ab_dir / ab_len;
302
303         // XXX I think this check is redundant, always false, due to the cross product order
304         //assert(dot(right, ab_dir) >= 0.0);
305         if(dot(right, ab_dir) < 0.0) {
306                 right = -right;
307         }
308         Vec3 aa = a + right * ra;
309         Vec3 bb = b + right * rb;
310
311         // project pt to the line segment bb-aa, see if the projection lies within the interval [0, 1)
312         Vec3 aabb_dir = bb - aa;
313         float aabb_len = length(aabb_dir);
314         Vec3 aap_dir = pt - aa;
315
316         float t = dot(aap_dir, aabb_dir / aabb_len) / aabb_len;
317         if(t < 0.0) {
318                 return sphere_distance(a, ra, pt);
319         }
320         if(t >= 1.0) {
321                 return sphere_distance(b, rb, pt);
322         }
323
324         Vec3 ppt = aa + aabb_dir * t;
325         Vec3 norm = ppt - pt;
326         float dist = length(norm);
327
328         if(dot(norm, right) < 0.0) {
329                 // inside the cone
330                 dist = -dist;
331         }
332         return dist;
333 }
334 #endif