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 }