1 // frustum.cpp
2 //
3 // Copyright (C) 2000, Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 
10 #include "frustum.h"
11 
12 
Frustum(float fov,float aspectRatio,float n)13 Frustum::Frustum(float fov, float aspectRatio, float n) :
14     infinite(true)
15 {
16     init(fov, aspectRatio, n, n);
17 }
18 
19 
Frustum(float fov,float aspectRatio,float n,float f)20 Frustum::Frustum(float fov, float aspectRatio, float n, float f) :
21     infinite(false)
22 {
23     init(fov, aspectRatio, n, f);
24 }
25 
26 
init(float fov,float aspectRatio,float n,float f)27 void Frustum::init(float fov, float aspectRatio, float n, float f)
28 {
29     float h = (float) tan(fov / 2.0f);
30     float w = h * aspectRatio;
31 
32     Vec3f normals[4];
33     normals[Bottom] = Vec3f(0, 1, -h);
34     normals[Top]    = Vec3f(0, -1, -h);
35     normals[Left]   = Vec3f(1, 0, -w);
36     normals[Right]  = Vec3f(-1, 0, -w);
37     for (int i = 0; i < 4; i++)
38     {
39         normals[i].normalize();
40         planes[i] = Planef(normals[i], Point3f(0, 0, 0));
41     }
42 
43     planes[Near] = Planef(Vec3f(0, 0, -1), -n);
44     planes[Far] = Planef(Vec3f(0, 0, 1), f);
45 }
46 
47 
test(const Point3f & p) const48 Frustum::Aspect Frustum::test(const Point3f& p) const
49 {
50     return testSphere(p, 0);
51 }
52 
53 
testSphere(const Point3f & center,float radius) const54 Frustum::Aspect Frustum::testSphere(const Point3f& center, float radius) const
55 {
56     int nPlanes = infinite ? 5 : 6;
57     int intersections = 0;
58 
59     for (int i = 0; i < nPlanes; i++)
60     {
61         float distanceToPlane = planes[i].distanceTo(center);
62         if (distanceToPlane < -radius)
63             return Outside;
64         else if (distanceToPlane <= radius)
65             intersections |= (1 << i);
66     }
67 
68     return (intersections == 0) ? Inside : Intersect;
69 }
70 
71 
testSphere(const Point3d & center,double radius) const72 Frustum::Aspect Frustum::testSphere(const Point3d& center, double radius) const
73 {
74     int nPlanes = infinite ? 5 : 6;
75     int intersections = 0;
76 
77     for (int i = 0; i < nPlanes; i++)
78     {
79         //TODO: Celestia should incorporate some casting operators overloading to accommodate all this kind of stuff:
80         Vec3f plNormal       = planes[i].normal;
81         Vec3d plNormalDbl(plNormal.x, plNormal.y, plNormal.z);
82 
83         double distanceToPlane = plNormalDbl.x * center.x + plNormalDbl.y * center.y + plNormalDbl.z * center.z + planes[i].d;
84         if (distanceToPlane < -radius)
85             return Outside;
86         else if (distanceToPlane <= radius)
87             intersections |= (1 << i);
88     }
89 
90     return (intersections == 0) ? Inside : Intersect;
91 }
92 
93 
testCapsule(const Capsulef & capsule) const94 Frustum::Aspect Frustum::testCapsule(const Capsulef& capsule) const
95 {
96     int nPlanes = infinite ? 5 : 6;
97     int intersections = 0;
98     float r2 = capsule.radius * capsule.radius;
99 
100     for (int i = 0; i < nPlanes; i++)
101     {
102         float signedDist0 = planes[i].normal * Vec3f(capsule.origin.x, capsule.origin.y, capsule.origin.z) + planes[i].d;
103         float signedDist1 = signedDist0 + planes[i].normal * capsule.axis;
104         if (signedDist0 * signedDist1 > r2)
105         {
106             // Endpoints of capsule are on same side of plane; test closest endpoint to see if it
107             // lies closer to the plane than radius
108             if (abs(signedDist0) <= abs(signedDist1))
109             {
110                 if (signedDist0 < -capsule.radius)
111                     return Outside;
112                 else if (signedDist0 < capsule.radius)
113                     intersections |= (1 << i);
114             }
115             else
116             {
117                 if (signedDist1 < -capsule.radius)
118                     return Outside;
119                 else if (signedDist1 < capsule.radius)
120                     intersections |= (1 << i);
121             }
122         }
123         else
124         {
125             // Capsule endpoints are on different sides of the plane, so we have an intersection
126             intersections |= (1 << i);
127         }
128 #if 0
129         Vec3f plNormal       = planes[i].normal;
130         Vec3d plNormalDbl(plNormal.x, plNormal.y, plNormal.z);
131 
132         double distanceToPlane = planes[i].distanceToSegment(capsule.origin, capsule.axis);
133         if (distanceToPlane < -capsule.radius)
134             return Outside;
135         else if (distanceToPlane <= capsule.radius)
136             intersections |= (1 << i);
137 #endif
138     }
139 
140     return (intersections == 0) ? Inside : Intersect;
141 }
142 
143 
144 #if 0
145 // See if the half space defined by the plane intersects the frustum.  For the
146 // plane equation p(x, y, z) = ax + by + cz + d = 0, the halfspace is
147 // contains all points p(x, y, z) < 0.
148 Frustum::Aspect Frustum::testHalfSpace(const Planef& plane)
149 {
150     return Intersect;
151 }
152 #endif
153 
154 
transform(const Mat3f & m)155 void Frustum::transform(const Mat3f& m)
156 {
157     int nPlanes = infinite ? 5 : 6;
158     Mat3f invTranspose = m.inverse().transpose();
159 
160     for (int i = 0; i < nPlanes; i++)
161     {
162         planes[i] = planes[i] * invTranspose;
163         float s = 1.0f / planes[i].normal.length();
164         planes[i].normal = planes[i].normal * s;
165         planes[i].d *= s;
166     }
167 }
168 
169 
transform(const Mat4f & m)170 void Frustum::transform(const Mat4f& m)
171 {
172     int nPlanes = infinite ? 5 : 6;
173     Mat4f invTranspose = m.inverse().transpose();
174 
175     for (int i = 0; i < nPlanes; i++)
176     {
177         planes[i] = planes[i] * invTranspose;
178         float s = 1.0f / planes[i].normal.length();
179         planes[i].normal = planes[i].normal * s;
180         planes[i].d *= s;
181     }
182 }
183