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 * $(U Shape) is a drawable class that allows to define and display a custom 27 * convex shape on a render target. 28 * 29 * It's only an abstract base, it needs to be specialized for concrete types of 30 * shapes (circle, rectangle, convex polygon, star, ...). 31 * 32 * In addition to the attributes provided by the specialized shape classes, a 33 * shape always has the following attributes: 34 * $(UL 35 * $(LI a texture) 36 * $(LI a texture rectangle) 37 * $(LI a fill color) 38 * $(LI an outline color) 39 * $(LI an outline thickness)) 40 * 41 * $(PARA Each feature is optional, and can be disabled easily:) 42 * $(UL 43 * $(LI the texture can be null) 44 * $(LI the fill/outline colors can be Color.Transparent) 45 * $(LI the outline thickness can be zero)) 46 * 47 * $(PARA You can write your own derived shape class, there are only two 48 * abstract functions to override:) 49 * $(UL 50 * $(LI `getPointCount` must return the number of points of the shape) 51 * $(LI `getPoint` must return the points of the shape)) 52 * 53 * See_Also: 54 * $(RECTANGLESHAPE_LINK), $(CIRCLESHAPE_LINK), $(CONVEXSHAPE_LINK), 55 * $(TRANSFORMABLE_LINK) 56 */ 57 module dsfml.graphics.shape; 58 59 import dsfml.system.vector2; 60 61 import dsfml.graphics.color; 62 import dsfml.graphics.drawable; 63 import dsfml.graphics.primitivetype; 64 import dsfml.graphics.rect; 65 import dsfml.graphics.rendertarget; 66 import dsfml.graphics.renderstates; 67 import dsfml.graphics.texture; 68 import dsfml.graphics.transformable; 69 import dsfml.graphics.vertexarray; 70 71 import std.typecons : Rebindable; 72 73 /** 74 * Base class for textured shapes with outline. 75 */ 76 class Shape : Drawable, Transformable 77 { 78 mixin NormalTransformable; 79 80 protected this() 81 { 82 m_vertices = new VertexArray(PrimitiveType.TrianglesFan,0); 83 m_outlineVertices = new VertexArray(PrimitiveType.TrianglesStrip,0); 84 } 85 86 private 87 { 88 // Texture of the shape 89 Rebindable!(const(Texture)) m_texture; 90 // Rectangle defining the area of the source texture to display 91 IntRect m_textureRect; 92 // Fill color 93 Color m_fillColor; 94 // Outline color 95 Color m_outlineColor; 96 // Thickness of the shape's outline 97 float m_outlineThickness = 0; 98 // Vertex array containing the fill geometry 99 VertexArray m_vertices; 100 // Vertex array containing the outline geometry 101 VertexArray m_outlineVertices; 102 // Bounding rectangle of the inside (fill) 103 FloatRect m_insideBounds; 104 // Bounding rectangle of the whole shape (outline + fill) 105 FloatRect m_bounds; 106 } 107 108 @property 109 { 110 /** 111 * The sub-rectangle of the texture that the shape will display. 112 * 113 * The texture rect is useful when you don't want to display the whole 114 * texture, but rather a part of it. By default, the texture rect covers 115 * the entire texture. 116 */ 117 IntRect textureRect(IntRect rect) 118 { 119 m_textureRect = rect; 120 updateTexCoords(); 121 return rect; 122 } 123 /// ditto 124 IntRect textureRect() const 125 { 126 return m_textureRect; 127 } 128 } 129 130 @property 131 { 132 /** 133 * The fill color of the shape. 134 * 135 * This color is modulated (multiplied) with the shape's texture if any. 136 * It can be used to colorize the shape, or change its global opacity. 137 * You can use `Color.Transparent` to make the inside of the shape 138 * transparent, and have the outline alone. By default, the shape's fill 139 * color is opaque white. 140 */ 141 Color fillColor(Color color) 142 { 143 m_fillColor = color; 144 updateFillColors(); 145 return color; 146 } 147 /// ditto 148 Color fillColor() const 149 { 150 return m_fillColor; 151 } 152 } 153 154 @property 155 { 156 /** 157 * The outline color of the shape. 158 * 159 * By default, the shape's outline color is opaque white. 160 */ 161 Color outlineColor(Color color) 162 { 163 m_outlineColor = color; 164 updateOutlineColors(); 165 return color; 166 } 167 /// ditto 168 Color outlineColor() const 169 { 170 return m_outlineColor; 171 } 172 } 173 174 @property 175 { 176 /** 177 * The thickness of the shape's outline. 178 * 179 * Note that negative values are allowed (so that the outline expands 180 * towards the center of the shape), and using zero disables the 181 * outline. By default, the outline thickness is 0. 182 */ 183 float outlineThickness(float thickness) 184 { 185 m_outlineThickness = thickness; 186 update(); 187 return thickness; 188 } 189 /// ditto 190 float outlineThickness() const 191 { 192 return m_outlineThickness; 193 } 194 } 195 196 @property 197 { 198 /** 199 * The total number of points in the shape. 200 */ 201 abstract uint pointCount(); 202 } 203 204 /** 205 * Get the global bounding rectangle of the entity. 206 * 207 * The returned rectangle is in global coordinates, which means that it 208 * takes in account the transformations (translation, rotation, scale, ...) 209 * that are applied to the entity. In other words, this function returns the 210 * bounds of the sprite in the global 2D world's coordinate system. 211 * 212 * Returns: Global bounding rectangle of the entity. 213 */ 214 FloatRect getGlobalBounds() 215 { 216 return getTransform().transformRect(getLocalBounds()); 217 } 218 219 /** 220 * Get the local bounding rectangle of the entity. 221 * 222 * The returned rectangle is in local coordinates, which means that it 223 * ignores the transformations (translation, rotation, scale, ...) that are 224 * applied to the entity. In other words, this function returns the bounds 225 * of the entity in the entity's coordinate system. 226 * 227 * Returns: Local bounding rectangle of the entity. 228 */ 229 FloatRect getLocalBounds() const 230 { 231 return m_bounds; 232 } 233 234 /** 235 * Get a point of the shape. 236 * 237 * The result is undefined if index is out of the valid range. 238 * 239 * Params: 240 * index = Index of the point to get, in range [0 .. getPointCount() - 1] 241 * 242 * Returns: Index-th point of the shape. 243 */ 244 abstract Vector2f getPoint(uint index) const; 245 246 /** 247 * Get the source texture of the shape. 248 * 249 * If the shape has no source texture, a NULL pointer is returned. The 250 * returned pointer is const, which means that you can't modify the texture 251 * when you retrieve it with this function. 252 * 253 * Returns: The shape's texture. 254 */ 255 const(Texture) getTexture() const 256 { 257 return m_texture; 258 } 259 260 /** 261 * Change the source texture of the shape. 262 * 263 * The texture argument refers to a texture that must exist as long as the 264 * shape uses it. Indeed, the shape doesn't store its own copy of the 265 * texture, but rather keeps a pointer to the one that you passed to this 266 * function. If the source texture is destroyed and the shape tries to use 267 * it, the behaviour is undefined. texture can be NULL to disable texturing. 268 * 269 * If resetRect is true, the TextureRect property of the shape is 270 * automatically adjusted to the size of the new texture. If it is false, 271 * the texture rect is left unchanged. 272 * 273 * Params: 274 * texture = New texture 275 * resetRect = Should the texture rect be reset to the size of the new 276 * texture? 277 */ 278 void setTexture(const(Texture) texture, bool resetRect = false) 279 { 280 if((texture !is null) && (resetRect || (m_texture is null))) 281 { 282 textureRect = IntRect(0, 0, texture.getSize().x, texture.getSize().y); 283 } 284 285 m_texture = (texture is null)? null:texture; 286 } 287 288 /** 289 * Draw the shape to a render target. 290 * 291 * Params: 292 * renderTarget = Target to draw to 293 * renderStates = Current render states 294 */ 295 override void draw(RenderTarget renderTarget, RenderStates renderStates) 296 { 297 renderStates.transform = renderStates.transform * getTransform(); 298 renderStates.texture = m_texture; 299 renderTarget.draw(m_vertices, renderStates); 300 301 // Render the outline 302 if (m_outlineThickness != 0) 303 { 304 renderStates.texture = null; 305 renderTarget.draw(m_outlineVertices, renderStates); 306 } 307 } 308 309 /** 310 * Recompute the internal geometry of the shape. 311 * 312 * This function must be called by the derived class everytime the shape's 313 * points change (ie. the result of either `getPointCount` or `getPoint` is 314 * different). 315 */ 316 protected void update() 317 { 318 // Get the total number of points of the shape 319 uint count = pointCount(); 320 if (count < 3) 321 { 322 m_vertices.resize(0); 323 m_outlineVertices.resize(0); 324 return; 325 } 326 327 m_vertices.resize(count + 2); // + 2 for center and repeated first point 328 329 // Position 330 for (uint i = 0; i < count; ++i) 331 { 332 m_vertices[i + 1].position = getPoint(i); 333 } 334 m_vertices[count + 1].position = m_vertices[1].position; 335 336 // Update the bounding rectangle 337 m_vertices[0] = m_vertices[1]; // so that the result of getBounds() is correct 338 m_insideBounds = m_vertices.getBounds(); 339 340 // Compute the center and make it the first vertex 341 m_vertices[0].position.x = m_insideBounds.left + m_insideBounds.width / 2; 342 m_vertices[0].position.y = m_insideBounds.top + m_insideBounds.height / 2; 343 344 // Color 345 updateFillColors(); 346 347 // Texture coordinates 348 updateTexCoords(); 349 350 // Outline 351 updateOutline(); 352 } 353 354 private 355 { 356 Vector2f computeNormal(Vector2f p1, Vector2f p2) 357 { 358 Vector2f normal = Vector2f(p1.y - p2.y, p2.x - p1.x); 359 float length = sqrt(normal.x * normal.x + normal.y * normal.y); 360 if (length != 0f) 361 { 362 normal /= length; 363 } 364 return normal; 365 } 366 367 float dotProduct(Vector2f p1, Vector2f p2) 368 { 369 return (p1.x * p2.x) + (p1.y * p2.y); 370 } 371 372 //update methods 373 void updateFillColors() 374 { 375 for(uint i = 0; i < m_vertices.getVertexCount(); ++i) 376 { 377 m_vertices[i].color = m_fillColor; 378 } 379 } 380 381 void updateTexCoords() 382 { 383 384 for (uint i = 0; i < m_vertices.getVertexCount(); ++i) 385 { 386 float xratio = (m_vertices[i].position.x - m_insideBounds.left) / m_insideBounds.width; 387 float yratio = (m_vertices[i].position.y - m_insideBounds.top) / m_insideBounds.height; 388 389 m_vertices[i].texCoords.x = m_textureRect.left + m_textureRect.width * xratio; 390 m_vertices[i].texCoords.y = m_textureRect.top + m_textureRect.height * yratio; 391 392 } 393 } 394 395 void updateOutline() 396 { 397 uint count = m_vertices.getVertexCount() - 2; 398 m_outlineVertices.resize((count + 1) * 2); 399 400 for (uint i = 0; i < count; ++i) 401 { 402 uint index = i + 1; 403 404 // Get the two segments shared by the current point 405 Vector2f p0 = (i == 0) ? m_vertices[count].position : m_vertices[index - 1].position; 406 Vector2f p1 = m_vertices[index].position; 407 Vector2f p2 = m_vertices[index + 1].position; 408 409 // Compute their normal 410 Vector2f n1 = computeNormal(p0, p1); 411 Vector2f n2 = computeNormal(p1, p2); 412 413 // Make sure that the normals point towards the outside of the shape 414 // (this depends on the order in which the points were defined) 415 if (dotProduct(n1, m_vertices[0].position - p1) > 0) 416 n1 = -n1; 417 if (dotProduct(n2, m_vertices[0].position - p1) > 0) 418 n2 = -n2; 419 420 // Combine them to get the extrusion direction 421 float factor = 1f + (n1.x * n2.x + n1.y * n2.y); 422 Vector2f normal = (n1 + n2) / factor; 423 424 // Update the outline points 425 m_outlineVertices[i * 2 + 0].position = p1; 426 m_outlineVertices[i * 2 + 1].position = p1 + normal * m_outlineThickness; 427 } 428 429 // Duplicate the first point at the end, to close the outline 430 m_outlineVertices[count * 2 + 0].position = m_outlineVertices[0].position; 431 m_outlineVertices[count * 2 + 1].position = m_outlineVertices[1].position; 432 433 // Update outline colors 434 updateOutlineColors(); 435 436 // Update the shape's bounds 437 m_bounds = m_outlineVertices.getBounds(); 438 } 439 440 void updateOutlineColors() 441 { 442 for (uint i = 0; i < m_outlineVertices.getVertexCount(); ++i) 443 { 444 m_outlineVertices[i].color = m_outlineColor; 445 } 446 } 447 } 448 } 449 450 unittest 451 { 452 //meant to be inherited. Unit test? 453 }