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