package squidpony.squidmath;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

import squidpony.GwtCompatibility;
import squidpony.squidai.AimLimit;
import squidpony.squidai.Reach;
import squidpony.squidgrid.Direction;
import squidpony.squidgrid.Radius;

/**
 * Provides static methods to encode Coords as single primitive ints in various
 * ways, hence the namesake, but also provides advanced methods to encode 2D
 * arrays of various sorts produced by SquidLib in extremely memory-efficient
 * representations, and decode those representations to various types of 2D
 * array on-demand. There's a detailed introduction on the SquidLib wiki,
 * https://github.com/SquidPony/SquidLib/wiki/Handling-Map-Regions-with-CoordPacker
 * , which is probably the best way to learn the techniques possible with this
 * class. Most methods in this aren't useful on their own, but can be mixed and
 * matched to get specific regions from a map, such as all floors not adjacent
 * to a wall, or all grass within 3 squares of deep or shallow water, with walls
 * blocking the distance measurement. You can also use packed data that this
 * class produces as keys for {@code RegionMap} to associate values with
 * regions. <br>
 * NOTE: Internally, this class is atypically complex and low-level for SquidLib
 * because it is attempting to attain some very challenging performance gains.
 * You should not consider it idiomatic SquidLib code or start modifying it
 * unless you have a good grasp of bitwise operations and the performance
 * implications, particularly in regard to memory consumption, that higher-level
 * and more convenient Java programming techniques have. <br>
 * The pack() methods in this class take a 2D array with a clear division
 * between cells in an "on" state and cells in an "off" state, and they produce
 * a very tightly compressed short array that can be losslessly decompressed
 * with the unpack() methods to a boolean 2D array that stores equivalent on/off
 * data to the input. The packMulti() method in this class takes a double 2D
 * array that has more than two states that may need to be encoded, such as an
 * FOV map that stores light level as a value between 0.0 and 1.0 instead of
 * just on or off, and an additional double array that defines what states
 * should be distinguished in the result (for example, if the FOV can store
 * values that differ by 0.1 for a FOV radius of 10, you could pass the array of
 * 10 levels: 0.1, 0.2, 0.3, ... 0.9, 1.0). The value returned by packMulti() is
 * a short[][], but with different array lengths for each sub-array (a jagged
 * array); the length of the short[][] is the same as the length of the levels
 * array, and each sub-array corresponds to a different level of FOV lighting or
 * other gradation as defined in levels. This short[][] can be passed to the
 * unpackMultiByte() method in this class to produce a byte 2D array where the
 * original levels correspond to progressively greater bytes, with 0 used for
 * cells that were less than the smallest value in levels, 1 for values that
 * were only greater than the smallest value, and no others, in levels, then 2
 * for larger values, etc. until it places a byte with a value equal to the
 * length of levels in the cells that are the highest. There is also the
 * unpackMultiDouble() method in this class that takes the same short[][]
 * unpackMultiByte() can take, but also takes a levels double array that should
 * be the same as the one used to compress short[][]. It will return a double 2D
 * array with any cells that were smaller than the smallest value in levels
 * assigned 0.0, and any other cells will be assigned a double that corresponds
 * to the highest value in levels that does not exceed the original double at
 * that location in the unpacked data. To make this more clear, if you have 4
 * levels: [0.25, 0.5, 0.75, 1.0] and you packMulti() on an FOV with a large
 * radius and sample values 0.1, 0.45, 0.8, 1.0, you will get a packed short[][]
 * with 4 sub-arrays to match the 4 levels. If you then pass the short[][] and
 * levels to unpackMultiDouble later, much of the same radius will be filled,
 * but because the sample value 0.1 was less than the smallest value in levels,
 * its cell will be given 0.0. What was originally 0.45 will be given the
 * next-lower levels value, 0.25; 0.8 will be given 0.75, and 1.0 will remain
 * 1.0. <br>
 * This compression is meant to produce a short[] or short[][] that uses as
 * little memory as possible for the specific case of compressing maps with
 * these qualities:
 * <ul>
 * <li>Maps are not especially large for a grid-based game; the maximum size is
 * 256x256 cells.</li>
 * <li>The vast majority of that 256x256 space is either unused or filled with
 * cells no greater than 0.</li>
 * <li>The cells that are greater than 0 are mostly near each other, though
 * separate areas are possible.</li>
 * </ul>
 * These properties are all shared by typical roguelike FOV maps, and the
 * specificity of these circumstances mean extraordinarily dense compression can
 * be achieved using the right combination of algorithms. In early testing,
 * using dungeon maps generated by
 * {@link squidpony.squidgrid.mapping.DungeonGenerator} that should be typical
 * of roguelike maps and a diamond-shaped FOV with radius 8, compression of the
 * short[] returned by pack() vs. the original double[][] (which wastefully
 * represents 2 states with 8 bytes) yields average memory usage ratios between
 * (with relatively optimal parameters) 0.0001237905030818498 in one of the best
 * cases, and (with some very poor parameters for the dungeon, but still using a
 * realistic FOV map) 0.003135985198889917 in one of the worst. <br>
 * This table shows the results for the average of 100 runs of pack() in a map
 * with a "good size" and 100 runs in a map with a "bad size." Both the
 * compression ratio vs. a double[][] that stores only whether a cell is on or
 * off and a boolean[][] that stores the same information are provided. <table
 * BORDER CELLPADDING=3 CELLSPACING=1> <caption>Memory Performance of
 * CoordPacker</caption>
 * <tr>
 * <th></th>
 * <th>Bytes of RAM used, double 2D array</th>
 * <th>Bytes of RAM used, boolean 2D array</th>
 * <th>Average Bytes of RAM used, short 1D array (packed)</th>
 * <th>Compression ratio, packed vs. doubles</th>
 * <th>Compression ratio, packed vs. booleans</th>
 * </tr>
 * <tr>
 * <td>240x240 dungeon map (good size)</td>
 * <td>464656</td>
 * <td>61456</td>
 * <td>57.52</td>
 * <td>0.0001237905030818498</td>
 * <td>0.000935954178599323</td>
 * </tr>
 * <tr>
 * <td>30x70 dungeon map (bad size)</td>
 * <td>17296</td>
 * <td>2656</td>
 * <td>54.24</td>
 * <td>0.003135985198889917</td>
 * <td>0.020421686746987953</td>
 * </tr>
 * </table>
 * In the best-case scenario of packing a 240x240 double array to a short array
 * encoding two states, the result uses less than 1/8000 the memory that the
 * input uses. Writing to disk can store both input and output more efficiently,
 * but the method used here should ensure that even encoding the input FOV map
 * as a flat sequence of single bits and compressing the file should still be on
 * par with the output of pack() due to optimization to ensure nearby cells on a
 * map are compressed together. <br>
 * The technique used by this class is to walk along a Hilbert Curve, storing
 * whether the walk is traveling through "on" or "off" cells, which can be
 * determined by a comparison to a number or a boolean, then encoding alternate
 * shorts into the short[] to be returned, with even-number indices (starting at
 * 0) in the array corresponding to the number of contiguous cells walked
 * through in the "off" state, and odd-number indices corresponding to the
 * number of contiguous cells walked through in the "on" state. A user of this
 * library does not need to understand the details and properties of this
 * algorithm unless they want to generate maps that will compress more
 * optimally. In short:
 * <ul>
 * <li>Smaller maps tend to be processed faster by pack(), since the nature of a
 * Hilbert Curve means a map that fits in one half the width and one half the
 * height of the curve only needs to walk one quarter of the Curve to get all
 * the needed information.</li>
 * <li>Smaller maps also compress less optimally ratio-wise than larger maps
 * with the same area of "on" cells. The compression ratio approaches its best
 * when using very large maps, such as 240x240, and encoding just a few cells on
 * that map (such as for a small FOV radius or a cramped room). A map that is
 * entirely "off" uses only 16 bytes of RAM (the minimum for any array on the
 * JVM).</li>
 * <li>Unusually shaped maps can cause compression problems by forcing adjacent
 * cells to sometimes require walking more cells than needed to get to an
 * adjacent cell. For example, a map greater than 64 cells tall, but less than
 * 33 cells wide, has properties that require walking through a large empty area
 * to get to sometimes only a few cells that are "on" before it walks back
 * through empty space. Similarly, a map that is greater than 128 cells tall but
 * is otherwise narrow has the same property of requiring walking through empty
 * space, but also requires the entire Curve to be walked even if the map's
 * width is only a tiny fraction of the Curve's 256 cells.</li>
 * </ul>
 * <b>In shorter-than-short</b>, you'll get particularly good results for
 * compression speed and compressed size with maps approximately these sizes:
 * 240x240, 240x120, 120x120, 60x120, 60x60, 60x30, 30x30. The biggest maps have
 * the best relative gain on compressed memory usage, and the smallest maps have
 * the best compression speed. <br>
 * The details of the algorithm are not terribly complex once you understand the
 * Hilbert Curve. The simplified version of the Hilbert Curve that SquidLib
 * employs is essentially a path through a square grid (it must have side
 * lengths that are powers of 2, and SquidLib always uses 256), starting in the
 * corner cell (x=0,y=0), ending in the corner cell (x=0,y=255), and traversing
 * every other cell on the grid along its path without ever traveling in a loop,
 * crossing the path it walked, or moving in any direction but one cell up,
 * down, left, or right. The shape of the path this takes has the useful
 * property of keeping most groups of cells walked through with similar x and y
 * at similar distances traveled from the start of the curve, and most groups of
 * cells with very dissimilar x and y at very different distances traveled.
 * Since FOV and several other things you might want to encode with CoordPacker
 * tends to be clustered in small areas and occupy more complicated shapes than
 * straight lines due to dungeon layout blocking sections of FOV, the simplest
 * paths of a wide zigzag from side-to-side, or an outward-going-in spiral, have
 * rather poor behavior when determining how much of an area they pass through
 * contiguously. The contiguous area trait is important because of the next
 * step: Run-Length Encoding. <br>
 * Run-Length Encoding is much simpler to explain than the Hilbert Curve,
 * especially without visual aids. In the version SquidLib uses, only on or off
 * states need to be recorded, so the method used here is smaller and more
 * efficient than most methods that need to store repeated characters in strings
 * (and letters, numbers, and punctuation clearly have more than 2 states). The
 * technique works like this: <br>
 * Start in the "off" state, walk down the Hilbert Curve counting how many cells
 * you walk through that are still "off," and when you encounter a cell that is
 * "on," you write down how many cells were off, transition to the "on" state.
 * Now keep walking the Hilbert Curve, but counting how many cells you walk
 * through that are still "on." When you reach an "off" cell, write down how
 * many were "on," then start walking and counting again, with your count
 * starting at 0. Repeat until you reach the end of the Hilbert Curve, but if
 * you reach the end while counting "off" cells, you don't need to write down
 * that number (a shortcut allows many maps to stop sooner than the 65,536th
 * element of the Curve). <br>
 * There are some additional traits that relate to the edge of the map being
 * treated as "off" even though no calculations are done for cells out of map
 * bounds, and some optimizations that ensure that maps that are smaller than a
 * half, a quarter, or an eighth of the 256x256 curve in both dimensions (and
 * sometimes just one) only need to walk a portion of the Hilbert Curve and
 * simply skip the rest without walking it. <br>
 * The Hilbert Curve has not been definitively proven to be the best possible
 * path to ensure 1D distance and 2D location are similar, but it has been
 * extensively used for tasks that require similar locations for similar
 * distances (in particular, it has become useful in supercomputing clusters for
 * allocating related work to physically nearby machines), and since there
 * hasn't been anything with better spatial properties discovered yet, this
 * technique should remain useful for some time. <br>
 * Created by Tommy Ettinger on 10/1/2015.
 * 
 * @author Tommy Ettinger
 */
public class CoordPacker {
	public static final int DEPTH = 8;
	private static final int BITS = DEPTH << 1;

	public static short[] hilbertX = new short[0x10000], hilbertY = new short[0x10000],
			hilbertDistances = new short[0x10000], mooreX = new short[0x100], mooreY = new short[0x100],
			mooreDistances = new short[0x100], hilbert3X = new short[0x200], hilbert3Y = new short[0x200],
			hilbert3Z = new short[0x200], hilbert3Distances = new short[0x200], ALL_WALL = new short[0],
			ALL_ON = new short[] { 0, -1 };
	static {
		Coord c;
		for (int i = 0; i < 0x10000; i++) {
			c = hilbertToCoordNoLUT(i);
			hilbertX[i] = (short) c.x;
			hilbertY[i] = (short) c.y;
			hilbertDistances[c.x + (c.y << 8)] = (short) i;
		}

		for (int x = 0; x < 8; x++) {
			for (int y = 0; y < 8; y++) {
				for (int z = 0; z < 8; z++) {
					computeHilbert3D(x, y, z);
				}
			}
		}

		for (int i = 64; i < 128; i++) {
			mooreX[i - 64] = hilbertX[i];
			mooreY[i - 64] = hilbertY[i];
			mooreDistances[mooreX[i - 64] + (mooreY[i - 64] << 4)] = (short) (i - 64);

			mooreX[i] = hilbertX[i];
			mooreY[i] = (short) (hilbertY[i] + 8);
			mooreDistances[mooreX[i] + (mooreY[i] << 4)] = (short) (i);

			mooreX[i + 64] = (short) (15 - hilbertX[i]);
			mooreY[i + 64] = (short) (15 - hilbertY[i]);
			mooreDistances[mooreX[i + 64] + (mooreY[i + 64] << 4)] = (short) (i + 64);

			mooreX[i + 128] = (short) (15 - hilbertX[i]);
			mooreY[i + 128] = (short) (7 - hilbertY[i]);
			mooreDistances[mooreX[i + 128] + (mooreY[i + 128] << 4)] = (short) (i + 128);
		}
	}

	private CoordPacker() {
	}

