1 /// Note: this module is not completly tested!
2 /// Use with special care, results might be wrong.
3 
4 module gl3n.frustum;
5 
6 private {
7     import gl3n.linalg : vec3, mat4, dot;
8     import gl3n.math : abs, cradians;
9     import gl3n.aabb : AABB;
10     import gl3n.plane : Plane;
11 }
12 
13 enum {
14     OUTSIDE = 0, /// Used as flag to indicate if the object intersects with the frustum.
15     INSIDE, /// ditto
16     INTERSECT /// ditto
17 }
18 
19 ///
20 struct Frustum {
21     enum {
22         LEFT, /// Used to access the planes array.
23         RIGHT, /// ditto
24         BOTTOM, /// ditto
25         TOP, /// ditto
26         NEAR, /// ditto
27         FAR /// ditto
28     }
29 
30     Plane[6] planes; /// Holds all 6 planes of the frustum.
31 
32     @safe pure nothrow:
33 
34     @property ref Plane left() { return planes[LEFT]; }
35     @property ref Plane right() { return planes[RIGHT]; }
36     @property ref Plane bottom() { return planes[BOTTOM]; }
37     @property ref Plane top() { return planes[TOP]; }
38     @property ref Plane near() { return planes[NEAR]; }
39     @property ref Plane far() { return planes[FAR]; }
40 
41     /// Constructs the frustum from a model-view-projection matrix.
42     /// Params:
43     /// mvp = a model-view-projection matrix
44     this(mat4 mvp) {
45         mvp.transpose(); // we store the matrix row-major
46         
47         planes = [
48             // left
49             Plane(mvp[0][3] + mvp[0][0],
50                   mvp[1][3] + mvp[1][0],
51                   mvp[2][3] + mvp[2][0],
52                   mvp[3][3] + mvp[3][0]),
53 
54             // right
55             Plane(mvp[0][3] - mvp[0][0],
56                   mvp[1][3] - mvp[1][0],
57                   mvp[2][3] - mvp[2][0],
58                   mvp[3][3] - mvp[3][0]),
59 
60             // bottom
61             Plane(mvp[0][3] + mvp[0][1],
62                   mvp[1][3] + mvp[1][1],
63                   mvp[2][3] + mvp[2][1],
64                   mvp[3][3] + mvp[3][1]),
65             // top
66             Plane(mvp[0][3] - mvp[0][1],
67                   mvp[1][3] - mvp[1][1],
68                   mvp[2][3] - mvp[2][1],
69                   mvp[3][3] - mvp[3][1]),
70             // near
71             Plane(mvp[0][3] + mvp[0][2],
72                   mvp[1][3] + mvp[1][2],
73                   mvp[2][3] + mvp[2][2],
74                   mvp[3][3] + mvp[3][2]),
75             // far
76             Plane(mvp[0][3] - mvp[0][2],
77                   mvp[1][3] - mvp[1][2],
78                   mvp[2][3] - mvp[2][2],
79                   mvp[3][3] - mvp[3][2])
80         ];
81 
82         normalize();
83     }
84 
85     /// Constructs the frustum from 6 planes.
86     /// Params:
87     /// planes = the 6 frustum planes in the order: left, right, bottom, top, near, far.
88     this(Plane[6] planes) {
89         this.planes = planes;
90         normalize();
91     }
92 
93     private void normalize() {
94         foreach(ref e; planes) {
95             e.normalize();
96         }
97     }
98 
99     /// Checks if the $(I aabb) intersects with the frustum.
100     /// Returns OUTSIDE (= 0), INSIDE (= 1) or INTERSECT (= 2).
101     int intersects(AABB aabb) {
102         vec3 hextent = aabb.half_extent;
103         vec3 center = aabb.center;
104 
105         int result = INSIDE;
106         foreach(plane; planes) {
107             float d = dot(center, plane.normal);
108             float r = dot(hextent, abs(plane.normal));
109 
110             if(d + r < -plane.d) {
111                 // outside
112                 return OUTSIDE;
113             }
114             if(d - r < -plane.d) {
115                result = INTERSECT;
116             }
117         }
118 
119         return result;
120     }
121 
122     /// Returns true if the $(I aabb) intersects with the frustum or is inside it.
123     bool opBinaryRight(string s : "in")(AABB aabb) {
124         return intersects(aabb) > 0;
125     }
126 }