1 2 /** 3 inmath.frustum 4 5 Note: this module is not completly tested! 6 Use with special care, results might be wrong. 7 8 Authors: David Herberth, Inochi2D Project 9 License: MIT 10 */ 11 module inmath.frustum; 12 13 private { 14 import inmath.linalg : vec3, mat4, dot; 15 import inmath.math : abs, cradians; 16 import inmath.aabb : AABB; 17 import inmath.plane : Plane; 18 } 19 20 enum { 21 OUTSIDE = 0, /// Used as flag to indicate if the object intersects with the frustum. 22 INSIDE, /// ditto 23 INTERSECT /// ditto 24 } 25 26 /// 27 struct Frustum { 28 enum { 29 LEFT, /// Used to access the planes array. 30 RIGHT, /// ditto 31 BOTTOM, /// ditto 32 TOP, /// ditto 33 NEAR, /// ditto 34 FAR /// ditto 35 } 36 37 Plane[6] planes; /// Holds all 6 planes of the frustum. 38 39 @safe pure nothrow: 40 41 @property ref inout(Plane) left() inout return { return planes[LEFT]; } 42 @property ref inout(Plane) right() inout return { return planes[RIGHT]; } 43 @property ref inout(Plane) bottom() inout return { return planes[BOTTOM]; } 44 @property ref inout(Plane) top() inout return { return planes[TOP]; } 45 @property ref inout(Plane) near() inout return { return planes[NEAR]; } 46 @property ref inout(Plane) far() inout return { return planes[FAR]; } 47 48 /// Constructs the frustum from a model-view-projection matrix. 49 /// Params: 50 /// mvp = a model-view-projection matrix 51 this(mat4 mvp) { 52 mvp.transpose(); // we store the matrix row-major 53 54 planes = [ 55 // left 56 Plane(mvp[0][3] + mvp[0][0], 57 mvp[1][3] + mvp[1][0], 58 mvp[2][3] + mvp[2][0], 59 mvp[3][3] + mvp[3][0]), 60 61 // right 62 Plane(mvp[0][3] - mvp[0][0], 63 mvp[1][3] - mvp[1][0], 64 mvp[2][3] - mvp[2][0], 65 mvp[3][3] - mvp[3][0]), 66 67 // bottom 68 Plane(mvp[0][3] + mvp[0][1], 69 mvp[1][3] + mvp[1][1], 70 mvp[2][3] + mvp[2][1], 71 mvp[3][3] + mvp[3][1]), 72 // top 73 Plane(mvp[0][3] - mvp[0][1], 74 mvp[1][3] - mvp[1][1], 75 mvp[2][3] - mvp[2][1], 76 mvp[3][3] - mvp[3][1]), 77 // near 78 Plane(mvp[0][3] + mvp[0][2], 79 mvp[1][3] + mvp[1][2], 80 mvp[2][3] + mvp[2][2], 81 mvp[3][3] + mvp[3][2]), 82 // far 83 Plane(mvp[0][3] - mvp[0][2], 84 mvp[1][3] - mvp[1][2], 85 mvp[2][3] - mvp[2][2], 86 mvp[3][3] - mvp[3][2]) 87 ]; 88 89 normalize(); 90 } 91 92 /// Constructs the frustum from 6 planes. 93 /// Params: 94 /// planes = the 6 frustum planes in the order: left, right, bottom, top, near, far. 95 this(Plane[6] planes) { 96 this.planes = planes; 97 normalize(); 98 } 99 100 private void normalize() { 101 foreach(ref e; planes) { 102 e.normalize(); 103 } 104 } 105 106 /// Checks if the $(I aabb) intersects with the frustum. 107 /// Returns OUTSIDE (= 0), INSIDE (= 1) or INTERSECT (= 2). 108 int intersects(AABB aabb) const { 109 vec3 hextent = aabb.halfExtent; 110 vec3 center = aabb.center; 111 112 int result = INSIDE; 113 foreach(plane; planes) { 114 float d = dot(center, plane.normal); 115 float r = dot(hextent, abs(plane.normal)); 116 117 if(d + r < -plane.d) { 118 // outside 119 return OUTSIDE; 120 } 121 if(d - r < -plane.d) { 122 result = INTERSECT; 123 } 124 } 125 126 return result; 127 } 128 129 @("frustrum") 130 unittest { 131 mat4 view = mat4.lookAt(vec3(0), vec3(0, 0, 1), vec3(0, 1, 0)); 132 enum aspect = 4.0/3.0; 133 enum fov = 60; 134 enum near = 1; 135 enum far = 100; 136 mat4 proj = mat4.perspective(aspect, 1.0, fov, near, far); 137 auto f = Frustum(proj * view); 138 assert(f.intersects(AABB(vec3(0, 0, 1), vec3(0, 0, 1))) == INSIDE); 139 assert(f.intersects(AABB(vec3(-1), vec3(1))) == INTERSECT); 140 assert(f.intersects(AABB(vec3(-1), vec3(0.99))) == OUTSIDE); 141 assert(f.intersects(AABB(vec3(-1000), vec3(1000))) == INTERSECT); 142 assert(f.intersects(AABB(vec3(0, 0, -1000), vec3(1, 1, 1000))) == INTERSECT); 143 assert(f.intersects(AABB(vec3(-1000, 0, 0), vec3(1000, 0.1, 0.1))) == OUTSIDE); 144 for(int i = near; i < far; i += 10) { 145 assert(f.intersects(AABB(vec3(0, 0, i), vec3(0.1, 0.1, i + 1))) == INSIDE); 146 assert(f.intersects(AABB(vec3(0, 0, -i), vec3(0.1, 0.1, -(i + 1)))) == OUTSIDE); 147 } 148 import std.math : tan; 149 float c = aspect * far / tan(cradians!fov); 150 assert(f.intersects(AABB(vec3(c, 0, 99), vec3(c + 1, 1, 101))) == INTERSECT); 151 assert(f.intersects(AABB(vec3(c - 4, 0, 98), vec3(c - 2, 1, 99.99))) == INSIDE); 152 assert(f.intersects(AABB(vec3(c, 0, 100), vec3(c + 1, 0, 101))) == OUTSIDE); 153 154 proj = mat4.orthographic(-aspect, aspect, -1.0, 1.0, 0, far); 155 f = Frustum(proj * view); 156 assert(f.intersects(AABB(vec3(0, 0, 1), vec3(0, 0, 1))) == INSIDE); 157 assert(f.intersects(AABB(vec3(-1), vec3(1))) == INTERSECT); 158 assert(f.intersects(AABB(vec3(-1), vec3(0.01))) == INTERSECT); 159 assert(f.intersects(AABB(vec3(0, 0, far - 5), vec3(1, 1, far))) == INSIDE); 160 assert(f.intersects(AABB(vec3(0, 0, far - 5), vec3(1, 1, far + 5))) == INTERSECT); 161 assert(f.intersects(AABB(vec3(-1000, 0, -0.01), vec3(1000, 1, 0))) == INTERSECT); 162 assert(f.intersects(AABB(vec3(-1000, 0, -0.02), vec3(1000, 1, -0.01))) == OUTSIDE); 163 } 164 165 /// Returns true if the $(I aabb) intersects with the frustum or is inside it. 166 bool opBinaryRight(string s : "in")(AABB aabb) const { 167 return intersects(aabb) > 0; 168 } 169 }