1 /** 2 2D Transformation. 3x2 matrix. 3 4 Copyright: 5 Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. 6 Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) 7 Copyright (c) 2017-2018 Godot-D contributors 8 9 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License) 10 11 12 */ 13 module godot.core.transform2d; 14 15 import godot.core.defs; 16 import godot.core.vector2; 17 import godot.core.rect2; 18 19 import std.math; 20 import std.algorithm.comparison; 21 import std.algorithm.mutation : swap; 22 23 /** 24 Represents one or many transformations in 2D space such as translation, rotation, or scaling. It is similar to a 3x2 matrix. 25 */ 26 struct Transform2D 27 { 28 @nogc nothrow: 29 30 union 31 { 32 Vector2[3] elements = [ Vector2(1,0), Vector2(0,1), Vector2(0,0) ]; 33 struct 34 { 35 Vector2 x_axis; /// 36 Vector2 y_axis; /// 37 Vector2 origin; /// 38 } 39 } 40 41 real_t tdotx(in Vector2 v) const { return elements[0][0] * v.x + elements[1][0] * v.y; } 42 real_t tdoty(in Vector2 v) const { return elements[0][1] * v.x + elements[1][1] * v.y; } 43 44 this(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy) 45 { 46 elements[0][0] = xx; 47 elements[0][1] = xy; 48 elements[1][0] = yx; 49 elements[1][1] = yy; 50 elements[2][0] = ox; 51 elements[2][1] = oy; 52 } 53 54 const(Vector2) opIndex(int axis) const { return elements[axis]; } 55 ref Vector2 opIndex(int axis) { return elements[axis]; } 56 57 58 59 Vector2 basisXform(in Vector2 v) const 60 { 61 return Vector2( 62 tdotx(v), 63 tdoty(v) 64 ); 65 } 66 67 Vector2 basisXformInv(in Vector2 v) const 68 { 69 return Vector2( 70 elements[0].dot(v), 71 elements[1].dot(v) 72 ); 73 } 74 75 Vector2 xform(in Vector2 v) const 76 { 77 return Vector2( 78 tdotx(v), 79 tdoty(v) 80 ) + elements[2]; 81 } 82 Vector2 xformInv(in Vector2 p_vec) const 83 { 84 Vector2 v = p_vec - elements[2]; 85 86 return Vector2( 87 elements[0].dot(v), 88 elements[1].dot(v) 89 ); 90 } 91 Rect2 xform(in Rect2 p_rect) const 92 { 93 Vector2 x=elements[0]*p_rect.size.x; 94 Vector2 y=elements[1]*p_rect.size.y; 95 Vector2 pos = xform( p_rect.pos ); 96 97 Rect2 new_rect; 98 new_rect.pos=pos; 99 new_rect.expandTo( pos+x ); 100 new_rect.expandTo( pos+y ); 101 new_rect.expandTo( pos+x+y ); 102 return new_rect; 103 } 104 105 void setRotationAndScale(real_t p_rot,in Vector2 p_scale) 106 { 107 elements[0][0]=cos(p_rot)*p_scale.x; 108 elements[1][1]=cos(p_rot)*p_scale.y; 109 elements[1][0]=-sin(p_rot)*p_scale.y; 110 elements[0][1]=sin(p_rot)*p_scale.x; 111 112 } 113 114 Rect2 xformInv(in Rect2 p_rect) const 115 { 116 Vector2[4] ends=[ 117 xformInv( p_rect.pos ), 118 xformInv( Vector2(p_rect.pos.x,p_rect.pos.y+p_rect.size.y ) ), 119 xformInv( Vector2(p_rect.pos.x+p_rect.size.x,p_rect.pos.y+p_rect.size.y ) ), 120 xformInv( Vector2(p_rect.pos.x+p_rect.size.x,p_rect.pos.y ) ) 121 ]; 122 123 Rect2 new_rect; 124 new_rect.pos=ends[0]; 125 new_rect.expandTo(ends[1]); 126 new_rect.expandTo(ends[2]); 127 new_rect.expandTo(ends[3]); 128 129 return new_rect; 130 } 131 132 void invert() 133 { 134 // FIXME: this function assumes the basis is a rotation matrix, with no scaling. 135 // affine_inverse can handle matrices with scaling, so GDScript should eventually use that. 136 swap(elements[0][1],elements[1][0]); 137 elements[2] = basisXform(-elements[2]); 138 } 139 140 Transform2D inverse() const 141 { 142 Transform2D inv=this; 143 inv.invert(); 144 return inv; 145 146 } 147 148 void affineInvert() 149 { 150 real_t det = basisDeterminant(); 151 ///ERR_FAIL_COND(det==0); 152 real_t idet = 1.0 / det; 153 154 swap( elements[0][0],elements[1][1] ); 155 elements[0]*=Vector2(idet,-idet); 156 elements[1]*=Vector2(-idet,idet); 157 158 elements[2] = basisXform(-elements[2]); 159 160 } 161 162 Transform2D affineInverse() const 163 { 164 Transform2D inv=this; 165 inv.affineInvert(); 166 return inv; 167 } 168 169 void rotate(real_t p_phi) 170 { 171 this = Transform2D(p_phi,Vector2()) * (this); 172 } 173 174 real_t getRotation() const 175 { 176 real_t det = basisDeterminant(); 177 Transform2D m = orthonormalized(); 178 if (det < 0) { 179 m.scaleBasis(Vector2(-1,-1)); 180 } 181 return atan2(m[0].y,m[0].x); 182 } 183 184 void setRotation(real_t p_rot) 185 { 186 real_t cr = cos(p_rot); 187 real_t sr = sin(p_rot); 188 elements[0][0]=cr; 189 elements[0][1]=sr; 190 elements[1][0]=-sr; 191 elements[1][1]=cr; 192 } 193 194 this(real_t p_rot, in Vector2 p_pos) 195 { 196 real_t cr = cos(p_rot); 197 real_t sr = sin(p_rot); 198 elements[0][0]=cr; 199 elements[0][1]=sr; 200 elements[1][0]=-sr; 201 elements[1][1]=cr; 202 elements[2]=p_pos; 203 } 204 205 Vector2 getScale() const 206 { 207 real_t det_sign = basisDeterminant() > 0 ? 1 : -1; 208 return det_sign * Vector2( elements[0].length(), elements[1].length() ); 209 } 210 211 void scale(in Vector2 p_scale) 212 { 213 scaleBasis(p_scale); 214 elements[2]*=p_scale; 215 } 216 void scaleBasis(in Vector2 p_scale) 217 { 218 elements[0][0]*=p_scale.x; 219 elements[0][1]*=p_scale.y; 220 elements[1][0]*=p_scale.x; 221 elements[1][1]*=p_scale.y; 222 223 } 224 void translate(real_t p_tx, real_t p_ty) 225 { 226 translate(Vector2(p_tx,p_ty)); 227 } 228 void translate(in Vector2 p_translation) 229 { 230 elements[2]+=basisXform(p_translation); 231 } 232 233 void orthonormalize() 234 { 235 // Gram-Schmidt Process 236 237 Vector2 x=elements[0]; 238 Vector2 y=elements[1]; 239 240 x.normalize(); 241 y = (y-x*(x.dot(y))); 242 y.normalize(); 243 244 elements[0]=x; 245 elements[1]=y; 246 } 247 Transform2D orthonormalized() const 248 { 249 Transform2D on=this; 250 on.orthonormalize(); 251 return on; 252 253 } 254 255 void opOpAssign(string op : "*")(in Transform2D p_transform) 256 { 257 elements[2] = xform(p_transform.elements[2]); 258 259 real_t x0,x1,y0,y1; 260 261 x0 = tdotx(p_transform.elements[0]); 262 x1 = tdoty(p_transform.elements[0]); 263 y0 = tdotx(p_transform.elements[1]); 264 y1 = tdoty(p_transform.elements[1]); 265 266 elements[0][0]=x0; 267 elements[0][1]=x1; 268 elements[1][0]=y0; 269 elements[1][1]=y1; 270 } 271 272 273 Transform2D opBinary(string op : "*")(in Transform2D p_transform) const 274 { 275 Transform2D t = this; 276 t*=p_transform; 277 return t; 278 279 } 280 281 Transform2D scaled(in Vector2 p_scale) const 282 { 283 Transform2D copy=this; 284 copy.scale(p_scale); 285 return copy; 286 287 } 288 289 Transform2D basisScaled(in Vector2 p_scale) const 290 { 291 Transform2D copy=this; 292 copy.scaleBasis(p_scale); 293 return copy; 294 295 } 296 297 Transform2D untranslated() const 298 { 299 Transform2D copy=this; 300 copy.elements[2]=Vector2(); 301 return copy; 302 } 303 304 Transform2D translated(in Vector2 p_offset) const 305 { 306 Transform2D copy=this; 307 copy.translate(p_offset); 308 return copy; 309 } 310 311 Transform2D rotated(real_t p_phi) const 312 { 313 Transform2D copy=this; 314 copy.rotate(p_phi); 315 return copy; 316 317 } 318 319 real_t basisDeterminant() const 320 { 321 return elements[0].x * elements[1].y - elements[0].y * elements[1].x; 322 } 323 324 Transform2D interpolateWith(in Transform2D p_transform, real_t p_c) const 325 { 326 //extract parameters 327 Vector2 p1 = origin; 328 Vector2 p2 = p_transform.origin; 329 330 real_t r1 = getRotation(); 331 real_t r2 = p_transform.getRotation(); 332 333 Vector2 s1 = getScale(); 334 Vector2 s2 = p_transform.getScale(); 335 336 //slerp rotation 337 Vector2 v1 = Vector2(cos(r1), sin(r1)); 338 Vector2 v2 = Vector2(cos(r2), sin(r2)); 339 340 real_t dot = v1.dot(v2); 341 342 dot = (dot < -1.0) ? -1.0 : ((dot > 1.0) ? 1.0 : dot); //clamp dot to [-1,1] 343 344 Vector2 v; 345 346 if (dot > 0.9995) 347 { 348 v = Vector2.linearInterpolate(v1, v2, p_c).normalized(); //linearly interpolate to avoid numerical precision issues 349 } 350 else 351 { 352 real_t angle = p_c*acos(dot); 353 Vector2 v3 = (v2 - v1*dot).normalized(); 354 v = v1*cos(angle) + v3*sin(angle); 355 } 356 357 //construct matrix 358 Transform2D res = Transform2D(atan2(v.y, v.x), Vector2.linearInterpolate(p1, p2, p_c)); 359 res.scaleBasis(Vector2.linearInterpolate(s1, s2, p_c)); 360 return res; 361 } 362 }