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