1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  *
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  *
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  *
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  *
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  *
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Graphic.Shape;
25 
26 private:
27 
28 import derelict.opengl3.gl;
29 
30 import Dgame.Graphic.Drawable;
31 import Dgame.Graphic.Transformable;
32 import Dgame.Graphic.Texture;
33 
34 import Dgame.Math.Vertex;
35 import Dgame.Math.Vector2;
36 import Dgame.Math.Rect;
37 import Dgame.Graphic.Color;
38 import Dgame.Math.Geometry;
39 
40 public:
41 
42 /**
43  * Shape defines a drawable geometric shape.
44  *
45  * Author: Randy Schuett (rswhite4@googlemail.com)
46  */
47 class Shape : Transformable, Drawable {
48 public:
49     /**
50      * Defines how the Shape should be filled.
51      */
52     enum Fill : ubyte {
53         Full, /// Full / Complete fill
54         Point, /// Show only the points
55         Line  /// Show only the lines
56     }
57 
58 protected:
59     Texture* _texture;
60     Vertex[] _vertices;
61 
62     @nogc
63     override void draw(ref const Window wnd) nothrow {
64         glPushAttrib(GL_ENABLE_BIT | GL_POLYGON_BIT | GL_LINE_BIT);
65         scope(exit) glPopAttrib();
66 
67         if (this.lineWidth != 1) {
68             glLineWidth(this.lineWidth);
69 
70             if (this.antiAliasing)
71                 glEnable(GL_LINE_SMOOTH);
72         }
73 
74         final switch (this.fill) {
75             case Fill.Full:
76                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
77             break;
78 
79             case Fill.Line:
80                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
81             break;
82 
83             case Fill.Point:
84                 glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
85             break;
86         }
87 
88         wnd.draw(this.geometry, super.getMatrix(), _texture, _vertices);
89     }
90 
91 public:
92     /**
93      * The geometric type of the shape
94      *
95      * See: Geometric enum
96      */
97     Geometry geometry;
98     /**
99      * Fill option. Default is Fill.Full
100      */
101     Fill fill = Fill.Full;
102     /**
103      * Option for the line width. Default is 1
104      */
105     ubyte lineWidth = 1;
106     /**
107      * Optional anti-alias for lines thicker than 1. Default is false.
108      *
109      * Note: this is redundant if you have already enabled anti-aliasing
110      */
111     bool antiAliasing = false;
112 
113 final:
114     /**
115      * CTor
116      */
117     @nogc
118     this(Geometry geo) pure nothrow {
119         this.geometry = geo;
120     }
121 
122     /**
123      * CTor
124      */
125     this(Geometry geo, Vertex[] vertices) pure nothrow {
126         this(geo);
127 
128         _vertices ~= vertices;
129     }
130 
131     /**
132      * CTor for circles
133      */
134     this(size_t radius, const Vector2f center, size_t vecNum = 30) pure nothrow {
135         import std.math : PI, cos, sin;
136 
137         assert(vecNum >= 10, "Too few edges for a circle");
138 
139         this(Geometry.TriangleFan);
140 
141         enum real PIx2 = PI * 2;
142         immutable float Deg2Rad = PIx2 / vecNum;
143 
144         _vertices.reserve(vecNum);
145 
146         for (size_t i = 0; i < vecNum; i++) {
147             immutable float degInRad = i * Deg2Rad;
148 
149             immutable float x = center.x + cos(degInRad) * radius;
150             immutable float y = center.y + sin(degInRad) * radius;
151 
152             this.append(Vector2f(x, y));
153         }
154     }
155 
156     /**
157      * Clear all Vertices but preserve the storage and capacity
158      */
159     void clear() nothrow {
160         _vertices.length = 0;
161         _vertices.assumeSafeAppend();
162     }
163 
164     /**
165      * Stores a Vertex
166      */
167     void append(ref const Vertex vertex) pure nothrow {
168         _vertices ~= vertex;
169     }
170 
171     /**
172      * Stores a Vertex
173      */
174     void append(const Vector2f vec) pure nothrow {
175         _vertices ~= Vertex(vec);
176     }
177 
178     /**
179      * Stores multiple Vertices
180      */
181     void append(Vertex[] vertices) pure nothrow {
182         _vertices.reserve(vertices.length);
183         _vertices ~= vertices;
184     }
185 
186     /**
187      * Returns all Vertices
188      */
189     @nogc
190     inout(Vertex[]) getVertices() inout pure nothrow {
191         return _vertices;
192     }
193 
194     /**
195      * Set the color of <b>all</b> Vertices
196      *
197      * Note: If you only want to set specific Vertices to a specific color, you should use getVertices()
198      *       and adapt the specific entries.
199      */
200     @nogc
201     void setColor(const Color4b col) pure nothrow {
202         foreach (ref Vertex v; _vertices) {
203             v.color = Color4f(col);
204         }
205     }
206 
207     /**
208      * Set or reset a Texture
209      */
210     @nogc
211     void setTexture(Texture* texture) pure nothrow {
212         _texture = texture;
213         if (texture)
214             this.setTextureRect(Rect(0, 0, texture.width, texture.height));
215     }
216 
217     /**
218      * Set (or reset) a Texture and set the corresponding Rect
219      */
220     @nogc
221     void setTexture(Texture* texture, const Rect rect) pure nothrow {
222         _texture = texture;
223 
224         if (texture)
225             this.setTextureRect(rect);
226     }
227 
228     /**
229      * Returns the current texture or null
230      */
231     @nogc
232     inout(Texture*) getTexture() inout pure nothrow {
233         return _texture;
234     }
235 
236     /**
237      * Set the corresponding Texture Rect
238      */
239     @nogc
240     void setTextureRect(const Rect rect) pure nothrow {
241         assert(_texture, "No texture defined");
242 
243         const Rect clip = this.getVertexRect();
244         foreach (ref Vertex v; _vertices) {
245             immutable float xratio = clip.width > 0 ? (v.position.x - clip.x) / clip.width : 0;
246             immutable float yratio = clip.height > 0 ? (v.position.y - clip.y) / clip.height : 0;
247 
248             v.texCoord.x = (rect.x + rect.width * xratio) / _texture.width;
249             v.texCoord.y = (rect.y + rect.height * yratio) / _texture.height;
250         }
251     }
252 
253     /**
254      * Returns the Rect which contains all vertices
255      */
256     @nogc
257     Rect getVertexRect() const pure nothrow {
258         assert(_vertices.length > 0, "No vertices");
259 
260         float left = _vertices[0].position.x;
261         float top = _vertices[0].position.y;
262         float right = _vertices[0].position.x;
263         float bottom = _vertices[0].position.y;
264 
265         foreach (ref const Vertex v; _vertices[1 .. $]) {
266             // Update left and right
267             if (v.position.x < left)
268                 left = v.position.x;
269             else if (v.position.x > right)
270                 right = v.position.x;
271             // Update top and bottom
272             if (v.position.y < top)
273                 top = v.position.y;
274             else if (v.position.y > bottom)
275                 bottom = v.position.y;
276         }
277 
278         immutable int l = cast(int) left;
279         immutable int t = cast(int) top;
280         immutable uint w = cast(uint)(right - left);
281         immutable uint h = cast(uint)(bottom - top);
282 
283         return Rect(l, t, w, h);
284     }
285 
286     /**
287      * Returns the clip Rect
288      */
289     @nogc
290     Rect getClipRect() const pure nothrow {
291         Rect rect = this.getVertexRect();
292         rect.move(Vector2i(super.getPosition()));
293 
294         return rect;
295     }
296 }