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.Spritesheet;
25 
26 private {
27 	import Dgame.Graphics.Texture;
28 	import Dgame.Graphics.Sprite;
29 	import Dgame.Math.Rect;
30 	import Dgame.System.Clock;
31 }
32 
33 /**
34  * SpriteSheet extends Sprite and has, besides the texture, 
35  * even a viewport and acts as a Texture Atlas.
36  * With slideViewport the viewport slides over the current row of the texture atlas.
37  * With setRow the current row can be changed (increased, decreased).
38  *
39  * Author: rschuett
40  */
41 class Spritesheet : Sprite {
42 	/**
43 	 * The Grid
44 	 */
45 	enum Grid : ubyte {
46 		None = 0, /// No Grid is used
47 		Row = 1,  /// Only Rows are used
48 		Column = 2, /// Only Columns are used
49 		Both = Row | Column /// Both, Columns <b>and</b> Rows are used
50 	}
51 	
52 	/**
53 	 * Set or get the current row.
54 	 * This only matters, if you slide without Grid.Column.
55 	 */
56 	ubyte row;
57 
58 private:
59 	short _loopCount;
60 	ushort _passedLoops;
61 	size_t _lastTick;
62 	ushort _tickOffset;
63 
64 	ShortRect _texView;
65 
66 protected:
67 	override float[8] _getTextureCoordinates() const pure nothrow {
68 		float tx = 0f;
69 		float ty = 0f;
70 		float tw = 1f;
71 		float th = 1f;
72 		
73 		if (!this._texView.isZero()) {
74 			tx = (0f + this._texView.x) / super._tex.width;
75 			ty = (0f + this._texView.y) / super._tex.height;
76 			
77 			if (!this._texView.isCollapsed()) {
78 				tw = (0f + this._texView.width) / this._tex.width;
79 				th = (0f + this._texView.height) / this._tex.height;
80 			}
81 		}
82 
83 		return [tx, ty, tx + tw, ty, tx + tw, ty + th, tx, ty + th];
84 	}
85 
86 public:
87 	/**
88 	 * Returns the current Clip Rect
89 	 */
90 	override ShortRect getClipRect() const pure nothrow {
91 		ushort[2] clipSize = void;
92 
93 		if (this._texView.isCollapsed())
94 			clipSize = [super._tex.width, super._tex.height];
95 		else
96 			clipSize = [this._texView.width, this._texView.height];
97 
98 		return ShortRect(cast(short) super.position.x, cast(short) super.position.y,
99 		                 clipSize[0], clipSize[1]);
100 	}
101 
102 final:
103 	/**
104 	 * CTor
105 	 */
106 	this(Texture tex, short lc = -1) {
107 		super(tex);
108 
109 		this.setLoopCount(lc);
110 	}
111 
112 	/**
113 	 * CTor
114 	 */
115 	this(Texture tex, ref const ShortRect texView, short lc = -1) {
116 		this(tex, lc);
117 
118 		this.setTextureRect(texView);
119 	}
120 	
121 	/**
122 	 * CTor
123 	 * 
124 	 * Rvalue version
125 	 */
126 	this(Texture tex, const ShortRect texView, short lc = -1) {
127 		this(tex, texView, lc);
128 	}
129 
130 	/**
131 	 * Set a Texture Rect.
132 	 * This indicates which area of the Texture is drawn.
133 	 */
134 	void setTextureRect(ref const ShortRect texView) {
135 		this._texView = texView;
136 	}
137 
138 	/**
139 	 * Rvalue version
140 	 */
141 	void setTextureRect(const ShortRect texView) {
142 		this.setTextureRect(texView);
143 	}
144 
145 	/**
146 	 * Fetch the current Texture Rect, so that you can modify it, if you want to.
147 	 * 
148 	 * Example:
149 	 * ----
150 	 * Spritesheet s = new Spritesheet(...);
151 	 * // A lot of code
152 	 * s.fetchTextureRect().collapse();
153 	 * ----
154 	 */
155 	inout(ShortRect)* fetchTextureRect() inout pure nothrow {
156 		return &this._texView;
157 	}
158 
159 	/**
160 	 * Set the current loop count.
161 	 * This specif how often the whole Animation is played.
162 	 * A value of < 0 means: infinite playing.
163 	 */
164 	void setLoopCount(short loopCount) pure nothrow {
165 		this._loopCount = loopCount;
166 		this._passedLoops = 0;
167 	}
168 
169 	/**
170 	 * Set the current tick offset.
171 	 * This is the offset between each animation / slide.
172 	 * Default is 0.
173 	 */
174 	void setTickOffset(ushort offset) pure nothrow {
175 		this._tickOffset = offset;
176 	}
177 
178 	/**
179 	 * Execute the animation N times where N is the number of the current loop count.
180 	 * If N is < 0, the animation runs infinite.
181 	 * 
182 	 * Returns if the loop is still running.
183 	 * 
184 	 * See: Grid
185 	 */
186 	bool execute(Grid grid = Grid.Both) {
187 		if (this._loopCount < 0 || this._loopCount > this._passedLoops) {
188 			this.slideTextureRect(grid);
189 
190 			return true;
191 		}
192 
193 		return false;
194 	}
195 	
196 	/**
197 	 * Slide/move the current Viewport of the Texture.
198 	 * So the next area of the Texture atlas will be drawn.
199 	 * With grid you can decide if both, x and y, or only one of them are updated.
200 	 * Default both are updated.
201 	 * 
202 	 * See: Grid
203 	 */
204 	void slideTextureRect(Grid grid = Grid.Both) in {
205 		assert(this._tex !is null, "No Texture.");
206 	} body {
207 		if ((this._lastTick + this._tickOffset) > Clock.getTicks())
208 			return;
209 
210 		this._lastTick = Clock.getTicks();
211 
212 		const short w = this._texView.width;
213 		const short h = this._texView.height;
214 
215 		if ((grid & Grid.Column) == 0) {
216 			// to avoid a cast...
217 			this._texView.y = this.row;
218 			this._texView.y *= h;
219 		}
220 		
221 		if (grid & Grid.Row) {
222 			if ((this._texView.x + w) < super._tex.width)
223 				this._texView.x += w;
224 			else {
225 				this._texView.x = 0;
226 				if ((grid & Grid.Column) == 0)
227 					this._passedLoops++;
228 			}
229 		}
230 		
231 		if (grid & Grid.Column && this._texView.x == 0) {
232 			if ((this._texView.y + h) < super._tex.height)
233 				this._texView.y += h;
234 			else {
235 				this._texView.y = 0;
236 				this._passedLoops++;
237 			}
238 		}
239 	}
240 }