1 /*
2
3 dsh-piccolo-tilemap Piccolo2D tile map nodes and supporting classes.
4 Copyright (c) 2006-2013 held jointly by the individual authors.
5
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with this library; if not, write to the Free Software Foundation,
18 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19
20 > http://www.fsf.org/licensing/licenses/lgpl.html
21 > http://www.opensource.org/licenses/lgpl-license.php
22
23 */
24 package org.dishevelled.piccolo.tilemap;
25
26 import org.piccolo2d.PNode;
27
28 import org.piccolo2d.util.PPaintContext;
29
30 import org.dishevelled.functor.UnaryProcedure;
31 import org.dishevelled.functor.TernaryProcedure;
32
33 import org.dishevelled.matrix.Matrix2D;
34
35 import org.dishevelled.piccolo.sprite.Sprite;
36
37 /**
38 * Abstract Piccolo2D tile map node.
39 *
40 * @author Michael Heuer
41 * @version $Revision$ $Date$
42 */
43 public abstract class AbstractTileMap
44 extends PNode
45 {
46 /** Map width, in number of tiles. */
47 private final long mapWidth;
48
49 /** Map height, in number of tiles. */
50 private final long mapHeight;
51
52 /** Tile width. */
53 private final double tileWidth;
54
55 /** Tile height. */
56 private final double tileHeight;
57
58 /** Map of tiles. */
59 private final Matrix2D<Sprite> tileMap;
60
61 /** Flag for validating proxies. */
62 private boolean proxiesInvalid;
63
64 /** Advance procedure. */
65 private static final UnaryProcedure<Sprite> ADVANCE = new UnaryProcedure<Sprite>()
66 {
67 @Override
68 public void run(final Sprite tile)
69 {
70 tile.advance();
71 }
72 };
73
74 /** Create proxy procedure. */
75 private final TernaryProcedure<Long, Long, Sprite> createProxy =
76 new TernaryProcedure<Long, Long, Sprite>()
77 {
78 @Override
79 public void run(final Long row, final Long column, final Sprite tile)
80 {
81 if (tile != null)
82 {
83 double x = column * getTileWidth();
84 double y = row * getTileHeight();
85 createProxy(x, y, tile);
86 }
87 }
88 };
89
90
91 /**
92 * Create a new abstract tile map node.
93 *
94 * @param mapWidth map width in number of tiles, must be at least one
95 * @param mapHeight map height in number of tiles, must be at least one
96 * @param tileWidth tile width, must be greater than or equal to zero
97 * @param tileHeight tile height, must be greater than or equal to zero
98 * @param tileMap map of tiles, must not be null and must have dimensions
99 * <code>mapHeight x mapWidth</code>
100 */
101 protected AbstractTileMap(final long mapWidth,
102 final long mapHeight,
103 final double tileWidth,
104 final double tileHeight,
105 final Matrix2D<Sprite> tileMap)
106 {
107 super();
108
109 if (mapWidth < 1L)
110 {
111 throw new IllegalArgumentException("mapWidth must be >= 1");
112 }
113 if (mapHeight < 1L)
114 {
115 throw new IllegalArgumentException("mapHeight must be >= 1");
116 }
117 if (tileWidth < 0.0d)
118 {
119 throw new IllegalArgumentException("tileWidth must be >= 0.0d");
120 }
121 if (tileHeight < 0.0d)
122 {
123 throw new IllegalArgumentException("tileHeight must be >= 0.0d");
124 }
125 if (tileMap == null)
126 {
127 throw new IllegalArgumentException("tileMap must not be null");
128 }
129 if (tileMap.rows() != mapHeight)
130 {
131 throw new IllegalArgumentException("tileMap rows must be equal to mapHeight");
132 }
133 if (tileMap.columns() != mapWidth)
134 {
135 throw new IllegalArgumentException("tileMap columns must be equal to mapWidth");
136 }
137 this.mapWidth = mapWidth;
138 this.mapHeight = mapHeight;
139 this.tileWidth = tileWidth;
140 this.tileHeight = tileHeight;
141 this.tileMap = tileMap;
142 proxiesInvalid = true;
143
144 setBounds(0.0d, 0.0d, mapWidth * tileWidth, mapHeight * tileHeight);
145 }
146
147
148 /**
149 * Return the map width in number of tiles for this tile map node. The map
150 * width is also the number of columns in this tile map node.
151 *
152 * @return the map width for this tile map node
153 */
154 public final long getMapWidth()
155 {
156 return mapWidth;
157 }
158
159 /**
160 * Return the number of columns for this tile map node.
161 *
162 * @return the number of columns for this tile map node
163 */
164 public final long columns()
165 {
166 return getMapWidth();
167 }
168
169 /**
170 * Return the map height in number of tiles for this tile map node. The map
171 * height is also the number of rows in this tile map node.
172 *
173 * @return the map height for this tile map node
174 */
175 public final long getMapHeight()
176 {
177 return mapHeight;
178 }
179
180 /**
181 * Return the number of rows for this tile map node.
182 *
183 * @return the number of rows for this tile map node
184 */
185 public final long rows()
186 {
187 return getMapHeight();
188 }
189
190 /**
191 * Return the tile width for this tile map node.
192 *
193 * @return the tile width for this tile map node
194 */
195 public final double getTileWidth()
196 {
197 return tileWidth;
198 }
199
200 /**
201 * Return the tile height for this tile map node.
202 *
203 * @return the tile height for this tile map node
204 */
205 public final double getTileHeight()
206 {
207 return tileHeight;
208 }
209
210 /**
211 * Fill this tile map node with the specified tile.
212 *
213 * @param tile tile to fill with
214 */
215 public final void fill(final Sprite tile)
216 {
217 tileMap.assign(tile);
218 invalidateProxies();
219 }
220
221 /**
222 * Fill <code>[0, y]</code> to <code>[getMapWidth(), y]</code> in this tile map node with the specified tile.
223 *
224 * @param y y in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
225 * @param tile tile to fill with
226 */
227 public final void fillY(final long y, final Sprite tile)
228 {
229 fillRow(y, tile);
230 }
231
232 /**
233 * Fill the specified row in this tile map node with the specified tile.
234 *
235 * @param row row in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
236 * @param tile tile to fill with
237 */
238 public final void fillRow(final long row, final Sprite tile)
239 {
240 tileMap.viewRow(row).assign(tile);
241 invalidateProxies();
242 }
243
244 /**
245 * Fill <code>[x, 0]</code> to <code>[x, getMapHeight()]</code> in this tile map node with the specified tile.
246 *
247 * @param x x in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
248 * @param tile tile to fill with
249 */
250 public final void fillX(final long x, final Sprite tile)
251 {
252 fillColumn(x, tile);
253 }
254
255 /**
256 * Fill the specified column in this tile map node with the specified tile.
257 *
258 * @param column column in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
259 * @param tile tile to fill with
260 */
261 public final void fillColumn(final long column, final Sprite tile)
262 {
263 tileMap.viewColumn(column).assign(tile);
264 invalidateProxies();
265 }
266
267 /**
268 * Fill the specified part of this tile map node with the specified tile.
269 *
270 * @param x x in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
271 * @param y y in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
272 * @param width width in number of tiles
273 * @param height height in number of tiles
274 * @param tile tile to fill with
275 */
276 public final void fillXY(final long x, final long y, final long width, final long height, final Sprite tile)
277 {
278 fillRowColumn(y, x, height, width, tile);
279 }
280
281 /**
282 * Fill the specified part of this tile map node with the specified tile.
283 *
284 * @param row row in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
285 * @param column column in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
286 * @param height height in number of tiles
287 * @param width width in number of tiles
288 * @param tile tile to fill with
289 */
290 public final void fillRowColumn(final long row, final long column, final long height, final long width, final Sprite tile)
291 {
292 tileMap.viewPart(row, column, height, width).assign(tile);
293 invalidateProxies();
294 }
295
296 /**
297 * Set the tile at the specified x and y in this tile map node to <code>tile</code>.
298 *
299 * @param x x in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
300 * @param y y in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
301 * @param tile tile
302 */
303 public final void setTileXY(final long x, final long y, final Sprite tile)
304 {
305 setTileRowColumn(y, x, tile);
306 }
307
308 /**
309 * Set the tile at the specified row and column in this tile map node to <code>tile</code>.
310 *
311 * @param row row in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
312 * @param column column in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
313 * @param tile tile
314 */
315 public final void setTileRowColumn(final long row, final long column, final Sprite tile)
316 {
317 tileMap.set(row, column, tile);
318 invalidateProxies();
319 }
320
321 /**
322 * Return the tile at the specified x and y in this tile map node, if any.
323 *
324 * @param x x in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
325 * @param y y in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
326 * @return the tile at the specified x and y in this tile map node, or <code>null</code>
327 * if one does not exist
328 */
329 public final Sprite getTileXY(final long x, final long y)
330 {
331 return getTileRowColumn(y, x);
332 }
333
334 /**
335 * Return the tile at the specified row and column in this tile map node, if any.
336 *
337 * @param row row in this tile map node, must be <code>>= 0</code> and <code>< getMapHeight()</code>
338 * @param column column in this tile map node, must be <code>>= 0</code> and <code>< getMapWidth()</code>
339 * @return the tile at the specified row and column in this tile map node, or <code>null</code>
340 * if one does not exist
341 */
342 public final Sprite getTileRowColumn(final long row, final long column)
343 {
344 return tileMap.get(row, column);
345 }
346
347 /**
348 * Advance this tile map node one frame.
349 */
350 public final void advance()
351 {
352 tileMap.forEachNonNull(ADVANCE);
353 repaint();
354 }
355
356 /**
357 * Invalidate proxies.
358 */
359 private void invalidateProxies()
360 {
361 proxiesInvalid = true;
362 }
363
364 /**
365 * Validate proxies, creating and laying them out where necessary.
366 */
367 private void validateProxies()
368 {
369 removeAllChildren();
370 tileMap.forEach(createProxy);
371
372 proxiesInvalid = false;
373 }
374
375 @Override
376 protected void layoutChildren()
377 {
378 if (proxiesInvalid)
379 {
380 validateProxies();
381 }
382 }
383
384 @Override
385 public void repaint()
386 {
387 if (proxiesInvalid)
388 {
389 validateProxies();
390 }
391 super.repaint();
392 }
393
394 /**
395 * Create and add a new proxy with the specified offset and tile.
396 *
397 * @param x x offset
398 * @param y y offset
399 * @param tile tile
400 */
401 private void createProxy(final double x, final double y, final Sprite tile)
402 {
403 addChild(new Proxy(x, y, tileWidth, tileHeight, tile));
404 }
405
406 // todo: protect addChild and similar
407
408
409 /**
410 * Proxy.
411 */
412 private static class Proxy
413 extends PNode
414 {
415 /** Tile. */
416 private final Sprite tile;
417
418
419 /**
420 * Create a new proxy with the specified offset and tile.
421 *
422 * @param x x offset
423 * @param y y offset
424 * @param width width
425 * @param height height
426 * @param tile tile
427 */
428 Proxy(final double x, final double y, final double width, final double height, final Sprite tile)
429 {
430 super();
431 offset(x, y);
432 setBounds(0.0d, 0.0d, width, height);
433 this.tile = tile;
434 }
435
436
437 @Override
438 protected void paint(final PPaintContext paintContext)
439 {
440 tile.paint(paintContext);
441 }
442
443 // todo: might need to override other paint-related methods
444 }
445 }