1 /++ 2 + Authors: Avaxar <avaxar@nekkl.org> 3 + Copyright: Copyright © 2023, Avaxar 4 + License: $(LINK2 https://mit-license.org, MIT License) 5 +/ 6 7 module dsdl2.rect; 8 @safe: 9 10 import bindbc.sdl; 11 import dsdl2.sdl; 12 import dsdl2.frect; 13 14 import std.conv : to; 15 import std.format : format; 16 import std.typecons : Nullable, nullable; 17 18 /++ 19 + D struct that wraps `SDL_Point` containing 2D integer coordinate pair 20 + 21 + `dsdl2.Point` stores signed `int`eger `x` and `y` coordinate points. This wrapper also implements vector-like 22 + operator overloading. 23 +/ 24 struct Point { 25 SDL_Point sdlPoint; /// Internal `SDL_Point` struct 26 27 this() @disable; 28 29 /++ 30 + Constructs a `dsdl2.Point` from a vanilla `SDL_Point` from bindbc-sdl 31 + 32 + Params: 33 + sdlPoint = the `dsdl2.Point` struct 34 +/ 35 this(SDL_Point sdlPoint) { 36 this.sdlPoint = sdlPoint; 37 } 38 39 /++ 40 + Constructs a `dsdl2.Point` by feeding in an `x` and `y` pair 41 + 42 + Params: 43 + x = x coordinate point 44 + y = y coordinate point 45 +/ 46 this(int x, int y) { 47 this.sdlPoint.x = x; 48 this.sdlPoint.y = y; 49 } 50 51 /++ 52 + Constructs a `dsdl2.Point` by feeding in an array of `x` and `y` 53 + 54 + Params: 55 + xy = x and y coordinate point array 56 +/ 57 this(int[2] xy) { 58 this.sdlPoint.x = xy[0]; 59 this.sdlPoint.y = xy[1]; 60 } 61 62 static if (sdlSupport >= SDLSupport.v2_0_10) { 63 /++ 64 + Constructs a `dsdl2.Point` from a `dsdl2.FPoint` (from SDL 2.0.10) 65 + 66 + Params: 67 + fpoint = `dsdl2.FPoint` whose attributes are to be copied 68 +/ 69 this(FPoint fpoint) { 70 this.sdlPoint.x = fpoint.x.to!int; 71 this.sdlPoint.y = fpoint.y.to!int; 72 } 73 } 74 75 /++ 76 + Unary element-wise operation overload template 77 +/ 78 Point opUnary(string op)() const { 79 return Point(mixin(op ~ "this.x"), mixin(op ~ "this.y")); 80 } 81 82 /++ 83 + Binary element-wise operation overload template 84 +/ 85 Point opBinary(string op)(const Point other) const { 86 return Point(mixin("this.x" ~ op ~ "other.x"), mixin("this.y" ~ op ~ "other.y")); 87 } 88 89 /++ 90 + Binary operation overload template with scalars 91 +/ 92 Point opBinary(string op)(int scalar) const if (op == "*" || op == "/") { 93 return Point(mixin("this.x" ~ op ~ "scalar"), mixin("this.y" ~ op ~ "scalar")); 94 } 95 96 /++ 97 + Element-wise operator assignment overload 98 +/ 99 ref inout(Point) opOpAssign(string op)(const Point other) return inout { 100 mixin("this.x" ~ op ~ "=other.x"); 101 mixin("this.y" ~ op ~ "=other.y"); 102 return this; 103 } 104 105 /++ 106 + Operator assignment overload with scalars 107 +/ 108 ref inout(Point) opOpAssign(string op)(const int scalar) return inout 109 if (op == "*" || op == "/") { 110 mixin("this.x" ~ op ~ "=scalar"); 111 mixin("this.y" ~ op ~ "=scalar"); 112 return this; 113 } 114 115 /++ 116 + Formats the `dsdl2.Point` into its construction representation: `"dsdl2.Point(<x>, <y>)"` 117 + 118 + Returns: the formatted `string` 119 +/ 120 string toString() const { 121 return "dsdl2.Point(%d, %d)".format(this.x, this.y); 122 } 123 124 /++ 125 + Proxy to the X value of the `dsdl2.Point` 126 + 127 + Returns: X value of the `dsdl2.Point` 128 +/ 129 ref inout(int) x() return inout @property { 130 return this.sdlPoint.x; 131 } 132 133 /++ 134 + Proxy to the Y value of the `dsdl2.Point` 135 + 136 + Returns: Y value of the `dsdl2.Point` 137 +/ 138 ref inout(int) y() return inout @property { 139 return this.sdlPoint.y; 140 } 141 142 /++ 143 + Static array proxy of the `dsdl2.Point` 144 + 145 + Returns: array of `x` and `y` 146 +/ 147 ref inout(int[2]) array() return inout @property @trusted { 148 return *cast(inout(int[2]*))&this.sdlPoint; 149 } 150 } 151 /// 152 unittest { 153 auto a = dsdl2.Point(1, 2); 154 auto b = a + a; 155 assert(b == dsdl2.Point(2, 4)); 156 157 auto c = a * 2; 158 assert(b == c); 159 } 160 161 /++ 162 + D struct that wraps `SDL_Rect` representing a rectangle of integer 2D coordinate and dimension 163 + 164 + `dsdl2.Rect` stores signed `int`eger `x` and `y` coordinate points, as well as `w`idth and `h`eight which 165 + specifies the rectangle's dimension. `x` and `y` symbolize the top-left coordinate of the rectangle, and 166 + the `w`idth and `h`eight extend to the positive plane of both axes. 167 +/ 168 struct Rect { 169 SDL_Rect sdlRect; /// Internal `SDL_Rect` struct 170 171 this() @disable; 172 173 /++ 174 + Constructs a `dsdl2.Rect` from a vanilla `SDL_Rect` from bindbc-sdl 175 + 176 + Params: 177 + sdlRect = the `SDL_Rect` struct 178 +/ 179 this(SDL_Rect sdlRect) { 180 this.sdlRect = sdlRect; 181 } 182 183 /++ 184 + Constructs a `dsdl2.Rect` by feeding in the `x`, `y`, `width`, and `height` of the rectangle 185 + 186 + Params: 187 + x = top-left x coordinate point of the rectangle 188 + y = top-left y coordinate point of the rectangle 189 + width = rectangle width 190 + height = rectangle height 191 +/ 192 this(int x, int y, int width, int height) { 193 this.sdlRect.x = x; 194 this.sdlRect.y = y; 195 this.sdlRect.w = width; 196 this.sdlRect.h = height; 197 } 198 199 /++ 200 + Constructs a `dsdl2.Rect` by feeding in a `dsdl2.Point` as the `x` and `y`, then `width` and `height` of the 201 + rectangle 202 + 203 + Params: 204 + point = top-left point of the rectangle 205 + width = rectangle width 206 + height = rectangle height 207 +/ 208 this(Point point, int width, int height) { 209 this.sdlRect.x = point.x; 210 this.sdlRect.y = point.y; 211 this.sdlRect.w = width; 212 this.sdlRect.h = height; 213 } 214 215 static if (sdlSupport >= SDLSupport.v2_0_10) { 216 /++ 217 + Constructs a `dsdl2.Rect` from a `dsdl2.FRect` (from SDL 2.0.10) 218 + 219 + Params: 220 + frect = `dsdl2.FRect` whose attributes are to be copied 221 +/ 222 this(FRect frect) { 223 this.sdlRect.x = frect.x.to!int; 224 this.sdlRect.y = frect.y.to!int; 225 this.sdlRect.w = frect.width.to!int; 226 this.sdlRect.h = frect.height.to!int; 227 } 228 } 229 230 /++ 231 + Binary operation overload template to move rectangle's position by an `offset` as a `dsdl2.Point` 232 +/ 233 Rect opBinary(string op)(const Point offset) const if (op == "+" || op == "-") { 234 return Rect(Point(mixin("this.x" ~ op ~ "offset.x"), mixin("this.y" ~ op ~ "offset.y")), 235 this.w, this.h); 236 } 237 238 /++ 239 + Operator assignment overload template to move rectangle's position in-place by an `offset` as a `dsdl2.Point` 240 +/ 241 ref inout(Point) opOpAssign(string op)(const Point offset) return inout 242 if (op == "+" || op == "-") { 243 mixin("this.x" ~ op ~ "=offset.x"); 244 mixin("this.y" ~ op ~ "=offset.y"); 245 return this; 246 } 247 248 /++ 249 + Formats the `dsdl2.Rect` into its construction representation: `"dsdl2.Rect(<x>, <y>, <w>, <h>)"` 250 + 251 + Returns: the formatted `string` 252 +/ 253 string toString() const { 254 return "dsdl2.Rect(%d, %d, %d, %d)".format(this.x, this.y, this.width, this.height); 255 } 256 257 /++ 258 + Proxy to the X value of the `dsdl2.Rect` 259 + 260 + Returns: X value of the `dsdl2.Rect` 261 +/ 262 ref inout(int) x() return inout @property { 263 return this.sdlRect.x; 264 } 265 266 /++ 267 + Proxy to the Y value of the `dsdl2.Rect` 268 + 269 + Returns: Y value of the `dsdl2.Rect` 270 +/ 271 ref inout(int) y() return inout @property { 272 return this.sdlRect.y; 273 } 274 275 /++ 276 + Proxy to the `dsdl2.Point` containing the `x` and `y` value of the `dsdl2.Rect` 277 + 278 + Returns: reference to the `dsdl2.Point` structure 279 +/ 280 ref inout(Point) point() return inout @property @trusted { 281 return *cast(inout(Point*))&this.sdlRect.x; 282 } 283 284 /++ 285 + Proxy to the width of the `dsdl2.Rect` 286 + 287 + Returns: width of the `dsdl2.Rect` 288 +/ 289 ref inout(int) width() return inout @property { 290 return this.sdlRect.w; 291 } 292 293 /++ 294 + Proxy to the height of the `dsdl2.Rect` 295 + 296 + Returns: height of the `dsdl2.Rect` 297 +/ 298 ref inout(int) height() return inout @property { 299 return this.sdlRect.h; 300 } 301 302 /++ 303 + Proxy to the size array containing the `width` and `height` of the `dsdl2.Rect` 304 + 305 + Returns: reference to the static `int[2]` array 306 +/ 307 ref inout(int[2]) size() return inout @property @trusted { 308 return *cast(inout(int[2]*))&this.sdlRect.w; 309 } 310 311 /++ 312 + Wraps `SDL_RectEmpty` which checks if the `dsdl2.Rect` is an empty rectangle 313 + 314 + Returns: `true` if it is empty, otherwise `false` 315 +/ 316 bool empty() const @trusted { 317 return SDL_RectEmpty(&this.sdlRect); 318 } 319 320 /++ 321 + Wraps `SDL_PointInRect` which sees whether the coordinate of a `dsdl2.Point` is inside the `dsdl2.Rect` 322 + 323 + Params: 324 + point = the `dsdl2.Point` to check its collision of with the `dsdl2.Rect` instance 325 + Returns: `true` if it is within, otherwise `false` 326 +/ 327 bool pointInRect(Point point) const @trusted { 328 return SDL_PointInRect(&point.sdlPoint, &this.sdlRect); 329 } 330 331 /++ 332 + Wraps `SDL_HasIntersection` which sees whether two `dsdl2.Rect`s intersect each other 333 + 334 + Params: 335 + other = other `dsdl2.Rect` to check its intersection of with the `dsdl2.Rect` 336 + Returns: `true` if both have intersection with each other, otherwise `false` 337 +/ 338 bool hasIntersection(Rect other) const @trusted { 339 return SDL_HasIntersection(&this.sdlRect, &other.sdlRect) == SDL_TRUE; 340 } 341 342 /++ 343 + Wraps `SDL_IntersectRectAndLine` which sees whether a line intersects with the `dsdl2.Rect` 344 + 345 + Params: 346 + line = set of two `dsdl2.Point`s denoting the start and end coordinates of the line to check its intersection 347 + of with the `dsdl2.Rect` 348 + Returns: `true` if it intersects, otherwise `false` 349 +/ 350 bool hasLineIntersection(Point[2] line) const @trusted { 351 return SDL_IntersectRectAndLine(&this.sdlRect, &line[0].sdlPoint.x, &line[0].sdlPoint.y, 352 &line[1].sdlPoint.x, &line[1].sdlPoint.y) == SDL_TRUE; 353 } 354 355 /++ 356 + Wraps `SDL_IntersectRect` which attempts to get the rectangle of intersection between two `dsdl2.Rect`s 357 + 358 + Params: 359 + other = other `dsdl2.Rect` with which the `dsdl2.Rect` is intersected 360 + Returns: non-null `Nullable!Rect` instance if intersection is present, otherwise a null one 361 +/ 362 Nullable!Rect intersectRect(Rect other) const @trusted { 363 Rect intersection = void; 364 if (SDL_IntersectRect(&this.sdlRect, &other.sdlRect, &intersection.sdlRect) == SDL_TRUE) { 365 return intersection.nullable; 366 } 367 else { 368 return Nullable!Rect.init; 369 } 370 } 371 372 /++ 373 + Wraps `SDL_IntersectRectAndLine` which attempts to clip a line segment in the boundaries of the `dsdl2.Rect` 374 + 375 + Params: 376 + line = set of two `dsdl2.Point`s denoting the start and end coordinates of the line to clip from 377 + its intersection with the `dsdl2.Rect` 378 + Returns: non-null `Nullable!(Point[2])` as the clipped line if there is an intersection, otherwise a null one 379 +/ 380 Nullable!(Point[2]) intersectLine(Point[2] line) const @trusted { 381 if (SDL_IntersectRectAndLine(&this.sdlRect, &line[0].sdlPoint.x, &line[0].sdlPoint.y, 382 &line[1].sdlPoint.x, &line[1].sdlPoint.y) == SDL_TRUE) { 383 Point[2] intersection = [line[0], line[1]]; 384 return intersection.nullable; 385 } 386 else { 387 return Nullable!(Point[2]).init; 388 } 389 } 390 391 /++ 392 + Wraps `SDL_UnionRect` which creates a `dsdl2.Rect` of the minimum size to enclose two given `dsdl2.Rect`s 393 + 394 + Params: 395 + other = other `dsdl2.Rect` to unify with the `dsdl2.Rect` 396 + Returns: `dsdl2.Rect` of the minimum size to enclose the `dsdl2.Rect` and `other` 397 +/ 398 Rect unify(Rect other) const @trusted { 399 Rect union_ = void; 400 SDL_UnionRect(&this.sdlRect, &other.sdlRect, &union_.sdlRect); 401 return union_; 402 } 403 } 404 /// 405 unittest { 406 auto rect1 = dsdl2.Rect(-2, -2, 3, 3); 407 auto rect2 = dsdl2.Rect(-1, -1, 3, 3); 408 409 assert(rect1.hasIntersection(rect2)); 410 assert(rect1.intersectRect(rect2).get == dsdl2.Rect(-1, -1, 2, 2)); 411 }