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.Graphics.Shape;
25 
26 private {
27 	import std.math : sin, cos, abs;
28 	import std.algorithm : remove, min, max;
29 	import core.stdc..string : memcpy;
30 	
31 	import derelict.opengl3.gl;
32 	
33 	import Dgame.Graphics.Color;
34 	import Dgame.Graphics.Drawable;
35 	import Dgame.Graphics.Transformable;
36 	import Dgame.Graphics.Texture;
37 	import Dgame.Graphics.Blend;
38 	import Dgame.Math.Vertex;
39 	import Dgame.Math.Rect;
40 	import Dgame.System.VertexRenderer;
41 }
42 
43 enum PIx2 = 3.14f * 2;
44 
45 /**
46  * Smooth wrapper
47  */
48 struct Smooth {
49 	/**
50 	 * Supported smooth targets.
51 	 */
52 	enum Target {
53 		None,                     /** No smooth (default). */
54 		Point = GL_POINT_SMOOTH,  /** Enable smooth for points. */
55 		Line  = GL_LINE_SMOOTH,   /** Enable smooth for lines. */
56 		Polygon = GL_POLYGON_SMOOTH /** Enable smooth for polygons. */
57 	}
58 	
59 	/**
60 	 * The smooth mode
61 	 */
62 	enum Mode {
63 		DontCare = GL_DONT_CARE, /** The OpenGL implementation decide on their own. */
64 		Fastest = GL_FASTEST,    /** Fastest kind of smooth (default). */
65 		Nicest  = GL_NICEST      /** Nicest but slowest kind of smooth. */
66 	}
67 	
68 	Target target; /// The Target
69 	Mode mode; /// The Mode
70 	GLenum hint; /// The GL Hint (is automatically set)
71 	
72 	this(Target trg, Mode mode) {
73 		this.target = target;
74 		this.mode = mode;
75 		
76 		final switch (this.target) {
77 			case Target.None: break;
78 			case Target.Point:
79 				this.hint = GL_POINT_SMOOTH_HINT;
80 				break;
81 			case Target.Line:
82 				this.hint = GL_LINE_SMOOTH_HINT;
83 				break;
84 			case Target.Polygon:
85 				this.hint = GL_POLYGON_SMOOTH_HINT;
86 				break;
87 		}
88 	}
89 }
90 
91 private struct Range {
92 	float min, max;
93 }
94 
95 private struct RangePoint {
96 	Range x;
97 	Range y;
98 }
99 
100 private RangePoint min_max(const Vertex[] vertices) pure nothrow {
101 	RangePoint rpoint = RangePoint(Range(vertices[0].x, vertices[0].x),
102 	                               Range(vertices[0].y, vertices[0].y));
103 
104 	for (size_t i = 1; i < vertices.length; i++) {
105 		rpoint.x.min = min(rpoint.x.min, vertices[i].x);
106 		rpoint.x.max = max(rpoint.x.max, vertices[i].x);
107 		rpoint.y.min = min(rpoint.y.min, vertices[i].y);
108 		rpoint.y.max = max(rpoint.y.max, vertices[i].y);
109 	}
110 
111 	return rpoint;
112 }
113 
114 /**
115  * Shape defines a drawable convex shape.
116  * It also defines helper functions to draw simple shapes like lines, rectangles, circles, etc.
117  *
118  * Author: rschuett
119  */
120 class Shape : Transformable, Drawable, Blendable {
121 	/**
122 	 * Supported shape types.
123 	 */
124 	enum Type {
125 		Quad = GL_QUADS,			/** Declare that the stored vertices are Quads. */
126 		QuadStrip = GL_QUAD_STRIP,	/** Declare that the stored vertices are Quad Strips*/
127 		Triangle = GL_TRIANGLES,	/** Declare that the stored vertices are Triangles. */
128 		TriangleStrip = GL_TRIANGLE_STRIP,	/** Declare that the stored vertices are Triangles Strips */
129 		TriangleFan = GL_TRIANGLE_FAN,		/** Declare that the stored vertices are Triangles Fans. */
130 		Lines = GL_LINES,			/** Declare that the stored vertices are Lines. */
131 		LineStrip = GL_LINE_STRIP,	/** Declare that the stored vertices are Line Strips. */
132 		LineLoop = GL_LINE_LOOP,	/** Declare that the stored vertices are Line Loops. */
133 		Polygon = GL_POLYGON,		/** Declare that the stored vertices are Polygons. */
134 		
135 		Unfilled = LineLoop,		/** Unfilled Type. It is identical to LineLoop. */
136 		Circle = TriangleFan		/** Circle Type. It is identical to TriangleFan. */
137 	}
138 	
139 protected:
140 	ubyte _lineWidth;
141 	bool _isFilled = true;
142 	bool _needUpdate;
143 	
144 	Type _type;
145 	Smooth _smooth;
146 	
147 	Vertex[] _vertices;
148 
149 	Texture _tex;
150 	ShortRect _texRect;
151 	Blend _blend;
152 	
153 protected:
154 	void _render() {
155 		if (this._vertices.length == 0)
156 			return;
157 		
158 		glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_COLOR_BUFFER_BIT);
159 		scope(exit) glPopAttrib();
160 		
161 		const bool texEnabled = glIsEnabled(GL_TEXTURE_2D) == GL_TRUE;
162 		if (this._tex is null && texEnabled)
163 			glDisable(GL_TEXTURE_2D);
164 		else if (this._tex !is null && !texEnabled)
165 			glEnable(GL_TEXTURE_2D);
166 		
167 		if (this._smooth.target != Smooth.Target.None) {
168 			if (!glIsEnabled(this._smooth.target))
169 				glEnable(this._smooth.target);
170 			glHint(this._smooth.hint, this._smooth.mode);
171 		}
172 		
173 		if (this._lineWidth > 1)
174 			glLineWidth(this._lineWidth);
175 		
176 		glPushMatrix();
177 		scope(exit) glPopMatrix();
178 		
179 		if (this._needUpdate && this._tex !is null) {
180 			this._needUpdate = false;
181 			this._updateTexCoords();
182 		}
183 
184 		Vertex* vptr = &this._vertices[0];
185 		VertexRenderer.pointTo(Target.Vertex,    vptr, Vertex.sizeof,  0);
186 		VertexRenderer.pointTo(Target.Color,     vptr, Vertex.sizeof, 12);
187 		VertexRenderer.pointTo(Target.TexCoords, vptr, Vertex.sizeof, 28);
188 		
189 		if (this._tex !is null)
190 			this._tex.bind();
191 
192 		if (this._blend !is null)
193 			this._blend.applyBlending();
194 
195 		scope(exit) {
196 			if (this._tex !is null)
197 				this._tex.unbind();
198 			
199 			VertexRenderer.disableAllStates();
200 		}
201 		
202 		super._applyTranslation();
203 		
204 		const Type type = !this.isFilled() && this._tex is null ? Type.Unfilled : this._type;
205 		VertexRenderer.drawArrays(type, this._vertices.length);
206 	}
207 	
208 	final void _updateTexCoords() pure nothrow {
209 		if (this._vertices.length == 0)
210 			return;
211 
212 		RangePoint rpoint = min_max(this._vertices);
213 
214 		const float diff_x = abs(rpoint.x.min - rpoint.x.max);
215 		const float diff_y = abs(rpoint.y.min - rpoint.y.max);
216 
217 		foreach (ref Vertex v; this._vertices) {
218 			v.tx = ((v.x - rpoint.x.min) / diff_x);
219 			v.ty = ((v.y - rpoint.y.min) / diff_y);
220 		}
221 		
222 		if (!this._texRect.isCollapsed()) {
223 			const float tx = (0f + this._texRect.x) / this._tex.width;
224 			const float ty = (0f + this._texRect.y) / this._tex.height;
225 			const float tw = (0f + this._texRect.width) / this._tex.width;
226 			const float th = (0f + this._texRect.height) / this._tex.height;
227 			
228 			foreach (ref Vertex v; this._vertices) {
229 				v.tx = (v.tx * tw) + tx;
230 				v.ty = (v.ty * th) + ty;
231 			}
232 		}
233 	}
234 	
235 public:
236 final:
237 	/**
238 	 * CTor
239 	 */
240 	this(Type type, Texture tex = null) {
241 		this._type = type;
242 		this._smooth = Smooth(Smooth.Target.None, Smooth.Mode.Fastest);
243 		
244 		this.bindTexture(tex);
245 	}
246 	
247 	/**
248 	 * Calculate, store and return the center point.
249 	 */
250 	override ref const(Vector2s) calculateCenter() pure nothrow {
251 		const RangePoint rpoint = min_max(this._vertices);
252 		super.setCenter(cast(short)(rpoint.x.max / 2), cast(short)(rpoint.y.max / 2));
253 
254 		return super.getCenter();
255 	}
256 
257 	/**
258 	 * Set (or reset) the current Blend instance.
259 	 */
260 	void setBlend(Blend blend) pure nothrow {
261 		this._blend = blend;
262 	}
263 
264 	/**
265 	 * Returns the current Blend instance, or null.
266 	 */
267 	inout(Blend) getBlend() inout pure nothrow {
268 		return this._blend;
269 	}
270 	
271 	/**
272 	* Bind (or unbind) a Texture.
273 	*/
274 	void bindTexture(Texture tex) {
275 		this.setColor(Color.White);
276 		
277 		this._tex = tex;
278 		if (tex !is null && this._type == Type.LineLoop)
279 			this._type = Type.Polygon;
280 	}
281 	
282 	/**
283 	 * Set a Texture Rect
284 	 */
285 	void setTextureRect(ref const ShortRect texRect) {
286 		this._texRect = texRect;
287 	}
288 	
289 	/**
290 	 * Rvalue version
291 	 */
292 	void setTextureRect(const ShortRect texRect) {
293 		this.setTextureRect(texRect);
294 	}
295 	
296 	/**
297 	 * Returns a pointer to the Texture Rect.
298 	 * With this you can change the existing Rect without setting a new one.
299 	 * You can e.g. collapse the Rect with this method.
300 	 * Example:
301 	 * ---
302 	 * Shape s = Shape.make(...);
303 	 * // A lot of code
304 	 * s.fetchTextureRect().collapse();
305 	 * ---
306 	 */
307 	inout(ShortRect*) fetchTextureRect() inout pure nothrow {
308 		return &this._texRect;
309 	}
310 	
311 	/**
312 	 * Set target and mode of smoothing.
313 	 */
314 	void setSmooth(Smooth.Target sTarget, Smooth.Mode sMode = Smooth.Mode.Fastest) pure nothrow {
315 		this._smooth.target = sTarget;
316 		this._smooth.mode = sMode;
317 	}
318 
319 	/**
320 	 * Set target and mode of smoothing.
321 	 */
322 	void setSmooth(ref const Smooth smooth) pure nothrow {
323 		this.setSmooth(smooth.target, smooth.mode);
324 	}
325 
326 	/**
327 	 * Return the current smooth
328 	 */
329 	ref const(Smooth) getSmooth() const pure nothrow {
330 		return this._smooth;
331 	}
332 	
333 	/**
334 	 * The current shape will be updated.
335 	 */
336 	void forceUpdate() pure nothrow {
337 		this._needUpdate = true;
338 	}
339 	
340 	/**
341 	 * Set or replace the current Shape type.
342 	 * 
343 	 * See: Shape.Type enum.
344 	 */
345 	void setType(Type type) pure nothrow {
346 		this._type = type;
347 	}
348 	
349 	/**
350 	 * Returns the Shape Type.
351 	 * 
352 	 * See: Shape.Type enum.
353 	 */
354 	Type getType() const pure nothrow {
355 		return this._type;
356 	}
357 	
358 	/**
359 	 * Set for <b>all</b> vertices a (new) color.
360 	 * 
361 	 * Note: This method does not need an update call.
362 	 */
363 	void setColor(ref const Color col) {
364 		foreach (ref Vertex v; this._vertices) {
365 			v.setColor(col);
366 		}
367 	}
368 	
369 	/**
370 	 * Rvalue version
371 	 */
372 	void setColor(const Color col) {
373 		this.setColor(col);
374 	}
375 	
376 	/**
377 	 * Activate fill mode.
378 	 * This means the whole shape is drawn and not only the outlines.
379 	 * 
380 	 * Note: This method does not need an update call.
381 	 */
382 	void fill(bool fill) pure nothrow {
383 		this._isFilled = fill;
384 		if (this._type == Type.LineLoop)
385 			this._type = Type.Polygon;
386 	}
387 	
388 	/**
389 	 * Returns if the fill mode is active or not.
390 	 */
391 	bool isFilled() const pure nothrow {
392 		return this._isFilled;
393 	}
394 	
395 	/**
396 	 * Set the line width.
397 	 * 
398 	 * Note: This method does not need an update call.
399 	 */
400 	void setLineWidth(ubyte width) pure nothrow {
401 		this._lineWidth = width;
402 	}
403 	
404 	/**
405 	 * Returns the line width.
406 	 */
407 	ubyte getLineWidth() const pure nothrow {
408 		return this._lineWidth;
409 	}
410 	
411 	/**
412 	 * Stores a Vertex for this Shape.
413 	 */
414 	void append(ref const Vertex vx) {
415 		this._needUpdate = true;
416 		
417 		this._vertices ~= vx;
418 	}
419 	
420 	/**
421 	 * Rvalue version.
422 	 */
423 	void append(const Vertex vec) {
424 		this.append(vec);
425 	}
426 	
427 	/**
428 	 * Stores multiple Vertices for this Shape.
429 	 */
430 	void append(const Vertex[] vertices) {
431 		this._needUpdate = true;
432 		
433 		this._vertices ~= vertices;
434 	}
435 	
436 	/**
437 	 * Remove the Vertex on the specific index.
438 	 * If vp is not null, the droped Vertex is stored there.
439 	 */
440 	void remove(uint index, Vertex* vp = null) {
441 		if (index >= this._vertices.length)
442 			return;
443 		
444 		this._needUpdate = true;
445 		
446 		if (vp !is null)
447 			.memcpy(vp, &this._vertices[index], Vertex.sizeof);
448 		
449 		this._vertices = .remove(this._vertices, index);
450 	}
451 	
452 	/**
453 	 * Returns all Vertex of this Shape.
454 	 */
455 	const(Vertex[]) getVertices() const pure nothrow {
456 		return this._vertices;
457 	}
458 	
459 	/**
460 	 * Returns the Vertex at the given index
461 	 * or throws an exception, if the index is out of rpoint.
462 	 */
463 	ref const(Vertex) getVertexAt(uint idx) const {
464 		if (idx < this._vertices.length)
465 			return this._vertices[idx];
466 		
467 		throw new Exception("No Vertex at this index.");
468 	}
469 	
470 	/**
471 	 * Returns a pointer of the Vertex at the given index
472 	 * or null if the index is out of rpoint.
473 	 */
474 	inout(Vertex)* fetchVertexAt(uint idx) inout {
475 		return idx < this._vertices.length ? &this._vertices[idx] : null;
476 	}
477 	
478 	/**
479 	* Add an array of floats
480 	* Note that 3 dimensional coordinate components are expected.
481 	*/
482 	static Shape make(Type type, const float[] mat) {
483 		Shape s = new Shape(type);
484 		
485 		const size_t size = mat.length % 3 == 0 ? mat.length : mat.length - (mat.length % 3);
486 		for (size_t i = 0; i < size; i += 3) {
487 			s.append(Vertex(mat[i], mat[i + 1], mat[i + 2]));
488 		}
489 		
490 		return s;
491 	}
492 	
493 	/**
494 	 * Make a new Shape object with the given type and vertices.
495 	 */
496 	static Shape make(Type type, const Vertex[] vertices) {
497 		Shape qs = new Shape(type);
498 		
499 		foreach (ref const Vertex v; vertices) {
500 			qs.append(v);
501 		}
502 		
503 		return qs;
504 	}
505 	
506 	/**
507 	 * Make a new Shape object as Circle.
508 	 */
509 	static Shape makeCircle(ubyte radius, const Vector2f center, ubyte vecNum = 30) in {
510 		assert(vecNum >= 10, "Need at least 10 vectors for a circle.");
511 	} body {
512 		const float Deg2Rad = PIx2 / vecNum;
513 		
514 		Shape s = new Shape(Type.Circle);
515 		
516 		for (ubyte i = 0; i < vecNum; i++) {
517 			const float degInRad = i * Deg2Rad;
518 			
519 			float x = center.x + cos(degInRad) * radius;
520 			float y = center.y + sin(degInRad) * radius;
521 			
522 			s.append(Vertex(x, y));
523 		}
524 		
525 		return s;
526 	}
527 }