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 * 30 * The interface and template are provided for convenience, on top of 31 * $(TRANSFORM_LINK). 32 * 33 * $(TRANSFORM_LINK), as a low-level class, offers a great level of flexibility 34 * but it is not always convenient to manage. Indeed, one can easily combine any 35 * kind of operation, such as a translation followed by a rotation followed by a 36 * scaling, but once the result transform is built, there's no way to go 37 * backward and, let's say, change only the rotation without modifying the 38 * translation and scaling. 39 * 40 * The entire transform must be recomputed, which means that you need to 41 * retrieve the initial translation and scale factors as well, and combine them 42 * the same way you did before updating the rotation. This is a tedious 43 * operation, and it requires to store all the individual components of the 44 * final transform. 45 * 46 * That's exactly what $(U Transformable) and $(U NormalTransformable) were 47 * written for: they hides these variables and the composed transform behind an 48 * easy to use interface. You can set or get any of the individual components 49 * without worrying about the others. It also provides the composed transform 50 * (as a $(TRANSFORM_LINK)), and keeps it up-to-date. 51 * 52 * In addition to the position, rotation and scale, $(U Transformable) provides 53 * an "origin" component, which represents the local origin of the three other 54 * components. Let's take an example with a 10x10 pixels sprite. By default, the 55 * sprite is positioned/rotated/scaled relatively to its top-left corner, 56 * because it is the local point (0, 0). But if we change the origin to be 57 * (5, 5), the sprite will be positioned/rotated/scaled around its center 58 * instead. And if we set the origin to (10, 10), it will be transformed around 59 * its bottom-right corner. 60 * 61 * To keep $(U Transformable) and $(U NormalTransformable) simple, there's only 62 * one origin for all the components. You cannot position the sprite relatively 63 * to its top-left corner while rotating it around its center, for example. To 64 * do such things, use $(TRANSFORM_LINK) directly. 65 * 66 * $(U Transformable) is meant to be used as a base for other classes. It is 67 * often combined with $(DRAWABLE_LINK) -- that's what DSFML's sprites, texts 68 * and shapes do. 69 * --- 70 * class MyEntity : Transformable, Drawable 71 * { 72 * //generates the boilerplate code for Transformable 73 * mixin NormalTransformable; 74 * 75 * void draw(RenderTarget target, RenderStates states) const 76 * { 77 * states.transform *= getTransform(); 78 * target.draw(..., states); 79 * } 80 * } 81 * 82 * auto entity = new MyEntity(); 83 * entity.position = Vector2f(10, 20); 84 * entity.rotation = 45; 85 * window.draw(entity); 86 * --- 87 * 88 * $(PARA If you don't want to use the API directly (because you don't need all 89 * the functions, or you have different naming conventions for example), you can 90 * have a $(U TransformableMember) as a member variable.) 91 * --- 92 * class MyEntity 93 * { 94 * this() 95 * { 96 * myTransform = new TransformableMember(); 97 * } 98 * 99 * void setPosition(MyVector v) 100 * { 101 * myTransform.setPosition(v.x, v.y); 102 * } 103 * 104 * void draw(RenderTarget target, RenderStates states) const 105 * { 106 * states.transform *= myTransform.getTransform(); 107 * target.draw(..., states); 108 * } 109 * 110 * private TransformableMember myTransform; 111 * } 112 * --- 113 * 114 * $(PARA A note on coordinates and undistorted rendering: 115 * By default, DSFML (or more exactly, OpenGL) may interpolate drawable objects 116 * such as sprites or texts when rendering. While this allows transitions like 117 * slow movements or rotations to appear smoothly, it can lead to unwanted 118 * results in some cases, for example blurred or distorted objects. In order to 119 * render a $(DRAWABLE_LINK) object pixel-perfectly, make sure the involved 120 * coordinates allow a 1:1 mapping of pixels in the window to texels (pixels in 121 * the texture). More specifically, this means:) 122 * $(UL 123 * $(LI The object's position, origin and scale have no fractional part) 124 * $(LI The object's and the view's rotation are a multiple of 90 degrees) 125 * $(LI The view's center and size have no fractional part)) 126 * 127 * See_Also: 128 * $(TRANSFORM_LINK) 129 */ 130 module nudsfml.graphics.transformable; 131 132 import nudsfml.system.vector2; 133 134 //public import so that people don't have to worry about 135 //importing transform when they import transformable 136 public import nudsfml.graphics.transform; 137 138 /** 139 * Decomposed transform defined by a position, a rotation, and a scale. 140 */ 141 interface Transformable 142 { 143 @property 144 { 145 /** 146 * The local origin of the object. 147 * 148 * The origin of an object defines the center point for all 149 * transformations (position, scale, ratation). 150 * 151 * The coordinates of this point must be relative to the top-left corner 152 * of the object, and ignore all transformations (position, scale, 153 * rotation). 154 * 155 * The default origin of a transformable object is (0, 0). 156 */ 157 Vector2f origin(Vector2f newOrigin); 158 159 /// ditto 160 Vector2f origin() const; 161 } 162 163 @property 164 { 165 /// The position of the object. The default is (0, 0). 166 Vector2f position(Vector2f newPosition); 167 168 /// ditto 169 Vector2f position() const; 170 } 171 172 @property 173 { 174 /// The orientation of the object, in degrees. The default is 0 degrees. 175 float rotation(float newRotation); 176 177 /// ditto 178 float rotation() const; 179 } 180 181 @property 182 { 183 /// The scale factors of the object. The default is (1, 1). 184 Vector2f scale(Vector2f newScale); 185 186 /// ditto 187 Vector2f scale() const; 188 } 189 190 /** 191 * Get the inverse of the combined transform of the object. 192 * 193 * Returns: Inverse of the combined transformations applied to the object. 194 */ 195 const(Transform) getTransform(); 196 197 /** 198 * Get the combined transform of the object. 199 * 200 * Returns: Transform combining the position/rotation/scale/origin of the 201 * object. 202 */ 203 const(Transform) getInverseTransform(); 204 205 /** 206 * Move the object by a given offset. 207 * 208 * This function adds to the current position of the object, unlike the 209 * position property which overwrites it. 210 * 211 * Params: 212 * offset = The offset 213 */ 214 void move(Vector2f offset); 215 } 216 217 /** 218 * Mixin template that generates the boilerplate code for the $(U Transformable) 219 * interface. 220 * 221 * This template is provided for convenience, so that you don't have to add the 222 * properties and functions manually. 223 */ 224 mixin template NormalTransformable() 225 { 226 private 227 { 228 // Origin of translation/rotation/scaling of the object 229 Vector2f m_origin = Vector2f(0,0); 230 // Position of the object in the 2D world 231 Vector2f m_position = Vector2f(0,0); 232 // Orientation of the object, in degrees 233 float m_rotation = 0; 234 // Scale of the object 235 Vector2f m_scale = Vector2f(1,1); 236 // Combined transformation of the object 237 Transform m_transform; 238 // Does the transform need to be recomputed? 239 bool m_transformNeedUpdate; 240 // Combined transformation of the object 241 Transform m_inverseTransform; 242 // Does the transform need to be recomputed? 243 bool m_inverseTransformNeedUpdate; 244 } 245 246 @property 247 { 248 Vector2f origin(Vector2f newOrigin) 249 { 250 m_origin = newOrigin; 251 m_transformNeedUpdate = true; 252 m_inverseTransformNeedUpdate = true; 253 return newOrigin; 254 } 255 256 Vector2f origin() const 257 { 258 return m_origin; 259 } 260 } 261 262 @property 263 { 264 Vector2f position(Vector2f newPosition) 265 { 266 m_position = newPosition; 267 m_transformNeedUpdate = true; 268 m_inverseTransformNeedUpdate = true; 269 return newPosition; 270 } 271 272 Vector2f position() const 273 { 274 return m_position; 275 } 276 } 277 278 @property 279 { 280 import std.math: fmod; 281 float rotation(float newRotation) 282 { 283 m_rotation = cast(float)fmod(newRotation, 360); 284 if(m_rotation < 0) 285 { 286 m_rotation += 360; 287 } 288 m_transformNeedUpdate = true; 289 m_inverseTransformNeedUpdate = true; 290 return newRotation; 291 } 292 293 float rotation() const 294 { 295 return m_rotation; 296 } 297 } 298 299 @property 300 { 301 Vector2f scale(Vector2f newScale) 302 { 303 m_scale = newScale; 304 m_transformNeedUpdate = true; 305 m_inverseTransformNeedUpdate = true; 306 return newScale; 307 } 308 309 Vector2f scale() const 310 { 311 return m_scale; 312 } 313 } 314 315 const(Transform) getInverseTransform() 316 { 317 if (m_inverseTransformNeedUpdate) 318 { 319 m_inverseTransform = getTransform().getInverse(); 320 m_inverseTransformNeedUpdate = false; 321 } 322 323 return m_inverseTransform; 324 } 325 326 const(Transform) getTransform() 327 { 328 import std.math: cos, sin; 329 if (m_transformNeedUpdate) 330 { 331 float angle = -m_rotation * 3.141592654f / 180f; 332 float cosine = cast(float)(cos(angle)); 333 float sine = cast(float)(sin(angle)); 334 float sxc = m_scale.x * cosine; 335 float syc = m_scale.y * cosine; 336 float sxs = m_scale.x * sine; 337 float sys = m_scale.y * sine; 338 float tx = -m_origin.x * sxc - m_origin.y * sys + m_position.x; 339 float ty = m_origin.x * sxs - m_origin.y * syc + m_position.y; 340 341 m_transform = Transform( sxc, sys, tx, 342 -sxs, syc, ty, 343 0f, 0f, 1f); 344 m_transformNeedUpdate = false; 345 } 346 347 return m_transform; 348 } 349 350 void move(Vector2f offset) 351 { 352 position = position + offset; 353 } 354 } 355 356 /** 357 * Concrete class that implements the $(U Transformable) interface. 358 * 359 * This class is provided for convenience, so that a $(U Transformable) object 360 * can be used as a member of a class instead of inheriting from 361 * $(U Transformable). 362 */ 363 class TransformableMember: Transformable 364 { 365 mixin NormalTransformable; 366 }