1 /* 2 * DSFML - The Simple and Fast Multimedia Library for D 3 * 4 * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com) 5 * 6 * This software is provided 'as-is', without any express or implied warranty. 7 * In no event will the authors be held liable for any damages arising from the 8 * use of this software. 9 * 10 * Permission is granted to anyone to use this software for any purpose, 11 * including commercial applications, and to alter it and redistribute it 12 * freely, subject to the following restrictions: 13 * 14 * 1. The origin of this software must not be misrepresented; you must not claim 15 * that you wrote the original software. If you use this software in a product, 16 * an acknowledgment in the product documentation would be appreciated but is 17 * not required. 18 * 19 * 2. Altered source versions must be plainly marked as such, and must not be 20 * misrepresented as being the original software. 21 * 22 * 3. This notice may not be removed or altered from any source distribution 23 * 24 * 25 * DSFML is based on SFML (Copyright Laurent Gomila) 26 */ 27 28 /** 29 * A $(U Transform) specifies how to translate, rotate, scale, shear, project, 30 * whatever things. In mathematical terms, it defines how to transform a 31 * coordinate system into another. 32 * 33 * For example, if you apply a rotation transform to a sprite, the result will 34 * be a rotated sprite. And anything that is transformed by this rotation 35 * transform will be rotated the same way, according to its initial position. 36 * 37 * Transforms are typically used for drawing. But they can also be used for any 38 * computation that requires to transform points between the local and global 39 * coordinate systems of an entity (like collision detection). 40 * 41 * Example: 42 * --- 43 * // define a translation transform 44 * Transform translation; 45 * translation.translate(20, 50); 46 * 47 * // define a rotation transform 48 * Transform rotation; 49 * rotation.rotate(45); 50 * 51 * // combine them 52 * Transform transform = translation * rotation; 53 * 54 * // use the result to transform stuff... 55 * Vector2f point = transform.transformPoint(Vector2f(10, 20)); 56 * FloatRect rect = transform.transformRect(FloatRect(0, 0, 10, 100)); 57 * --- 58 * 59 * See_Also: 60 * $(TRANSFORMABLE_LINK), $(RENDERSTATES_LINK) 61 */ 62 module dsfml.graphics.transform; 63 64 import dsfml.system.vector2; 65 import dsfml.graphics.rect; 66 67 68 /** 69 * Define a 3x3 transform matrix. 70 */ 71 struct Transform 72 { 73 /// 4x4 matrix defining the transformation. 74 package float[16] m_matrix = [1.0f, 0.0f, 0.0f, 0.0f, 75 0.0f, 1.0f, 0.0f, 0.0f, 76 0.0f, 0.0f, 1.0f, 0.0f, 77 0.0f, 0.0f, 0.0f, 1.0f]; 78 79 /** 80 * Construct a transform from a 3x3 matrix. 81 * 82 * Params: 83 * a00 = Element (0, 0) of the matrix 84 * a01 = Element (0, 1) of the matrix 85 * a02 = Element (0, 2) of the matrix 86 * a10 = Element (1, 0) of the matrix 87 * a11 = Element (1, 1) of the matrix 88 * a12 = Element (1, 2) of the matrix 89 * a20 = Element (2, 0) of the matrix 90 * a21 = Element (2, 1) of the matrix 91 * a22 = Element (2, 2) of the matrix 92 */ 93 this(float a00, float a01, float a02, float a10, float a11, float a12, float a20, float a21, float a22) 94 { 95 m_matrix = [ a00, a10, 0.0f, a20, 96 a01, a11, 0.0f, a21, 97 0.0f, 0.0f, 1.0f, 0.0f, 98 a02, a12, 0.0f, a22]; 99 } 100 101 /// Construct a transform from a float array describing a 3x3 matrix. 102 this(float[9] matrix) 103 { 104 m_matrix = [matrix[0], matrix[3], 0.0f, matrix[6], 105 matrix[1], matrix[4], 0.0f, matrix[7], 106 0.0f, 0.0f, 1.0f, 0.0f, 107 matrix[2], matrix[5], 0.0f, matrix[8]]; 108 } 109 110 /** 111 * Return the inverse of the transform. 112 * 113 * If the inverse cannot be computed, an identity transform is returned. 114 * 115 * Returns: A new transform which is the inverse of self. 116 */ 117 Transform getInverse() const 118 { 119 Transform temp; 120 sfTransform_getInverse(m_matrix.ptr,temp.m_matrix.ptr); 121 return temp; 122 } 123 124 /** 125 * Return the transform as a 4x4 matrix. 126 * 127 * This function returns a pointer to an array of 16 floats containing the 128 * transform elements as a 4x4 matrix, which is directly compatible with 129 * OpenGL functions. 130 * 131 * Returns: A 4x4 matrix. 132 */ 133 const(float)[] getMatrix() const 134 { 135 return m_matrix; 136 } 137 138 /** 139 * Combine the current transform with another one. 140 * 141 * The result is a transform that is equivalent to applying this followed by 142 * transform. Mathematically, it is equivalent to a matrix multiplication. 143 * 144 * Params: 145 * otherTransform = Transform to combine with this one 146 * 147 * Returns: Reference to this. 148 */ 149 ref Transform combine(Transform otherTransform) 150 { 151 sfTransform_combine(m_matrix.ptr, otherTransform.m_matrix.ptr); 152 return this; 153 } 154 155 /** 156 * Transform a 2D point. 157 * 158 * Params: 159 * x = X coordinate of the point to transform 160 * y = Y coordinate of the point to transform 161 * 162 * Returns: Transformed point. 163 */ 164 Vector2f transformPoint(float x, float y) const 165 { 166 Vector2f temp; 167 sfTransform_transformPoint(m_matrix.ptr, x, y, &temp.x, &temp.y); 168 return temp; 169 } 170 171 /** 172 * Transform a 2D point. 173 * 174 * Params: 175 * point = the point to transform 176 * 177 * Returns: Transformed point. 178 */ 179 Vector2f transformPoint(Vector2f point) const 180 { 181 Vector2f temp; 182 sfTransform_transformPoint(m_matrix.ptr,point.x, point.y, &temp.x, &temp.y); 183 return temp; 184 } 185 186 /** 187 * Transform a rectangle. 188 * 189 * Since SFML doesn't provide support for oriented rectangles, the result of 190 * this function is always an axis-aligned rectangle. Which means that if 191 * the transform contains a rotation, the bounding rectangle of the 192 * transformed rectangle is returned. 193 * 194 * Params: 195 * rect = Rectangle to transform 196 * 197 * Returns: Transformed rectangle. 198 */ 199 FloatRect transformRect(const(FloatRect) rect)const 200 { 201 FloatRect temp; 202 sfTransform_transformRect(m_matrix.ptr,rect.left, rect.top, rect.width, rect.height, &temp.left, &temp.top, &temp.width, &temp.height); 203 return temp; 204 } 205 206 /** 207 * Combine the current transform with a translation. 208 * 209 * This function returns a reference to this, so that calls can be chained. 210 * 211 * Params: 212 * offset = Translation offset to apply 213 * 214 * Returns: this 215 */ 216 ref Transform translate(Vector2f offset) 217 { 218 sfTransform_translate(m_matrix.ptr, offset.x, offset.y); 219 return this; 220 } 221 222 /** 223 * Combine the current transform with a translation. 224 * 225 * This function returns a reference to this, so that calls can be chained. 226 * 227 * Params: 228 * x = Offset to apply on X axis 229 * y = Offset to apply on Y axis 230 * 231 * Returns: this 232 */ 233 ref Transform translate(float x, float y) 234 { 235 sfTransform_translate(m_matrix.ptr, x, y); 236 return this; 237 } 238 239 /** 240 * Combine the current transform with a rotation. 241 * 242 * This function returns a reference to this, so that calls can be chained. 243 * 244 * Params: 245 * angle = Rotation angle, in degrees 246 * 247 * Returns: this 248 */ 249 ref Transform rotate(float angle) 250 { 251 sfTransform_rotate(m_matrix.ptr, angle); 252 return this; 253 } 254 255 /** 256 * Combine the current transform with a rotation. 257 * 258 * The center of rotation is provided for convenience as a second argument, 259 * so that you can build rotations around arbitrary points more easily (and 260 * efficiently) than the usual 261 * translate(-center).rotate(angle).translate(center). 262 * 263 * This function returns a reference to this, so that calls can be chained. 264 * 265 * Params: 266 * angle = Rotation angle, in degrees 267 * centerX = X coordinate of the center of rotation 268 * centerY = Y coordinate of the center of rotation 269 * 270 * Returns: this 271 */ 272 ref Transform rotate(float angle, float centerX, float centerY) 273 { 274 sfTransform_rotateWithCenter(m_matrix.ptr, angle, centerX, centerY); 275 return this; 276 } 277 278 /** 279 * Combine the current transform with a rotation. 280 * 281 * The center of rotation is provided for convenience as a second argument, 282 * so that you can build rotations around arbitrary points more easily (and 283 * efficiently) than the usual 284 * translate(-center).rotate(angle).translate(center). 285 * 286 * This function returns a reference to this, so that calls can be chained. 287 * 288 * Params: 289 * angle = Rotation angle, in degrees 290 * center = Center of rotation 291 * 292 * Returns: this 293 */ 294 ref Transform rotate(float angle, Vector2f center) 295 { 296 sfTransform_rotateWithCenter(m_matrix.ptr, angle, center.x, center.y); 297 return this; 298 } 299 300 /** 301 * Combine the current transform with a scaling. 302 * 303 * This function returns a reference to this, so that calls can be chained. 304 * 305 * Params: 306 * scaleX = Scaling factor on the X-axis. 307 * scaleY = Scaling factor on the Y-axis. 308 * 309 * Returns: this 310 */ 311 ref Transform scale(float scaleX, float scaleY) 312 { 313 sfTransform_scale(m_matrix.ptr, scaleX, scaleY); 314 return this; 315 } 316 317 /** 318 * Combine the current transform with a scaling. 319 * 320 * This function returns a reference to this, so that calls can be chained. 321 * 322 * Params: 323 * factors = Scaling factors 324 * 325 * Returns: this 326 */ 327 ref Transform scale(Vector2f factors) 328 { 329 sfTransform_scale(m_matrix.ptr, factors.x, factors.y); 330 return this; 331 } 332 333 /** 334 * Combine the current transform with a scaling. 335 * 336 * The center of scaling is provided for convenience as a second argument, 337 * so that you can build scaling around arbitrary points more easily 338 * (and efficiently) than the usual 339 * translate(-center).scale(factors).translate(center). 340 * 341 * This function returns a reference to this, so that calls can be chained. 342 * 343 * Params: 344 * scaleX = Scaling factor on the X-axis 345 * scaleY = Scaling factor on the Y-axis 346 * centerX = X coordinate of the center of scaling 347 * centerY = Y coordinate of the center of scaling 348 * 349 * Returns: this 350 */ 351 ref Transform scale(float scaleX, float scaleY, float centerX, float centerY) 352 { 353 sfTransform_scaleWithCenter(m_matrix.ptr, scaleX, scaleY, centerX, centerY); 354 return this; 355 } 356 357 /** 358 * Combine the current transform with a scaling. 359 * 360 * The center of scaling is provided for convenience as a second argument, 361 * so that you can build scaling around arbitrary points more easily 362 * (and efficiently) than the usual 363 * translate(-center).scale(factors).translate(center). 364 * 365 * This function returns a reference to this, so that calls can be chained. 366 * 367 * Params: 368 * factors = Scaling factors 369 * center = Center of scaling 370 * 371 * Returns: this 372 */ 373 ref Transform scale(Vector2f factors, Vector2f center) 374 { 375 sfTransform_scaleWithCenter(m_matrix.ptr, factors.x, factors.y, center.x, center.y); 376 return this; 377 } 378 379 string toString() const 380 { 381 return "";//text(InternalsfTransform.matrix); 382 } 383 384 /** 385 * Overload of binary operator `*` to combine two transforms. 386 * 387 * This call is equivalent to: 388 * --- 389 * Transform combined = transform; 390 * combined.combine(rhs); 391 * --- 392 * 393 * Params: 394 * rhs = the second transform to be combined with the first 395 * 396 * Returns: New combined transform. 397 */ 398 Transform opBinary(string op)(Transform rhs) 399 if(op == "*") 400 { 401 Transform temp = this; 402 return temp.combine(rhs); 403 } 404 405 /** 406 * Overload of assignment operator `*=` to combine two transforms. 407 * 408 * This call is equivalent to calling `transform.combine(rhs)`. 409 * 410 * Params: 411 * rhs = the second transform to be combined with the first 412 * 413 * Returns: The combined transform. 414 */ 415 ref Transform opOpAssign(string op)(Transform rhs) 416 if(op == "*") 417 { 418 return this.combine(rhs); 419 } 420 421 /** 422 * Overload of binary operator * to transform a point 423 * 424 * This call is equivalent to calling `transform.transformPoint(vector)`. 425 * 426 * Params: 427 * vector = the point to transform 428 * 429 * Returns: New transformed point. 430 */ 431 Vextor2f opBinary(string op)(Vector2f vector) 432 if(op == "*") 433 { 434 return transformPoint(vector); 435 } 436 437 /// Indentity transform (does nothing). 438 static const(Transform) Identity; 439 } 440 441 unittest 442 { 443 version(DSFML_Unittest_Graphics) 444 { 445 import std.stdio; 446 import std.math; 447 448 bool compareTransform(Transform a, Transform b) 449 { 450 /* 451 * There's a slight difference in precision between D's and C++'s 452 * sine and cosine functions, so we'll use approxEqual here. 453 */ 454 return approxEqual(a.getMatrix(), b.getMatrix()); 455 } 456 457 writeln("Unit Test for Transform"); 458 459 assert(compareTransform(Transform.Identity.getInverse(), Transform.Identity)); 460 461 Transform scaledTransform; 462 scaledTransform.scale(2, 3); 463 464 Transform comparisonTransform; 465 comparisonTransform.m_matrix = [2.0f, 0.0f, 0.0f, 0.0f, 466 0.0f, 3.0f, 0.0f, 0.0f, 467 0.0f, 0.0f, 1.0f, 0.0f, 468 0.0f, 0.0f, 0.0f, 1.0f]; 469 470 assert(compareTransform(scaledTransform, comparisonTransform)); 471 472 Transform rotatedTransform; 473 rotatedTransform.rotate(20); 474 475 float rad = 20 * 3.141592654f / 180.0f; 476 float cos = cos(rad); 477 float sin = sin(rad); 478 479 // combine identity with rotational matrix (what rotate() should do) 480 comparisonTransform = Transform().combine(Transform(cos, -sin, 0, 481 sin, cos, 0, 482 0, 0, 1)); 483 484 assert(compareTransform(rotatedTransform, comparisonTransform)); 485 486 writeln(); 487 } 488 } 489 490 private extern(C): 491 492 //Return the inverse of a transform 493 void sfTransform_getInverse(const float* transform, float* inverse); 494 495 //Apply a transform to a 2D point 496 void sfTransform_transformPoint(const float* transform, float xIn, float yIn, float* xOut, float* yOut); 497 498 //Apply a transform to a rectangle 499 void sfTransform_transformRect(const float* transform, float leftIn, float topIn, float widthIn, float heightIn, float* leftOut, float* topOut, float* widthOut, float* heightOut); 500 501 //Combine two transforms 502 void sfTransform_combine(float* transform, const float* other); 503 504 //Combine a transform with a translation 505 void sfTransform_translate(float* transform, float x, float y); 506 507 //Combine the current transform with a rotation 508 void sfTransform_rotate(float* transform, float angle); 509 510 //Combine the current transform with a rotation 511 void sfTransform_rotateWithCenter(float* transform, float angle, float centerX, float centerY); 512 513 //Combine the current transform with a scaling 514 void sfTransform_scale(float* transform, float scaleX, float scaleY); 515 516 //Combine the current transform with a scaling 517 void sfTransform_scaleWithCenter(float* transform, float scaleX, float scaleY, float centerX, float centerY);