	/**
	 * Compresses a double[][] (typically one generated by
	 * {@link squidpony.squidgrid.FOV}) that only stores two relevant states (one of
	 * which should be 0 or less, the other greater than 0), returning a short[] as
	 * described in the {@link CoordPacker} class documentation. This short[] can be
	 * passed to CoordPacker.unpack() to restore the relevant states and their
	 * positions as a boolean[][] (with false meaning 0 or less and true being any
	 * double greater than 0). As stated in the class documentation, the compressed
	 * result is intended to use as little memory as possible for most roguelike FOV
	 * maps. To avoid floating-point number comparison issues, this actually needs
	 * doubles to be greater than 0.0001, which should never cause incorrect
	 * behavior with FOV's double[][] maps. <br>
	 * <b>To store more than two states</b>, you should use packMulti().
	 *
	 * @param map
	 *            a double[][] that probably was returned by FOV. If you obtained a
	 *            double[][] from DijkstraMap, it will not meaningfully compress
	 *            with this method.
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(double[][] map) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}

		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			current = map[hilbertX[i]][hilbertY[i]] > 0.0001;
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a double[][] (typically one generated by
	 * {@link squidpony.squidai.DijkstraMap}) that only stores two relevant states
	 * (one of which should be equal to or less than threshold, the other greater
	 * than threshold), returning a short[] as described in the {@link CoordPacker}
	 * class documentation. This short[] can be passed to CoordPacker.unpack() to
	 * restore the relevant states and their positions as a boolean[][] (with true
	 * meaning threshold or less and false being any double greater than threshold).
	 * As stated in the class documentation, the compressed result is intended to
	 * use as little memory as possible for most roguelike FOV maps, but here is
	 * also useful for compressing physical maps and gradient maps from DijkstraMap.
	 * <br>
	 * <b>To store more than two states</b>, you should use packMulti().
	 *
	 * @param map
	 *            a double[][] that probably relates in some way to DijkstraMap.
	 * @param threshold
	 *            upper inclusive; any double greater than this will be off, any
	 *            equal or less will be on
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(double[][] map, double threshold) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}

		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			current = map[hilbertX[i]][hilbertY[i]] <= threshold;
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a double[][] (typically one generated by
	 * {@link squidpony.squidai.DijkstraMap}) that only stores two relevant states
	 * (a state for values between lowerBound (inclusive) and upperBound
	 * (exclusive), and another state for anything else), returning a short[] as
	 * described in the {@link CoordPacker} class documentation. This short[] can be
	 * passed to CoordPacker.unpack() to restore the relevant states and their
	 * positions as a boolean[][] (with true meaning between the bounds and false
	 * being anything outside them). As stated in the class documentation, the
	 * compressed result is intended to use as little memory as possible for most
	 * roguelike FOV maps, but here is also useful for compressing physical maps and
	 * gradient maps from DijkstraMap. <br>
	 * <b>To store more than two states</b>, you should use packMulti().
	 *
	 * @param map
	 *            a double[][] that probably relates in some way to DijkstraMap.
	 * @param lowerBound
	 *            lower inclusive; any double lower than this will be off, any equal
	 *            to or greater than this, but less than upper, will be on
	 * @param upperBound
	 *            upper exclusive; any double greater than this will be off, any
	 *            doubles both less than this and equal to or greater than lower
	 *            will be on
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(double[][] map, double lowerBound, double upperBound) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}

		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			current = map[hilbertX[i]][hilbertY[i]] >= lowerBound && map[hilbertX[i]][hilbertY[i]] < upperBound;
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a byte[][] (typically one generated by an FOV-like method) that
	 * only stores two relevant states (one of which should be 0 or less, the other
	 * greater than 0), returning a short[] as described in the {@link CoordPacker}
	 * class documentation. This short[] can be passed to CoordPacker.unpack() to
	 * restore the relevant states and their positions as a boolean[][] (with false
	 * meaning 0 or less and true being any byte greater than 0). As stated in the
	 * class documentation, the compressed result is intended to use as little
	 * memory as possible for most roguelike FOV maps. <br>
	 * <b>To store more than two states</b>, you should use packMulti().
	 *
	 * @param map
	 *            a byte[][] that probably was returned by an FOV-like method.
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(byte[][] map) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}

		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			current = map[hilbertX[i]][hilbertY[i]] > 0;
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a boolean[][], returning a short[] as described in the
	 * {@link CoordPacker} class documentation. This short[] can be passed to
	 * CoordPacker.unpack() to restore the relevant states and their positions as a
	 * boolean[][] As stated in the class documentation, the compressed result is
	 * intended to use as little memory as possible for most roguelike FOV maps.
	 *
	 * @param map
	 *            a boolean[][] that should ideally be mostly false.
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(boolean[][] map) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}
		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			current = map[hilbertX[i]][hilbertY[i]];
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a char[][] (typically one generated by a map generating method) so
	 * only the cells that equal the yes parameter will be encoded as "on",
	 * returning a short[] as described in the {@link CoordPacker} class
	 * documentation. This short[] can be passed to CoordPacker.unpack() to restore
	 * the positions of chars that equal the parameter yes as a boolean[][] (with
	 * false meaning not equal and true equal to yes). As stated in the class
	 * documentation, the compressed result is intended to use as little memory as
	 * possible for most roguelike FOV maps, but this will typically not be used for
	 * FOV (more typical uses are for walls, floors, and so on). This can still be
	 * useful for certain kinds of processing that can be done more efficiently on
	 * packed data than on 2D arrays, like unions, intersections, and random
	 * elements or subsets.
	 *
	 * @param map
	 *            a char[][] that may contain some area of cells that you want
	 *            stored as packed data
	 * @param yes
	 *            the char to encode as "on" in the result; all others are encoded
	 *            as "off"
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(char[][] map, char yes) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}

		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			current = map[hilbertX[i]][hilbertY[i]] == yes;
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a char[][] (typically one generated by a map generating method) so
	 * only the cells that are contained in the yes parameter will be encoded as
	 * "on", returning a short[] as described in the {@link CoordPacker} class
	 * documentation. This short[] can be passed to CoordPacker.unpack() to restore
	 * the positions of chars that equal the parameter yes as a boolean[][] (with
	 * false meaning not equal and true equal to yes). As stated in the class
	 * documentation, the compressed result is intended to use as little memory as
	 * possible for most roguelike FOV maps, but this will typically not be used for
	 * FOV (more typical uses are for walls, floors, and so on). This can still be
	 * useful for certain kinds of processing that can be done more efficiently on
	 * packed data than on 2D arrays, like unions, intersections, and random
	 * elements or subsets.
	 *
	 * @param map
	 *            a char[][] that may contain some area of cells that you want
	 *            stored as packed data
	 * @param yes
	 *            the vararg or array of chars to encode as "on" in the result; all
	 *            others are encoded as "off"
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(char[][] map, char... yes) {
		if (yes == null || yes.length == 0)
			return ALL_WALL;
		if (yes.length == 1)
			return pack(map, yes[0]);
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}
		char c;
		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			c = map[hilbertX[i]][hilbertY[i]];
			current = false;
			for (int j = 0; j < yes.length; j++) {
				if (yes[j] == c) {
					current = true;
					break;
				}
			}
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a int[][] (typically one generated by
	 * MixedGenerator.getEnvironment()) so only the cells that equal the yes
	 * parameter will be encoded as "on", returning a short[] as described in the
	 * {@link CoordPacker} class documentation. This short[] can be passed to
	 * CoordPacker.unpack() to restore the positions of ints that equal the
	 * parameter yes as a boolean[][] (with false meaning not equal and true equal
	 * to yes). As stated in the class documentation, the compressed result is
	 * intended to use as little memory as possible for most roguelike FOV maps, but
	 * this will typically not be used for FOV (more typical uses are for walls,
	 * floors, and so on). This can still be useful for certain kinds of processing
	 * that can be done more efficiently on packed data than on 2D arrays, like
	 * unions, intersections, and random elements or subsets.
	 *
	 * @param map
	 *            a int[][] that may contain some area of cells that you want stored
	 *            as packed data
	 * @param yes
	 *            the int to encode as "on" in the result; all others are encoded as
	 *            "off"
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(int[][] map, int yes) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}

		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			current = map[hilbertX[i]][hilbertY[i]] == yes;
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Compresses a int[][] (typically one generated by
	 * MixedGenerator.getEnvironment()) so only the cells that are contained in the
	 * yes parameter will be encoded as "on", returning a short[] as described in
	 * the {@link CoordPacker} class documentation. This short[] can be passed to
	 * CoordPacker.unpack() to restore the positions of ints that equal the
	 * parameter yes as a boolean[][] (with false meaning not equal and true equal
	 * to yes). As stated in the class documentation, the compressed result is
	 * intended to use as little memory as possible for most roguelike FOV maps, but
	 * this will typically not be used for FOV (more typical uses are for walls,
	 * floors, and so on). This can still be useful for certain kinds of processing
	 * that can be done more efficiently on packed data than on 2D arrays, like
	 * unions, intersections, and random elements or subsets.
	 *
	 * @param map
	 *            a int[][] that may contain some area of cells that you want stored
	 *            as packed data
	 * @param yes
	 *            the vararg or array of ints to encode as "on" in the result; all
	 *            others are encoded as "off"
	 * @return a packed short[] that should, in most circumstances, be passed to
	 *         unpack() when it needs to be used.
	 */
	public static short[] pack(int[][] map, int... yes) {
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.pack() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, anyAdded = false, current;
		int skip = 0, limit = 0x10000, mapLimit = xSize * ySize;
		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}
		int c;
		for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip++) {
			if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
				if (on) {
					on = false;
					packing.add((short) skip);
					skip = 0;
					anyAdded = true;
				}
				continue;
			}
			ml++;
			c = map[hilbertX[i]][hilbertY[i]];
			current = false;
			for (int j = 0; j < yes.length; j++) {
				if (yes[j] == c) {
					current = true;
					break;
				}
			}
			if (current != on) {
				packing.add((short) skip);
				skip = 0;
				on = current;
				anyAdded = true;
			}
		}
		if (on)
			packing.add((short) skip);
		else if (!anyAdded)
			return ALL_WALL;
		return packing.toArray();
	}

	/**
	 * Given a number of total levels to consider separate in a double[][] such as
	 * an FOV result, this produces a levels array that can be passed to packMulti()
	 * to ensure that you have the requested number of separate levels in the
	 * multi-packed result. For example, if you pass 6 to this method, it will
	 * return a length-6 double array, and if you pass that as the levels parameter
	 * to packMulti(), then that method will return a length-6 array of short arrays
	 * that each encode a region that met a different minimum value in the
	 * originally packed double[][]. The behavior of this method causes any doubles
	 * that are closer to 1.0 / totalLevels than they are to 0.0 to be packed as
	 * "on" in at least one of packMulti()'s resultant sub-arrays. This allows
	 * Radius.CIRCLE or similar FOV that produces cells with values that aren't
	 * evenly distributed between 0.0 and 1.0 to be used without causing an
	 * explosion in the number of required levels. <br>
	 * <b>This method should not be used to generate levels for unpacking; it is
	 * only intended for packing.</b> Use the similar method generateLightLevels()
	 * to generate a levels array that is suitable for unpacking FOV.
	 * 
	 * @param totalLevels
	 *            the number of separate levels to group doubles into
	 * @return a double[] suitable as a levels parameter for packMulti()
	 */
	public static double[] generatePackingLevels(int totalLevels) {
		if (totalLevels > 63 || totalLevels <= 0)
			throw new UnsupportedOperationException(
					"Bad totalLevels; should be 0 < totalLevels < 64 but was given " + totalLevels);

		double[] levels = new double[totalLevels];
		for (int i = 0; i < totalLevels; i++) {
			levels[i] = 1.0 * i / totalLevels + 0.5 / totalLevels;
		}
		return levels;
	}

	/**
	 * Given a number of total levels to consider separate in a double[][] such as
	 * an FOV result, this produces a levels array that can be passed to
	 * unpackMultiDouble() to ensure that the minimum double returned for an "on"
	 * cell is 1.0 / totalLevels, and every progressively tighter level in the
	 * short[][] being unpacked will be close to a multiple of that minimum double
	 * value. This only applies to "on" cells; any cells that did not meet a minimum
	 * value when packed will still be 0.0. For example, if you pass 6 to this
	 * method, it will return a length-6 double array, and if you pass that as the
	 * levels parameter to unpackMultiDouble(), then that method will return a
	 * double[][] with no more than totalLevels + 1 used values, ranging from 0.0 to
	 * 1.0 with evenly spaced values, all multiples of 1.0 / totalLevels, in
	 * between. <br>
	 * <b>This method should not be used to generate levels for packing; it is only
	 * intended for unpacking.</b> Use the similar method generatePackingLevels() to
	 * generate a levels array that is suitable for packing double[][] values.
	 * 
	 * @param totalLevels
	 *            the number of separate levels to assign doubles; this MUST match
	 *            the size of the levels parameter used to pack a double[][] with
	 *            packMulti() if this is used to unpack that data
	 * @return a double[] suitable as a levels parameter for unpackMultiDouble()
	 */
	public static double[] generateLightLevels(int totalLevels) {
		if (totalLevels > 63 || totalLevels <= 0)
			throw new UnsupportedOperationException(
					"Bad totalLevels; should be 0 < totalLevels < 64 but was given " + totalLevels);

		double[] levels = new double[totalLevels];
		for (int i = 0; i < totalLevels; i++) {
			levels[i] = (i + 1.0) / totalLevels;
		}
		return levels;
	}

	/**
	 * Compresses a double[][] (typically one generated by
	 * {@link squidpony.squidgrid.FOV}) that stores any number of states and a
	 * double[] storing up to 63 states, ordered from lowest to highest, returning a
	 * short[][] as described in the {@link CoordPacker} class documentation. This
	 * short[][] can be passed to CoordPacker.unpackMultiDouble() to restore the
	 * state at a position to the nearest state in levels, rounded down, and return
	 * a double[][] that should preserve the states as closely as intended for most
	 * purposes. <b>For compressing FOV, you should generate levels with
	 * CoordPacker.generatePackingLevels()</b> instead of manually creating the
	 * array, because some imprecision is inherent in floating point math and
	 * comparisons are often incorrect between FOV with multiple levels and exact
	 * levels generated as simply as possible. generatePackingLevels() adds a small
	 * correction to the levels to compensate for floating-point math issues, which
	 * shouldn't affect the correctness of the results for FOV radii under 100. <br>
	 * As stated in the class documentation, the compressed result is intended to
	 * use as little memory as possible for most roguelike FOV maps. <br>
	 * <b>To store only two states</b>, you should use pack(), unless the double[][]
	 * divides data into on and off based on a relationship to some number other
	 * than 0.0. To (probably poorly) pack all the walls (and any cells with values
	 * higher than DijkstraMap.WALL) in a DijkstraMap's 2D array of doubles called
	 * dijkstraArray, you could call
	 * <code>packMulti(dijkstraArray, new double[]{DijkstraMap.WALL});</code> Then,
	 * you would use only the one sub-element of the returned short[][].
	 *
	 * @param map
	 *            a double[][] that probably was returned by FOV. If you obtained a
	 *            double[][] from DijkstraMap, it will not meaningfully compress
	 *            with this method unless you have very specific needs.
	 * @param levels
	 *            a double[] starting with the lowest value that should be counted
	 *            as "on" (the outermost cells of an FOV map that has multiple
	 *            grades of brightness would be counted by this) and ascending until
	 *            the last value; the last value should be highest (commonly 1.0 for
	 *            FOV), and will be used for any cells higher than all the other
	 *            levels values. An example is an array of: 0.25, 0.5, 0.75, 1.0
	 * @return a packed short[][] that should, in most circumstances, be passed to
	 *         unpackMultiDouble() or unpackMultiByte() when it needs to be used.
	 *         The 2D array will be jagged with an outer length equal to the length
	 *         of levels and sub-arrays that go from having longer lengths early on
	 *         to very compact lengths by the end of the short[][].
	 */
	public static short[][] packMulti(double[][] map, double[] levels) {
		if (levels == null || levels.length == 0)
			throw new UnsupportedOperationException("Must be given at least one level");
		if (levels.length > 63)
			throw new UnsupportedOperationException(
					"Too many levels to efficiently pack; should be less than 64 but was given " + levels.length);
		if (map == null || map.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.packMulti() must be given a non-empty array");
		int xSize = map.length, ySize = map[0].length;
		if (xSize > 256 || ySize > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		int limit = 0x10000, llen = levels.length, mapLimit = xSize * ySize;
		long on = 0, current = 0;
		ShortVLA[] packing = new ShortVLA[llen];
		int[] skip = new int[llen];

		if (ySize <= 128) {
			limit >>= 1;
			if (xSize <= 128) {
				limit >>= 1;
				if (xSize <= 64) {
					limit >>= 1;
					if (ySize <= 64) {
						limit >>= 1;
						if (ySize <= 32) {
							limit >>= 1;
							if (xSize <= 32) {
								limit >>= 1;
							}
						}
					}
				}
			}
		}
		short[][] packed = new short[llen][];
		for (int l = 0; l < llen; l++) {
			packing[l] = new ShortVLA(64);
			boolean anyAdded = false;
			for (int i = 0, ml = 0; i < limit && ml < mapLimit; i++, skip[l]++) {
				if (hilbertX[i] >= xSize || hilbertY[i] >= ySize) {
					if ((on & (1L << l)) != 0L) {
						on ^= (1L << l);
						packing[l].add((short) skip[l]);
						skip[l] = 0;
						anyAdded = true;
					}
					continue;
				}
				ml++;
				// sets the bit at position l in current to 1 if the following is true, 0 if it
				// is false:
				// map[hilbertX[i]][hilbertY[i]] > levels[l]
				// looks more complicated than it is.
				current ^= ((map[hilbertX[i]][hilbertY[i]] > levels[l] ? -1 : 0) ^ current) & (1 << l);
				if (((current >> l) & 1L) != ((on >> l) & 1L)) {
					packing[l].add((short) skip[l]);
					skip[l] = 0;
					on = current;

					// sets the bit at position l in on to the same as the bit at position l in
					// current.
					on ^= (-((current >> l) & 1L) ^ on) & (1L << l);

					anyAdded = true;
				}
			}

			if (((on >> l) & 1L) == 1L) {
				packing[l].add((short) skip[l]);
				anyAdded = true;
			}
			if (!anyAdded)
				packed[l] = ALL_WALL;
			else
				packed[l] = packing[l].toArray();
		}
		return packed;
	}

	/**
	 * Decompresses a short[] returned by pack() or a sub-array of a short[][]
	 * returned by packMulti(), as described in the {@link CoordPacker} class
	 * documentation. This returns a boolean[][] that stores the same values that
	 * were packed if the overload of pack() taking a boolean[][] was used. If a
	 * double[][] was compressed with pack(), the boolean[][] this returns will have
	 * true for all values greater than 0 and false for all others. If this is one
	 * of the sub-arrays compressed by packMulti(), the index of the sub-array will
	 * correspond to an index in the levels array passed to packMulti(), and any
	 * cells that were at least equal to the corresponding value in levels will be
	 * true, while all others will be false. Width and height do not technically
	 * need to match the dimensions of the original 2D array, but under most
	 * circumstances where they don't match, the data produced will be junk.
	 * 
	 * @param packed
	 *            a short[] encoded by calling one of this class' packing methods on
	 *            a 2D array.
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width.
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height.
	 * @return a boolean[][] storing which cells encoded by packed are on (true) or
	 *         off (false).
	 */
	public static boolean[][] unpack(short[] packed, int width, int height) {
		if (packed == null)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.unpack() must be given a non-null array");
		boolean[][] unpacked = new boolean[width][height];
		if (packed.length == 0)
			return unpacked;
		boolean on = false;
		int idx = 0;
		short x = 0, y = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int toSkip = idx + (packed[p] & 0xffff); idx < toSkip && idx < 0x10000; idx++) {
					x = hilbertX[idx];
					y = hilbertY[idx];
					if (x >= width || y >= height)
						continue;
					unpacked[x][y] = true;
				}
			} else {
				idx += packed[p] & 0xffff;
			}
		}
		return unpacked;
	}

	/**
	 * Decompresses a short[] returned by pack() or a sub-array of a short[][]
	 * returned by packMulti(), as described in the {@link CoordPacker} class
	 * documentation. This returns a double[][] that stores 1.0 for true and 0.0 for
	 * false if the overload of pack() taking a boolean[][] was used. If a
	 * double[][] was compressed with pack(), the double[][] this returns will have
	 * 1.0 for all values greater than 0 and 0.0 for all others. If this is one of
	 * the sub-arrays compressed by packMulti(), the index of the sub-array will
	 * correspond to an index in the levels array passed to packMulti(), and any
	 * cells that were at least equal to the corresponding value in levels will be
	 * 1.0, while all others will be 0.0. Width and height do not technically need
	 * to match the dimensions of the original 2D array, but under most
	 * circumstances where they don't match, the data produced will be junk.
	 * 
	 * @param packed
	 *            a short[] encoded by calling one of this class' packing methods on
	 *            a 2D array.
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width.
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height.
	 * @return a double[][] storing which cells encoded by packed are on (1.0) or
	 *         off (0.0).
	 */
	public static double[][] unpackDouble(short[] packed, int width, int height) {
		if (packed == null)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.unpack() must be given a non-null array");
		double[][] unpacked = new double[width][height];
		if (packed.length == 0)
			return unpacked;
		boolean on = false;
		int idx = 0;
		short x = 0, y = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int toSkip = idx + (packed[p] & 0xffff); idx < toSkip && idx < 0x10000; idx++) {
					x = hilbertX[idx];
					y = hilbertY[idx];
					if (x >= width || y >= height)
						continue;
					unpacked[x][y] = 1.0;
				}
			} else {
				idx += packed[p] & 0xffff;
			}
		}
		return unpacked;
	}

	/**
	 * Decompresses a short[] returned by pack() or a sub-array of a short[][]
	 * returned by packMulti(), as described in the {@link CoordPacker} class
	 * documentation. This returns a double[][] that stores 1.0 for true and 0.0 for
	 * false if the overload of pack() taking a boolean[][] was used. If a
	 * double[][] was compressed with pack(), the double[][] this returns will have
	 * 1.0 for all values greater than 0 and 0.0 for all others. If this is one of
	 * the sub-arrays compressed by packMulti(), the index of the sub-array will
	 * correspond to an index in the levels array passed to packMulti(), and any
	 * cells that were at least equal to the corresponding value in levels will be
	 * 1.0, while all others will be 0.0. Width and height do not technically need
	 * to match the dimensions of the original 2D array, but under most
	 * circumstances where they don't match, the data produced will be junk.
	 * 
	 * @param packed
	 *            a short[] encoded by calling one of this class' packing methods on
	 *            a 2D array.
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width.
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height.
	 * @return a double[][] storing which cells encoded by packed are on (1.0) or
	 *         off (0.0).
	 */
	public static double[][] unpackDoubleConical(short[] packed, int width, int height, int centerX, int centerY,
			double angle, double span) {
		if (packed == null)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.unpack() must be given a non-null array");
		double[][] unpacked = new double[width][height];
		if (packed.length == 0)
			return unpacked;
		boolean on = false;
		int idx = 0;
		short x = 0, y = 0;
		double angle2 = Math.toRadians(
				(angle > 360.0 || angle < 0.0) ? GwtCompatibility.IEEEremainder(angle + 720.0, 360.0) : angle);
		double span2 = Math.toRadians(span);

		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int toSkip = idx + (packed[p] & 0xffff); idx < toSkip && idx < 0x10000; idx++) {
					x = hilbertX[idx];
					y = hilbertY[idx];
					if (x >= width || y >= height)
						continue;
					double newAngle = Math.atan2(y - centerY, x - centerX) + Math.PI * 2;
					if (Math.abs(GwtCompatibility.IEEEremainder(angle2 - newAngle + Math.PI * 8, Math.PI * 2)) > span2
							/ 2.0)
						unpacked[x][y] = 0.0;
					else
						unpacked[x][y] = 1.0;
				}
			} else {
				idx += packed[p] & 0xffff;
			}
		}
		return unpacked;
	}

	/**
	 * Decompresses a short[][] returned by packMulti() and produces an
	 * approximation of the double[][] it compressed using the given levels double[]
	 * as the values to assign, as described in the {@link CoordPacker} class
	 * documentation. The length of levels and the length of the outer array of
	 * packed must be equal. However, the levels array passed to this method should
	 * not be identical to the levels array passed to packMulti(); for FOV
	 * compression, you should get an array for levels using
	 * generatePackingLevels(), but for decompression, you should create levels
	 * using generateLightLevels(), which should more appropriately fit the desired
	 * output. Reusing the levels array used to pack the FOV will usually produce
	 * values at the edge of FOV that are less than 0.01 but greater than 0, and
	 * will have a maximum value somewhat less than 1.0; neither are usually
	 * desirable, but using a different array made with generateLightLevels() will
	 * produce doubles ranging from 1.0 / levels.length to 1.0 at the highest. Width
	 * and height do not technically need to match the dimensions of the original 2D
	 * array, but under most circumstances where they don't match, the data produced
	 * will be junk.
	 * 
	 * @param packed
	 *            a short[][] encoded by calling this class' packMulti() method on a
	 *            2D array.
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width.
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height.
	 * @param levels
	 *            a double[] that must have the same length as packed, and will be
	 *            used to assign cells in the returned double[][] based on what
	 *            levels parameter was used to compress packed
	 * @return a double[][] where the values that corresponded to the nth value in
	 *         the levels parameter used to compress packed will now correspond to
	 *         the nth value in the levels parameter passed to this method.
	 */
	public static double[][] unpackMultiDouble(short[][] packed, int width, int height, double[] levels) {
		if (packed == null || packed.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.unpackMultiDouble() must be given a non-empty array");
		if (levels == null || levels.length != packed.length)
			throw new UnsupportedOperationException("The lengths of packed and levels must be equal");
		if (levels.length > 63)
			throw new UnsupportedOperationException(
					"Too many levels to be packed by CoordPacker; should be less than 64 but was given "
							+ levels.length);
		double[][] unpacked = new double[width][height];
		short x = 0, y = 0;
		for (int l = 0; l < packed.length; l++) {
			boolean on = false;
			int idx = 0;
			for (int p = 0; p < packed[l].length; p++, on = !on) {
				if (on) {
					for (int toSkip = idx + (packed[l][p] & 0xffff); idx < toSkip && idx < 0x10000; idx++) {
						x = hilbertX[idx];
						y = hilbertY[idx];
						if (x >= width || y >= height)
							continue;
						unpacked[x][y] = levels[l];
					}
				} else {
					idx += packed[l][p] & 0xffff;
				}
			}
		}
		return unpacked;
	}

	/**
	 * Decompresses a short[][] returned by packMulti() and produces an
	 * approximation of the double[][] it compressed using the given levels double[]
	 * as the values to assign, but only using the innermost indices up to limit, as
	 * described in the {@link CoordPacker} class documentation. The length of
	 * levels and the length of the outer array of packed do not have to be equal.
	 * However, the levels array passed to this method should not be identical to
	 * the levels array passed to packMulti(); for FOV compression, you should get
	 * an array for levels using generatePackingLevels(), but for decompression, you
	 * should create levels using generateLightLevels(), which should more
	 * appropriately fit the desired output. Reusing the levels array used to pack
	 * the FOV will usually produce values at the edge of FOV that are less than
	 * 0.01 but greater than 0, and will have a maximum value somewhat less than
	 * 1.0; neither are usually desirable, but using a different array made with
	 * generateLightLevels() will produce doubles ranging from 1.0 / levels.length
	 * to 1.0 at the highest. Width and height do not technically need to match the
	 * dimensions of the original 2D array, but under most circumstances where they
	 * don't match, the data produced will be junk.
	 * 
	 * @param packed
	 *            a short[][] encoded by calling this class' packMulti() method on a
	 *            2D array.
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width.
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height.
	 * @param levels
	 *            a double[] that must have the same length as packed, and will be
	 *            used to assign cells in the returned double[][] based on what
	 *            levels parameter was used to compress packed
	 * @param limit
	 *            the number of elements to consider from levels and packed,
	 *            starting from the innermost.
	 * @return a double[][] where the values that corresponded to the nth value in
	 *         the levels parameter used to compress packed will now correspond to
	 *         the nth value in the levels parameter passed to this method.
	 */
	public static double[][] unpackMultiDoublePartial(short[][] packed, int width, int height, double[] levels,
			int limit) {
		if (packed == null || packed.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.unpackMultiDouble() must be given a non-empty array");
		if (levels == null || levels.length != packed.length)
			throw new UnsupportedOperationException("The lengths of packed and levels must be equal");
		if (levels.length > 63)
			throw new UnsupportedOperationException(
					"Too many levels to be packed by CoordPacker; should be less than 64 but was given "
							+ levels.length);
		if (limit > levels.length)
			limit = levels.length;
		double[][] unpacked = new double[width][height];
		short x = 0, y = 0;
		for (int l = packed.length - limit; l < packed.length; l++) {
			boolean on = false;
			int idx = 0;
			for (int p = 0; p < packed[l].length; p++, on = !on) {
				if (on) {
					for (int toSkip = idx + (packed[l][p] & 0xffff); idx < toSkip && idx < 0x10000; idx++) {
						x = hilbertX[idx];
						y = hilbertY[idx];
						if (x >= width || y >= height)
							continue;
						unpacked[x][y] = levels[l];
					}
				} else {
					idx += packed[l][p] & 0xffff;
				}
			}
		}
		return unpacked;
	}

	/**
	 * Decompresses a short[][] returned by packMulti() and produces an
	 * approximation of the double[][] it compressed using the given levels double[]
	 * as the values to assign, but only using the innermost indices up to limit, as
	 * described in the {@link CoordPacker} class documentation. The length of
	 * levels and the length of the outer array of packed do not have to be equal.
	 * However, the levels array passed to this method should not be identical to
	 * the levels array passed to packMulti(); for FOV compression, you should get
	 * an array for levels using generatePackingLevels(), but for decompression, you
	 * should create levels using generateLightLevels(), which should more
	 * appropriately fit the desired output. Reusing the levels array used to pack
	 * the FOV will usually produce values at the edge of FOV that are less than
	 * 0.01 but greater than 0, and will have a maximum value somewhat less than
	 * 1.0; neither are usually desirable, but using a different array made with
	 * generateLightLevels() will produce doubles ranging from 1.0 / levels.length
	 * to 1.0 at the highest. This method takes an angle and span as well as a
	 * centerX and centerY; the only values that will be greater than 0.0 in the
	 * result will be within the round-based conical section that could be produced
	 * by traveling from (centerX,centerY) along angle in a limitless line and
	 * expanding the cone to be span degrees broad (circularly), centered on angle.
	 * Width and height do not technically need to match the dimensions of the
	 * original 2D array, but under most circumstances where they don't match, the
	 * data produced will be junk.
	 * 
	 * @param packed
	 *            a short[][] encoded by calling this class' packMulti() method on a
	 *            2D array.
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width.
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height.
	 * @param levels
	 *            a double[] that must have the same length as packed, and will be
	 *            used to assign cells in the returned double[][] based on what
	 *            levels parameter was used to compress packed
	 * @param limit
	 *            the number of elements to consider from levels and packed,
	 *            starting from the innermost.
	 * @param centerX
	 *            the x position of the corner or origin of the conical FOV
	 * @param centerY
	 *            the y position of the corner or origin of the conical FOV
	 * @param angle
	 *            the center of the conical area to limit this to, in degrees
	 * @param span
	 *            the total span of the conical area to limit this to, in degrees
	 * @return a double[][] where the values that corresponded to the nth value in
	 *         the levels parameter used to compress packed will now correspond to
	 *         the nth value in the levels parameter passed to this method.
	 */
	public static double[][] unpackMultiDoublePartialConical(short[][] packed, int width, int height, double[] levels,
			int limit, int centerX, int centerY, double angle, double span) {
		if (packed == null || packed.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.unpackMultiDouble() must be given a non-empty array");
		if (levels == null || levels.length != packed.length)
			throw new UnsupportedOperationException("The lengths of packed and levels must be equal");
		if (levels.length > 63)
			throw new UnsupportedOperationException(
					"Too many levels to be packed by CoordPacker; should be less than 64 but was given "
							+ levels.length);
		if (limit > levels.length)
			limit = levels.length;

		double angle2 = Math.toRadians(
				(angle > 360.0 || angle < 0.0) ? GwtCompatibility.IEEEremainder(angle + 720.0, 360.0) : angle);
		double span2 = Math.toRadians(span);
		double[][] unpacked = new double[width][height];
		short x = 0, y = 0;
		for (int l = packed.length - limit; l < packed.length; l++) {
			boolean on = false;
			int idx = 0;
			for (int p = 0; p < packed[l].length; p++, on = !on) {
				if (on) {
					for (int toSkip = idx + (packed[l][p] & 0xffff); idx < toSkip && idx < 0x10000; idx++) {
						x = hilbertX[idx];
						y = hilbertY[idx];
						if (x >= width || y >= height)
							continue;
						double newAngle = Math.atan2(y - centerY, x - centerX) + Math.PI * 2;
						if (Math.abs(
								GwtCompatibility.IEEEremainder(angle2 - newAngle + Math.PI * 8, Math.PI * 2)) > span2
										/ 2.0)
							unpacked[x][y] = 0.0;
						else
							unpacked[x][y] = levels[l];
					}
				} else {
					idx += packed[l][p] & 0xffff;
				}
			}
		}
		return unpacked;
	}

	/**
	 * Decompresses a short[][] returned by packMulti() and produces a simple 2D
	 * array where the values are bytes corresponding to 1 + the highest index into
	 * levels (that is, the original levels parameter passed to packMulti) matched
	 * by a cell, or 0 if the cell didn't match any levels during compression, as
	 * described in the {@link CoordPacker} class documentation. Width and height do
	 * not technically need to match the dimensions of the original 2D array, but
	 * under most circumstances where they don't match, the data produced will be
	 * junk.
	 * 
	 * @param packed
	 *            a short[][] encoded by calling this class' packMulti() method on a
	 *            2D array.
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width.
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height.
	 * @return a byte[][] where the values that corresponded to the nth value in the
	 *         levels parameter used to compress packed will now correspond to bytes
	 *         with the value n+1, or 0 if they were "off" in the original array.
	 */
	public static byte[][] unpackMultiByte(short[][] packed, int width, int height) {
		if (packed == null || packed.length == 0)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.unpackMultiByte() must be given a non-empty array");
		byte[][] unpacked = new byte[width][height];
		byte lPlus = 1;
		short x = 0, y = 0;
		for (int l = 0; l < packed.length; l++, lPlus++) {
			boolean on = false;
			int idx = 0;
			for (int p = 0; p < packed[l].length; p++, on = !on) {
				if (on) {
					for (int toSkip = idx + (packed[l][p] & 0xffff); idx < toSkip && idx <= 0xffff; idx++) {
						x = hilbertX[idx];
						y = hilbertY[idx];
						if (x >= width || y >= height)
							continue;
						unpacked[x][y] = lPlus;
					}
				} else {
					idx += packed[l][p] & 0xffff;
				}
			}
		}
		return unpacked;
	}

	/**
	 * Given a piece of packed data defining a region to use from that map, a char
	 * to use for "on" cells and a char to use for "off" cells, produces a 2D char
	 * array where all positions that are "off" in packed are filled with the char
	 * passed as f, and the cells that are "on" are filled with the char passed as
	 * t. Finds the bounding rectangle starting at the origin and extending to the
	 * highest x and highest y values encoded in packed, and uses that to determine
	 * the width and height of the returned 2D array.
	 * 
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @param t
	 *            the char to use for "on" positions in packed
	 * @param f
	 *            the char to use for "off" positions in packed
	 * @return a 2D char array, with dimensions determined by the bounds of packed,
	 *         where any "on" cells equal t and anything else equals f.
	 */
	public static char[][] unpackChar(short[] packed, char t, char f) {
		if (packed == null || packed.length <= 1)
			throw new UnsupportedOperationException("packed has no contents in unpackChar");

		Coord max = bounds(packed)[1];
		int width = max.x + 1, height = max.y + 1;
		if (width <= 0 || height <= 0)
			throw new UnsupportedOperationException("Height and width must both be positive in unpackChar");

		char[][] c = new char[width][height];

		for (int i = 0; i < width; i++) {
			Arrays.fill(c[i], f);
		}
		boolean on = false;
		int idx = 0, x, y;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					if (x >= width || y >= height)
						continue;
					c[x][y] = t;
				}
			}
			idx += packed[p] & 0xffff;
		}
		return c;
	}

	/**
	 * Given a piece of packed data defining a region to use from that map, a
	 * desired width and height, a char to use for "on" cells and a char to use for
	 * "off" cells, produces a 2D char array where all positions that are "off" in
	 * packed are filled with the char passed as f, and the cells that are "on" are
	 * filled with the char passed as t.
	 * 
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @param width
	 *            the desired 2D array width
	 * @param height
	 *            the desired 2D array height
	 * @param t
	 *            the char to use for "on" positions in packed
	 * @param f
	 *            the char to use for "off" positions in packed
	 * @return a 2D char array, width by height in dimensions, where any "on" cells
	 *         equal t and anything else equals f.
	 */
	public static char[][] unpackChar(short[] packed, int width, int height, char t, char f) {
		if (width <= 0 || height <= 0)
			throw new UnsupportedOperationException("Height and width must both be positive in unpackChar");

		char[][] c = new char[width][height];

		if (packed.length <= 1)
			return c;
		for (int i = 0; i < width; i++) {
			Arrays.fill(c[i], f);
		}
		boolean on = false;
		int idx = 0, x, y;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					if (x >= width || y >= height)
						continue;
					c[x][y] = t;
				}
			}
			idx += packed[p] & 0xffff;
		}
		return c;
	}

	/**
	 * Simple utility method that constructs a GreasedRegion (a faster but
	 * more-memory-hungry way to encode regions) from a short array of packed data.
	 * 
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @param width
	 *            the desired GreasedRegion's width
	 * @param height
	 *            the desired GreasedRegion's height
	 * @return a GreasedRegion that contains the same data as packed, with the
	 *         specified width and height
	 */
	public static GreasedRegion unpackGreasedRegion(short[] packed, int width, int height) {
		return new GreasedRegion(unpack(packed, width, height));
	}

	/**
	 * Quickly determines if an x,y position is true or false in the given packed
	 * array, without unpacking it.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param x
	 *            between 0 and 255, inclusive
	 * @param y
	 *            between 0 and 255, inclusive
	 * @return true if the packed data stores true at the given x,y location, or
	 *         false in any other case.
	 */
	public static boolean queryPacked(short[] packed, int x, int y) {
		int hilbertDistance = posToHilbert(x, y), total = 0;
		boolean on = false;
		for (int p = 0; p < packed.length; p++, on = !on) {
			total += packed[p] & 0xffff;
			if (hilbertDistance < total)
				return on;
		}
		return false;
	}

	/**
	 * Quickly determines if a Hilbert Curve index corresponds to true or false in
	 * the given packed array, without unpacking it. <br>
	 * Typically this method will not be needed by library-consuming code unless
	 * that code deals with Hilbert Curves in a frequent and deeply involved manner.
	 * It does have the potential to avoid converting to and from x,y coordinates
	 * and Hilbert Curve indices unnecessarily, which could matter for
	 * high-performance code.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param hilbert
	 *            a Hilbert Curve index, such as one taken directly from a packed
	 *            short[] without extra processing
	 * @return true if the packed data stores true at the given Hilbert Curve index,
	 *         or false in any other case.
	 */
	public static boolean queryPackedHilbert(short[] packed, short hilbert) {
		int hilbertDistance = hilbert & 0xffff, total = 0;
		boolean on = false;
		for (int p = 0; p < packed.length; p++, on = !on) {
			total += packed[p] & 0xffff;
			if (hilbertDistance < total)
				return on;
		}
		return false;
	}

	/**
	 * Quickly determines if an x,y position is true or false in one of the given
	 * packed arrays, without unpacking them, and returns a List of all packed
	 * arrays that contain the position.
	 * 
	 * @param x
	 *            between 0 and 255, inclusive
	 * @param y
	 *            between 0 and 255, inclusive
	 * @param packed
	 *            an array or vararg of short[], such as those returned by pack() or
	 *            one of the sub-arrays in what is returned by packMulti(); null
	 *            elements in packed will be skipped.
	 * @return an OrderedSet of all packed arrays that store true at the given x,y
	 *         location.
	 */
	public static OrderedSet<short[]> findManyPacked(int x, int y, short[]... packed) {
		OrderedSet<short[]> packs = new OrderedSet<>(packed.length, CrossHash.shortHasher);
		int hilbertDistance = posToHilbert(x, y);
		for (int a = 0; a < packed.length; a++) {
			if (packed[a] == null)
				continue;
			int total = 0;
			boolean on = false;
			for (int p = 0; p < packed[a].length; p++, on = !on) {
				total += packed[a][p] & 0xffff;
				if (hilbertDistance < total) {
					if (on)
						packs.add(packed[a]);
					break;
				}
			}
		}
		return packs;
	}

	/**
	 * Quickly determines if a region is contained in one of the given packed
	 * arrays, without unpacking them, and returns true if the region checking has
	 * some overlap with any of the packed arrays, or false otherwise.
	 * 
	 * @param checking
	 *            the packed data to check for overlap with the other regions
	 * @param packed
	 *            an array or vararg of short[], such as those returned by pack() or
	 *            one of the sub-arrays in what is returned by packMulti(); null
	 *            elements in packed will be skipped
	 * @return true if checking overlaps with any of the packed arrays, or false
	 *         otherwise
	 */
	public static boolean regionsContain(short[] checking, short[]... packed) {
		OrderedSet<short[]> packs = new OrderedSet<>(packed.length, CrossHash.shortHasher);
		for (int a = 0; a < packed.length; a++) {
			if (packed[a] == null)
				continue;
			int total = 0;
			if (intersects(checking, packed[a]))
				return true;
		}
		return false;
	}

	/**
	 * Quickly determines if a Hilbert Curve index corresponds to true or false in
	 * one of the given packed arrays, without unpacking them, and returns a List of
	 * all packed arrays that contain the position. <br>
	 * Typically this method will not be needed by library-consuming code unless
	 * that code deals with Hilbert Curves in a frequent and deeply involved manner.
	 * It does have the potential to avoid converting to and from x,y coordinates
	 * and Hilbert Curve indices unnecessarily, which could matter for
	 * high-performance code.
	 * 
	 * @param hilbert
	 *            a Hilbert Curve index, such as one taken directly from a packed
	 *            short[] without extra processing
	 * @param packed
	 *            an array or vararg of short[], such as those returned by pack() or
	 *            one of the sub-arrays in what is returned by packMulti(); null
	 *            elements in packed will be skipped.
	 * @return an ArrayList of all packed arrays that store true at the given x,y
	 *         location.
	 */
	public static ArrayList<short[]> findManyPackedHilbert(short hilbert, short[]... packed) {
		ArrayList<short[]> packs = new ArrayList<>(packed.length);
		int hilbertDistance = hilbert & 0xffff;
		for (int a = 0; a < packed.length; a++) {
			int total = 0;
			boolean on = false;
			for (int p = 0; p < packed[a].length; p++, on = !on) {
				total += packed[a][p] & 0xffff;
				if (hilbertDistance < total) {
					if (on)
						packs.add(packed[a]);
					break;
				}
			}
		}
		return packs;
	}

	/**
	 * Gets all positions that are "on" in the given packed array, without unpacking
	 * it, and returns them as a Coord[].
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @return a Coord[], ordered by distance along the Hilbert Curve, corresponding
	 *         to all "on" cells in packed.
	 */
	public static Coord[] allPacked(short[] packed) {
		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				vla.addRange(idx, idx + (packed[p] & 0xffff));
			}
			idx += packed[p] & 0xffff;
		}
		int[] distances = vla.asInts();
		Coord[] cs = new Coord[distances.length];
		for (int i = 0; i < distances.length; i++) {
			cs[i] = Coord.get(hilbertX[distances[i]], hilbertY[distances[i]]);
		}
		return cs;
	}

	/**
	 * Gets all positions that are "on" in the given packed array, without unpacking
	 * it, and returns them as an array of Hilbert Curve indices. <br>
	 * Typically this method will not be needed by library-consuming code unless
	 * that code deals with Hilbert Curves in a frequent and deeply involved manner.
	 * It does have the potential to avoid converting to and from x,y coordinates
	 * and Hilbert Curve indices unnecessarily, which could matter for
	 * high-performance code.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @return a Hilbert Curve index array, in ascending distance order,
	 *         corresponding to all "on" cells in packed.
	 */
	public static short[] allPackedHilbert(short[] packed) {
		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				vla.addRange(idx, idx + (packed[p] & 0xffff));
			}
			idx += packed[p] & 0xffff;
		}
		return vla.toArray();
	}

	/**
	 * Gets the nth position that is "on" in the given packed array, without
	 * unpacking it, and returns it as a Coord. Uses Hilbert Curve ordering for the
	 * exact Hilbert Curve CoordPacker uses, so any two given Coords will always
	 * have the same before-after relationship. Returns null if n is not between 0
	 * (inclusive) and the count of packed (exclusive), as by
	 * {@code CoordPacker.count()}.
	 *
	 * You can technically use nth to iterate over only the Coords that are defined
	 * in some packed data, but it's drastically more efficient to store a Coord
	 * array once with allPacked(). Using nth() as an iterator is essentially
	 * running a growing portion of what allPacked() does, over and over again,
	 * until the last Coord encoded in packed takes almost as long to process as one
	 * call to allPacked(). That said, for a single Coord this can be significantly
	 * faster than getting an array with allPacked() and fetching only one item from
	 * it.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param n
	 *            the index to get in packed
	 * @return the nth Coord encoded as "on" by packed, ordered by distance along
	 *         the Hilbert Curve, or null if n is out of bounds
	 */
	public static Coord nth(final short[] packed, final int n) {
		if (n < 0)
			return null;
		boolean on = false;
		int idx = 0, ct = n, tmp;
		for (int p = 0; p < packed.length; p++, on = !on) {
			tmp = (packed[p] & 0xffff);
			if (on) {
				if (ct - tmp < 0) {
					idx += ct;
					ct -= tmp;
					break;
				}
				ct -= tmp;
			}
			idx += tmp;
		}
		if (ct >= 0)
			return null;
		return Coord.get(hilbertX[idx], hilbertY[idx]);
	}

	/**
	 * Gets the positions that are "on" in the given packed array, without unpacking
	 * it, repeatedly goes through a number of "on" cells equal to fraction and
	 * stores one of those cells as a Coord, and returns the accumulated portion of
	 * positions as a Coord[]. <br>
	 * For purposes of finding mostly cells with a similar distance to each other
	 * but without obvious patterns, a value of 5, 6, or 7 for fraction works well
	 * for relatively-close Coords, but larger values are better for packed data
	 * with wide, expansive areas. If you want to make the regular pattern this uses
	 * impossible to discern, you can use {@code randomSeparated()} to keep distance
	 * between Coords and sample most areas of some packed data. Values for fraction
	 * that are multiples of 4 are likely to show a pattern in large open spaces
	 * more easily.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param fraction
	 *            the denominator of the approximate fraction of "on" cells to use
	 * @return a Coord[] corresponding to a fraction of the "on" cells in packed.
	 */
	public static Coord[] fractionPacked(short[] packed, int fraction) {
		if (fraction <= 1)
			return allPacked(packed);
		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0, ctr = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++, ctr = (ctr + 1) % fraction) {
					if (ctr == 0)
						vla.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] distances = vla.asInts();
		Coord[] cs = new Coord[distances.length];
		for (int i = 0; i < distances.length; i++) {
			cs[i] = Coord.get(hilbertX[distances[i]], hilbertY[distances[i]]);
		}
		return cs;
	}

	/**
	 * Gets the positions that are "on" in the given packed array, without unpacking
	 * it, repeatedly goes through a number of "on" cells equal to fraction and
	 * stores a random one of those cells as a Coord, and returns the accumulated
	 * random portion of positions as a Coord[]. Because of how this works, it is
	 * much more likely that the Coords will be dispersed so that there's a good
	 * amount of minimum distance between most Coords, while methods like
	 * randomPortion() do not make such dispersal a priority and may return tight
	 * clusters of Coords. <br>
	 * For purposes of finding mostly cells with a similar distance to each other
	 * but without obvious patterns, a value of at least 7 for fraction works well.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param separation
	 *            the denominator of the approximate fraction of "on" cells to use
	 * @param rng
	 *            the RNG to use to incorporate a random factor to the generation
	 * @return a Coord[] corresponding to a fraction of the "on" cells in packed.
	 */
	public static Coord[] randomSeparated(short[] packed, int separation, RNG rng) {
		if (separation <= 1)
			return allPacked(packed);
		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0, ctr = 0, tgt = rng.nextInt(separation);
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++, ctr++) {
					if (ctr >= separation) {
						ctr %= separation;
						tgt = rng.nextInt(separation);
					}
					if (ctr == tgt)
						vla.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] distances = vla.asInts();
		Coord[] cs = new Coord[distances.length];
		for (int i = 0; i < distances.length; i++) {
			cs[i] = Coord.get(hilbertX[distances[i]], hilbertY[distances[i]]);
		}
		return cs;
	}

	/**
	 * Gets the positions that are "on" in the given packed array, without unpacking
	 * it, repeatedly goes through a number of "on" cells equal to fraction and
	 * stores one of those cells as a Coord, and returns the accumulated portion of
	 * positions as an array of Hilbert Curve indices. <br>
	 * For purposes of finding mostly cells with a similar distance to each other
	 * but without obvious patterns, a value of 5, 6, or 7 for fraction works well.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param fraction
	 *            the approximate fraction of "on" cells to use
	 * @return a Hilbert Curve index array corresponding to a fraction of the "on"
	 *         cells in packed.
	 */
	public static short[] fractionPackedHilbert(short[] packed, int fraction) {
		if (fraction <= 1)
			return allPackedHilbert(packed);
		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0, ctr = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++, ctr = (ctr + 1) % fraction) {
					if (ctr == 0)
						vla.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		return vla.toArray();
	}

	/**
	 * Gets the positions that are "on" in the given packed array, without unpacking
	 * it, keeps only positions that are at least minDistance apart from other
	 * positions this will return, and returns the positions as a Coord[].
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param minDistance
	 *            the minimum distance (measured 8-way, Chebyshev) between any
	 *            positions this returns
	 * @return a Coord[] corresponding to a portion of the "on" cells in packed.
	 */
	public static Coord[] apartPacked(short[] packed, int minDistance) {
		if (minDistance < 1)
			return allPacked(packed);
		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0;
		ShortSet ss = new ShortSet(256);
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					int x = hilbertX[i], y = hilbertY[i], dist = hilbertDistances[x + (y << 8)];
					if (ss.add((short) dist)) {
						for (int xx = Math.max(0, x - minDistance); xx <= Math.min(255, x + minDistance); xx++) {
							for (int yy = Math.max(0, y - minDistance); yy <= Math.min(255, y + minDistance); yy++) {
								dist = hilbertDistances[xx + (yy << 8)];
								if (dist >= i)
									ss.add((short) dist);
							}
						}
						vla.add((short) i);
					}
				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] distances = vla.asInts();
		Coord[] cs = new Coord[distances.length];
		for (int i = 0; i < distances.length; i++) {
			cs[i] = Coord.get(hilbertX[distances[i]], hilbertY[distances[i]]);
		}
		return cs;
	}

	/**
	 * Gets the positions that are "on" in the given packed array, without unpacking
	 * it, keeps only positions that are at least minDistance apart from other
	 * positions this will return, and returns the positions as a Coord[].
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param minDistance
	 *            the minimum distance (measurement depends on eightWay) between any
	 *            positions this returns
	 * @param eightWay
	 *            true if distance should be measured equally in 8 directions, false
	 *            to use 4 directions
	 * @return a Coord[] corresponding to a portion of the "on" cells in packed.
	 */
	public static Coord[] apartPacked(short[] packed, int minDistance, boolean eightWay) {
		if (minDistance < 1)
			return allPacked(packed);
		if (eightWay)
			return apartPacked(packed, minDistance);

		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0;
		ShortSet ss = new ShortSet(256);
		int xx, yy;
		int[] xOffsets = new int[] { 0, 1, 0, -1, 0 }, yOffsets = new int[] { 1, 0, -1, 0, 1 };
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					int x = hilbertX[i], y = hilbertY[i], dist = hilbertDistances[x + (y << 8)];
					if (ss.add((short) dist)) {
						for (int d = 0; d < 4; d++) {
							for (int e = 1; e <= minDistance; e++) {
								for (int e2 = 0; e2 < minDistance; e2++) {
									xx = Math.min(255, Math.max(0, x + xOffsets[d] * e + yOffsets[d + 1] * e2));
									yy = Math.min(255, Math.max(0, y + yOffsets[d] * e + xOffsets[d + 1] * e2));
									dist = hilbertDistances[xx + (yy << 8)];
									if (dist >= i)
										ss.add((short) dist);
								}
							}
						}
						vla.add((short) i);
					}
				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] distances = vla.asInts();
		Coord[] cs = new Coord[distances.length];
		for (int i = 0; i < distances.length; i++) {
			cs[i] = Coord.get(hilbertX[distances[i]], hilbertY[distances[i]]);
		}
		return cs;
	}

	/**
	 * Gets the positions that are "on" in the given packed array, without unpacking
	 * it, keeps only positions that are at least minDistance apart from other
	 * positions this will return, and returns the positions as an array of Hilbert
	 * Curve indices.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param minDistance
	 *            the minimum distance (measured 8-way, Chebyshev) between any
	 *            positions this returns
	 * @return a Hilbert Curve index array corresponding to a portion of the "on"
	 *         cells in packed.
	 */
	public static short[] apartPackedHilbert(short[] packed, int minDistance) {
		if (minDistance < 1)
			return allPackedHilbert(packed);
		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0;
		ShortSet ss = new ShortSet(256);
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					int x = hilbertX[i], y = hilbertY[i], dist = hilbertDistances[x + (y << 8)];
					if (ss.add((short) dist)) {
						for (int xx = Math.max(0, x - minDistance); xx <= Math.min(255, x + minDistance); xx++) {
							for (int yy = Math.max(0, y - minDistance); yy <= Math.min(255, y + minDistance); yy++) {
								dist = hilbertDistances[xx + (yy << 8)];
								if (dist >= i)
									ss.add((short) dist);
							}
						}
						vla.add((short) i);
					}
				}
			}
			idx += packed[p] & 0xffff;
		}
		return vla.toArray();
	}

	/**
	 * Gets the positions that are "on" in the given packed array, without unpacking
	 * it, keeps only positions that are at least minDistance apart from other
	 * positions this will return, and returns the positions as an array of Hilbert
	 * Curve indices.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check due to very tight performance constraints).
	 * @param minDistance
	 *            the minimum distance (measurement depends on eightWay) between any
	 *            positions this returns
	 * @param eightWay
	 *            true if distance should be measured equally in 8 directions, false
	 *            to use 4 directions
	 * @return a Hilbert Curve index array corresponding to a portion of the "on"
	 *         cells in packed.
	 */
	public static short[] apartPackedHilbert(short[] packed, int minDistance, boolean eightWay) {
		if (minDistance < 1)
			return allPackedHilbert(packed);
		if (eightWay)
			return apartPackedHilbert(packed, minDistance);

		ShortVLA vla = new ShortVLA(64);
		boolean on = false;
		int idx = 0;
		ShortSet ss = new ShortSet(256);
		int xx, yy;
		int[] xOffsets = new int[] { 0, 1, 0, -1, 0 }, yOffsets = new int[] { 1, 0, -1, 0, 1 };
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					int x = hilbertX[i], y = hilbertY[i], dist = hilbertDistances[x + (y << 8)];
					if (ss.add((short) dist)) {
						for (int d = 0; d < 4; d++) {
							for (int e = 1; e <= minDistance; e++) {
								for (int e2 = 0; e2 < minDistance; e2++) {
									xx = Math.min(255, Math.max(0, x + xOffsets[d] * e + yOffsets[d + 1] * e2));
									yy = Math.min(255, Math.max(0, y + yOffsets[d] * e + xOffsets[d + 1] * e2));
									dist = hilbertDistances[xx + (yy << 8)];
									if (dist >= i)
										ss.add((short) dist);
								}
							}
						}
						vla.add((short) i);
					}
				}
			}
			idx += packed[p] & 0xffff;
		}
		return vla.toArray();
	}

	private static int clamp(int n, int min, int max) {
		return Math.min(Math.max(min, n), max - 1);
	}

	/**
	 * Move all "on" positions in packed by the number of cells given in xMove and
	 * yMove, unless the move would take them further than 0, width - 1 (for xMove)
	 * or height - 1 (for yMove), in which case that cell is stopped at the edge
	 * (moving any shape by an xMove greater than width or yMove greater than height
	 * will move all "on" cells to that edge, in a 1-cell thick line). Returns a new
	 * packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param xMove
	 *            distance to move the x-coordinate; can be positive or negative
	 * @param yMove
	 *            distance to move the y-coordinate; can be positive or negative
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @return a packed array that encodes "on" for cells that were moved from cells
	 *         that were "on" in packed
	 */
	public static short[] translate(short[] packed, int xMove, int yMove, int width, int height) {
		if (packed == null || packed.length <= 1) {
			return ALL_WALL;
		}
		ShortVLA vla = new ShortVLA(256);
		boolean on = false;
		int idx = 0, x, y;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = clamp(hilbertX[i] + xMove, 0, width);
					y = clamp(hilbertY[i] + yMove, 0, height);
					vla.add(hilbertDistances[x + (y << 8)]);
				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);
		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Expand each "on" position in packed to cover a a square with side length
	 * equal to 1 + expansion * 2, centered on the original "on" position, unless
	 * the expansion would take a cell further than 0, width - 1 (for xMove) or
	 * height - 1 (for yMove), in which case that cell is stopped at the edge. Uses
	 * 8-way movement (Chebyshev distance) unless the overload of this function that
	 * takes a boolean argument eightWay is used and that argument is false. Returns
	 * a new packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param expansion
	 *            the positive (square) radius, in cells, to expand each cell out by
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @return a packed array that encodes "on" for packed and cells that expanded
	 *         from cells that were "on" in packed
	 */
	public static short[] expand(short[] packed, int expansion, int width, int height) {
		if (packed == null || packed.length <= 1) {
			return ALL_WALL;
		}
		ShortVLA vla = new ShortVLA(256);
		ShortSet ss = new ShortSet(256);
		boolean on = false;
		int idx = 0, x, y;
		short dist;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					for (int j = Math.max(0, x - expansion); j <= Math.min(width - 1, x + expansion); j++) {
						for (int k = Math.max(0, y - expansion); k <= Math.min(height - 1, y + expansion); k++) {
							dist = hilbertDistances[j + (k << 8)];
							if (ss.add(dist))
								vla.add(dist);
						}
					}
				}
			}
			idx += packed[p] & 0xffff;
		}

		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));
		return vla.toArray();
	}

	/**
	 * Expand each "on" position in packed to cover a a square with side length
	 * equal to 1 + expansion * 2, centered on the original "on" position, unless
	 * the expansion would take a cell further than 0, width - 1 (for xMove) or
	 * height - 1 (for yMove), in which case that cell is stopped at the edge.
	 * Returns a new packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param expansion
	 *            the positive (square) radius, in cells, to expand each cell out by
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @param eightWay
	 *            true if the expansion should be both diagonal and orthogonal;
	 *            false for just orthogonal
	 * @return a packed array that encodes "on" for packed and cells that expanded
	 *         from cells that were "on" in packed
	 */
	public static short[] expand(short[] packed, int expansion, int width, int height, boolean eightWay) {
		if (eightWay)
			return expand(packed, expansion, width, height);
		if (packed == null || packed.length <= 1) {
			return ALL_WALL;
		}
		ShortVLA vla = new ShortVLA(256);
		ShortSet ss = new ShortSet(256);
		boolean on = false;
		int idx = 0, x, y, j, k;
		short dist;
		int[] xOffsets = new int[] { 0, 1, 0, -1, 0 }, yOffsets = new int[] { 1, 0, -1, 0, 1 };
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					dist = hilbertDistances[x + (y << 8)];
					if (ss.add(dist))
						vla.add(dist);
					for (int d = 0; d < 4; d++) {
						for (int e = 1; e <= expansion; e++) {
							for (int e2 = 0; e2 <= expansion - e; e2++) {
								j = Math.min(width - 1, Math.max(0, x + xOffsets[d] * e + yOffsets[d + 1] * e2));
								k = Math.min(height - 1, Math.max(0, y + yOffsets[d] * e + xOffsets[d + 1] * e2));
								dist = hilbertDistances[j + (k << 8)];
								if (ss.add(dist))
									vla.add(dist);
							}
						}
					}
				}
			}
			idx += packed[p] & 0xffff;
		}

		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Finds the area made by removing the "on" positions in packed that are within
	 * the specified retraction distance of an "off" position or the edge of the
	 * map. This essentially finds a shrunken version of packed. Uses 8-way movement
	 * (Chebyshev distance) unless the overload of this function that takes a
	 * boolean argument eightWay is used and that argument is false. Returns a new
	 * packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param retraction
	 *            the positive (square) radius, in cells, to pull each cell in by
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @return a short[] that encodes "on" for cells that were "on" in packed and
	 *         were far enough from an "off" cell
	 */
	public static short[] retract(short[] packed, int retraction, int width, int height) {
		return differencePacked(packed, expand(negatePacked(packed), retraction, width, height, true));
	}

	/*
	 * public static short[] retract(short[] packed, int retraction, int width, int
	 * height) { if(packed == null || packed.length <= 1) { return ALL_WALL; }
	 * ShortVLA vla = new ShortVLA(256); boolean on = false; int idx = 0, x, y;
	 * for(int p = 0; p < packed.length; p++, on = !on) { if (on) { INDICES: for
	 * (int i = idx; i < idx + (packed[p] & 0xffff); i++) { x = hilbertX[i]; y =
	 * hilbertY[i]; for (int j = x - retraction; j <= x + retraction; j++) { for
	 * (int k = y - retraction; k <= y + retraction; k++) { if(j < 0 || k < 0 || j
	 * >= width || k >= height || !queryPackedHilbert(packed, hilbertDistances[j +
	 * (k << 8)])) continue INDICES; } }
	 * 
	 * vla.add((short)i); } } idx += packed[p] & 0xffff; }
	 * 
	 * int[] indices = vla.asInts(); if(indices.length < 1) return ALL_WALL;
	 * Arrays.sort(indices);
	 * 
	 * vla = new ShortVLA(128); int current, past = indices[0], skip = 0;
	 * 
	 * vla.add((short)indices[0]); for (int i = 1; i < indices.length; i++) {
	 * current = indices[i]; if (current - past > 1) { vla.add((short) (skip+1));
	 * skip = 0; vla.add((short)(current - past - 1)); } else if(current != past)
	 * skip++; past = current; } vla.add((short)(skip+1)); return vla.toArray(); }
	 */
	/**
	 * Finds the area made by removing the "on" positions in packed that are within
	 * the specified retraction distance of an "off" position or the edge of the
	 * map. This essentially finds a shrunken version of packed. Returns a new
	 * packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param retraction
	 *            the positive (square) radius, in cells, to pull each cell in by
	 * @param width
	 *            the maximum width; cells outside this are considered "off" for
	 *            this method's purposes
	 * @param height
	 *            the maximum height; cells outside this are considered "off" for
	 *            this method's purposes
	 * @param eightWay
	 *            true if the retraction should be both diagonal and orthogonal;
	 *            false for just orthogonal
	 * @return a packed array that encodes "on" for packed and cells that expanded
	 *         from cells that were "on" in packed
	 */
	public static short[] retract(short[] packed, int retraction, int width, int height, boolean eightWay) {
		return differencePacked(packed, expand(negatePacked(packed), retraction, width, height, eightWay));
	}
	/*
	 * { if(eightWay) return retract(packed, retraction, width, height); if(packed
	 * == null || packed.length <= 1) { return ALL_WALL; } ShortVLA vla = new
	 * ShortVLA(256); boolean on = false; int idx = 0, x, y, j, k; int[] xOffsets =
	 * new int[]{0, 1, 0, -1, 0}, yOffsets = new int[]{1, 0, -1, 0, 1}; for(int p =
	 * 0; p < packed.length; p++, on = !on) { if (on) { INDICES: for (int i = idx; i
	 * < idx + (packed[p] & 0xffff); i++) { x = hilbertX[i]; y = hilbertY[i]; for
	 * (int d = 0; d < 4; d++) { for (int e = 1; e <= retraction; e++) { for (int e2
	 * = 0; e2 < retraction; e2++) { j = x + xOffsets[d] * e + yOffsets[d + 1] * e2;
	 * k = y + yOffsets[d] * e + xOffsets[d + 1] * e2; if (j < 0 || k < 0 || j >=
	 * width || k >= height || !queryPackedHilbert(packed, hilbertDistances[j + (k
	 * << 8)])) continue INDICES; } } } vla.add((short)i); } } idx += packed[p] &
	 * 0xffff; }
	 * 
	 * int[] indices = vla.asInts(); if(indices.length < 1) return ALL_WALL;
	 * Arrays.sort(indices);
	 * 
	 * vla = new ShortVLA(128); int current, past = indices[0], skip = 0;
	 * 
	 * vla.add((short)indices[0]); for (int i = 1; i < indices.length; i++) {
	 * current = indices[i]; if (current - past > 1) { vla.add((short) (skip+1));
	 * skip = 0; vla.add((short)(current - past - 1)); } else if(current != past)
	 * skip++; past = current; } vla.add((short)(skip+1)); return vla.toArray(); }
	 */

	/**
	 * Finds the area around the cells encoded in packed, without including those
	 * cells. For each "on" position in packed, expand it to cover a a square with
	 * side length equal to 1 + expansion * 2, centered on the original "on"
	 * position, unless the expansion would take a cell further than 0, width - 1
	 * (for xMove) or height - 1 (for yMove), in which case that cell is stopped at
	 * the edge. If a cell is "on" in packed, it will always be "off" in the result.
	 * Uses 8-way movement (Chebyshev distance) unless the overload of this function
	 * that takes a boolean argument eightWay is used and that argument is false.
	 * Returns a new packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param expansion
	 *            the positive (square-shaped) radius, in cells, to expand each cell
	 *            out by
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @return a packed array that encodes "on" for cells that were pushed from the
	 *         edge of packed's "on" cells
	 */
	public static short[] fringe(short[] packed, int expansion, int width, int height) {
		if (packed == null || packed.length <= 1) {
			return ALL_WALL;
		}
		ShortVLA vla = new ShortVLA(256);
		ShortSet ss = new ShortSet(256);
		boolean on = false;
		int idx = 0;
		short x, y, dist;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					ss.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		on = false;
		idx = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					for (int j = Math.max(0, x - expansion); j <= Math.min(width - 1, x + expansion); j++) {
						for (int k = Math.max(0, y - expansion); k <= Math.min(height - 1, y + expansion); k++) {
							dist = hilbertDistances[j + (k << 8)];
							if (ss.add(dist))
								vla.add(dist);
						}
					}
				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Finds the area around the cells encoded in packed, without including those
	 * cells. For each "on" position in packed, expand it to cover a a square with
	 * side length equal to 1 + expansion * 2, centered on the original "on"
	 * position, unless the expansion would take a cell further than 0, width - 1
	 * (for xMove) or height - 1 (for yMove), in which case that cell is stopped at
	 * the edge. If a cell is "on" in packed, it will always be "off" in the result.
	 * Returns a new packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param expansion
	 *            the positive (square-shaped) radius, in cells, to expand each cell
	 *            out by
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @param eightWay
	 *            true if the expansion should be both diagonal and orthogonal;
	 *            false for just orthogonal
	 * @return a packed array that encodes "on" for cells that were pushed from the
	 *         edge of packed's "on" cells
	 */
	public static short[] fringe(short[] packed, int expansion, int width, int height, boolean eightWay) {
		if (eightWay)
			return fringe(packed, expansion, width, height);
		if (packed == null || packed.length <= 1) {
			return ALL_WALL;
		}
		ShortVLA vla = new ShortVLA(256);
		ShortSet ss = new ShortSet(256);
		boolean on = false;
		int idx = 0;
		short x, y, dist;
		int[] xOffsets = new int[] { 0, 1, 0, -1, 0 }, yOffsets = new int[] { 1, 0, -1, 0, 1 };
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					ss.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		on = false;
		idx = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					for (int d = 0; d < 4; d++) {
						for (int e = 1; e <= expansion; e++) {
							for (int e2 = 0; e2 <= expansion - e; e2++) {
								int j = Math.min(width - 1, Math.max(0, x + xOffsets[d] * e + yOffsets[d + 1] * e2));
								int k = Math.min(height - 1, Math.max(0, y + yOffsets[d] * e + xOffsets[d + 1] * e2));
								dist = hilbertDistances[j + (k << 8)];
								if (ss.add(dist))
									vla.add(dist);
							}
						}
					}

				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Finds the area around the cells encoded in packed, without including those
	 * cells. For each "on" position in packed, expand it to cover a a square with
	 * side length equal to 1 + expansion * 2, centered on the original "on"
	 * position, unless the expansion would take a cell further than 0, width - 1
	 * (for xMove) or height - 1 (for yMove), in which case that cell is removed if
	 * drop is true, or stopped at the edge if drop is false. If a cell is "on" in
	 * packed, it will always be "off" in the result. Returns a new packed short[]
	 * and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param expansion
	 *            the positive (square-shaped) radius, in cells, to expand each cell
	 *            out by
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @param eightWay
	 *            true if the expansion should be both diagonal and orthogonal;
	 *            false for just orthogonal
	 * @param drop
	 *            true to drop cells that would expand into negative coordinates or
	 *            past width/height, false to stop their expansion early
	 * @return a packed array that encodes "on" for cells that were pushed from the
	 *         edge of packed's "on" cells
	 */
	public static short[] fringe(short[] packed, int expansion, int width, int height, boolean eightWay, boolean drop) {
		if (!drop)
			return fringe(packed, expansion, width, height, eightWay);
		if (packed == null || packed.length <= 1) {
			return ALL_WALL;
		}
		ShortVLA vla = new ShortVLA(256);
		ShortSet ss = new ShortSet(256);
		boolean on = false;
		int idx = 0;
		short x, y, dist;
		int[] xOffsets = new int[] { 0, 1, 0, -1, 0 }, yOffsets = new int[] { 1, 0, -1, 0, 1 };
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					ss.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		on = false;
		idx = 0;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					if (eightWay) {
						for (int j = x - expansion; j <= x + expansion; j++) {
							for (int k = y - expansion; k <= y + expansion; k++) {
								if (j >= 0 && k >= 0 && j < width && k < height) {
									dist = hilbertDistances[j + (k << 8)];
									if (ss.add(dist))
										vla.add(dist);
								}
							}
						}
					} else {
						for (int d = 0; d < 4; d++) {
							for (int e = 1; e <= expansion; e++) {
								for (int e2 = 0; e2 <= expansion - e; e2++) {
									int j = x + xOffsets[d] * e + yOffsets[d + 1] * e2;
									int k = y + yOffsets[d] * e + xOffsets[d + 1] * e2;

									if (j >= 0 && k >= 0 && j < width && k < height) {
										dist = hilbertDistances[j + (k << 8)];
										if (ss.add(dist))
											vla.add(dist);
									}
								}
							}
						}
					}

				}
			}
			idx += packed[p] & 0xffff;
		}
		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Finds the concentric areas around the cells encoded in packed, without
	 * including those cells. For each "on" position in packed, expand it to cover a
	 * a square with side length equal to 1 + n * 2, where n starts at 1 and goes up
	 * to include the expansions parameter, with each expansion centered on the
	 * original "on" position, unless the expansion would take a cell further than
	 * 0, width - 1 (for xMove) or height - 1 (for yMove), in which case that cell
	 * is stopped at the edge. If a cell is "on" in packed, it will always be "off"
	 * in the results. Returns a new packed short[][] where the outer array has
	 * length equal to expansions and the inner arrays are packed data encoding a
	 * one-cell-wide concentric fringe region. Uses 8-way measurement. Does not
	 * modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param expansions
	 *            the positive (square-shaped) radius, in cells, to expand each cell
	 *            out by, also the length of the outer array returned by this method
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @return an array of packed arrays that encode "on" for cells that were pushed
	 *         from the edge of packed's "on" cells; the outer array will have
	 *         length equal to expansions, and inner arrays will normal packed data
	 */
	public static short[][] fringes(short[] packed, int expansions, int width, int height) {
		short[][] finished = new short[expansions][];
		if (packed == null || packed.length <= 1) {
			Arrays.fill(finished, ALL_WALL);
			return finished;
		}
		ShortSet ss = new ShortSet(256);
		boolean on = false;
		int idx = 0;
		short x, y, dist;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					ss.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		for (int expansion = 1; expansion <= expansions; expansion++) {
			ShortVLA vla = new ShortVLA(256);
			on = false;
			idx = 0;
			for (int p = 0; p < packed.length; p++, on = !on) {
				if (on) {
					for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
						x = hilbertX[i];
						y = hilbertY[i];
						for (int j = Math.max(0, x - expansion); j <= Math.min(width - 1, x + expansion); j++) {
							for (int k = Math.max(0, y - expansion); k <= Math.min(height - 1, y + expansion); k++) {
								dist = hilbertDistances[j + (k << 8)];
								if (ss.add(dist))
									vla.add(dist);
							}
						}
					}
				}
				idx += packed[p] & 0xffff;
			}
			int[] indices = vla.asInts();
			if (indices.length < 1) {
				finished[expansion - 1] = ALL_WALL;
				continue;
			}
			Arrays.sort(indices);

			vla = new ShortVLA(128);
			int current, past = indices[0], skip = 0;

			vla.add((short) indices[0]);
			for (int i = 1; i < indices.length; i++) {
				current = indices[i];
				if (current - past > 1) {
					vla.add((short) (skip + 1));
					skip = 0;
					vla.add((short) (current - past - 1));
				} else if (current != past)
					skip++;
				past = current;
			}
			vla.add((short) (skip + 1));

			finished[expansion - 1] = vla.toArray();
		}
		return finished;
	}

	/**
	 * Finds the concentric areas around the cells encoded in packed, without
	 * including those cells. For each "on" position in packed, expand it to cover a
	 * a square or diamond with radius equal to n, where n starts at 1 and goes up
	 * to include the expansions parameter, with each expansion centered on the
	 * original "on" position, unless the expansion would take a cell further than
	 * 0, width - 1 (for xMove) or height - 1 (for yMove), in which case that cell
	 * is stopped at the edge. If a cell is "on" in packed, it will always be "off"
	 * in the results. Returns a new packed short[][] where the outer array has
	 * length equal to expansions and the inner arrays are packed data encoding a
	 * one-cell-wide concentric fringe region. Does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param expansions
	 *            the positive (square-shaped) radius, in cells, to expand each cell
	 *            out by, also the length of the outer array returned by this method
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @param eightWay
	 *            true if the expansion should be both diagonal and orthogonal;
	 *            false for just orthogonal
	 * @return an array of packed arrays that encode "on" for cells that were pushed
	 *         from the edge of packed's "on" cells; the outer array will have
	 *         length equal to expansions, and inner arrays will normal packed data
	 */
	public static short[][] fringes(short[] packed, int expansions, int width, int height, boolean eightWay) {
		if (eightWay)
			return fringes(packed, expansions, width, height);
		short[][] finished = new short[expansions][];
		if (packed == null || packed.length <= 1) {
			Arrays.fill(finished, ALL_WALL);
			return finished;
		}
		ShortSet ss = new ShortSet(256);
		boolean on = false;
		int idx = 0;
		short x, y, dist;
		int[] xOffsets = new int[] { 0, 1, 0, -1, 0 }, yOffsets = new int[] { 1, 0, -1, 0, 1 };
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					ss.add((short) i);
				}
			}
			idx += packed[p] & 0xffff;
		}
		for (int expansion = 1; expansion <= expansions; expansion++) {
			ShortVLA vla = new ShortVLA(256);
			on = false;
			idx = 0;
			for (int p = 0; p < packed.length; p++, on = !on) {
				if (on) {
					for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
						x = hilbertX[i];
						y = hilbertY[i];
						for (int d = 0; d < 4; d++) {
							for (int e = 1; e <= expansion; e++) {
								for (int e2 = 0; e2 <= expansion - e; e2++) {
									int j = Math.min(width - 1,
											Math.max(0, x + xOffsets[d] * e + yOffsets[d + 1] * e2));
									int k = Math.min(height - 1,
											Math.max(0, y + yOffsets[d] * e + xOffsets[d + 1] * e2));
									dist = hilbertDistances[j + (k << 8)];
									if (ss.add(dist))
										vla.add(dist);
								}
							}
						}
					}
				}
				idx += packed[p] & 0xffff;
			}
			int[] indices = vla.asInts();
			if (indices.length < 1) {
				finished[expansion - 1] = ALL_WALL;
				continue;
			}
			Arrays.sort(indices);

			vla = new ShortVLA(128);
			int current, past = indices[0], skip = 0;

			vla.add((short) indices[0]);
			for (int i = 1; i < indices.length; i++) {
				current = indices[i];
				if (current - past > 1) {
					vla.add((short) (skip + 1));
					skip = 0;
					vla.add((short) (current - past - 1));
				} else if (current != past)
					skip++;
				past = current;
			}
			vla.add((short) (skip + 1));

			finished[expansion - 1] = vla.toArray();
		}
		return finished;
	}

	/**
	 * Finds the area consisting of the "on" positions in packed that are within the
	 * specified depth distance of an "off" position or the edge of the map. This
	 * essentially finds the part of packed that is close to its edge. Uses 8-way
	 * movement (Chebyshev distance) unless the overload of this function that takes
	 * a boolean argument eightWay is used and that argument is false. Returns a new
	 * packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param depth
	 *            the positive (square) radius, in cells, to go inward from an "off"
	 *            cell into the "in" cells
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @return a short[] that encodes "on" for cells that were "on" in packed and
	 *         were close enough to an "off" cell
	 */
	public static short[] surface(short[] packed, int depth, int width, int height) {
		return intersectPacked(packed, expand(negatePacked(packed), depth, width, height, true));
	}

	/*
	 * { if(packed == null || packed.length <= 1) { return ALL_WALL; } ShortVLA vla
	 * = new ShortVLA(256); boolean on = false; int idx = 0, x, y; for(int p = 0; p
	 * < packed.length; p++, on = !on) { if (on) { INDICES: for (int i = idx; i <
	 * idx + (packed[p] & 0xffff); i++) { x = hilbertX[i]; y = hilbertY[i]; for (int
	 * j = Math.max(0, x - depth); j <= Math.min(width - 1, x + depth); j++) { for
	 * (int k = Math.max(0, y - depth); k <= Math.min(height - 1, y + depth); k++) {
	 * if(!queryPackedHilbert(packed, hilbertDistances[j + (k << 8)])) {
	 * vla.add((short)i); continue INDICES; } } } } } idx += packed[p] & 0xffff; }
	 * 
	 * int[] indices = vla.asInts(); if(indices.length < 1) return ALL_WALL;
	 * Arrays.sort(indices);
	 * 
	 * vla = new ShortVLA(128); int current, past = indices[0], skip = 0;
	 * 
	 * vla.add((short)indices[0]); for (int i = 1; i < indices.length; i++) {
	 * current = indices[i]; if (current - past > 1) { vla.add((short) (skip+1));
	 * skip = 0; vla.add((short)(current - past - 1)); } else if(current != past)
	 * skip++; past = current; } vla.add((short)(skip+1)); return vla.toArray(); }
	 */
	/**
	 * Finds the area consisting of the "on" positions in packed that are within the
	 * specified depth distance of an "off" position or the edge of the map. This
	 * essentially finds the part of packed that is close to its edge. Returns a new
	 * packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param depth
	 *            the positive (square) radius, in cells, to go inward from an "off"
	 *            cell into the "in" cells
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @param eightWay
	 *            true if the retraction should be both diagonal and orthogonal;
	 *            false for just orthogonal
	 * @return a short[] that encodes "on" for cells that were "on" in packed and
	 *         were close enough to an "off" cell
	 */
	public static short[] surface(short[] packed, int depth, int width, int height, boolean eightWay) {
		return intersectPacked(packed, expand(negatePacked(packed), depth, width, height, eightWay));
	}

	/**
	 * Finds the concentric, progressively-smaller surfaces of packed as if packed
	 * was shrinking with each iteration. Essentially, this is the inverse of
	 * fringes, where fringe finds a ring around packed and fringes finds concentric
	 * rings around growing versions of packed, while surface finds a ring at the
	 * edge and surfaces finds rings at the edge of shrinking versions of packed.
	 * Returns a new packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param depth
	 *            the positive (square) radius, in cells, to go inward from an "off"
	 *            cell into the "in" cells
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @return an array of packed short[] that each encodes "on" for cells that were
	 *         "on" in packed and were at a distance between 1 and depth to an "off"
	 *         cell
	 */
	public static short[][] surfaces(short[] packed, int depth, int width, int height) {
		short[][] sfs = new short[depth][], frs = fringes(negatePacked(packed), depth, width, height);
		for (int i = 0; i < depth; i++) {
			sfs[i] = intersectPacked(packed, frs[i]);
		}
		return sfs;
	}

	/**
	 * Finds the concentric, progressively-smaller surfaces of packed as if packed
	 * was shrinking with each iteration. Essentially, this is the inverse of
	 * fringes, where fringe finds a ring around packed and fringes finds concentric
	 * rings around growing versions of packed, while surface finds a ring at the
	 * edge and surfaces finds rings at the edge of shrinking versions of packed.
	 * Returns a new packed short[] and does not modify packed.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti()
	 * @param depth
	 *            the positive (square) radius, in cells, to go inward from an "off"
	 *            cell into the "in" cells
	 * @param width
	 *            the maximum width; if a cell would move to x at least equal to
	 *            width, it stops at width - 1
	 * @param height
	 *            the maximum height; if a cell would move to y at least equal to
	 *            height, it stops at height - 1
	 * @param eightWay
	 *            true if the retraction should be both diagonal and orthogonal;
	 *            false for just orthogonal
	 * @return an array of packed short[] that each encodes "on" for cells that were
	 *         "on" in packed and were at a distance between 1 and depth to an "off"
	 *         cell
	 */
	public static short[][] surfaces(short[] packed, int depth, int width, int height, boolean eightWay) {
		short[][] sfs = new short[depth][], frs = fringes(negatePacked(packed), depth, width, height, eightWay);
		for (int i = 0; i < depth; i++) {
			sfs[i] = intersectPacked(packed, frs[i]);
		}
		return sfs;
	}

	/*
	 * { if(eightWay) return surface(packed, depth, width, height); if(packed ==
	 * null || packed.length <= 1) { return ALL_WALL; } ShortVLA vla = new
	 * ShortVLA(256); boolean on = false; int idx = 0, x, y, j, k; int[] xOffsets =
	 * new int[]{0, 1, 0, -1, 0}, yOffsets = new int[]{1, 0, -1, 0, 1}; for(int p =
	 * 0; p < packed.length; p++, on = !on) { if (on) { INDICES: for (int i = idx; i
	 * < idx + (packed[p] & 0xffff); i++) { x = hilbertX[i]; y = hilbertY[i]; for
	 * (int d = 0; d < 4; d++) { for (int e = 1; e <= depth; e++) { for (int e2 = 0;
	 * e2 < depth; e2++) { j = x + xOffsets[d] * e + yOffsets[d + 1] * e2; k = y +
	 * yOffsets[d] * e + xOffsets[d + 1] * e2; if (j < 0 || k < 0 || j >= width || k
	 * >= height || !queryPackedHilbert(packed, hilbertDistances[j + (k << 8)])) {
	 * vla.add((short)i); continue INDICES; } } } } } } idx += packed[p] & 0xffff; }
	 * 
	 * int[] indices = vla.asInts(); if(indices.length < 1) return ALL_WALL;
	 * Arrays.sort(indices);
	 * 
	 * vla = new ShortVLA(128); int current, past = indices[0], skip = 0;
	 * 
	 * vla.add((short)indices[0]); for (int i = 1; i < indices.length; i++) {
	 * current = indices[i]; if (current - past > 1) { vla.add((short) (skip+1));
	 * skip = 0; vla.add((short)(current - past - 1)); } else if(current != past)
	 * skip++; past = current; } vla.add((short)(skip+1)); return vla.toArray(); }
	 */

	/**
	 * Given a packed array encoding a larger area, a packed array encoding one or
	 * more points inside bounds, and an amount of expansion, expands each cell in
	 * start by a Manhattan (diamond) radius equal to expansion, limiting any
	 * expansion to within bounds and returning the final expanded (limited) packed
	 * data. Notably, if a small area is not present within bounds, then the flood
	 * will move around the "hole" similarly to DijkstraMap's behavior; essentially,
	 * it needs to expand around the hole to get to the other side, and this takes
	 * more steps of expansion than crossing straight over. Returns a new packed
	 * short[] and does not modify bounds or start.
	 * 
	 * @param bounds
	 *            packed data representing the maximum extent of the region to
	 *            flood-fill; often floors
	 * @param start
	 *            a packed array that encodes position(s) that the flood will spread
	 *            outward from
	 * @param expansion
	 *            the positive (square) radius, in cells, to expand each cell out by
	 * @return a packed array that encodes "on" for cells that are "on" in bounds
	 *         and are within expansion Manhattan distance from a Coord in start
	 */
	public static short[] flood(short[] bounds, short[] start, int expansion) {
		if (bounds == null || bounds.length <= 1) {
			return ALL_WALL;
		}
		int boundSize = count(bounds);
		ShortVLA vla = new ShortVLA(256);
		ShortSet ss = new ShortSet(boundSize), quickBounds = new ShortSet(boundSize);
		boolean on = false, justAdded;
		int idx = 0;
		short x, y, dist;
		for (int p = 0; p < bounds.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (bounds[p] & 0xffff); i++) {
					quickBounds.add((short) i);
				}
			}
			idx += bounds[p] & 0xffff;
		}
		short[] s2 = allPackedHilbert(start);
		ss.addAll(s2);
		vla.addAll(s2);
		int[] xOffsets = new int[] { 0, 1, 0, -1 }, yOffsets = new int[] { 1, 0, -1, 0 };
		for (int e = 0; e < expansion; e++) {
			justAdded = false;
			ShortVLA edge = new ShortVLA(128);
			for (int s = 0; s < s2.length; s++) {
				int i = s2[s] & 0xffff;
				x = hilbertX[i];
				y = hilbertY[i];

				for (int d = 0; d < 4; d++) {
					int j = Math.min(255, Math.max(0, x + xOffsets[d]));
					int k = Math.min(255, Math.max(0, y + yOffsets[d]));
					dist = hilbertDistances[j + (k << 8)];
					if (quickBounds.contains(dist)) {
						if (ss.add(dist)) {
							vla.add(dist);
							edge.add(dist);
							justAdded = true;
						}
					}
				}
			}
			if (!justAdded)
				break;
			s2 = edge.toArray();
		}

		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Given a packed array encoding a larger area, a packed array encoding one or
	 * more points inside bounds, and an amount of expansion, expands each cell in
	 * start by a radius (if eightWay is true, it uses Chebyshev distance; if it is
	 * false, it uses Manhattan distance) equal to expansion, limiting any expansion
	 * to within bounds and returning the final expanded (limited) packed data.
	 * Notably, if a small area is not present within bounds, then the flood will
	 * move around the "hole" similarly to DijkstraMap's behavior; essentially, it
	 * needs to expand around the hole to get to the other side, and this takes more
	 * steps of expansion than crossing straight over. Returns a new packed short[]
	 * and does not modify bounds or start.
	 * 
	 * @param bounds
	 *            packed data representing the maximum extent of the region to
	 *            flood-fill; often floors
	 * @param start
	 *            a packed array that encodes position(s) that the flood will spread
	 *            outward from
	 * @param expansion
	 *            the positive (square) radius, in cells, to expand each cell out by
	 * @param eightWay
	 *            true to flood-fill out in all eight directions at each step, false
	 *            for just orthogonal
	 * @return a packed array that encodes "on" for cells that are "on" in bounds
	 *         and are within expansion either Chebyshev (if eightWay is true) or
	 *         Manhattan (otherwise) distance from a Coord in start
	 */
	public static short[] flood(short[] bounds, short[] start, int expansion, boolean eightWay) {
		if (!eightWay)
			return flood(bounds, start, expansion);
		if (bounds == null || bounds.length <= 1) {
			return ALL_WALL;
		}
		int boundSize = count(bounds);
		ShortVLA vla = new ShortVLA(256);
		ShortSet ss = new ShortSet(boundSize), quickBounds = new ShortSet(boundSize);
		boolean on = false, justAdded;
		int idx = 0;
		short x, y, dist;
		for (int p = 0; p < bounds.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (bounds[p] & 0xffff); i++) {
					quickBounds.add((short) i);
				}
			}
			idx += bounds[p] & 0xffff;
		}
		short[] s2 = allPackedHilbert(start);
		ss.addAll(s2);
		vla.addAll(s2);
		int[] xOffsets = new int[] { -1, 0, 1, -1, 1, -1, 0, 1 }, yOffsets = new int[] { -1, -1, -1, 0, 0, 1, 1, 1 };
		for (int e = 0; e < expansion; e++) {
			justAdded = false;
			ShortVLA edge = new ShortVLA(128);
			for (int s = 0; s < s2.length; s++) {
				int i = s2[s] & 0xffff;
				x = hilbertX[i];
				y = hilbertY[i];
				for (int d = 0; d < 8; d++) {
					int j = Math.min(255, Math.max(0, x + xOffsets[d]));
					int k = Math.min(255, Math.max(0, y + yOffsets[d]));
					dist = hilbertDistances[j + (k << 8)];
					if (quickBounds.contains(dist)) {
						if (ss.add(dist)) {
							vla.add(dist);
							edge.add(dist);
							justAdded = true;
						}
					}
				}
			}
			if (!justAdded)
				break;
			s2 = edge.toArray();
		}

		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (int i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	private static void modifiedShadowFOV(int expansion, int viewerX, int viewerY, Radius metric, ShortSet bounds,
			ShortSet storedSet, ShortVLA vla) {
		if (expansion < 1)
			return;
		short start = hilbertDistances[viewerX + (viewerY << 8)];
		if (storedSet.add(start))
			vla.add(start);

		for (Direction d : Direction.DIAGONALS) {
			modifiedShadowCast(expansion, 1, 1.0, 0.0, 0, d.deltaX, d.deltaY, 0, viewerX, viewerY, metric, bounds,
					storedSet, vla);
			modifiedShadowCast(expansion, 1, 1.0, 0.0, d.deltaX, 0, 0, d.deltaY, viewerX, viewerY, metric, bounds,
					storedSet, vla);
		}
	}

	private static void modifiedShadowCast(int expansion, int row, double start, double end, int xx, int xy, int yx,
			int yy, int viewerX, int viewerY, Radius metric, ShortSet bounds, ShortSet storedSet, ShortVLA vla) {
		double newStart = 0;
		if (start < end) {
			return;
		}

		boolean blocked = false;
		int dist;
		short currentPos;
		for (int distance = row; distance <= expansion && !blocked; distance++) {
			int deltaY = -distance;
			for (int deltaX = -distance; deltaX <= 0; deltaX++) {
				int currentX = viewerX + deltaX * xx + deltaY * xy;
				int currentY = viewerY + deltaX * yx + deltaY * yy;
				double leftSlope = (deltaX - 0.5f) / (deltaY + 0.5f);
				double rightSlope = (deltaX + 0.5f) / (deltaY - 0.5f);
				currentPos = hilbertDistances[currentX + (currentY << 8)];

				/*
				 * if (!bounds.contains(currentPos)) { newStart = rightSlope; continue; } else
				 */
				if (!(currentX - viewerX + expansion >= 0 && currentX - viewerX <= expansion
						&& currentY - viewerY + expansion >= 0 && currentY - viewerY <= expansion)
						|| start < rightSlope) {
					continue;
				} else if (end > leftSlope) {
					break;
				}

				if (blocked) { // previous cell was a blocking one
					if (!bounds.contains(currentPos)) {// hit a wall
						newStart = rightSlope;
					} else {
						blocked = false;
						start = newStart;
						dist = metric.roughDistance(currentX - viewerX, currentY - viewerY);
						// check if it's within the lightable area and light if needed
						if (dist <= expansion * 2) {
							if (storedSet.add(currentPos))
								vla.add(currentPos);
						}
					}
				} else {
					if (!bounds.contains(currentPos) && distance < expansion) {// hit a wall within sight line
						blocked = true;
						modifiedShadowCast(expansion, distance + 1, start, leftSlope, xx, xy, yx, yy, viewerX, viewerY,
								metric, bounds, storedSet, vla);
						newStart = rightSlope;
					} else {
						if (bounds.contains(currentPos)) {
							dist = metric.roughDistance(currentX - viewerX, currentY - viewerY);
							// check if it's within the lightable area and light if needed
							if (dist <= expansion * 2) {
								if (storedSet.add(currentPos))
									vla.add(currentPos);
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Given a packed array encoding a larger area, a packed array encoding one or
	 * more points inside bounds, and an amount of expansion, expands each cell in
	 * start by a Manhattan (diamond) radius equal to expansion, limiting any
	 * expansion to within bounds and returning the final expanded (limited) packed
	 * data. Though this is otherwise similar to flood(), radiate() behaves like FOV
	 * and will not move around obstacles and will instead avoid expanding if it
	 * would go into any cell that cannot be reached by a straight line (drawn
	 * directly, not in grid steps) that is mostly unobstructed. Returns a new
	 * packed short[] and does not modify bounds or start.
	 * 
	 * @param bounds
	 *            packed data representing the maximum extent of the region to
	 *            flood-fill; often floors
	 * @param start
	 *            a packed array that encodes position(s) that the flood will spread
	 *            outward from
	 * @param expansion
	 *            the positive (square) radius, in cells, to expand each cell out by
	 * @return a packed array that encodes "on" for cells that are "on" in bounds
	 *         and are within expansion Manhattan distance from a Coord in start
	 */
	public static short[] radiate(short[] bounds, short[] start, int expansion) {
		return radiate(bounds, start, expansion, Radius.DIAMOND);
	}

	/**
	 * Given a packed array encoding a larger area, a packed array encoding one or
	 * more points inside bounds, and an amount of expansion, expands each cell in
	 * start by a radius, with a shape determined by metric, equal to expansion,
	 * limiting any expansion to within bounds and returning the final expanded
	 * (limited) packed data. Though this is otherwise similar to flood(), radiate()
	 * behaves like FOV and will not move around obstacles and will instead avoid
	 * expanding if it would go into any cell that cannot be reached by a straight
	 * line (drawn directly, not in grid steps) that is mostly unobstructed. Returns
	 * a new packed short[] and does not modify bounds or start.
	 * 
	 * @param bounds
	 *            packed data representing the maximum extent of the region to
	 *            flood-fill; often floors
	 * @param start
	 *            a packed array that encodes position(s) that the flood will spread
	 *            outward from
	 * @param expansion
	 *            the positive (square) radius, in cells, to expand each cell out by
	 * @param metric
	 *            a Radius that defines how this should expand, SQUARE for 8-way,
	 *            DIAMOND for 4-way, CIRCLE for Euclidean expansion (not guaranteed
	 *            to be perfectly circular)
	 * @return a packed array that encodes "on" for cells that are "on" in bounds
	 *         and are within expansion Manhattan distance from a Coord in start
	 */
	public static short[] radiate(short[] bounds, short[] start, int expansion, Radius metric) {
		if (bounds == null || bounds.length <= 1) {
			return ALL_WALL;
		}
		int boundSize = count(bounds);
		ShortVLA vla = new ShortVLA(256);
		ShortSet storedSet = new ShortSet(boundSize), quickBounds = new ShortSet(boundSize);
		boolean on = false;
		int idx = 0, i;
		short x, y;
		for (int p = 0; p < bounds.length; p++, on = !on) {
			if (on) {
				for (i = idx; i < idx + (bounds[p] & 0xffff); i++) {
					quickBounds.add((short) i);
				}
			}
			idx += bounds[p] & 0xffff;
		}
		short[] s2 = allPackedHilbert(start);
		for (int s = 0; s < s2.length; s++) {
			i = s2[s] & 0xffff;
			x = hilbertX[i];
			y = hilbertY[i];

			modifiedShadowFOV(expansion, x, y, metric, quickBounds, storedSet, vla);
		}

		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Given a packed array encoding a larger area, a packed array encoding one or
	 * more points inside bounds, and an amount of expansion, expands each cell in
	 * start by a radius, with a square shape if eightWay is true or a diamond
	 * otherwise, equal to expansion, limiting any expansion to within bounds and
	 * returning the final expanded (limited) packed data. Though this is otherwise
	 * similar to flood(), radiate() behaves like FOV and will not move around
	 * obstacles and will instead avoid expanding if it would go into any cell that
	 * cannot be reached by a straight line (drawn directly, not in grid steps) that
	 * is mostly unobstructed. Returns a new packed short[] and does not modify
	 * bounds or start.
	 * 
	 * @param bounds
	 *            packed data representing the maximum extent of the region to
	 *            flood-fill; often floors
	 * @param start
	 *            a packed array that encodes position(s) that the flood will spread
	 *            outward from
	 * @param expansion
	 *            the positive (square) radius, in cells, to expand each cell out by
	 * @param eightWay
	 *            true to flood-fill out in all eight directions at each step, false
	 *            for just orthogonal
	 * @return a packed array that encodes "on" for cells that are "on" in bounds
	 *         and are within expansion either Chebyshev (if eightWay is true) or
	 *         Manhattan (otherwise) distance from a Coord in start
	 */
	public static short[] radiate(short[] bounds, short[] start, int expansion, boolean eightWay) {
		if (eightWay)
			return radiate(bounds, start, expansion, Radius.SQUARE);
		return radiate(bounds, start, expansion, Radius.DIAMOND);
	}

	/**
	 * Given a packed array encoding a larger area, a packed array encoding one or
	 * more points inside bounds, and a Reach object that determines targeting
	 * constraints, gets all cells contained within bounds that can be targeted from
	 * a cell in start using the rules defined by reach. Though this is otherwise
	 * similar to flood(), reachable() behaves like FOV and will not move around
	 * obstacles and will instead avoid expanding if it would go into any cell that
	 * cannot be reached by a straight line (drawn directly, not in grid steps) that
	 * is mostly unobstructed. This does not behave quite like FOV if an AimLimit
	 * has been set in reach to any value other than null or AimLimit.FREE; in these
	 * cases it requires an exactly straight orthogonal or diagonal line without
	 * obstructions, checking only cells along the precise path. For diagonals and
	 * eight-way targeting, this means it can target through walls that only meet at
	 * a perpendicular diagonal, such as an X shape where one line is a
	 * one-cell-thick diagonal wall and the other is the targeting line. This is
	 * normally only allowed in some games and only if they use Chebyshev
	 * (Radius.SQUARE) distance, so be advised that it may not be desirable
	 * behavior. Returns a new packed short[] and does not modify bounds or start.
	 * 
	 * @param bounds
	 *            packed data representing the max extent of the region to check for
	 *            reach-ability; often floors
	 * @param start
	 *            a packed array that encodes position(s) that the flood will spread
	 *            outward from
	 * @param reach
	 *            a {@link Reach} object that determines minimum and maximum range,
	 *            distance metric, and AimLimit
	 * @return a packed array that encodes "on" for cells that are "on" in bounds
	 *         and can be targeted from a cell in start using the given Reach
	 */
	public static short[] reachable(short[] bounds, short[] start, Reach reach) {
		if (bounds == null || bounds.length <= 1) {
			return ALL_WALL;
		}
		int boundSize = count(bounds);
		ShortVLA vla = new ShortVLA(256), discard = new ShortVLA(128);
		ShortSet storedSet = new ShortSet(boundSize), quickBounds = new ShortSet(boundSize);
		boolean on = false;
		int idx = 0, i;
		short x, y;
		for (int p = 0; p < bounds.length; p++, on = !on) {
			if (on) {
				for (i = idx; i < idx + (bounds[p] & 0xffff); i++) {
					quickBounds.add((short) i);
				}
			}
			idx += bounds[p] & 0xffff;
		}
		short[] s2 = allPackedHilbert(start);
		if (reach.limit == null || reach.limit == AimLimit.FREE) {
			for (int s = 0; s < s2.length; s++) {
				i = s2[s] & 0xffff;
				x = hilbertX[i];
				y = hilbertY[i];
				// add all cells at less than minimum distance to storedSet.
				modifiedShadowFOV(reach.minDistance - 1, x, y, reach.metric, quickBounds, storedSet, discard);
				discard.clear();
				modifiedShadowFOV(reach.maxDistance, x, y, reach.metric, quickBounds, storedSet, vla);
			}
		} else {
			for (int s = 0; s < s2.length; s++) {
				i = s2[s] & 0xffff;
				x = hilbertX[i];
				y = hilbertY[i];
				Direction[] dirs;
				switch (reach.limit) {
				case ORTHOGONAL:
					dirs = Direction.CARDINALS;
					break;
				case DIAGONAL:
					dirs = Direction.DIAGONALS;
					break;
				default:
					dirs = Direction.OUTWARDS;
				}
				Direction dir;
				DIRECTIONAL: for (int which = 0; which < dirs.length; which++) {
					dir = dirs[which];
					int d;
					// add all cells at less than minimum distance to storedSet.
					for (d = 1; d < reach.minDistance; d++) {
						int extended = (x + dir.deltaX * d) + ((y + dir.deltaY * d) << 8);
						if (extended < 0 || extended > 0xffff)
							continue DIRECTIONAL;
						short next = hilbertDistances[extended];
						if (quickBounds.contains(next))
							storedSet.add(next);
						else
							continue DIRECTIONAL;
					}
					for (; d <= reach.maxDistance; d++) {
						int extended = (x + dir.deltaX * d) + ((y + dir.deltaY * d) << 8);
						if (extended < 0 || extended > 0xffff)
							continue DIRECTIONAL;
						short next = hilbertDistances[extended];
						if (quickBounds.contains(next)) {
							if (storedSet.add(next))
								vla.add(next);
						} else
							continue DIRECTIONAL;
					}
				}
			}
		}
		int[] indices = vla.asInts();
		if (indices.length < 1)
			return ALL_WALL;
		Arrays.sort(indices);

		vla = new ShortVLA(128);
		int current, past = indices[0], skip = 0;

		vla.add((short) indices[0]);
		for (i = 1; i < indices.length; i++) {
			current = indices[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));

		return vla.toArray();
	}

	/**
	 * Given a width and height, returns a packed array that encodes "on" for the
	 * rectangle from (0,0) to (width - 1, height - 1). Primarily useful with
	 * intersectPacked() to ensure things like negatePacked() that can encode "on"
	 * cells in any position are instead limited to the bounds of the map.
	 * 
	 * @param width
	 *            the width of the rectangle
	 * @param height
	 *            the height of the rectangle
	 * @return a packed short[] encoding "on" for all cells with x less than width
	 *         and y less than height.
	 */
	public static short[] rectangle(int width, int height) {
		if (width > 256 || height > 256)
			throw new UnsupportedOperationException("Map size is too large to efficiently pack, aborting");
		boolean[][] rect = new boolean[width][height];
		for (int i = 0; i < width; i++) {
			Arrays.fill(rect[i], true);
		}
		return pack(rect);
	}

	/**
	 * Given x, y, width and height, returns a packed array that encodes "on" for
	 * the rectangle from (x,y) to (width + x - 1, height + y - 1). Primarily useful
	 * with intersectPacked() to ensure things like negatePacked() that can encode
	 * "on" cells in any position are instead limited to the bounds of the map, but
	 * also handy for basic "box drawing" for other uses.
	 * 
	 * @param x
	 *            the minimum x coordinate
	 * @param y
	 *            the minimum y coordinate
	 * @param width
	 *            the width of the rectangle
	 * @param height
	 *            the height of the rectangle
	 * @return a packed short[] encoding "on" for all cells between (x,y) and
	 *         (x+width-1,y+height-1).
	 */
	public static short[] rectangle(int x, int y, int width, int height) {
		int width2 = width, height2 = height;
		if (x + width >= 256)
			width2 = 255 - x;
		if (y + height >= 256)
			height2 = 255 - y;
		if (width2 < 0 || height2 < 0 || x < 0 || y < 0)
			return ALL_WALL;
		boolean[][] rect = new boolean[x + width2][y + height2];
		for (int i = x; i < x + width2; i++) {
			Arrays.fill(rect[i], y, y + height2, true);
		}
		return pack(rect);
	}

	/**
	 * Given x, y, width and height, returns an array of all Hilbert distance within
	 * the rectangle from (x,y) to (width + x - 1, height + y - 1).
	 * 
	 * @param x
	 *            the minimum x coordinate
	 * @param y
	 *            the minimum y coordinate
	 * @param width
	 *            the width of the rectangle
	 * @param height
	 *            the height of the rectangle
	 * @return a short[] that is not packed, and instead stores individual Hilbert
	 *         distances in the rectangle
	 */
	public static short[] rectangleHilbert(int x, int y, int width, int height) {
		int width2 = width, height2 = height;
		if (x + width >= 256)
			width2 = 256 - x;
		if (y + height >= 256)
			height2 = 256 - y;
		if (width2 <= 0 || height2 <= 0 || x < 0 || y < 0)
			return new short[0];
		short[] hilberts = new short[width2 * height2];
		int idx = 0;
		for (int i = x; i < x + width2; i++) {
			for (int j = y; j < y + height2; j++) {
				hilberts[idx++] = hilbertDistances[i + (j << 8)];
			}
		}
		return hilberts;
	}

	/**
	 * Given a center and radius for a circle, plus the width and height for the map
	 * boundaries, returns the packed data that encodes the circle.
	 * 
	 * @param center
	 *            center position of the circle
	 * @param radius
	 *            radius to extend in all directions from center
	 * @param width
	 *            the maximum width of the map (exclusive); the circle will not
	 *            extend past this or below 0
	 * @param height
	 *            the maximum height of the map (exclusive); the circle will not
	 *            extend past this or below 0
	 * @return a packed short[] encoding "on" for all elements inside the circle
	 */
	public static short[] circle(Coord center, int radius, int width, int height) {
		return packSeveral(Radius.CIRCLE.pointsInside(center, radius, false, width, height));
	}

	/**
	 * Counts the number of "on" cells encoded in a packed array without unpacking
	 * it.
	 * 
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @return the number of "on" cells.
	 */
	public static int count(short[] packed) {
		return count(packed, true);
	}

	/**
	 * Counts the number of cells encoding a boolean equal to wanted in a packed
	 * array without unpacking it.
	 * 
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @param wanted
	 *            the boolean you want to count, true for "on" and false for "off"
	 * @return the number of cells that encode a value equal to wanted.
	 */
	public static int count(short[] packed, boolean wanted) {
		int c = 0;
		boolean on = false;
		for (int i = 0; i < packed.length; i++, on = !on) {
			if (on == wanted)
				c += packed[i] & 0xffff;
		}
		return c;
	}

	/**
	 * Finds how many cells are encoded in a packed array (both on and off) without
	 * unpacking it.
	 * 
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @return the number of cells that are encoded explicitly in the packed data as
	 *         either on or off.
	 */
	public static int covered(short[] packed) {
		int c = 0;
		for (int i = 0; i < packed.length; i++) {
			c += packed[i] & 0xffff;
		}
		return c;
	}

	/**
	 * Finds the minimum bounding rectangle for a packed array without unpacking it.
	 * Returns a Coord array with 2 elements: the minimum-x/minimum-y Coord corner
	 * of the bounds, then the maximum-x/maximum-y Coord corner. Both array elements
	 * will be the Coord (-1,-1) if the packed array does not encode any "on" values
	 * (all empty).
	 * 
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @return a 2-element Coord array starting with the bounds' minimum corner and
	 *         followed by the maximum corner
	 */
	public static Coord[] bounds(short[] packed) {
		Coord[] c = new Coord[] { Coord.get(-1, -1), Coord.get(-1, -1) };
		boolean on = false;
		int idx = 0, min_x = 256, min_y = 256, max_x = -1, max_y = -1, x, y;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					if (min_x > x)
						min_x = x;
					if (min_y > y)
						min_y = y;
					if (max_x < x)
						max_x = x;
					if (max_y < y)
						max_y = y;
				}
			}
			idx += packed[p] & 0xffff;
		}
		if (min_x < 256) {
			c[0] = Coord.get(min_x, min_y);
			c[1] = Coord.get(max_x, max_y);
		}
		return c;
	}

	/**
	 * Given a 2D char array for a map, a piece of packed data defining a region to
	 * use from that map, and a filler char, produces a 2D char array where all
	 * positions that are "off" in packed are filled with filler, and the rest are
	 * the same as in map.
	 * 
	 * @param map
	 *            a 2D char array that will not be modified
	 * @param packed
	 *            a packed short array, as produced by pack()
	 * @param filler
	 *            the char to use for "off" positions in packed
	 * @return a 2D char array similar to map but with any "off" positions in packed
	 *         replaced with filler
	 */
	public static char[][] mask(char[][] map, short[] packed, char filler) {
		if (map.length <= 0)
			return map;
		char[][] c = new char[map.length][map[0].length];
		for (int i = 0; i < map.length; i++) {
			Arrays.fill(c[i], filler);
		}
		boolean on = false;
		int idx = 0, x, y;
		for (int p = 0; p < packed.length; p++, on = !on) {
			if (on) {
				for (int i = idx; i < idx + (packed[p] & 0xffff); i++) {
					x = hilbertX[i];
					y = hilbertY[i];
					if (x >= map.length || y >= map[x].length)
						continue;
					c[x][y] = map[x][y];
				}
			}
			idx += packed[p] & 0xffff;
		}
		return c;
	}

	/**
	 * Given two packed short arrays, left and right, this produces a packed short
	 * array that encodes "on" for any cell that was "on" in either left or in
	 * right, and only encodes "off" for cells that were off in both. This method
	 * does not do any unpacking (which can be somewhat computationally expensive)
	 * and so should be strongly preferred when merging two pieces of packed data.
	 * 
	 * @param left
	 *            A packed array such as one produced by pack()
	 * @param right
	 *            A packed array such as one produced by pack()
	 * @return A packed array that encodes "on" for all cells that were "on" in
	 *         either left or right
	 */
	public static short[] unionPacked(short[] left, short[] right) {
		if (left.length == 0)
			return right;
		if (right.length == 0)
			return left;
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, onLeft = false, onRight = false;
		int idx = 0, skip = 0, elemLeft = 0, elemRight = 0, totalLeft = 0, totalRight = 0;
		while ((elemLeft < left.length || elemRight < right.length) && idx <= 0xffff) {
			if (elemLeft >= left.length) {
				totalLeft = 0x20000;
				onLeft = false;
			} else if (totalLeft <= idx) {
				totalLeft += left[elemLeft] & 0xffff;
			}
			if (elemRight >= right.length) {
				totalRight = 0x20000;
				onRight = false;
			} else if (totalRight <= idx) {
				totalRight += right[elemRight] & 0xffff;
			}
			// 300, 5, 6, 8, 2, 4
			// 290, 12, 9, 1
			// =
			// 290, 15, 6, 8, 2, 4
			// 290 off in both, 10 in right, 2 in both, 3 in left, 6 off in both, 1 on in
			// both, 7 on in left, 2 off in
			// both, 4 on in left
			if (totalLeft < totalRight) {
				onLeft = !onLeft;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft || onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
			} else if (totalLeft == totalRight) {
				onLeft = !onLeft;
				onRight = !onRight;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft || onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
				elemRight++;

			} else {
				onRight = !onRight;
				skip += totalRight - idx;
				idx = totalRight;
				if (on != (onLeft || onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemRight++;
			}
		}
		return packing.toArray();
	}

	/**
	 * Given two packed short arrays, left and right, this produces a packed short
	 * array that encodes "on" for any cell that was "on" in both left and in right,
	 * and encodes "off" for cells that were off in either array. This method does
	 * not do any unpacking (which can be somewhat computationally expensive) and so
	 * should be strongly preferred when finding the intersection of two pieces of
	 * packed data.
	 * 
	 * @param left
	 *            A packed array such as one produced by pack()
	 * @param right
	 *            A packed array such as one produced by pack()
	 * @return A packed array that encodes "on" for all cells that were "on" in both
	 *         left and right
	 */
	public static short[] intersectPacked(short[] left, short[] right) {
		if (left.length == 0 || right.length == 0)
			return ALL_WALL;
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, onLeft = false, onRight = false;
		int idx = 0, skip = 0, elemLeft = 0, elemRight = 0, totalLeft = 0, totalRight = 0;
		while ((elemLeft < left.length && elemRight < right.length) && idx <= 0xffff) {
			if (elemLeft >= left.length) {
				totalLeft = 0x20000;
				onLeft = false;
			} else if (totalLeft <= idx) {
				totalLeft += left[elemLeft] & 0xffff;
			}
			if (elemRight >= right.length) {
				totalRight = 0x20000;
				onRight = false;
			} else if (totalRight <= idx) {
				totalRight += right[elemRight] & 0xffff;
			}
			// 300, 5, 6, 8, 2, 4
			// 290, 12, 9, 1
			// =
			// 300, 2, 9, 1
			// 300 off, 2 on, 9 off, 1 on
			if (totalLeft < totalRight) {
				onLeft = !onLeft;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft && onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
			} else if (totalLeft == totalRight) {
				onLeft = !onLeft;
				onRight = !onRight;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft && onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
				elemRight++;

			} else {
				onRight = !onRight;
				skip += totalRight - idx;
				idx = totalRight;
				if (on != (onLeft && onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemRight++;
			}
		}
		return packing.toArray();
	}

	/**
	 * Checks if no cells are encoded as "on" in packed.
	 * 
	 * @param packed
	 *            a packed array such as one produced by pack()
	 * @return false if there is at least one "on" cell in packed; true if there are
	 *         no "on" cells
	 */
	public static boolean isEmpty(short[] packed) {
		return packed == null || packed.length <= 1;
	}

	/**
	 * Given two packed short arrays, left and right, this returns true if they
	 * encode any overlapping area (their areas intersect), or false if they do not
	 * overlap at all (they don't intersect). This is more efficient than
	 * calculating the intersection with intersectPacked() just to check if it is
	 * non-empty, since this method can short-circuit and return true the moment it
	 * finds an intersection, plus it needs less overhead (slightly) to do so.
	 * 
	 * @param left
	 *            A packed array such as one produced by pack()
	 * @param right
	 *            A packed array such as one produced by pack()
	 * @return The boolean true if left and right overlap at all, or false if they
	 *         lack any intersecting area
	 */
	public static boolean intersects(short[] left, short[] right) {
		if (left.length == 0 || right.length == 0)
			return false;
		boolean onLeft = false, onRight = false;
		int idx = 0, elemLeft = 0, elemRight = 0, totalLeft = 0, totalRight = 0;
		while ((elemLeft < left.length && elemRight < right.length) && idx <= 0xffff) {
			if (elemLeft >= left.length) {
				totalLeft = 0x20000;
				onLeft = false;
			} else if (totalLeft <= idx) {
				totalLeft += left[elemLeft] & 0xffff;
			}
			if (elemRight >= right.length) {
				totalRight = 0x20000;
				onRight = false;
			} else if (totalRight <= idx) {
				totalRight += right[elemRight] & 0xffff;
			}
			// 300, 5, 6, 8, 2, 4
			// 290, 12, 9, 1
			// =
			// 300, 2, 9, 1
			// 300 off, 2 on, 9 off, 1 on
			if (totalLeft < totalRight) {
				onLeft = !onLeft;
				idx = totalLeft;
				if (onLeft && onRight)
					return true;
				elemLeft++;
			} else if (totalLeft == totalRight) {
				onLeft = !onLeft;
				onRight = !onRight;
				idx = totalLeft;
				if (onLeft && onRight)
					return true;
				elemLeft++;
				elemRight++;

			} else {
				onRight = !onRight;
				idx = totalRight;
				if (onLeft && onRight)
					return true;
				elemRight++;
			}
		}
		return false;
	}

	/**
	 * Given one packed short array, this produces a packed short array that is the
	 * exact opposite of the one passed in, that is, every "on" cell becomes "off"
	 * and every "off" cell becomes "on", including cells that were "off" because
	 * they were beyond the boundaries of the original 2D array passed to pack() or
	 * a similar method. This method does not do any unpacking (which can be
	 * somewhat computationally expensive), and actually requires among the lowest
	 * amounts of computation to get a result of any methods in CoordPacker.
	 * However, because it will cause cells to be considered "on" that would cause
	 * an exception if directly converted to x,y positions and accessed in the
	 * source 2D array, this method should primarily be used in conjunction with
	 * operations such as intersectPacked(), or have the checking for boundaries
	 * handled internally by unpack() or related methods such as
	 * unpackMultiDouble().
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @return A packed array that encodes "on" all cells that were "off" in
	 *         original
	 */
	public static short[] negatePacked(short[] original) {
		if (original.length <= 1) {
			return ALL_ON;
		}
		if (original[0] == 0) {
			short[] copy = new short[original.length - 2];
			System.arraycopy(original, 1, copy, 0, original.length - 2);
			// copy[original.length - 3] = (short) (0xFFFF - covered(copy));
			return copy;
		}
		short[] copy = new short[original.length + 2];
		copy[0] = 0;
		System.arraycopy(original, 0, copy, 1, original.length);
		copy[copy.length - 1] = (short) (0xFFFF - covered(copy));
		return copy;
	}

	/**
	 * Given two packed short arrays, left and right, this produces a packed short
	 * array that encodes "on" for any cell that was "on" in left but "off" in
	 * right, and encodes "off" for cells that were "on" in right or "off" in left.
	 * This method does not do any unpacking (which can be somewhat computationally
	 * expensive) and so should be strongly preferred when finding a region of one
	 * packed array that is not contained in another packed array.
	 * 
	 * @param left
	 *            A packed array such as one produced by pack()
	 * @param right
	 *            A packed array such as one produced by pack()
	 * @return A packed array that encodes "on" for all cells that were "on" in left
	 *         and "off" in right
	 */
	public static short[] differencePacked(short[] left, short[] right) {
		if (left.length <= 1)
			return ALL_WALL;
		if (right.length <= 1)
			return left;
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, onLeft = false, onRight = false;
		int idx = 0, skip = 0, elemLeft = 0, elemRight = 0, totalLeft = 0, totalRight = 0;
		while ((elemLeft < left.length || elemRight < right.length) && idx <= 0xffff) {
			if (elemLeft >= left.length) {
				totalLeft = 0x20000;
				onLeft = false;
			} else if (totalLeft <= idx) {
				totalLeft += left[elemLeft] & 0xffff;
			}
			if (elemRight >= right.length) {
				totalRight = 0x20000;
				onRight = false;
			} else if (totalRight <= idx) {
				totalRight += right[elemRight] & 0xffff;
			}
			if (totalLeft < totalRight) {
				onLeft = !onLeft;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft && !onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
			} else if (totalLeft == totalRight) {
				onLeft = !onLeft;
				onRight = !onRight;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft && !onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
				elemRight++;

			} else {
				onRight = !onRight;
				skip += totalRight - idx;
				idx = totalRight;
				if (on != (onLeft && !onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemRight++;
			}
		}
		return packing.toArray();
	}

	/**
	 * Given two packed short arrays, left and right, this produces a packed short
	 * array that encodes "on" for any cell that was "on" only in left or only in
	 * right, but not a cell that was "off" in both or "on" in both. This method
	 * does not do any unpacking (which can be somewhat computationally expensive)
	 * and so should be strongly preferred when performing an exclusive-or operation
	 * on two pieces of packed data. <br>
	 * Could more-correctly be called exclusiveDisjunctionPacked to match the other
	 * terms, but... seriously?
	 * 
	 * @param left
	 *            A packed array such as one produced by pack()
	 * @param right
	 *            A packed array such as one produced by pack()
	 * @return A packed array that encodes "on" for all cells such that left's cell
	 *         ^ right's cell returns true
	 */
	public static short[] xorPacked(short[] left, short[] right) {
		if (left.length == 0)
			return right;
		if (right.length == 0)
			return left;
		ShortVLA packing = new ShortVLA(64);
		boolean on = false, onLeft = false, onRight = false;
		int idx = 0, skip = 0, elemLeft = 0, elemRight = 0, totalLeft = 0, totalRight = 0;
		while ((elemLeft < left.length || elemRight < right.length) && idx <= 0xffff) {
			if (elemLeft >= left.length) {
				totalLeft = 0x20000;
				onLeft = false;
			} else if (totalLeft <= idx) {
				totalLeft += left[elemLeft] & 0xffff;
			}
			if (elemRight >= right.length) {
				totalRight = 0x20000;
				onRight = false;
			} else if (totalRight <= idx) {
				totalRight += right[elemRight] & 0xffff;
			}
			// 300, 5, 6, 8, 2, 4
			// 290, 12, 9, 1
			// =
			// 290, 15, 6, 8, 2, 4
			// 290 off in both, 10 in right, 2 in both, 3 in left, 6 off in both, 1 on in
			// both, 7 on in left, 2 off in
			// both, 4 on in left
			if (totalLeft < totalRight) {
				onLeft = !onLeft;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft ^ onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
			} else if (totalLeft == totalRight) {
				onLeft = !onLeft;
				onRight = !onRight;
				skip += totalLeft - idx;
				idx = totalLeft;
				if (on != (onLeft ^ onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemLeft++;
				elemRight++;

			} else {
				onRight = !onRight;
				skip += totalRight - idx;
				idx = totalRight;
				if (on != (onLeft ^ onRight)) {
					packing.add((short) skip);
					skip = 0;
					on = !on;
				}
				elemRight++;
			}
		}
		return packing.toArray();
	}

	/**
	 * Returns a new packed short[] containing the Hilbert distance hilbert as "on",
	 * and all other cells "off". Much more efficient than packSeveral called with
	 * only one argument.
	 * 
	 * @param hilbert
	 *            a Hilbert distance that will be encoded as "on"
	 * @return the point given to this encoded as "on" in a packed short array
	 */
	public static short[] packOne(int hilbert) {
		return new short[] { (short) hilbert, 1 };
	}

	/**
	 * Returns a new packed short[] containing the Coord point as "on", and all
	 * other cells "off". Much more efficient than packSeveral called with only one
	 * argument.
	 * 
	 * @param point
	 *            a Coord that will be encoded as "on"
	 * @return the point given to this encoded as "on" in a packed short array
	 */
	public static short[] packOne(Coord point) {
		return new short[] { (short) coordToHilbert(point), 1 };
	}

	/**
	 * Returns a new packed short[] containing the given x,y cell as "on", and all
	 * other cells "off". Much more efficient than packSeveral called with only one
	 * argument.
	 * 
	 * @param x
	 *            the x component of the point that will be encoded as "on"
	 * @param y
	 *            the y component of the point that will be encoded as "on"
	 * @return the point given to this encoded as "on" in a packed short array
	 */
	public static short[] packOne(int x, int y) {
		return new short[] { (short) posToHilbert(x, y), 1 };
	}

	/**
	 * Returns a new packed short[] containing the Hilbert distances in hilbert as
	 * "on" cells, and all other cells "off"
	 * 
	 * @param hilbert
	 *            a vararg or array of Hilbert distances that will be encoded as
	 *            "on"
	 * @return the points given to this encoded as "on" in a packed short array
	 */
	public static short[] packSeveral(int... hilbert) {
		if (hilbert == null || hilbert.length == 0)
			return ALL_WALL;
		Arrays.sort(hilbert);
		ShortVLA vla = new ShortVLA(128);
		int current, past = hilbert[0], skip = 0;

		vla.add((short) hilbert[0]);
		for (int i = 1; i < hilbert.length; i++) {
			current = hilbert[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));
		return vla.toArray();
	}

	/**
	 * Returns a new packed short[] containing the Coords in points as "on" cells,
	 * and all other cells "off"
	 * 
	 * @param points
	 *            a vararg or array of Coords that will be encoded as "on"
	 * @return the points given to this encoded as "on" in a packed short array
	 */
	public static short[] packSeveral(Coord... points) {
		if (points == null || points.length == 0)
			return ALL_WALL;
		int[] hilbert = new int[points.length];
		for (int i = 0; i < points.length; i++) {
			if (points[i] == null)
				return ALL_WALL;
			hilbert[i] = coordToHilbert(points[i]);
		}

		Arrays.sort(hilbert);
		ShortVLA vla = new ShortVLA(128);
		int current, past = hilbert[0], skip = 0;

		vla.add((short) hilbert[0]);
		for (int i = 1; i < hilbert.length; i++) {
			current = hilbert[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));
		return vla.toArray();
	}

	/**
	 * Returns a new packed short[] containing the Coords in points as "on" cells,
	 * and all other cells "off"
	 * 
	 * @param points
	 *            a Collection of Coords that will be encoded as "on"
	 * @return the points given to this encoded as "on" in a packed short array
	 */
	public static short[] packSeveral(Collection<Coord> points) {
		if (points == null || points.isEmpty())
			return ALL_WALL;
		int sz = points.size();
		int[] hilbert = new int[sz];
		int idx = 0;
		for (Coord c : points) {
			if (c == null)
				return ALL_WALL;
			hilbert[idx++] = coordToHilbert(c);
		}
		Arrays.sort(hilbert);
		ShortVLA vla = new ShortVLA(128);
		int current, past = hilbert[0], skip = 0;

		vla.add((short) hilbert[0]);
		for (int i = 1; i < hilbert.length; i++) {
			current = hilbert[i];
			if (current - past > 1) {
				vla.add((short) (skip + 1));
				skip = 0;
				vla.add((short) (current - past - 1));
			} else if (current != past)
				skip++;
			past = current;
		}
		vla.add((short) (skip + 1));
		return vla.toArray();
	}

	/**
	 * Given one packed short array, original, and a Hilbert Curve index, hilbert,
	 * this produces a packed short array that encodes "on" for any cell that was
	 * "on" in original, always encodes "on" for the position referred to by
	 * hilbert, and encodes "off" for cells that were "off" in original and are not
	 * the cell hilbert refers to. This method does not do any unpacking (which can
	 * be somewhat computationally expensive) and so should be strongly preferred
	 * when finding a region of one packed array that is not contained in another
	 * packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param hilbert
	 *            A Hilbert Curve index that should be inserted into the result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original or correspond to hilbert
	 */
	public static short[] insertPacked(short[] original, short hilbert) {
		return unionPacked(original, new short[] { hilbert, 1 });
	}

	/**
	 * Given one packed short array, original, and a position as x,y numbers, this
	 * produces a packed short array that encodes "on" for any cell that was "on" in
	 * original, always encodes "on" for the position referred to by x and y, and
	 * encodes "off" for cells that were "off" in original and are not the cell x
	 * and y refer to. This method does not do any unpacking (which can be somewhat
	 * computationally expensive) and so should be strongly preferred when finding a
	 * region of one packed array that is not contained in another packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param x
	 *            The x position at which to insert the "on" cell
	 * @param y
	 *            The y position at which to insert the "on" cell
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original or correspond to x,y
	 */
	public static short[] insertPacked(short[] original, int x, int y) {
		return unionPacked(original, new short[] { (short) posToHilbert(x, y), 1 });
	}

	/**
	 * Given one packed short array, original, and a number of Hilbert Curve
	 * indices, hilbert, this produces a packed short array that encodes "on" for
	 * any cell that was "on" in original, always encodes "on" for the position
	 * referred to by any element of hilbert, and encodes "off" for cells that were
	 * "off" in original and are not in any cell hilbert refers to. This method does
	 * not do any unpacking (which can be somewhat computationally expensive) and so
	 * should be strongly preferred when you have several Hilbert Curve indices,
	 * possibly nearby each other but just as possibly not, that you need inserted
	 * into a packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param hilbert
	 *            an array or vararg of Hilbert Curve indices that should be
	 *            inserted into the result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original or are contained in hilbert
	 */
	public static short[] insertSeveralPacked(short[] original, int... hilbert) {
		return unionPacked(original, packSeveral(hilbert));
	}

	/**
	 * Given one packed short array, original, and a number of Coords, points, this
	 * produces a packed short array that encodes "on" for any cell that was "on" in
	 * original, always encodes "on" for the position referred to by any element of
	 * points, and encodes "off" for cells that were "off" in original and are not
	 * in any cell points refers to. This method does not do any unpacking (which
	 * can be somewhat computationally expensive) and so should be strongly
	 * preferred when you have several Coords, possibly nearby each other but just
	 * as possibly not, that you need inserted into a packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param points
	 *            an array or vararg of Coords that should be inserted into the
	 *            result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original or are contained in hilbert
	 */
	public static short[] insertSeveralPacked(short[] original, Coord... points) {
		return unionPacked(original, packSeveral(points));
	}

	/**
	 * Given one packed short array, original, and a Collection of Coords, points,
	 * this produces a packed short array that encodes "on" for any cell that was
	 * "on" in original, always encodes "on" for the position referred to by any
	 * element of points, and encodes "off" for cells that were "off" in original
	 * and are not in any cell points refers to. This method does not do any
	 * unpacking (which can be somewhat computationally expensive) and so should be
	 * strongly preferred when you have several Coords, possibly nearby each other
	 * but just as possibly not, that you need inserted into a packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param points
	 *            an array or vararg of Coords that should be inserted into the
	 *            result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original or are contained in hilbert
	 */
	public static short[] insertSeveralPacked(short[] original, Collection<Coord> points) {
		return unionPacked(original, packSeveral(points));
	}

	/**
	 * Given one packed short array, original, and a Hilbert Curve index, hilbert,
	 * this produces a packed short array that encodes "on" for any cell that was
	 * "on" in original, unless it was the position referred to by hilbert, and
	 * encodes "off" for cells that were "off" in original or are the cell hilbert
	 * refers to. This method does not do any unpacking (which can be somewhat
	 * computationally expensive) and so should be strongly preferred when finding a
	 * region of one packed array that is not contained in another packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param hilbert
	 *            A Hilbert Curve index that should be removed from the result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original and don't correspond to hilbert
	 */
	public static short[] removePacked(short[] original, short hilbert) {
		return differencePacked(original, new short[] { hilbert, 1 });
	}

	/**
	 * Given one packed short array, original, and a position as x,y numbers, this
	 * produces a packed short array that encodes "on" for any cell that was "on" in
	 * original, unless it was the position referred to by x and y, and encodes
	 * "off" for cells that were "off" in original or are the cell x and y refer to.
	 * This method does not do any unpacking (which can be somewhat computationally
	 * expensive) and so should be strongly preferred when finding a region of one
	 * packed array that is not contained in another packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param x
	 *            The x position at which to remove any "on" cell
	 * @param y
	 *            The y position at which to remove any "on" cell
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original and don't correspond to x,y
	 */
	public static short[] removePacked(short[] original, int x, int y) {
		int dist = posToHilbert(x, y);
		return differencePacked(original, new short[] { (short) dist, 1 });
	}

	/**
	 * Given one packed short array, original, and a number of Hilbert Curve
	 * indices, hilbert, this produces a packed short array that encodes "on" for
	 * any cell that was "on" in original, unless it was a position referred to by
	 * hilbert, and encodes "off" for cells that were "off" in original and are a
	 * cell hilbert refers to. This method does not do any unpacking (which can be
	 * somewhat computationally expensive) and so should be strongly preferred when
	 * you have several Hilbert Curve indices, possibly nearby each other but just
	 * as possibly not, that you need removed from a packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param hilbert
	 *            an array or vararg of Hilbert Curve indices that should be
	 *            inserted into the result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original and aren't contained in hilbert
	 */
	public static short[] removeSeveralPacked(short[] original, int... hilbert) {
		return differencePacked(original, packSeveral(hilbert));
	}

	/**
	 * Given one packed short array, original, and a number of Coords, points, this
	 * produces a packed short array that encodes "on" for any cell that was "on" in
	 * original, unless it was a position referred to by an element in points, and
	 * encodes "off" for cells that were "off" in original and are a cell points
	 * refers to. This method does not do any unpacking (which can be somewhat
	 * computationally expensive) and so should be strongly preferred when you have
	 * several Hilbert Curve indices, possibly nearby each other but just as
	 * possibly not, that you need removed from a packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param points
	 *            an array or vararg of Coords that should be removed from the
	 *            result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original and aren't contained in points
	 */
	public static short[] removeSeveralPacked(short[] original, Coord... points) {
		return differencePacked(original, packSeveral(points));
	}

	/**
	 * Given one packed short array, original, and a number of Coords, points, this
	 * produces a packed short array that encodes "on" for any cell that was "on" in
	 * original, unless it was a position referred to by an element in points, and
	 * encodes "off" for cells that were "off" in original and are a cell points
	 * refers to. This method does not do any unpacking (which can be somewhat
	 * computationally expensive) and so should be strongly preferred when you have
	 * several Hilbert Curve indices, possibly nearby each other but just as
	 * possibly not, that you need removed from a packed array.
	 * 
	 * @param original
	 *            A packed array such as one produced by pack()
	 * @param points
	 *            a Collection of Coords that should be removed from the result
	 * @return A packed array that encodes "on" for all cells that are "on" in
	 *         original and aren't contained in points
	 */
	public static short[] removeSeveralPacked(short[] original, Collection<Coord> points) {
		return differencePacked(original, packSeveral(points));
	}

	/**
	 * Given a packed data array that encodes multiple unconnected "on" areas, this
	 * finds each isolated area (areas that are only adjacent diagonally are
	 * considered separate from each other) and returns it as an element in an
	 * ArrayList of short[], with one short[] array per isolated area. Useful when
	 * you have, for example, all the rooms in a dungeon with their connecting
	 * corridors removed, but want to separate the rooms. You can get the
	 * aforementioned data assuming a bare dungeon called map with WIDTH and HEIGHT
	 * constants using: <br>
	 * {@code short[] floors = pack(map, '.'),
	 * rooms = flood(floors, retract(floors, 1, WIDTH, HEIGHT, true), 2, false),
	 * corridors = differencePacked(floors, rooms),
	 * doors = intersectPacked(rooms, fringe(corridors, 1, WIDTH, HEIGHT, false));}
	 * <br>
	 * You can then get all rooms as separate regions with
	 * {@code List<short[]> apart = split(rooms);}, or substitute
	 * {@code split(corridors)} to get the corridors. The room-finding technique
	 * works by shrinking floors by a radius of 1 (8-way), which causes thin areas
	 * like corridors of 2 or less width to be removed, then flood-filling the
	 * floors out from the area that produces by 2 cells (4-way this time) to
	 * restore the original size of non-corridor areas (plus some extra to ensure
	 * odd shapes are kept). Corridors are obtained by removing the rooms from
	 * floors. The example code also gets the doors (which overlap with rooms, not
	 * corridors) by finding where the a room and a corridor are adjacent. This
	 * technique is used with some enhancements in the RoomFinder class.
	 * 
	 * @see squidpony.squidgrid.mapping.RoomFinder for a class that uses this
	 *      technique without exposing CoordPacker
	 * @param packed
	 *            a packed data array that probably encodes multiple unconnected
	 *            "on" areas
	 * @return an ArrayList of short[] containing each unconnected area from packed
	 *         as a short[] element
	 */
	public static ArrayList<short[]> split(short[] packed) {
		ArrayList<short[]> arrays = new ArrayList<>(32);
		short[] remaining = new short[packed.length];
		System.arraycopy(packed, 0, remaining, 0, packed.length);
		while (remaining.length > 1) {
			boolean on = false;
			int idx = 0;
			for (int p = 0; p < remaining.length; p++, on = !on) {
				if (on) {
					short[] area = flood(packed, packOne((short) idx), 512, false);
					arrays.add(area);
					remaining = differencePacked(remaining, area);
					break;
				}
				idx += remaining[p] & 0xffff;
			}
		}
		return arrays;
	}

	public static short[] removeIsolated(short[] packed) {
		short[] remaining = new short[packed.length], viable = new short[packed.length];
		System.arraycopy(packed, 0, remaining, 0, packed.length);
		System.arraycopy(packed, 0, viable, 0, packed.length);
		while (remaining.length > 1) {
			boolean on = false;
			int idx = 0;
			for (int p = 0; p < remaining.length; p++, on = !on) {
				if (on) {
					short[] area = flood(packed, packOne((short) idx), 512, false);
					if (count(area) <= 4)
						viable = differencePacked(viable, area);
					remaining = differencePacked(remaining, area);
					break;
				}
				idx += remaining[p] & 0xffff;
			}
		}
		return viable;
	}

	/**
	 * Gets a single randomly chosen position that is "on" in the given packed
	 * array, without unpacking it, and returns it as a Coord or returns null of the
	 * array is empty. Random numbers are generated by the rng parameter. More
	 * efficient in most cases than randomSample(), and will always return at least
	 * one Coord for non-empty arrays.
	 * 
	 * @param packed
	 *            a short[] returned by pack() or one of the sub-arrays in what is
	 *            returned by packMulti(); must not be null (this method does not
	 *            check).
	 * @param rng
	 *            the random number generator used to decide random factors
	 * @return a Coord corresponding to a random "on" cell in packed, or the Coord
	 *         (-1, -1) if packed is empty
	 */
	public static Coord singleRandom(short[] packed, RNG rng) {
		int counted = count(packed);
		if (counted == 0)
			return Coord.get(-1, -1);
		int r = rng.nextInt(counted);
		int c = 0, idx = 0;
		boolean on = false;
		for (int i = 0; i < packed.length; on = !on, idx += packed[i] & 0xFFFF, i++) {
			if (on) {
				if (c + (packed[i] & 0xFFFF) > r) {
					idx += r - c;
					return Coord.get(hilbertX[idx], hilbertY[idx]);
				}
				c += packed[i] & 0xFFFF;
			}
		}
		return Coord.get(-1, -1);

	}

	/**
	 * Takes multiple pieces of packed data as short[], encoded by pack() or another
	 * similar method of this class, and generates a 2D int array with the specified
	 * width and height and a starting value of 0 for all elements, then where every
	 * occurrence of a cell as "on" in a piece of packed data increments the cell's
	 * value in the returned array. Width and height do not technically need to
	 * match the dimensions of the original 2D array, but under most circumstances
	 * where they don't match, the data produced will be junk.
	 * 
	 * @param width
	 *            the width of the 2D array that will be returned; should match the
	 *            unpacked array's width
	 * @param height
	 *            the height of the 2D array that will be returned; should match the
	 *            unpacked array's height
	 * @param many
	 *            a vararg or array of short[] encoded by calling one of this class'
	 *            packing methods on a 2D array
	 * @return an int[][] storing at least 0 for all cells, plus 1 for every element
	 *         of packed that has that cell "on"
	 */
	public static int[][] sumMany(int width, int height, short[]... many) {
		if (many == null)
			throw new ArrayIndexOutOfBoundsException("CoordPacker.sumMany() must be given a non-null many arg");
		int[][] unpacked = new int[width][height];
		for (int e = 0; e < many.length; e++) {
			boolean on = false;
			int idx = 0;
			short x = 0, y = 0;
			for (int p = 0; p < many[e].length; p++, on = !on) {
				if (on) {
					for (int toSkip = idx + (many[e][p] & 0xffff); idx < toSkip && idx < 0x10000; idx++) {
						x = hilbertX[idx];
						y = hilbertY[idx];
						if (x >= width || y >= height)
							continue;
						unpacked[x][y]++;
					}
				} else {
					idx += many[e][p] & 0xffff;
				}
			}
		}
		return unpacked;
	}

	/**
	 * Quick utility method for printing packed data as a grid of 1 (on) and/or 0
	 * (off). Useful primarily for debugging.
	 * 
	 * @param packed
	 *            a packed short[] such as one produced by pack()
	 * @param width
	 *            the width of the packed 2D array
	 * @param height
	 *            the height of the packed 2D array
	 */
	public static void printPacked(short[] packed, int width, int height) {
		boolean[][] unpacked = unpack(packed, width, height);
		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				System.out.print(unpacked[x][y] ? '1' : '0');
			}
			System.out.println();
		}
	}

	public static void printCompressedData(short[] packed) {
		if (packed == null || packed.length == 0) {
			System.out.println("[]");
			return;
		}
		System.out.print("[" + packed[0]);
		for (int i = 1; i < packed.length; i++) {
			System.out.print(", " + packed[i]);
		}
		System.out.println("]");
	}

	/**
	 * Encodes a short array of packed data as a (larger, more memory-hungry) ASCII
	 * string, which can be decoded using CoordPacker.decodeASCII() . Uses 64
	 * printable chars, from '?' (ASCII 63) to '~' (ASCII 126).
	 * 
	 * @param packed
	 *            a packed data item produced by pack() or some other method from
	 *            this class.
	 * @return a printable String, which can be decoded with
	 *         CoordPacker.decodeASCII()
	 */
	public static String encodeASCII(short[] packed) {
		int len = packed.length * 3;
		char[] chars = new char[len];
		for (int i = 0, c = 0; c < len; i++, c += 3) {
			chars[c] = (char) ((packed[i] & 31) + 63);
			chars[c + 1] = (char) (((packed[i] >> 5) & 31) + 63);
			chars[c + 2] = (char) (((packed[i] >>> 10) & 63) + 63);
		}
		return new String(chars);
	}

	/**
	 * Given a String specifically produced by CoordPacker.encodeASCII(), this will
	 * produce a packed data array.
	 * 
	 * @param text
	 *            a String produced by CoordPacker.encodeASCII(); this will almost
	 *            certainly fail on other strings.
	 * @return the packed data as a short array that was originally used to encode
	 *         text
	 */
	public static short[] decodeASCII(String text) {
		int len = text.length();
		if (len % 3 != 0)
			return ALL_WALL;
		char[] chars = text.toCharArray();
		short[] packed = new short[len / 3];
		for (int c = 0, i = 0; c < len; i++, c += 3) {
			packed[i] = (short) (((chars[c] - 63) & 31) | (((chars[c + 1] - 63) & 31) << 5)
					| (((chars[c + 2] - 63) & 63) << 10));
		}
		return packed;
	}

	/**
	 * Encodes a short array of packed data as a (larger, slightly more
	 * memory-hungry) Unicode string using only Braille characters, which can be
	 * decoded using CoordPacker.decodeBraille(). Uses 256 semi-printable chars,
	 * from 0x2800 to 0x28FF, which allows UTF-8 encoding (and some other encodings)
	 * to more efficiently store the data. These are only semi-printable because
	 * many fonts do not support Braille, and 0x2800 is sometimes printed
	 * equivalently to a space char (or sometimes as 8 small dots or open circles,
	 * which is preferable). Braille was chosen because it's available as a full
	 * Unicode block of 256 characters with no gaps or chars that require special
	 * treatment, like newlines and carriage returns.
	 * 
	 * @param packed
	 *            a packed data item produced by pack() or some other method from
	 *            this class.
	 * @return a semi-printable String, which can be decoded with
	 *         CoordPacker.decodeBraille()
	 */
	public static String encodeBraille(short[] packed) {
		int len = packed.length * 2;
		char[] chars = new char[len];
		for (int i = 0, c = 0; c < len; i++, c += 2) {
			chars[c] = (char) ((packed[i] & 0xff) | 0x2800);
			chars[c + 1] = (char) ((packed[i] >>> 8) | 0x2800);
		}
		return new String(chars);
	}

	/**
	 * Given a String specifically produced by CoordPacker.encodeBraille(), this
	 * will produce a packed data array. Uses 256 semi-printable chars, from 0x2800
	 * to 0x28FF, which allows UTF-8 encoding (and some other encodings) to more
	 * efficiently store the data. These are only semi-printable because many fonts
	 * do not support Braille, and 0x2800 is sometimes printed equivalently to a
	 * space char (or sometimes as 8 small dots or open circles, which is
	 * preferable). Braille was chosen because it's available as a full Unicode
	 * block of 256 characters with no gaps or chars that require special treatment,
	 * like newlines and carriage returns.
	 * 
	 * @param text
	 *            a String produced by CoordPacker.encodeBraille(); this will almost
	 *            certainly fail on other strings.
	 * @return the packed data as a short array that was originally used to encode
	 *         text
	 */
	public static short[] decodeBraille(String text) {
		int len = text.length();
		if (len % 2 != 0 || len == 0)
			return ALL_WALL;
		char[] chars = text.toCharArray();
		short[] packed = new short[len / 2];
		for (int c = 0, i = 0; c < len; i++, c += 2) {
			packed[i] = (short) ((chars[c] ^ 0x2800) | ((chars[c + 1] ^ 0x2800) << 8));
		}
		return packed;
	}

	/**
	 * Encode a number n as a Gray code; Gray codes have a relation to the Hilbert
	 * curve and may be useful. Source: http://xn--2-umb.com/15/hilbert ,
	 * http://aggregate.org/MAGIC/#Gray%20Code%20Conversion
	 * 
	 * @param n
	 *            any int
	 * @return the gray code for n
	 */
	public static int grayEncode(int n) {
		return n ^ (n >> 1);
	}

	/**
	 * Decode a number from a Gray code n; Gray codes have a relation to the Hilbert
	 * curve and may be useful. Source: http://xn--2-umb.com/15/hilbert ,
	 * http://aggregate.org/MAGIC/#Gray%20Code%20Conversion
	 * 
	 * @param n
	 *            a gray code, as produced by grayEncode
	 * @return the decoded int
	 */
	public static int grayDecode(int n) {
		int p = n;
		while ((n >>= 1) != 0)
			p ^= n;
		return p;
	}

	/**
	 * Takes an x, y position and returns the length to travel along the 256x256
	 * Hilbert curve to reach that position. This assumes x and y are between 0 and
	 * 255, inclusive. This uses a lookup table for the 256x256 Hilbert Curve, which
	 * should make it faster than calculating the distance along the Hilbert Curve
	 * repeatedly. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param x
	 *            between 0 and 255 inclusive
	 * @param y
	 *            between 0 and 255 inclusive
	 * @return the distance to travel along the 256x256 Hilbert Curve to get to the
	 *         given x, y point.
	 */
	public static int posToHilbert(final int x, final int y) {
		// int dist = posToHilbertNoLUT(x, y);
		// return dist;
		return hilbertDistances[x + (y << 8)] & 0xffff;
	}

	/**
	 * Takes an x, y, z position and returns the length to travel along the 32x32x32
	 * Hilbert curve to reach that position. This assumes x, y, and z are between 0
	 * and 31, inclusive. This uses a lookup table for the 32x32x32 Hilbert Curve,
	 * which should make it faster than calculating the distance along the Hilbert
	 * Curve repeatedly. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param x
	 *            between 0 and 31 inclusive
	 * @param y
	 *            between 0 and 31 inclusive
	 * @param z
	 *            between 0 and 31 inclusive
	 * @return the distance to travel along the 32x32x32 Hilbert Curve to get to the
	 *         given x, y, z point.
	 */
	public static int posToHilbert3D(final int x, final int y, final int z) {
		return hilbert3Distances[x + (y << 5) + (z << 10)];
	}

	/**
	 * Takes an x, y position and returns the length to travel along the 16x16 Moore
	 * curve to reach that position. This assumes x and y are between 0 and 15,
	 * inclusive. This uses a lookup table for the 16x16 Moore Curve, which should
	 * make it faster than calculating the distance along the Moore Curve
	 * repeatedly.
	 * 
	 * @param x
	 *            between 0 and 15 inclusive
	 * @param y
	 *            between 0 and 15 inclusive
	 * @return the distance to travel along the 16x16 Moore Curve to get to the
	 *         given x, y point.
	 */
	public static int posToMoore(final int x, final int y) {
		return mooreDistances[x + (y << 4)] & 0xff;
	}
	/*
	 * Takes an x, y position and returns the length to travel along the 256x256
	 * Hilbert curve to reach that position. This assumes x and y are between 0 and
	 * 255, inclusive. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-
	 * and.html
	 * 
	 * @param x between 0 and 255 inclusive
	 * 
	 * @param y between 0 and 255 inclusive
	 * 
	 * @return the distance to travel along the 256x256 Hilbert Curve to get to the
	 * given x, y point.
	 */

	private static int posToHilbertNoLUT(final int x, final int y) {
		int hilbert = 0, remap = 0xb4, mcode, hcode;
		/*
		 * while( block > 0 ) { --block; mcode = ( ( x >> block ) & 1 ) | ( ( ( y >> (
		 * block ) ) & 1 ) << 1); hcode = ( ( remap >> ( mcode << 1 ) ) & 3 ); remap ^=
		 * ( 0x82000028 >> ( hcode << 3 ) ); hilbert = ( ( hilbert << 2 ) + hcode ); }
		 */

		mcode = ((x >> 7) & 1) | (((y >> (7)) & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);
		remap ^= (0x82000028 >> (hcode << 3));
		hilbert = ((hilbert << 2) + hcode);

		mcode = ((x >> 6) & 1) | (((y >> (6)) & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);
		remap ^= (0x82000028 >> (hcode << 3));
		hilbert = ((hilbert << 2) + hcode);

		mcode = ((x >> 5) & 1) | (((y >> (5)) & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);
		remap ^= (0x82000028 >> (hcode << 3));
		hilbert = ((hilbert << 2) + hcode);

		mcode = ((x >> 4) & 1) | (((y >> (4)) & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);
		remap ^= (0x82000028 >> (hcode << 3));
		hilbert = ((hilbert << 2) + hcode);

		mcode = ((x >> 3) & 1) | (((y >> (3)) & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);
		remap ^= (0x82000028 >> (hcode << 3));
		hilbert = ((hilbert << 2) + hcode);

		mcode = ((x >> 2) & 1) | (((y >> (2)) & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);
		remap ^= (0x82000028 >> (hcode << 3));
		hilbert = ((hilbert << 2) + hcode);

		mcode = ((x >> 1) & 1) | (((y >> (1)) & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);
		remap ^= (0x82000028 >> (hcode << 3));
		hilbert = ((hilbert << 2) + hcode);

		mcode = (x & 1) | ((y & 1) << 1);
		hcode = ((remap >> (mcode << 1)) & 3);

		hilbert = ((hilbert << 2) + hcode);

		return hilbert;
	}

	/**
	 * Takes a position as a Morton code, with interleaved x and y bits and x in the
	 * least significant bit, and returns the length to travel along the 256x256
	 * Hilbert Curve to reach that position. This uses 16 bits of the Morton code
	 * and requires that the code is non-negative. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param morton
	 *            a Morton code that interleaves two 8-bit unsigned numbers, with x
	 *            as index1 and y as index2.
	 * @return a distance to travel down the Hilbert Curve to reach the location
	 *         that can be decoded from morton.
	 */
	public static int mortonToHilbert(final int morton) {
		int hilbert = 0;
		int remap = 0xb4;
		int block = BITS;
		while (block > 0) {
			block -= 2;
			int mcode = ((morton >> block) & 3);
			int hcode = ((remap >> (mcode << 1)) & 3);
			remap ^= (0x82000028 >> (hcode << 3));
			hilbert = ((hilbert << 2) + hcode);
		}
		return hilbert;
	}

	/**
	 * Takes a distance to travel along the 256x256 Hilbert curve and returns a
	 * Morton code representing the position in 2D space that corresponds to that
	 * point on the Hilbert Curve; the Morton code will have interleaved x and y
	 * bits and x in the least significant bit. This uses a lookup table for the
	 * 256x256 Hilbert curve, which should make it faster than calculating the
	 * position repeatedly. The parameter hilbert is an int but only 16 unsigned
	 * bits are used.
	 * 
	 * @param hilbert
	 *            a distance to travel down the Hilbert Curve
	 * @return a Morton code that stores x and y interleaved; can be converted to a
	 *         Coord with other methods.
	 */

	public static int hilbertToMorton(final int hilbert) {
		return mortonEncode(hilbertX[hilbert], hilbertY[hilbert]);
	}

	/**
	 * Takes a distance to travel along the 256x256 Hilbert curve and returns a
	 * Coord representing the position in 2D space that corresponds to that point on
	 * the Hilbert curve. This uses a lookup table for the 256x256 Hilbert curve,
	 * which should make it faster than calculating the position repeatedly. The
	 * parameter hilbert is an int but only 16 unsigned bits are used.
	 * 
	 * @param hilbert
	 *            a distance to travel down the Hilbert Curve
	 * @return a Coord corresponding to the position in 2D space at the given
	 *         distance down the Hilbert Curve
	 */
	public static Coord hilbertToCoord(final int hilbert) {
		return Coord.get(hilbertX[hilbert], hilbertY[hilbert]);
	}

	/**
	 * Takes a distance to travel along the 16x16 Hilbert curve and returns a Coord
	 * representing the position in 2D space that corresponds to that point on the
	 * Hilbert curve. This uses a lookup table for the 16x16 Hilbert curve, which
	 * should make it faster than calculating the position repeatedly. The parameter
	 * moore is an int but only 8 unsigned bits are used, and since the Moore Curve
	 * loops, it is calculated as {@code moore % 256}.
	 * 
	 * @param moore
	 *            a distance to travel down the Moore Curve
	 * @return a Coord corresponding to the position in 2D space at the given
	 *         distance down the Hilbert Curve
	 */
	public static Coord mooreToCoord(final int moore) {
		return Coord.get(mooreX[moore % 256], mooreY[moore % 256]);
	}

	/*
	 * Takes a distance to travel along the 256x256 Hilbert curve and returns a
	 * Morton code representing the position in 2D space that corresponds to that
	 * point on the Hilbert curve; the Morton code will have interleaved x and y
	 * bits and x in the least significant bit. This variant does not use a lookup
	 * table, and is likely slower. The parameter hilbert is an int but only 16
	 * unsigned bits are used.
	 * 
	 * @param hilbert
	 * 
	 * @return
	 */
	/*
	 * public static int hilbertToMortonNoLUT( final int hilbert ) { int morton = 0;
	 * int remap = 0xb4; int block = BITS; while( block > 0 ) { block -= 2; int
	 * hcode = ( ( hilbert >> block ) & 3 ); int mcode = ( ( remap >> ( hcode << 1 )
	 * ) & 3 ); remap ^= ( 0x330000cc >> ( hcode << 3 ) ); morton = ( ( morton << 2
	 * ) + mcode ); } return morton; }
	 */
	/*
	 * Takes a distance to travel along the 256x256 Hilbert curve and returns a
	 * Coord representing the position in 2D space that corresponds to that point on
	 * the Hilbert curve. This variant does not use a lookup table, and is likely
	 * slower. The parameter hilbert is an int but only 16 unsigned bits are used.
	 * 
	 * @param hilbert
	 * 
	 * @return
	 */

	private static Coord hilbertToCoordNoLUT(final int hilbert) {
		int x = 0, y = 0;
		int remap = 0xb4;
		int block = BITS;
		while (block > 0) {
			block -= 2;
			int hcode = ((hilbert >> block) & 3);
			int mcode = ((remap >> (hcode << 1)) & 3);
			remap ^= (0x330000cc >> (hcode << 3));
			x = (x << 1) + (mcode & 1);
			y = (y << 1) + ((mcode & 2) >> 1);
		}
		return Coord.get(x, y);
	}

	/**
	 * Takes a position as a Coord called pt and returns the length to travel along
	 * the 256x256 Hilbert curve to reach that position. This assumes pt.x and pt.y
	 * are between 0 and 255, inclusive. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param pt
	 *            a Coord with values between 0 and 255, inclusive
	 * @return a distance from the start of the 256x256 Hilbert curve to get to the
	 *         position of pt
	 */
	public static int coordToHilbert(final Coord pt) {
		return posToHilbert(pt.x, pt.y);
	}

	/**
	 * Takes a position as a Coord called pt and returns the length to travel along
	 * the 16x16 Moore curve to reach that position. This assumes pt.x and pt.y are
	 * between 0 and 15, inclusive.
	 * 
	 * @param pt
	 *            a Coord with values between 0 and 15, inclusive
	 * @return a distance from the "start" of the 16x16 Moore curve to get to the
	 *         position of pt
	 */
	public static int coordToMoore(final Coord pt) {
		return posToMoore(pt.x, pt.y);
	}

	public static int mortonEncode3D(int index1, int index2, int index3) { // pack 3 5-bit indices into a 15-bit Morton
																			// code
		index1 &= 0x0000001f;
		index2 &= 0x0000001f;
		index3 &= 0x0000001f;
		index1 *= 0x01041041;
		index2 *= 0x01041041;
		index3 *= 0x01041041;
		index1 &= 0x10204081;
		index2 &= 0x10204081;
		index3 &= 0x10204081;
		index1 *= 0x00011111;
		index2 *= 0x00011111;
		index3 *= 0x00011111;
		index1 &= 0x12490000;
		index2 &= 0x12490000;
		index3 &= 0x12490000;
		return ((index1 >> 16) | (index2 >> 15) | (index3 >> 14));
	}

	public static Coord3D mortonDecode3D(int morton) { // unpack 3 5-bit indices from a 15-bit Morton code
		int value1 = morton;
		int value2 = (value1 >>> 1);
		int value3 = (value1 >>> 2);
		value1 &= 0x00001249;
		value2 &= 0x00001249;
		value3 &= 0x00001249;
		value1 |= (value1 >>> 2);
		value2 |= (value2 >>> 2);
		value3 |= (value3 >>> 2);
		value1 &= 0x000010c3;
		value2 &= 0x000010c3;
		value3 &= 0x000010c3;
		value1 |= (value1 >>> 4);
		value2 |= (value2 >>> 4);
		value3 |= (value3 >>> 4);
		value1 &= 0x0000100f;
		value2 &= 0x0000100f;
		value3 &= 0x0000100f;
		value1 |= (value1 >>> 8);
		value2 |= (value2 >>> 8);
		value3 |= (value3 >>> 8);
		value1 &= 0x0000001f;
		value2 &= 0x0000001f;
		value3 &= 0x0000001f;
		return new Coord3D(value1, value2, value3);
	}

	public static int mortonBitDecode3D(int morton) { // unpack 3 5-bit indices from a 15-bit Morton code
		int value1 = morton;
		int value2 = (value1 >>> 1);
		int value3 = (value1 >>> 2);
		value1 &= 0x00001249;
		value2 &= 0x00001249;
		value3 &= 0x00001249;
		value1 |= (value1 >>> 2);
		value2 |= (value2 >>> 2);
		value3 |= (value3 >>> 2);
		value1 &= 0x000010c3;
		value2 &= 0x000010c3;
		value3 &= 0x000010c3;
		value1 |= (value1 >>> 4);
		value2 |= (value2 >>> 4);
		value3 |= (value3 >>> 4);
		value1 &= 0x0000100f;
		value2 &= 0x0000100f;
		value3 &= 0x0000100f;
		value1 |= (value1 >>> 8);
		value2 |= (value2 >>> 8);
		value3 |= (value3 >>> 8);
		value1 &= 0x0000001f;
		value2 &= 0x0000001f;
		value3 &= 0x0000001f;
		return value1 | (value2 << 5) | (value3 << 10);
	}

	private static void computeHilbert3D(int x, int y, int z) {
		int hilbert = mortonEncode3D(x, y, z);
		int block = 6;
		int hcode = ((hilbert >> block) & 7);
		int mcode, shift, signs;
		shift = signs = 0;
		while (block > 0) {
			block -= 3;
			hcode <<= 2;
			mcode = ((0x20212021 >> hcode) & 3);
			shift = ((0x48 >> (7 - shift - mcode)) & 3);
			signs = ((signs | (signs << 3)) >> mcode);
			signs = ((signs ^ (0x53560300 >> hcode)) & 7);
			mcode = ((hilbert >> block) & 7);
			hcode = mcode;
			hcode = (((hcode | (hcode << 3)) >> shift) & 7);
			hcode ^= signs;
			hilbert ^= ((mcode ^ hcode) << block);
		}

		hilbert ^= ((hilbert >> 1) & 0x92492492);
		hilbert ^= ((hilbert & 0x92492492) >> 1);

		hilbert3X[hilbert] = (short) x;
		hilbert3Y[hilbert] = (short) y;
		hilbert3Z[hilbert] = (short) z;
		hilbert3Distances[x | (y << 3) | (z << 6)] = (short) hilbert;
	}

	/**
	 * Gets the x coordinate for a given index into the 16x16x(8*n) Moore curve.
	 * Expects indices to touch the following corners of the 16x16x(8*n) cube in
	 * this order, using x,y,z syntax: (0,0,0) (0,0,(8*n)) (0,16,(8*n)) (0,16,0)
	 * (16,16,0) (16,16,(8*n)) (16,0,(8*n)) (16,0,0)
	 * 
	 * @param index
	 *            the index into the 3D 16x16x(8*n) Moore Curve, must be less than
	 *            0x1000
	 * @param n
	 *            the number of 8-deep layers to use as part of the box shape this
	 *            travels through
	 * @return the x coordinate of the given distance traveled through the 3D
	 *         16x16x(8*n) Moore Curve
	 */
	public static int getXMoore3D(final int index, final int n) {
		int hilbert = index & 0x1ff;
		int sector = index >> 9;
		if (sector < 2 * n)
			return 7 - hilbert3X[hilbert];
		else
			return 8 + hilbert3X[hilbert];
	}

	/**
	 * Gets the y coordinate for a given index into the 16x16x(8*n) Moore curve.
	 * Expects indices to touch the following corners of the 16x16x(8*n) cube in
	 * this order, using x,y,z syntax: (0,0,0) (0,0,(8*n)) (0,16,(8*n)) (0,16,0)
	 * (16,16,0) (16,16,(8*n)) (16,0,(8*n)) (16,0,0)
	 * 
	 * @param index
	 *            the index into the 3D 16x16x(8*n) Moore Curve, must be less than
	 *            0x1000
	 * @param n
	 *            the number of 8-deep layers to use as part of the box shape this
	 *            travels through
	 * @return the y coordinate of the given distance traveled through the 3D
	 *         16x16x(8*n) Moore Curve
	 */
	public static int getYMoore3D(final int index, final int n) {
		int hilbert = index & 0x1ff;
		int sector = index >> 9;
		if (sector < n || sector >= 3 * n)
			return 7 - hilbert3Y[hilbert];
		else
			return 8 + hilbert3Y[hilbert];

	}

	/**
	 * Gets the z coordinate for a given index into the 16x16x(8*n) Moore curve.
	 * Expects indices to touch the following corners of the 16x16x(8*n) cube in
	 * this order, using x,y,z syntax: (0,0,0) (0,0,(8*n)) (0,16,(8*n)) (0,16,0)
	 * (16,16,0) (16,16,(8*n)) (16,0,(8*n)) (16,0,0)
	 * 
	 * @param index
	 *            the index into the 3D 16x16x(8*n) Moore Curve, must be less than
	 *            0x1000
	 * @param n
	 *            the number of 8-deep layers to use as part of the box shape this
	 *            travels through
	 * @return the z coordinate of the given distance traveled through the 3D
	 *         16x16x(8*n) Moore Curve
	 */
	public static int getZMoore3D(final int index, final int n) {
		int hilbert = index & 0x1ff;
		int sector = index >> 9;
		if ((sector / n) % 2 == 0)
			return hilbert3Z[hilbert] + 8 * (sector % n);
		else
			return (8 * n - 1) - hilbert3Z[hilbert] - 8 * (sector % n);
	}

	/**
	 * Takes two 8-bit unsigned integers index1 and index2, and returns a Morton
	 * code, with interleaved index1 and index2 bits and index1 in the least
	 * significant bit. With this method, index1 and index2 can have up to 8 bits.
	 * This returns a 16-bit Morton code and WILL encode information in the sign bit
	 * if the inputs are large enough. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param index1
	 *            a non-negative integer using at most 8 bits, to be placed in the
	 *            "x" slots
	 * @param index2
	 *            a non-negative integer using at most 8 bits, to be placed in the
	 *            "y" slots
	 * @return a Morton code/Z-Code that interleaves the two numbers into one 16-bit
	 *         short
	 */
	public static short zEncode(short index1, short index2) { // pack 2 8-bit (unsigned) indices into a 16-bit
																// (signed...) Morton code/Z-Code
		index1 &= 0x000000ff;
		index2 &= 0x000000ff;
		index1 |= (index1 << 4);
		index2 |= (index2 << 4);
		index1 &= 0x00000f0f;
		index2 &= 0x00000f0f;
		index1 |= (index1 << 2);
		index2 |= (index2 << 2);
		index1 &= 0x00003333;
		index2 &= 0x00003333;
		index1 |= (index1 << 1);
		index2 |= (index2 << 1);
		index1 &= 0x00005555;
		index2 &= 0x00005555;
		return (short) (index1 | (index2 << 1));
	}

	/**
	 * Takes two 8-bit unsigned integers index1 and index2, and returns a Morton
	 * code, with interleaved index1 and index2 bits and index1 in the least
	 * significant bit. With this method, index1 and index2 can have up to 8 bits.
	 * This returns a 32-bit Morton code but only uses 16 bits, and will not encode
	 * information in the sign bit. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param index1
	 *            a non-negative integer using at most 8 bits, to be placed in the
	 *            "x" slots
	 * @param index2
	 *            a non-negative integer using at most 8 bits, to be placed in the
	 *            "y" slots
	 * @return a Morton code that interleaves the two numbers as one 32-bit int, but
	 *         only in 16 bits of it
	 */
	public static int mortonEncode(int index1, int index2) { // pack 2 8-bit (unsigned) indices into a 32-bit
																// (signed...) Morton code
		index1 &= 0x000000ff;
		index2 &= 0x000000ff;
		index1 |= (index1 << 4);
		index2 |= (index2 << 4);
		index1 &= 0x00000f0f;
		index2 &= 0x00000f0f;
		index1 |= (index1 << 2);
		index2 |= (index2 << 2);
		index1 &= 0x00003333;
		index2 &= 0x00003333;
		index1 |= (index1 << 1);
		index2 |= (index2 << 1);
		index1 &= 0x00005555;
		index2 &= 0x00005555;
		return index1 | (index2 << 1);
	}

	/**
	 * Takes a Morton code, with interleaved x and y bits and x in the least
	 * significant bit, and returns the Coord representing the same x, y position.
	 * This uses 16 bits of the Morton code and requires that the code is
	 * non-negative. Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param morton
	 *            an int containing two interleaved numbers, from 0 to 255 each
	 * @return a Coord matching the x and y extracted from the Morton code
	 */
	public static Coord mortonDecode(final int morton) { // unpack 2 8-bit (unsigned) indices from a 32-bit (signed...)
															// Morton code
		int value1 = morton;
		int value2 = (value1 >> 1);
		value1 &= 0x5555;
		value2 &= 0x5555;
		value1 |= (value1 >> 1);
		value2 |= (value2 >> 1);
		value1 &= 0x3333;
		value2 &= 0x3333;
		value1 |= (value1 >> 2);
		value2 |= (value2 >> 2);
		value1 &= 0x0f0f;
		value2 &= 0x0f0f;
		value1 |= (value1 >> 4);
		value2 |= (value2 >> 4);
		value1 &= 0x00ff;
		value2 &= 0x00ff;
		return Coord.get(value1, value2);
	}

	/**
	 * Takes a Morton code, with interleaved x and y bits and x in the least
	 * significant bit, and returns the Coord representing the same x, y position.
	 * This takes a a 16-bit Z-Code with data in the sign bit, as returned by
	 * zEncode(). Source:
	 * http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
	 * 
	 * @param morton
	 *            a short containing two interleaved numbers, from 0 to 255 each
	 * @return a Coord matching the x and y extracted from the Morton code
	 */
	public static Coord zDecode(final short morton) { // unpack 2 8-bit (unsigned) indices from a 32-bit (signed...)
														// Morton code
		int value1 = morton & 0xffff;
		int value2 = (value1 >> 1);
		value1 &= 0x5555;
		value2 &= 0x5555;
		value1 |= (value1 >> 1);
		value2 |= (value2 >> 1);
		value1 &= 0x3333;
		value2 &= 0x3333;
		value1 |= (value1 >> 2);
		value2 |= (value2 >> 2);
		value1 &= 0x0f0f;
		value2 &= 0x0f0f;
		value1 |= (value1 >> 4);
		value2 |= (value2 >> 4);
		value1 &= 0x00ff;
		value2 &= 0x00ff;
		return Coord.get(value1, value2);
	}
}
