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 inout(Plane) left() inout return { return planes[LEFT]; } 35 @property ref inout(Plane) right() inout return { return planes[RIGHT]; } 36 @property ref inout(Plane) bottom() inout return { return planes[BOTTOM]; } 37 @property ref inout(Plane) top() inout return { return planes[TOP]; } 38 @property ref inout(Plane) near() inout return { return planes[NEAR]; } 39 @property ref inout(Plane) far() inout return { 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) const { 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 unittest { 123 mat4 view = mat4.look_at(vec3(0), vec3(0, 0, 1), vec3(0, 1, 0)); 124 enum aspect = 4.0/3.0; 125 enum fov = 60; 126 enum near = 1; 127 enum far = 100; 128 mat4 proj = mat4.perspective(aspect, 1.0, fov, near, far); 129 auto f = Frustum(proj * view); 130 assert(f.intersects(AABB(vec3(0, 0, 1), vec3(0, 0, 1))) == INSIDE); 131 assert(f.intersects(AABB(vec3(-1), vec3(1))) == INTERSECT); 132 assert(f.intersects(AABB(vec3(-1), vec3(0.99))) == OUTSIDE); 133 assert(f.intersects(AABB(vec3(-1000), vec3(1000))) == INTERSECT); 134 assert(f.intersects(AABB(vec3(0, 0, -1000), vec3(1, 1, 1000))) == INTERSECT); 135 assert(f.intersects(AABB(vec3(-1000, 0, 0), vec3(1000, 0.1, 0.1))) == OUTSIDE); 136 for(int i = near; i < far; i += 10) { 137 assert(f.intersects(AABB(vec3(0, 0, i), vec3(0.1, 0.1, i + 1))) == INSIDE); 138 assert(f.intersects(AABB(vec3(0, 0, -i), vec3(0.1, 0.1, -(i + 1)))) == OUTSIDE); 139 } 140 import std.math : tan; 141 float c = aspect * far / tan(cradians!fov); 142 assert(f.intersects(AABB(vec3(c, 0, 99), vec3(c + 1, 1, 101))) == INTERSECT); 143 assert(f.intersects(AABB(vec3(c - 4, 0, 98), vec3(c - 2, 1, 99.99))) == INSIDE); 144 assert(f.intersects(AABB(vec3(c, 0, 100), vec3(c + 1, 0, 101))) == OUTSIDE); 145 146 proj = mat4.orthographic(-aspect, aspect, -1.0, 1.0, 0, far); 147 f = Frustum(proj * view); 148 assert(f.intersects(AABB(vec3(0, 0, 1), vec3(0, 0, 1))) == INSIDE); 149 assert(f.intersects(AABB(vec3(-1), vec3(1))) == INTERSECT); 150 assert(f.intersects(AABB(vec3(-1), vec3(0.01))) == INTERSECT); 151 assert(f.intersects(AABB(vec3(0, 0, far - 5), vec3(1, 1, far))) == INSIDE); 152 assert(f.intersects(AABB(vec3(0, 0, far - 5), vec3(1, 1, far + 5))) == INTERSECT); 153 assert(f.intersects(AABB(vec3(-1000, 0, -0.01), vec3(1000, 1, 0))) == INTERSECT); 154 assert(f.intersects(AABB(vec3(-1000, 0, -0.02), vec3(1000, 1, -0.01))) == OUTSIDE); 155 } 156 157 /// Returns true if the $(I aabb) intersects with the frustum or is inside it. 158 bool opBinaryRight(string s : "in")(AABB aabb) const { 159 return intersects(aabb) > 0; 160 } 161 }