View Javadoc

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>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; getMapWidth()</code>
271      * @param y y in this tile map node, must be <code>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; getMapHeight()</code>
285      * @param column column in this tile map node, must be <code>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; getMapWidth()</code>
300      * @param y y in this tile map node, must be <code>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; getMapHeight()</code>
312      * @param column column in this tile map node, must be <code>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; getMapWidth()</code>
325      * @param y y in this tile map node, must be <code>&gt;= 0</code> and <code>&lt; 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>&gt;= 0</code> and <code>&lt; getMapHeight()</code>
338      * @param column column in this tile map node, must be <code>&gt;= 0</code> and <code>&lt; 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 }