1 /** 2 * Type and dimension generic Axis-Aligned Bounding Box (AABB). 3 */ 4 module bettercmath.box; 5 6 @safe @nogc nothrow pure: 7 8 version (unittest) 9 { 10 alias Rectangle = BoundingBox!(float, 2, BoundingBoxOptions.storeSize); 11 } 12 13 /// Options for the BoundingBox template. 14 enum BoundingBoxOptions 15 { 16 /// Default options: store `end` corner information and derive `size`. 17 none = 0, 18 /// Store `size` information and derive `end` corner. 19 storeSize = 1, 20 } 21 22 /** 23 * Generic Axis-Aligned Bounding Box. 24 * 25 * May be stored as the starting and ending corners, 26 * or as starting point and size. 27 * 28 * Params: 29 * T = Element type 30 * N = Box dimension, must be positive 31 * options = Additional options, like storage meaning 32 */ 33 struct BoundingBox(T, uint Dim, BoundingBoxOptions options = BoundingBoxOptions.none) 34 if (Dim > 0) 35 { 36 import bettercmath.vector : Vector; 37 38 alias ElementType = T; 39 /// Bounding Box dimension. 40 enum dimension = Dim; 41 /// Point type, a Vector with the same type and dimension. 42 alias Point = Vector!(T, Dim); 43 private alias PointArg = T[Dim]; 44 /// Size type, a Vector with the same type and dimension. 45 alias Size = Vector!(T, Dim); 46 private alias SizeArg = T[Dim]; 47 48 private enum storeSize = options & BoundingBoxOptions.storeSize; 49 50 /// Starting BoundingBox corner. 51 Point origin = 0; 52 53 static if (storeSize) 54 { 55 /// Size of a BoundingBox, may be negative. 56 Size size = 1; 57 58 /// Get the `end` corner of a BoundingBox. 59 @property Point end() const 60 { 61 return origin + size; 62 } 63 /// Set the `end` corner of a BoundingBox. 64 @property void end(const PointArg value) 65 { 66 size = value - origin; 67 } 68 69 /// Cast BoundingBox to another storage type. 70 auto opCast(U : BoundingBox!(T, Dim, options ^ BoundingBoxOptions.storeSize))() const 71 { 72 typeof(return) box = { 73 origin = this.origin, 74 end = this.end, 75 }; 76 return box; 77 } 78 } 79 else 80 { 81 /// Ending BoundingBox corner. 82 Point end = 1; 83 84 /// Get the size of a BoundingBox, may be negative. 85 @property Size size() const 86 { 87 return end - origin; 88 } 89 /// Set the size of a BoundingBox, using `origin` as the pivot. 90 @property void size(const SizeArg value) 91 { 92 end = origin + value; 93 } 94 95 /// Cast BoundingBox to another storage type. 96 auto opCast(U : BoundingBox!(T, Dim, options ^ BoundingBoxOptions.storeSize))() const 97 { 98 typeof(return) box = { 99 origin = this.origin, 100 size = this.size, 101 }; 102 return box; 103 } 104 } 105 106 /// Get the width of a BoundingBox, may be negative. 107 @property T width() const 108 { 109 return size.width; 110 } 111 /// Set the width of a BoundingBox, using `origin` as the pivot. 112 @property void width(const T value) 113 { 114 auto s = size; 115 s.width = value; 116 size = s; 117 } 118 119 static if (Dim >= 2) 120 { 121 /// Get the height of a BoundingBox, may be negative. 122 @property T height() const 123 { 124 return size.height; 125 } 126 /// Set the height of a BoundingBox, using `origin` as the pivot. 127 @property void height(const T value) 128 { 129 auto s = size; 130 s.height = value; 131 size = s; 132 } 133 } 134 static if (Dim >= 3) 135 { 136 /// Get the depth of a BoundingBox, may be negative. 137 @property T depth() const 138 { 139 return size.depth; 140 } 141 /// Set the depth of a BoundingBox, using `origin` as the pivot. 142 @property void depth(const T value) 143 { 144 auto s = size; 145 s.depth = value; 146 size = s; 147 } 148 } 149 150 /// Get the central point of BoundingBox. 151 @property Point center() const 152 { 153 return (origin + end) / 2; 154 } 155 /// Set the central point of BoundingBox. 156 @property void center(const PointArg value) 157 { 158 immutable delta = value - center; 159 origin += delta; 160 static if (!storeSize) 161 { 162 end += delta; 163 } 164 } 165 /// Ditto 166 @property void center(const T value) 167 { 168 center(Point(value)); 169 } 170 /// 171 unittest 172 { 173 Rectangle rect; 174 rect.center = 2; 175 assert(rect.size == Rectangle.init.size); 176 rect.center = [1, 2]; 177 assert(rect.size == Rectangle.init.size); 178 } 179 180 /// Returns whether BoundingBox have any non-positive size values. 181 @property bool empty() const 182 { 183 import std.algorithm : any; 184 return size[].any!"a <= 0"; 185 } 186 187 /// Returns a copy of BoundingBox with sorted corners, so that `size` only presents non-negative values. 188 BoundingBox abs() const 189 { 190 import std.algorithm : swap; 191 typeof(return) result = this; 192 foreach (i; 0 .. result.dimension) 193 { 194 if (result.origin[i] < result.end[i]) 195 { 196 swap(result.origin[i], result.end[i]); 197 } 198 } 199 return result; 200 } 201 202 /// Get the volume of the BoundingBox. 203 @property T volume() const 204 { 205 import std.algorithm : fold; 206 return size.fold!"a * b"; 207 } 208 209 static if (Dim == 2) 210 { 211 /// 2D area is the same as generic box volume. 212 alias area = volume; 213 } 214 215 static if (Dim == 3) 216 { 217 /// Get the surface area of a 3D BoundingBox. 218 @property T surfaceArea() const 219 { 220 auto s = size; 221 return 2 * (s.x * s.y + s.y * s.z + s.x * s.z); 222 } 223 } 224 225 /// Returns a new BoundingBox by insetting this one by `delta`. 226 BoundingBox inset(const SizeArg delta) 227 { 228 immutable halfDelta = Size(delta) / 2; 229 typeof(return) box; 230 box.origin = this.origin + halfDelta; 231 box.size = this.size - halfDelta; 232 return box; 233 } 234 /// Ditto 235 BoundingBox inset(const T delta) 236 { 237 return inset(Size(delta)); 238 } 239 240 /// Returns true if Point is contained within BoundingBox. 241 bool contains(T, uint N)(const auto ref T[N] point) const 242 { 243 import std.algorithm : all, map, min; 244 import std.range : iota; 245 enum minDimension = min(this.dimension, N); 246 return iota(minDimension).map!(i => point[i] >= origin[i] && point[i] <= end[i]).all; 247 } 248 249 /// Returns true if `box` is completely contained within `this` BoundingBox. 250 bool contains(Args...)(const auto ref BoundingBox!(T, Args) box) const 251 { 252 return contains(box.origin) && contains(box.end); 253 } 254 255 /// Returns the intersection between two BoundingBoxes. 256 auto intersection(Args...)(const auto ref BoundingBox!(T, Args) box) const 257 { 258 import std.algorithm : map, min, max; 259 import std.range : iota, zip; 260 enum minDimension = min(this.dimension, box.dimension); 261 BoundingBox!(T, minDimension, options) result; 262 result.origin = zip(this.origin[0 .. minDimension], box.origin[0 .. minDimension]).map!(max); 263 result.end = zip(this.end[0 .. minDimension], box.end[0 .. minDimension]).map!(min); 264 return result; 265 } 266 267 /// Returns true if `box` intersects `this`. 268 auto intersects(Args...)(const auto ref BoundingBox!(T, Args) box) const 269 { 270 return !intersection(box).empty; 271 } 272 } 273 274 /// Common alias for Bounding Boxes. 275 alias AABB = BoundingBox;