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