package squidpony.squidmath; import squidpony.ArrayTools; import squidpony.StringKit; import squidpony.annotation.Beta; import squidpony.squidgrid.Radius; import squidpony.squidgrid.zone.MutableZone; import squidpony.squidgrid.zone.Zone; import java.io.Serializable; import java.util.*; /** * Region encoding of on/off information about areas using bitsets; uncompressed (fatty), but fast (greased lightning). * This can handle any size of 2D data, and is not strictly limited to 256x256 as CoordPacker is. It stores several long * arrays and uses each bit in one of those numbers to represent a single point, though sometimes this does waste bits * if the height of the area this encodes is not a multiple of 64 (if you store a 80x64 map, this uses 80 longs; if you * store an 80x65 map, this uses 160 longs, 80 for the first 64 rows and 80 more to store the next row). It's much * faster than CoordPacker at certain operations (anything that expands or retracts an area, including * {@link #expand()}), {@link #retract()}), {@link #fringe()}), {@link #surface()}, and {@link #flood(GreasedRegion)}, * and slightly faster on others, like {@link #and(GreasedRegion)} (called intersectPacked() in CoordPacker) and * {@link #or(GreasedRegion)} (called unionPacked() in CoordPacker). * <br> * Each GreasedRegion is mutable, and instance methods typically modify that instance and return it for chaining. There * are exceptions, usually where multiple GreasedRegion values are returned and the instance is not modified. * <br> * Typical usage involves constructing a GreasedRegion from some input data, like a char[][] for a map or a double[][] * from DijkstraMap, and modifying it spatially with expand(), retract(), flood(), etc. It's common to mix in data from * other GreasedRegions with and() (which gets the intersection of two GreasedRegions and stores it in one), or() (which * is like and() but for the union), xor() (like and() but for exclusive or, finding only cells that are on in exactly * one of the two GreasedRegions), and andNot() (which can be considered the "subtract another region from me" method). * There are 8-way (Chebyshev distance) variants on all of the spatial methods, and methods without "8way" in the name * are either 4-way (Manhattan distance) or not affected by distance measurement. Once you have a GreasedRegion, you may * want to get a single random point from it (use {@link #singleRandom(RNG)}), get several random points from it (use * {@link #randomPortion(RNG, int)} for random sampling or {@link #randomSeparated(double, RNG)} for points that have * some distance between each other), or get all points from it (use {@link #asCoords()}. You may also want to produce * some 2D data from one or more GreasedRegions, such as with {@link #sum(GreasedRegion...)} or {@link #toChars()}. The * most effective techniques regarding GreasedRegion involve multiple methods, like getting a few random points from an * existing GreasedRegion representing floor tiles in a dungeon with {@link #randomPortion(RNG, int)}, then inserting * those into a new GreasedRegion with {@link #insertSeveral(Coord...)}, and then finding a random expansion of those * initial points with {@link #spill(GreasedRegion, int, RNG)}, giving the original GreasedRegion of floor tiles as the * first argument. This could be used to position puddles of water or patches of mold in a dungeon level, while still * keeping the starting points and finished points within the boundaries of valid (floor) cells. * <br> * For efficiency, you can place one GreasedRegion into another (typically a temporary value that is no longer needed * and can be recycled) using {@link #remake(GreasedRegion)}, or give the information that would normally be used to * construct a fresh GreasedRegion to an existing one of the same dimensions with {@link #refill(boolean[][])} or any * of the overloads of refill(). These re-methods don't do as much work as a constructor does if the width and height * of their argument are identical to their current width and height, and don't create more garbage for the GC. * <br> * Created by Tommy Ettinger on 6/24/2016. */ @Beta public class GreasedRegion extends Zone.Skeleton implements Collection<Coord>, Serializable, MutableZone { private static final long serialVersionUID = 0; private static final SobolQRNG sobol = new SobolQRNG(2); public long[] data; public int height; public int width; protected int ySections; protected long yEndMask; /** * Constructs an empty 64x64 GreasedRegion. * GreasedRegions are mutable, so you can add to this with insert() or insertSeveral(), among others. */ public GreasedRegion() { width = 64; height = 64; ySections = 1; yEndMask = -1L; data = new long[64]; } /** * Constructs a GreasedRegion with the given rectangular boolean array, with width of bits.length and height of * bits[0].length, any value of true considered "on", and any value of false considered "off." * @param bits a rectangular 2D boolean array where true is on and false is off */ public GreasedRegion(final boolean[][] bits) { width = bits.length; height = bits[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(bits[x][y]) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Reassigns this GreasedRegion with the given rectangular boolean array, reusing the current data storage (without * extra allocations) if this.width == map.length and this.height == map[0].length. The current values stored in * this are always cleared, then any value of true in map is considered "on", and any value of false in map is * considered "off." * @param map a rectangular 2D boolean array where true is on and false is off * @return this for chaining */ public GreasedRegion refill(final boolean[][] map) { if (map != null && map.length > 0 && width == map.length && height == map[0].length) { Arrays.fill(data, 0L); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { data[x * ySections + (y >> 6)] |= (map[x][y] ? 1L : 0L) << (y & 63); } } return this; } else { width = (map == null) ? 0 : map.length; height = (map == null || map.length <= 0) ? 0 : map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y]) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructs a GreasedRegion with the given rectangular char array, with width of map.length and height of * map[0].length, any value that equals yes is considered "on", and any other value considered "off." * @param map a rectangular 2D char array where yes is on and everything else is off * @param yes which char to encode as "on" */ public GreasedRegion(final char[][] map, final char yes) { width = map.length; height = map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] == yes) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Reassigns this GreasedRegion with the given rectangular char array, reusing the current data storage (without * extra allocations) if this.width == map.length and this.height == map[0].length. The current values stored in * this are always cleared, then any value that equals yes is considered "on", and any other value considered "off." * @param map a rectangular 2D char array where yes is on and everything else is off * @param yes which char to encode as "on" * @return this for chaining */ public GreasedRegion refill(final char[][] map, final char yes) { if (map != null && map.length > 0 && width == map.length && height == map[0].length) { Arrays.fill(data, 0L); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { data[x * ySections + (y >> 6)] |= ((map[x][y] == yes) ? 1L : 0L) << (y & 63); } } return this; } else { width = (map == null) ? 0 : map.length; height = (map == null || map.length <= 0) ? 0 : map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] == yes) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Weird constructor that takes a String array, _as it would be printed_, so each String is a row and indexing would * be done with y, x instead of the normal x, y. * @param map String array (as printed, not the normal storage) where each String is a row * @param yes the char to consider "on" in the GreasedRegion */ public GreasedRegion(final String[] map, final char yes) { height = map.length; width = map[0].length(); ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[y].charAt(x) == yes) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Weird refill method that takes a String array, _as it would be printed_, so each String is a row and indexing * would be done with y, x instead of the normal x, y. * @param map String array (as printed, not the normal storage) where each String is a row * @param yes the char to consider "on" in the GreasedRegion * @return this for chaining */ public GreasedRegion refill(final String[] map, final char yes) { if (map != null && map.length > 0 && height == map.length && width == map[0].length()) { Arrays.fill(data, 0L); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { data[x * ySections + (y >> 6)] |= ((map[y].charAt(x) == yes) ? 1L : 0L) << (y & 63); } } return this; } else { height = (map == null) ? 0 : map.length; width = (map == null || map.length <= 0) ? 0 : map[0].length(); ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[y].charAt(y) == yes) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructs a GreasedRegion with the given rectangular int array, with width of map.length and height of * map[0].length, any value that equals yes is considered "on", and any other value considered "off." * @param map a rectangular 2D int array where an int == yes is on and everything else is off * @param yes which int to encode as "on" */ public GreasedRegion(final int[][] map, final int yes) { width = map.length; height = map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] == yes) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Reassigns this GreasedRegion with the given rectangular int array, reusing the current data storage (without * extra allocations) if this.width == map.length and this.height == map[0].length. The current values stored in * this are always cleared, then any value that equals yes is considered "on", and any other value considered "off." * @param map a rectangular 2D int array where an int == yes is on and everything else is off * @param yes which int to encode as "on" * @return this for chaining */ public GreasedRegion refill(final int[][] map, final int yes) { if (map != null && map.length > 0 && width == map.length && height == map[0].length) { Arrays.fill(data, 0L); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { data[x * ySections + (y >> 6)] |= ((map[x][y] == yes) ? 1L : 0L) << (y & 63); } } return this; } else { width = (map == null) ? 0 : map.length; height = (map == null || map.length <= 0) ? 0 : map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] == yes) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructs this GreasedRegion using an int[][], treating cells as on if they are greater than or equal to lower * and less than upper, or off otherwise. * @param map an int[][] that should have some ints between lower and upper * @param lower lower bound, inclusive; all on cells will have values in map that are at least equal to lower * @param upper upper bound, exclusive; all on cells will have values in map that are less than upper */ public GreasedRegion(final int[][] map, final int lower, final int upper) { width = map.length; height = map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; int[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { if(column[y] >= lower && column[y] < upper) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Reassigns this GreasedRegion with the given rectangular int array, reusing the current data storage (without * extra allocations) if this.width == map.length and this.height == map[0].length. The current values stored in * this are always cleared, then cells are treated as on if they are greater than or equal to lower and less than * upper, or off otherwise. * @param map a rectangular 2D int array that should have some values between lower and upper * @param lower lower bound, inclusive; all on cells will have values in map that are at least equal to lower * @param upper upper bound, exclusive; all on cells will have values in map that are less than upper * @return this for chaining */ public GreasedRegion refill(final int[][] map, final int lower, final int upper) { if (map != null && map.length > 0 && width == map.length && height == map[0].length) { Arrays.fill(data, 0L); int[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { data[x * ySections + (y >> 6)] |= ((column[y] >= lower && column[y] < upper) ? 1L : 0L) << (y & 63); } } return this; } else { width = (map == null) ? 0 : map.length; height = (map == null || map.length <= 0) ? 0 : map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; int[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { if(column[y] >= lower && column[y] < upper) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructs this GreasedRegion using a short[][], treating cells as on if they are greater than or equal to lower * and less than upper, or off otherwise. * @param map a short[][] that should have some shorts between lower and upper * @param lower lower bound, inclusive; all on cells will have values in map that are at least equal to lower * @param upper upper bound, exclusive; all on cells will have values in map that are less than upper */ public GreasedRegion(final short[][] map, final int lower, final int upper) { width = map.length; height = map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; short[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { if(column[y] >= lower && column[y] < upper) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Reassigns this GreasedRegion with the given rectangular short array, reusing the current data storage (without * extra allocations) if this.width == map.length and this.height == map[0].length. The current values stored in * this are always cleared, then cells are treated as on if they are greater than or equal to lower and less than * upper, or off otherwise. * @param map a rectangular 2D short array that should have some values between lower and upper * @param lower lower bound, inclusive; all on cells will have values in map that are at least equal to lower * @param upper upper bound, exclusive; all on cells will have values in map that are less than upper * @return this for chaining */ public GreasedRegion refill(final short[][] map, final int lower, final int upper) { if (map != null && map.length > 0 && width == map.length && height == map[0].length) { Arrays.fill(data, 0L); short[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { data[x * ySections + (y >> 6)] |= ((column[y] >= lower && column[y] < upper) ? 1L : 0L) << (y & 63); } } return this; } else { width = (map == null) ? 0 : map.length; height = (map == null || map.length <= 0) ? 0 : map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; short[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { if(column[y] >= lower && column[y] < upper) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructs this GreasedRegion using a double[][] (typically one generated by * {@link squidpony.squidai.DijkstraMap}) that only stores two relevant states: an "on" state for values less than * or equal to upperBound (inclusive), and an "off" state for anything else. * @param map a double[][] that probably relates in some way to DijkstraMap. * @param upperBound upper inclusive; any double greater than this will be off, any others will be on */ public GreasedRegion(final double[][] map, final double upperBound) { width = map.length; height = map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] <= upperBound) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Reassigns this GreasedRegion with the given rectangular double array, reusing the current data storage (without * extra allocations) if this.width == map.length and this.height == map[0].length. The current values stored in * this are always cleared, then cells are treated as on if they are less than or equal to upperBound, or off * otherwise. * @param map a rectangular 2D double array that should usually have some values less than or equal to upperBound * @param upperBound upper bound, inclusive; all on cells will have values in map that are less than or equal to this * @return this for chaining */ public GreasedRegion refill(final double[][] map, final double upperBound) { if (map != null && map.length > 0 && width == map.length && height == map[0].length) { Arrays.fill(data, 0L); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] <= upperBound) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } else { width = (map == null) ? 0 : map.length; height = (map == null || map.length <= 0) ? 0 : map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] <= upperBound) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructs this GreasedRegion using a double[][] (typically one generated by * {@link squidpony.squidai.DijkstraMap}) that only stores two relevant states: an "on" state for values between * lowerBound (inclusive) and upperBound (exclusive), and an "off" state for anything else. * @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 or equal to this this will be off, any doubles both * less than this and equal to or greater than lower will be on */ public GreasedRegion(final double[][] map, final double lowerBound, final double upperBound) { width = map.length; height = map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if(map[x][y] >= lowerBound && map[x][y] < upperBound) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Reassigns this GreasedRegion with the given rectangular double array, reusing the current data storage (without * extra allocations) if this.width == map.length and this.height == map[0].length. The current values stored in * this are always cleared, then cells are treated as on if they are greater than or equal to lower and less than * upper, or off otherwise. * @param map a rectangular 2D double array that should have some values between lower and upper * @param lower lower bound, inclusive; all on cells will have values in map that are at least equal to lower * @param upper upper bound, exclusive; all on cells will have values in map that are less than upper * @return this for chaining */ public GreasedRegion refill(final double[][] map, final double lower, final double upper) { if (map != null && map.length > 0 && width == map.length && height == map[0].length) { Arrays.fill(data, 0L); double[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { data[x * ySections + (y >> 6)] |= ((column[y] >= lower && column[y] < upper) ? 1L : 0L) << (y & 63); } } return this; } else { width = (map == null) ? 0 : map.length; height = (map == null || map.length <= 0) ? 0 : map[0].length; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; double[] column; for (int x = 0; x < width; x++) { column = map[x]; for (int y = 0; y < height; y++) { if(column[y] >= lower && column[y] < upper) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructs this GreasedRegion using a double[][] (this was made for use inside * {@link squidpony.squidgrid.BevelFOV}) that only stores two relevant states: an "on" state for values between * lowerBound (inclusive) and upperBound (exclusive), and an "off" state for anything else. This variant scales the * input so each "on" position in map produces a 2x2 on area if scale is 2, a 3x3 area if scale is 3, and so on. * @param map a double[][] that may relate in some way to BevelFOV * @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 or equal to this this will be off, any doubles both * less than this and equal to or greater than lower will be on * @param scale the size of the square of cells in this that each "on" value in map will correspond to */ public GreasedRegion(final double[][] map, final double lowerBound, final double upperBound, int scale) { scale = Math.min(63, Math.max(1, scale)); int baseWidth = map.length, baseHeight = map[0].length; width = baseWidth * scale; height = baseHeight * scale; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; long shape = (1L << scale) - 1L, leftover; for (int bx = 0, x = 0; bx < baseWidth; bx++, x += scale) { for (int by = 0, y = 0; by < baseHeight; by++, y += scale) { if(map[bx][by] >= lowerBound && map[bx][by] < upperBound) { for (int i = 0; i < scale; i++) { data[(x + i) * ySections + (y >> 6)] |= shape << (y & 63); if((leftover = (y + scale - 1 & 63) + 1) < (y & 63) + 1 && (y + leftover >> 6) < ySections) { data[(x + i) * ySections + (y >> 6) + 1] |= (1L << leftover) - 1L; } } } } } } /** * Reassigns this GreasedRegion with the given rectangular double array, reusing the current data storage (without * extra allocations) if {@code this.width == map.length * scale && this.height == map[0].length * scale}. The * current values stored in this are always cleared, then cells are treated as on if they are greater than or equal * to lower and less than upper, or off otherwise, before considering scaling. This variant scales the input so each * "on" position in map produces a 2x2 on area if scale is 2, a 3x3 area if scale is 3, and so on. * @param map a double[][] that may relate in some way to BevelFOV * @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 or equal to this this will be off, any doubles both * less than this and equal to or greater than lower will be on * @param scale the size of the square of cells in this that each "on" value in map will correspond to * @return this for chaining */ public GreasedRegion refill(final double[][] map, final double lowerBound, final double upperBound, int scale) { scale = Math.min(63, Math.max(1, scale)); if (map != null && map.length > 0 && width == map.length * scale && height == map[0].length * scale) { Arrays.fill(data, 0L); double[] column; int baseWidth = map.length, baseHeight = map[0].length; long shape = (1L << scale) - 1L, leftover; for (int bx = 0, x = 0; bx < baseWidth; bx++, x += scale) { column = map[bx]; for (int by = 0, y = 0; by < baseHeight; by++, y += scale) { if(column[by] >= lowerBound && column[by] < upperBound) { for (int i = 0; i < scale; i++) { data[(x + i) * ySections + (y >> 6)] |= shape << (y & 63); if((leftover = (y + scale - 1 & 63) + 1) < (y & 63) + 1 && (y + leftover >> 6) < ySections) { data[(x + i) * ySections + (y >> 6) + 1] |= (1L << leftover) - 1L; } } } } } return this; } else { int baseWidth = (map == null) ? 0 : map.length, baseHeight = (map == null || map.length <= 0) ? 0 : map[0].length; width = baseWidth * scale; height = baseHeight * scale; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; long shape = (1L << scale) - 1L, leftover; for (int bx = 0, x = 0; bx < baseWidth; bx++, x += scale) { for (int by = 0, y = 0; by < baseHeight; by++, y += scale) { if(map[bx][by] >= lowerBound && map[bx][by] < upperBound) { for (int i = 0; i < scale; i++) { data[(x + i) * ySections + (y >> 6)] |= shape << (y & 63); if((leftover = (y + scale - 1 & 63)) < y && y < height - leftover) { data[(x + i) * ySections + (y >> 6) + 1] |= (1L << leftover) - 1L; } } } } } return this; } } /** * Constructs a GreasedRegion with the given 1D boolean array, with the given width and height, where an [x][y] * position is obtained from bits given an index n with x = n / height, y = n % height, any value of true * considered "on", and any value of false considered "off." * @param bits a 1D boolean array where true is on and false is off * @param width the width of the desired GreasedRegion; width * height should equal bits.length * @param height the height of the desired GreasedRegion; width * height should equal bits.length */ public GreasedRegion(final boolean[] bits, final int width, final int height) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int a = 0, x = 0, y = 0; a < bits.length; a++, x = a / height, y = a % height) { if(bits[a]) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } /** * Reassigns this GreasedRegion with the given 1D boolean array, reusing the current data storage (without * extra allocations) if this.width == width and this.height == height, where an [x][y] * position is obtained from bits given an index n with x = n / height, y = n % height, any value of true * considered "on", and any value of false considered "off." * @param bits a 1D boolean array where true is on and false is off * @param width the width of the desired GreasedRegion; width * height should equal bits.length * @param height the height of the desired GreasedRegion; width * height should equal bits.length * @return this for chaining */ public GreasedRegion refill(final boolean[] bits, final int width, final int height) { if (bits != null && this.width == width && this.height == height) { Arrays.fill(data, 0L); for (int a = 0, x = 0, y = 0; a < bits.length; a++, x = a / height, y = a % height) { data[x * ySections + (y >> 6)] |= (bits[a] ? 1L : 0L) << (y & 63); } return this; } else { this.width = (bits == null || width < 0) ? 0 : width; this.height = (bits == null || bits.length <= 0 || height < 0) ? 0 : height; ySections = (this.height + 63) >> 6; yEndMask = -1L >>> (64 - (this.height & 63)); data = new long[this.width * ySections]; if(bits != null) { for (int a = 0, x = 0, y = 0; a < bits.length; a++, x = a / this.height, y = a % this.height) { if (bits[a]) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } return this; } } /** * Constructor for an empty GreasedRegion of the given width and height. * GreasedRegions are mutable, so you can add to this with insert() or insertSeveral(), among others. * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion */ public GreasedRegion(final int width, final int height) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; } /** * Constructor for a GreasedRegion that contains a single "on" cell, and has the given width and height. * Note that to avoid confusion with the constructor that takes multiple Coord values, this takes the single "on" * Coord first, while the multiple-Coord constructor takes its vararg or array of Coords last. * @param single the one (x,y) point to store as "on" in this GreasedRegion * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion */ public GreasedRegion(final Coord single, final int width, final int height) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; if(single.x < width && single.y < height && single.x >= 0 && single.y >= 0) data[single.x * ySections + (single.y >> 6)] |= 1L << (single.y & 63); } /** * Constructor for a GreasedRegion that can have several "on" cells specified, and has the given width and height. * Note that to avoid confusion with the constructor that takes one Coord value, this takes the vararg or array of * Coords last, while the single-Coord constructor takes its one Coord first. * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion * @param points an array or vararg of Coord to store as "on" in this GreasedRegion */ public GreasedRegion(final int width, final int height, final Coord... points) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; if(points != null) { for (int i = 0, x, y; i < points.length; i++) { x = points[i].x; y = points[i].y; if(x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Constructor for a GreasedRegion that can have several "on" cells specified, and has the given width and height. * Note that to avoid confusion with the constructor that takes one Coord value, this takes the Iterable of * Coords last, while the single-Coord constructor takes its one Coord first. * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion * @param points an array or vararg of Coord to store as "on" in this GreasedRegion */ public GreasedRegion(final int width, final int height, final Iterable<Coord> points) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; if(points != null) { int x, y; for (Coord c : points) { x = c.x; y = c.y; if (x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } } } /** * Constructor for a random GreasedRegion of the given width and height, typically assigning approximately half of * the cells in this to "on" and the rest to off. A RandomnessSource can be slightly more efficient than an RNG when * you're making a lot of calls on it. * @param random a RandomnessSource that should have a good nextLong() method; LightRNG, XoRoRNG, and ThunderRNG do * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion */ public GreasedRegion(final RandomnessSource random, final int width, final int height) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int i = 0; i < width * ySections; i++) { data[i] = random.nextLong(); } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } } /** * Reassigns this GreasedRegion by filling it with random values from random, reusing the current data storage * (without extra allocations) if this.width == width and this.height == height, and typically assigning * approximately half of the cells in this to "on" and the rest to off. A RandomnessSource can be slightly more * efficient than an RNG when you're making a lot of calls on it. * @param random a RandomnessSource that should have a good nextLong() method; LightRNG, XoRoRNG, and ThunderRNG do * @param width the width of the desired GreasedRegion * @param height the height of the desired GreasedRegion * @return this for chaining */ public GreasedRegion refill(final RandomnessSource random, final int width, final int height) { if (random != null){ if(this.width == width && this.height == height) { for (int i = 0; i < width * ySections; i++) { data[i] = random.nextLong(); } } else { this.width = (width <= 0) ? 0 : width; this.height = (height <= 0) ? 0 : height; ySections = (this.height + 63) >> 6; yEndMask = -1L >>> (64 - (this.height & 63)); data = new long[this.width * ySections]; for (int i = 0; i < this.width * ySections; i++) { data[i] = random.nextLong(); } } } return this; } /** * Constructor for a random GreasedRegion of the given width and height. * GreasedRegions are mutable, so you can add to this with insert() or insertSeveral(), among others. * @param random a RandomnessSource (such as LightRNG or ThunderRNG) that this will use to generate its contents * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion */ public GreasedRegion(final RNG random, final int width, final int height) { this(random.getRandomness(), width, height); } /** * Reassigns this GreasedRegion by filling it with random values from random, reusing the current data storage * (without extra allocations) if this.width == width and this.height == height, and typically assigning * approximately half of the cells in this to "on" and the rest to off. * @param random an RNG that should have a good nextLong() method; the default (LightRNG internally) should be fine * @param width the width of the desired GreasedRegion * @param height the height of the desired GreasedRegion * @return this for chaining */ public GreasedRegion refill(final RNG random, final int width, final int height) { return refill(random.getRandomness(), width, height); } /** * Constructor for a random GreasedRegion of the given width and height, trying to set the given fraction of cells * to on. Depending on the value of fraction, this makes between 0 and 6 calls to the nextLong() method of random's * internal RandomnessSource, per 64 cells of this GreasedRegion (if height is not a multiple of 64, round up to get * the number of calls this makes). As such, this sacrifices the precision of the fraction to obtain significantly * better speed than generating one random number per cell, although the precision is probably good enough (fraction * is effectively rounded down to the nearest multiple of 0.015625, and clamped between 0.0 and 1.0). * @param random an RNG that should have a good approximateBits() method; the default (LightRNG internally) should be fine * @param fraction between 0.0 and 1.0 (clamped), only considering a precision of 1/64.0 (0.015625) between steps * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion */ public GreasedRegion(final RNG random, final double fraction, final int width, final int height) { this.width = width; this.height = height; int bitCount = (int) (fraction * 64); ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; for (int i = 0; i < width * ySections; i++) { data[i] = random.approximateBits(bitCount); } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } } /** * Reassigns this GreasedRegion randomly, reusing the current data storage (without extra allocations) if this.width * == width and this.height == height, while trying to set the given fraction of cells to on. Depending on the value * of fraction, this makes between 0 and 6 calls to the nextLong() method of random's internal RandomnessSource, per * 64 cells of this GreasedRegion (if height is not a multiple of 64, round up to get the number of calls this * makes). As such, this sacrifices the precision of the fraction to obtain significantly better speed than * generating one random number per cell, although the precision is probably good enough (fraction is effectively * rounded down to the nearest multiple of 0.015625, and clamped between 0.0 and 1.0). * @param random an RNG that should have a good approximateBits() method; the default (LightRNG internally) should be fine * @param fraction between 0.0 and 1.0 (clamped), only considering a precision of 1/64.0 (0.015625) between steps * @param width the maximum width for the GreasedRegion * @param height the maximum height for the GreasedRegion * @return this for chaining */ public GreasedRegion refill(final RNG random, final double fraction, final int width, final int height) { if (random != null){ int bitCount = (int) (fraction * 64); if(this.width == width && this.height == height) { for (int i = 0; i < width * ySections; i++) { data[i] = random.approximateBits(bitCount); } } else { this.width = (width <= 0) ? 0 : width; this.height = (height <= 0) ? 0 : height; ySections = (this.height + 63) >> 6; yEndMask = -1L >>> (64 - (this.height & 63)); data = new long[this.width * ySections]; for (int i = 0; i < this.width * ySections; i++) { data[i] = random.approximateBits(bitCount); } } } return this; } /** * Copy constructor that takes another GreasedRegion and copies all of its data into this new one. * If you find yourself frequently using this constructor and assigning it to the same variable, consider using the * {@link #remake(GreasedRegion)} method on the variable instead, which will, if it has the same width and height * as the other GreasedRegion, avoid creating garbage and quickly fill the variable with the other's contents. * @see #copy() for a convenience method that just uses this constructor * @param other another GreasedRegion that will be copied into this new GreasedRegion */ public GreasedRegion(final GreasedRegion other) { width = other.width; height = other.height; ySections = other.ySections; yEndMask = other.yEndMask; data = new long[width * ySections]; System.arraycopy(other.data, 0, data, 0, width * ySections); } /** * Primarily for internal use, this constructor copies data2 exactly into the internal long array the new * GreasedRegion will use, and does not perform any validation steps to ensure that cells that would be "on" but are * outside the actual height of the GreasedRegion are actually removed (this only matters if height is not a * multiple of 64). * @param data2 a long array that is typically from another GreasedRegion, and would be hard to make otherwise * @param width the width of the GreasedRegion to construct * @param height the height of the GreasedRegion to construct */ public GreasedRegion(final long[] data2, final int width, final int height) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; System.arraycopy(data2, 0, data, 0, width * ySections); } /** * Primarily for internal use, this constructor copies data2 into the internal long array the new GreasedRegion will * use, but treats data2 as having the dimensions [dataWidth][dataHeight], and uses the potentially-different * dimensions [width][height] for the constructed GreasedRegion. This will truncate data2 on width, height, or both * if width or height is smaller than dataWidth or dataHeight. It will fill extra space with all "off" if width or * height is larger than dataWidth or dataHeight. It will interpret data2 as the same 2D shape regardless of the * width or height it is being assigned to, and data2 will not be reshaped by truncation. * @param data2 a long array that is typically from another GreasedRegion, and would be hard to make otherwise * @param dataWidth the width to interpret data2 as having * @param dataHeight the height to interpret data2 as having * @param width the width of the GreasedRegion to construct * @param height the height of the GreasedRegion to construct */ public GreasedRegion(final long[] data2, final int dataWidth, final int dataHeight, final int width, final int height) { this.width = width; this.height = height; ySections = (height + 63) >> 6; yEndMask = -1L >>> (64 - (height & 63)); data = new long[width * ySections]; final int ySections2 = (dataHeight + 63) >> 6; if(ySections == 1) { System.arraycopy(data2, 0, data, 0, dataWidth * Math.min(ySections, ySections2)); } else { if(dataHeight >= height) { for (int i = 0, j = 0; i < width && i < dataWidth; i += ySections2, j += ySections) { System.arraycopy(data2, i, data, j, ySections); } } else { for (int i = 0, j = 0; i < width && i < dataWidth; i += ySections2, j += ySections) { System.arraycopy(data2, i, data, j, ySections2); } } } } /** * A useful method for efficiency, remake() reassigns this GreasedRegion to have its contents replaced by other. If * other and this GreasedRegion have identical width and height, this is very efficient and performs no additional * allocations, simply replacing the cell data in this with the cell data from other. If width and height are not * both equal between this and other, this does allocate a new data array, but still reassigns this GreasedRegion * in-place and acts similarly to when width and height are both equal (it just uses some more memory). * <br> * Using remake() or the similar refill() methods in chains of operations on multiple GreasedRegions can be key to * maintaining good performance and memory usage. You often can recycle a no-longer-used GreasedRegion by assigning * a GreasedRegion you want to keep to it with remake(), then mutating either the remade value or the one that was * just filled into this but keeping one version around for later usage. * @param other another GreasedRegion to replace the data in this GreasedRegion with * @return this for chaining */ public GreasedRegion remake(GreasedRegion other) { if (width == other.width && height == other.height) { System.arraycopy(other.data, 0, data, 0, width * ySections); return this; } else { width = other.width; height = other.height; ySections = other.ySections; yEndMask = other.yEndMask; data = new long[width * ySections]; System.arraycopy(other.data, 0, data, 0, width * ySections); return this; } } /** * Changes the width and/or height of this GreasedRegion, enlarging or shrinking starting at the edges where * {@code x == width - 1} and {@code y == height - 1}. There isn't an especially efficient way to expand from the * other edges, but this method is able to copy data in bulk, so at least this method should be very fast. You can * use {@code insert(int, int, GreasedRegion)} if you want to place one GreasedRegion inside another one, * potentially with a different size. The space created by any enlargement starts all off; shrinking doesn't change * the existing data where it isn't removed by the shrink. * @param widthChange the amount to change width by; can be positive, negative, or zero * @param heightChange the amount to change height by; can be positive, negative, or zero * @return this for chaining */ public GreasedRegion alterBounds(int widthChange, int heightChange) { int newWidth = width + widthChange; int newHeight = height + heightChange; if(newWidth <= 0 || newHeight <= 0) { width = 0; height = 0; ySections= 0; yEndMask = -1; data = new long[0]; return this; } int newYSections = (newHeight + 63) >> 6; yEndMask = -1L >>> (64 - (newHeight & 63)); long[] newData = new long[newWidth * newYSections]; for (int x = 0; x < width && x < newWidth; x++) { for (int ys = 0; ys < ySections && ys < newYSections; ys++) { newData[x * newYSections + ys] = data[x * ySections + ys]; } } ySections = newYSections; width = newWidth; height = newHeight; data = newData; if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } /** * Sets the cell at x,y to on if value is true or off if value is false. Does nothing if x,y is out of bounds. * @param value the value to set in the cell * @param x the x-position of the cell * @param y the y-position of the cell * @return this for chaining */ public GreasedRegion set(boolean value, int x, int y) { if(x < width && y < height && x >= 0 && y >= 0) { if(value) data[x * ySections + (y >> 6)] |= 1L << (y & 63); else data[x * ySections + (y >> 6)] &= ~(1L << (y & 63)); } return this; } /** * Sets the cell at point to on if value is true or off if value is false. Does nothing if point is out of bounds, * or if point is null. * @param value the value to set in the cell * @param point the x,y Coord of the cell to set * @return this for chaining */ public GreasedRegion set(boolean value, Coord point) { if(point == null) return this; return set(value, point.x, point.y); } /** * Sets the cell at x,y to "on". Does nothing if x,y is out of bounds. * More efficient, slightly, than {@link #set(boolean, int, int)} if you just need to set a cell to "on". * @param x the x-position of the cell * @param y the y-position of the cell * @return this for chaining */ public GreasedRegion insert(int x, int y) { if(x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] |= 1L << (y & 63); return this; } /** * Sets the cell at point to "on". Does nothing if point is out of bounds, or if point is null. * More efficient, slightly, than {@link #set(boolean, Coord)} if you just need to set a cell to "on". * @param point the x,y Coord of the cell * @return this for chaining */ public GreasedRegion insert(Coord point) { if(point == null) return this; return insert(point.x, point.y); } /** * Takes another GreasedRegion, called other, with potentially different size and inserts its "on" cells into thi * GreasedRegion at the given x,y offset, allowing negative x and/or y to put only part of other in this. * <br> * This is a rather complex method internally, but should be about as efficient as a general insert-region method * can be. * @param x the x offset to start inserting other at; may be negative * @param y the y offset to start inserting other at; may be negative * @param other the other GreasedRegion to insert * @return this for chaining */ public GreasedRegion insert(int x, int y, GreasedRegion other) { if(other == null || other.ySections <= 0 || other.width <= 0) return this; int start = Math.max(0, x), len = Math.min(width, Math.min(other.width, other.width + x) - start), oys = other.ySections, jump = (y == 0) ? 0 : (y < 0) ? -(1-y >>> 6) : (y-1 >>> 6), lily = (y < 0) ? -(-y & 63) : (y & 63), originalJump = Math.max(0, -jump), alterJump = Math.max(0, jump); long[] data2 = new long[other.width * ySections]; long prev, tmp; if(oys == ySections) { if (x < 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * ySections + oi]; } } } else { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = other.data[j * ySections + oi]; } } } } else if(oys < ySections) { if (x < 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) {// oi < oys - Math.max(0, jump) for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = other.data[j * oys + oi]; } } } } else { if (x < 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = other.data[j * oys + oi]; } } } } if(lily < 0) { for (int i = start; i < len; i++) { prev = 0L; for (int j = 0; j < ySections; j++) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L << -lily)) << (64 + lily); data2[i * ySections + j] >>>= -lily; data2[i * ySections + j] |= tmp; } } } else if(lily > 0) { for (int i = start; i < start + len; i++) { prev = 0L; for (int j = 0; j < ySections; j++) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L >>> lily)) >>> (64 - lily); data2[i * ySections + j] <<= lily; data2[i * ySections + j] |= tmp; } } } len = Math.min(width, start + len); for (int i = start; i < len; i++) { for (int j = 0; j < ySections; j++) { data[i * ySections + j] |= data2[i * ySections + j]; } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } public GreasedRegion insertSeveral(Coord... points) { for (int i = 0, x, y; i < points.length; i++) { x = points[i].x; y = points[i].y; if(x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } return this; } public GreasedRegion insertSeveral(Iterable<Coord> points) { int x, y; for (Coord pt : points) { x = pt.x; y = pt.y; if(x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] |= 1L << (y & 63); } return this; } public GreasedRegion insertRectangle(int startX, int startY, int rectangleWidth, int rectangleHeight) { if(rectangleWidth < 1 || rectangleHeight < 1 || ySections <= 0) return this; if(startX < 0) startX = 0; else if(startX >= width) startX = width - 1; if(startY < 0) startY = 0; else if(startY >= height) startY = height - 1; int endX = Math.min(width, startX + rectangleWidth) - 1, endY = Math.min(height, startY + rectangleHeight) - 1, startSection = startY >> 6, endSection = endY >> 6; if(startSection < endSection) { long startMask = -1L << (startY & 63), endMask = -1L >>> (~endY & 63); for (int a = startX * ySections + startSection; a <= endX * ySections + startSection; a += ySections) { data[a] |= startMask; } if(endSection - startSection > 1) { for (int b = 1; b < endSection - startSection; b++) { for (int a = startX * ySections + startSection + b; a < endX * ySections + ySections; a += ySections) { data[a] = -1; } } } for (int a = startX * ySections + endSection; a <= endX * ySections + endSection; a += ySections) { data[a] |= endMask; } } else { long mask = (-1L << (startY & 63)) & (-1L >>> (~endY & 63)); for (int a = startX * ySections + startSection; a <= endX * ySections + startSection; a += ySections) { data[a] |= mask; } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } public GreasedRegion insertCircle(Coord center, int radius) { return insertSeveral(Radius.CIRCLE.pointsInside(center, radius, false, width, height)); } public GreasedRegion remove(int x, int y) { if(x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] &= ~(1L << (y & 63)); return this; } public GreasedRegion remove(Coord point) { return remove(point.x, point.y); } /** * Takes another GreasedRegion, called other, with potentially different size and removes its "on" cells from this * GreasedRegion at the given x,y offset, allowing negative x and/or y to remove only part of other in this. * <br> * This is a rather complex method internally, but should be about as efficient as a general remove-region method * can be. The code is identical to {@link #insert(int, int, GreasedRegion)} except that where insert only adds * cells, this only removes cells. Essentially, insert() is to {@link #or(GreasedRegion)} as remove() is to * {@link #andNot(GreasedRegion)}. * @param x the x offset to start removing other from; may be negative * @param y the y offset to start removing other from; may be negative * @param other the other GreasedRegion to remove * @return this for chaining */ public GreasedRegion remove(int x, int y, GreasedRegion other) { if(other == null || other.ySections <= 0 || other.width <= 0) return this; int start = Math.max(0, x), len = Math.min(width, Math.min(other.width, other.width + x) - start), oys = other.ySections, jump = (y == 0) ? 0 : (y < 0) ? -(1-y >>> 6) : (y-1 >>> 6), lily = (y < 0) ? -(-y & 63) : (y & 63), originalJump = Math.max(0, -jump), alterJump = Math.max(0, jump); long[] data2 = new long[other.width * ySections]; long prev, tmp; if(oys == ySections) { if (x < 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * ySections + oi]; } } } else { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = other.data[j * ySections + oi]; } } } } else if(oys < ySections) { if (x < 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) {// oi < oys - Math.max(0, jump) for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = other.data[j * oys + oi]; } } } } else { if (x < 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = other.data[j * oys + oi]; } } } else { for (int i = alterJump, oi = originalJump; i < ySections && oi < oys; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = other.data[j * oys + oi]; } } } } if(lily < 0) { for (int i = start; i < len; i++) { prev = 0L; for (int j = 0; j < ySections; j++) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L << -lily)) << (64 + lily); data2[i * ySections + j] >>>= -lily; data2[i * ySections + j] |= tmp; } } } else if(lily > 0) { for (int i = start; i < start + len; i++) { prev = 0L; for (int j = 0; j < ySections; j++) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L >>> lily)) >>> (64 - lily); data2[i * ySections + j] <<= lily; data2[i * ySections + j] |= tmp; } } } len = Math.min(width, start + len); for (int i = start; i < len; i++) { for (int j = 0; j < ySections; j++) { data[i * ySections + j] &= ~data2[i * ySections + j]; } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } public GreasedRegion removeSeveral(Coord... points) { for (int i = 0, x, y; i < points.length; i++) { x = points[i].x; y = points[i].y; if(x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] &= ~(1L << (y & 63)); } return this; } public GreasedRegion removeSeveral(Iterable<Coord> points) { int x, y; for (Coord pt : points) { x = pt.x; y = pt.y; if(x < width && y < height && x >= 0 && y >= 0) data[x * ySections + (y >> 6)] &= ~(1L << (y & 63)); } return this; } public GreasedRegion removeRectangle(int startX, int startY, int rectangleWidth, int rectangleHeight) { if(rectangleWidth < 1 || rectangleHeight < 1 || ySections <= 0) return this; if(startX < 0) startX = 0; else if(startX >= width) startX = width - 1; if(startY < 0) startY = 0; else if(startY >= height) startY = height - 1; int endX = Math.min(width, startX + rectangleWidth) - 1, endY = Math.min(height, startY + rectangleHeight) - 1, startSection = startY >> 6, endSection = endY >> 6; if(startSection < endSection) { long startMask = ~(-1L << (startY & 63)), endMask = ~(-1L >>> (~endY & 63)); for (int a = startX * ySections + startSection; a <= endX * ySections; a += ySections) { data[a] &= startMask; } if(endSection - startSection > 1) { for (int b = 1; b < endSection - startSection; b++) { for (int a = startX * ySections + startSection + b; a < endX * ySections + ySections; a += ySections) { data[a] = 0; } } } for (int a = startX * ySections + endSection; a <= endX * ySections + ySections; a += ySections) { data[a] &= endMask; } } else { long mask = ~((-1L << (startY & 63)) & (-1L >>> (~endY & 63))); for (int a = startX * ySections + startSection; a <= endX * ySections + startSection; a += ySections) { data[a] &= mask; } } return this; } public GreasedRegion removeCircle(Coord center, int radius) { return removeSeveral(Radius.CIRCLE.pointsInside(center, radius, false, width, height)); } /** * Equivalent to {@link #clear()}, setting all cells to "off," but also returns this for chaining. * @return this for chaining */ public GreasedRegion empty() { Arrays.fill(data, 0L); return this; } /** * Sets all cells in this to "on." * @return this for chaining */ public GreasedRegion allOn() { if(ySections > 0) { if(yEndMask == -1) { Arrays.fill(data, -1); } else { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] = yEndMask; for (int i = 0; i < ySections - 1; i++) { data[a-i-1] = -1; } } } } return this; } /** * Sets all cells in this to "on" if contents is true, or "off" if contents is false. * @param contents true to set all cells to on, false to set all cells to off * @return this for chaining */ public GreasedRegion fill(boolean contents) { if(contents) { if(ySections > 0) { if(yEndMask == -1) { Arrays.fill(data, -1); } else { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] = yEndMask; for (int i = 0; i < ySections - 1; i++) { data[a-i-1] = -1; } } } } //else... what, if ySections is 0 there's nothing to do } else { Arrays.fill(data, 0L); } return this; } /** * Simple method that returns a newly-allocated copy of this GreasedRegion; modifications to one won't change the * other, and this method returns the copy while leaving the original unchanged. * @return a copy of this GreasedRegion; the copy can be changed without altering the original */ public GreasedRegion copy() { return new GreasedRegion(this); } /** * Returns this GreasedRegion's data as a 2D boolean array, [width][height] in size, with on treated as true and off * treated as false. * @return a 2D boolean array that represents this GreasedRegion's data */ public boolean[][] decode() { boolean[][] bools = new boolean[width][height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { bools[x][y] = (data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0; } } return bools; } /** * Returns this GreasedRegion's data as a 2D char array, [width][height] in size, with "on" cells filled with the * char parameter on and "off" cells with the parameter off. * @param on the char to use for "on" cells * @param off the char to use for "off" cells * @return a 2D char array that represents this GreasedRegion's data */ public char[][] toChars(char on, char off) { char[][] chars = new char[width][height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { chars[x][y] = (data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0 ? on : off; } } return chars; } /** * Returns this GreasedRegion's data as a 2D char array, [width][height] in size, with "on" cells filled with '.' * and "off" cells with '#'. * @return a 2D char array that represents this GreasedRegion's data */ public char[][] toChars() { return toChars('.', '#'); } /** * Returns this GreasedRegion's data as a StringBuilder, with each row made of the parameter on for "on" cells and * the parameter off for "off" cells, separated by newlines, with no trailing newline at the end. * @param on the char to use for "on" cells * @param off the char to use for "off" cells * @return a StringBuilder that stores each row of this GreasedRegion as chars, with rows separated by newlines. */ public StringBuilder show(char on, char off) { StringBuilder sb = new StringBuilder((width+1) * height); for (int y = 0; y < height;) { for (int x = 0; x < width; x++) { sb.append((data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0 ? on : off); } if(++y < height) sb.append('\n'); } return sb; } /** * Returns a legible String representation of this that can be printed over multiple lines, with all "on" cells * represented by '.' and all "off" cells by '#', in roguelike floors-on walls-off convention, separating each row * by newlines (without a final trailing newline, so you could append text right after this). * @return a String representation of this GreasedRegion using '.' for on, '#' for off, and newlines between rows */ @Override public String toString() { return show('.', '#').toString(); } /** * Returns a copy of map where if a cell is "on" in this GreasedRegion, this keeps the value in map intact, * and where a cell is "off", it instead writes the char filler. * @param map a 2D char array that will not be modified * @param filler the char to use where this GreasedRegion stores an "off" cell * @return a masked copy of map */ public char[][] mask(char[][] map, char filler) { if(map == null || map.length == 0) return new char[0][0]; int width2 = Math.min(width, map.length), height2 = Math.min(height, map[0].length); char[][] chars = new char[width2][height2]; for (int x = 0; x < width2; x++) { for (int y = 0; y < height2; y++) { chars[x][y] = (data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0 ? map[x][y] : filler; } } return chars; } /** * Returns a copy of map where if a cell is "on" in this GreasedRegion, this keeps the value in map intact, * and where a cell is "off", it instead writes the short filler. Meant for use with MultiSpill, but may be * used anywhere you have a 2D short array. {@link #mask(char[][], char)} is more likely to be useful. * @param map a 2D short array that will not be modified * @param filler the short to use where this GreasedRegion stores an "off" cell * @return a masked copy of map */ public short[][] mask(short[][] map, short filler) { if(map == null || map.length == 0) return new short[0][0]; int width2 = Math.min(width, map.length), height2 = Math.min(height, map[0].length); short[][] shorts = new short[width2][height2]; for (int x = 0; x < width2; x++) { for (int y = 0; y < height2; y++) { shorts[x][y] = (data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0 ? map[x][y] : filler; } } return shorts; } /** * Returns a copy of map where if a cell is "off" in this GreasedRegion, this keeps the value in map intact, * and where a cell is "on", it instead writes the char toWrite. * @param map a 2D char array that will not be modified * @param toWrite the char to use where this GreasedRegion stores an "on" cell * @return a masked copy of map */ public char[][] inverseMask(char[][] map, char toWrite) { if(map == null || map.length == 0) return new char[0][0]; int width2 = Math.min(width, map.length), height2 = Math.min(height, map[0].length); char[][] chars = new char[width2][height2]; for (int x = 0; x < width2; x++) { for (int y = 0; y < height2; y++) { chars[x][y] = (data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0 ? toWrite : map[x][y]; } } return chars; } /** * "Inverse mask for ints;" returns a copy of map where if a cell is "off" in this GreasedRegion, this keeps * the value in map intact, and where a cell is "on", it instead writes the int toWrite. * @param map a 2D int array that will not be modified * @param toWrite the int to use where this GreasedRegion stores an "on" cell * @return an altered copy of map */ public int[][] writeInts(int[][] map, int toWrite) { if(map == null || map.length == 0) return new int[0][0]; int width2 = Math.min(width, map.length), height2 = Math.min(height, map[0].length); int[][] ints = new int[width2][height2]; for (int x = 0; x < width2; x++) { for (int y = 0; y < height2; y++) { ints[x][y] = (data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0 ? toWrite : map[x][y]; } } return ints; } /** * "Inverse mask for ints;" returns a copy of map where if a cell is "off" in this GreasedRegion, this keeps * the value in map intact, and where a cell is "on", it instead writes the int toWrite. Modifies map in-place, * unlike {@link #writeInts(int[][], int)}. * @param map a 2D int array that <b>will</b> be modified * @param toWrite the int to use where this GreasedRegion stores an "on" cell * @return map, with the changes applied; not a copy */ public int[][] writeIntsInto(int[][] map, int toWrite) { if(map == null || map.length == 0) return map; int width2 = Math.min(width, map.length), height2 = Math.min(height, map[0].length); for (int x = 0; x < width2; x++) { for (int y = 0; y < height2; y++) { if((data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0) map[x][y] = toWrite; } } return map; } /** * Union of two GreasedRegions, assigning the result into this GreasedRegion. Any cell that is "on" in either * GreasedRegion will be made "on" in this GreasedRegion. * @param other another GreasedRegion that will not be modified * @return this, after modification, for chaining */ public GreasedRegion or(GreasedRegion other) { for (int x = 0; x < width && x < other.width; x++) { for (int y = 0; y < ySections && y < other.ySections; y++) { data[x * ySections + y] |= other.data[x * ySections + y]; } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } /** * Intersection of two GreasedRegions, assigning the result into this GreasedRegion. Any cell that is "on" in both * GreasedRegions will be kept "on" in this GreasedRegion, but all other cells will be made "off." * @param other another GreasedRegion that will not be modified * @return this, after modification, for chaining */ public GreasedRegion and(GreasedRegion other) { for (int x = 0; x < width && x < other.width; x++) { for (int y = 0; y < ySections && y < other.ySections; y++) { data[x * ySections + y] &= other.data[x * ySections + y]; } } return this; } /** * Difference of two GreasedRegions, assigning the result into this GreasedRegion. Any cell that is "on" in this * GreasedRegion and "off" in other will be kept "on" in this GreasedRegion, but all other cells will be made "off." * @param other another GreasedRegion that will not be modified * @return this, after modification, for chaining * @see #notAnd(GreasedRegion) notAnd is a very similar method that acts sort-of in reverse of this method */ public GreasedRegion andNot(GreasedRegion other) { for (int x = 0; x < width && x < other.width; x++) { for (int y = 0; y < ySections && y < other.ySections; y++) { data[x * ySections + y] &= ~other.data[x * ySections + y]; } } return this; } /** * Like andNot, but subtracts this GreasedRegion from other and stores the result in this GreasedRegion, without * mutating other. * @param other another GreasedRegion that will not be modified * @return this, after modification, for chaining * @see #andNot(GreasedRegion) andNot is a very similar method that acts sort-of in reverse of this method */ public GreasedRegion notAnd(GreasedRegion other) { for (int x = 0; x < width && x < other.width; x++) { for (int y = 0; y < ySections && y < other.ySections; y++) { data[x * ySections + y] = other.data[x * ySections + y] & ~data[x * ySections + y]; } } return this; } /** * Symmetric difference (more commonly known as exclusive or, hence the name) of two GreasedRegions, assigning the * result into this GreasedRegion. Any cell that is "on" in this and "off" in other, or "off" in this and "on" in * other, will be made "on" in this; all other cells will be made "off." Useful to find cells that are "on" in * exactly one of two GreasedRegions (not "on" in both, or "off" in both). * @param other another GreasedRegion that will not be modified * @return this, after modification, for chaining */ public GreasedRegion xor(GreasedRegion other) { for (int x = 0; x < width && x < other.width; x++) { for (int y = 0; y < ySections && y < other.ySections; y++) { data[x * ySections + y] ^= other.data[x * ySections + y]; } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } /** * Negates this GreasedRegion, turning "on" to "off" and "off" to "on." * @return this, after modification, for chaining */ public GreasedRegion not() { for (int a = 0; a < data.length; a++) { data[a] = ~data[a]; } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } /** * Moves the "on" cells in this GreasedRegion to the given x and y offset, removing cells that move out of bounds. * @param x the x offset to translate by; can be negative * @param y the y offset to translate by; can be negative * @return this for chaining */ public GreasedRegion translate(int x, int y) { if(width < 1 || ySections <= 0 || (x == 0 && y == 0)) return this; int start = Math.max(0, x), len = Math.min(width, width + x) - start, jump = (y == 0) ? 0 : (y < 0) ? -(1-y >>> 6) : (y-1 >>> 6), lily = (y < 0) ? -(-y & 63) : (y & 63), originalJump = Math.max(0, -jump), alterJump = Math.max(0, jump); long[] data2 = new long[width * ySections]; long prev, tmp; if (x < 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < ySections; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = data[j * ySections + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i < ySections && oi < ySections; i++, oi++) { for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = data[j * ySections + oi]; } } } else { for (int i = alterJump, oi = originalJump; i < ySections && oi < ySections; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = data[j * ySections + oi]; } } } if(lily < 0) { for (int i = start; i < len; i++) { prev = 0L; for (int j = 0; j < ySections; j++) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L << -lily)) << (64 + lily); data2[i * ySections + j] >>>= -lily; data2[i * ySections + j] |= tmp; } } } else if(lily > 0) { for (int i = start; i < start + len; i++) { prev = 0L; for (int j = 0; j < ySections; j++) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L >>> lily)) >>> (64 - lily); data2[i * ySections + j] <<= lily; data2[i * ySections + j] |= tmp; } } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data2[a] &= yEndMask; } } data = data2; return this; } /** * Effectively doubles the x and y values of each cell this contains (not scaling each cell to be larger, so each * "on" cell will be surrounded by "off" cells), and re-maps the positions so the given x and y in the doubled space * become 0,0 in the resulting GreasedRegion (which is this, assigning to itself). * @param x in the doubled coordinate space, the x position that should become 0 x in the result; can be negative * @param y in the doubled coordinate space, the y position that should become 0 y in the result; can be negative * @return this for chaining */ public GreasedRegion zoom(int x, int y) { if(width < 1 || ySections <= 0) return this; x = -x; y = -y; int width2 = width + 1 >>> 1, ySections2 = ySections + 1 >>> 1, start = Math.max(0, x), len = Math.min(width, width + x) - start, //tall = (Math.min(height, height + y) - Math.max(0, y)) + 63 >> 6, jump = (y == 0) ? 0 : (y < 0) ? -(1-y >>> 6) : (y-1 >>> 6), lily = (y < 0) ? -(-y & 63) : (y & 63), originalJump = Math.max(0, -jump), alterJump = Math.max(0, jump), oddX = (x & 1), oddY = (y & 1); long[] data2 = new long[width * ySections]; long prev, tmp, yEndMask2 = -1L >>> (64 - ((height + 1 >>> 1) & 63)); if (x < 0) { for (int i = alterJump, oi = originalJump; i <= ySections2 && oi < ySections; i++, oi++) { for (int j = Math.max(0, -x), jj = 0; jj < len; j++, jj++) { data2[jj * ySections + i] = data[j * ySections + oi]; } } } else if (x > 0) { for (int i = alterJump, oi = originalJump; i <= ySections2 && oi < ySections; i++, oi++) { for (int j = 0, jj = start; j < len; j++, jj++) { data2[jj * ySections + i] = data[j * ySections + oi]; } } } else { for (int i = alterJump, oi = originalJump; i <= ySections2 && oi < ySections; i++, oi++) { for (int j = 0; j < len; j++) { data2[j * ySections + i] = data[j * ySections + oi]; } } } if(lily < 0) { for (int i = start; i < len; i++) { prev = 0L; for (int j = ySections2; j >= 0; j--) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L << -lily)) << (64 + lily); data2[i * ySections + j] >>>= -lily; data2[i * ySections + j] |= tmp; } } } else if(lily > 0) { for (int i = start; i < start + len; i++) { prev = 0L; for (int j = 0; j < ySections2; j++) { tmp = prev; prev = (data2[i * ySections + j] & ~(-1L >>> lily)) >>> (64 - lily); data2[i * ySections + j] <<= lily; data2[i * ySections + j] |= tmp; } } } if(ySections2 > 0 && yEndMask2 != -1) { for (int a = ySections2 - 1; a < data2.length; a += ySections) { data2[a] &= yEndMask2; if(ySections2 < ySections) data2[a+1] = 0L; } } for (int i = 0; i < width2; i++) { for (int j = 0; j < ySections2; j++) { prev = data2[i * ySections + j]; tmp = prev >>> 32; prev &= 0xFFFFFFFFL; prev = (prev | (prev << 16)) & 0x0000FFFF0000FFFFL; prev = (prev | (prev << 8)) & 0x00FF00FF00FF00FFL; prev = (prev | (prev << 4)) & 0x0F0F0F0F0F0F0F0FL; prev = (prev | (prev << 2)) & 0x3333333333333333L; prev = (prev | (prev << 1)) & 0x5555555555555555L; prev <<= oddY; if(oddX == 1) { if (i * 2 + 1 < width) data[(i * ySections + j) * 2 + ySections] = prev; if (i * 2 < width) data[(i * ySections + j) * 2] = 0L; } else { if (i * 2 < width) data[(i * ySections + j) * 2] = prev; if (i * 2 + 1 < width) data[(i * ySections + j) * 2 + ySections] = 0L; } if(j * 2 + 1 < ySections) { tmp = (tmp | (tmp << 16)) & 0x0000FFFF0000FFFFL; tmp = (tmp | (tmp << 8)) & 0x00FF00FF00FF00FFL; tmp = (tmp | (tmp << 4)) & 0x0F0F0F0F0F0F0F0FL; tmp = (tmp | (tmp << 2)) & 0x3333333333333333L; tmp = (tmp | (tmp << 1)) & 0x5555555555555555L; tmp <<= oddY; if(oddX == 1) { if (i * 2 + 1 < width) data[(i * ySections + j) * 2 + ySections + 1] = tmp; if (i * 2 < width) data[(i * ySections + j) * 2 + 1] = 0L; } else { if (i * 2 < width) data[(i * ySections + j) * 2 + 1] = tmp; if (i * 2 + 1 < width) data[(i * ySections + j) * 2 + ySections + 1] = 0L; } } } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < data.length; a += ySections) { data[a] &= yEndMask; } } return this; } /** * Takes the pairs of "on" cells in this GreasedRegion that are separated by exactly one cell in an orthogonal line, * and changes the gap cells to "on" as well. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return this for chaining */ public GreasedRegion connect() { if(width < 2 || ySections == 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, 0, next, 0, width * ySections); for (int a = 0; a < ySections; a++) { next[a] |= ((data[a] << 1) & (data[a] >>> 1)) | data[a+ySections]; next[(width-1)*ySections+a] |= ((data[(width-1)*ySections+a] << 1) & (data[(width-1)*ySections+a] >>> 1)) | data[(width-2) *ySections+a]; for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] |= ((data[i] << 1) & (data[i] >>> 1)) | (data[i - ySections] & data[i + ySections]); } if(a > 0) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i - 1] & 0x8000000000000000L) >>> 63 & (data[i] >>> 1); } } else { for (int i = ySections; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i] >>> 1 & 1L); } } if(a < ySections - 1) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i + 1] & 1L) << 63 & (data[i] << 1); } } else { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i] << 1 & 0x8000000000000000L); } } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } /** * Takes the pairs of "on" cells in this GreasedRegion that are separated by exactly one cell in an orthogonal or * diagonal line, and changes the gap cells to "on" as well. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return this for chaining */ public GreasedRegion connect8way() { if(width < 2 || ySections == 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, 0, next, 0, width * ySections); for (int a = 0; a < ySections; a++) { next[a] |= ((data[a] << 1) & (data[a] >>> 1)) | data[a+ySections] | (data[a+ySections] << 1) | (data[a+ySections] >>> 1); next[(width-1)*ySections+a] |= ((data[(width-1)*ySections+a] << 1) & (data[(width-1)*ySections+a] >>> 1)) | data[(width-2) *ySections+a] | (data[(width-2)*ySections+a] << 1) | (data[(width-2)*ySections+a] >>> 1); for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] |= ((data[i] << 1) & (data[i] >>> 1)) | (data[i - ySections] & data[i + ySections]) | ((data[i - ySections] << 1) & (data[i + ySections] >>> 1)) | ((data[i + ySections] << 1) & (data[i - ySections] >>> 1)); } if(a > 0) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= ((data[i - 1] & 0x8000000000000000L) >>> 63 & (data[i] >>> 1)) | ((data[i - ySections - 1] & 0x8000000000000000L) >>> 63 & (data[i + ySections] >>> 1)) | ((data[i + ySections - 1] & 0x8000000000000000L) >>> 63 & (data[i - ySections] >>> 1)); } } else { for (int i = ySections; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i] >>> 1 & 1L) | (data[i - ySections] >>> 1 & 1L) | (data[i + ySections] >>> 1 & 1L); } } if(a < ySections - 1) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= ((data[i + 1] & 1L) << 63 & (data[i] << 1)) | ((data[i - ySections + 1] & 1L) << 63 & (data[i + ySections] << 1)) | ((data[i + ySections + 1] & 1L) << 63 & (data[i - ySections] << 1)) ; } } else { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i] << 1 & 0x8000000000000000L) | (data[i - ySections] << 1 & 0x8000000000000000L) | (data[i + ySections] << 1 & 0x8000000000000000L); } } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } /** * Takes the pairs of "on" cells in this GreasedRegion that are separated by exactly one cell in an orthogonal or * diagonal line, and changes the gap cells to "on" as well. As a special case, this requires diagonals to either * have no "on" cells adjacent along the perpendicular diagonal, or both cells on that perpendicular diagonal need * to be "on." This is useful to counteract some less-desirable behavior of {@link #connect8way()}, where a right * angle would always get the inner corners filled because it was considered a diagonal. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return this for chaining */ public GreasedRegion connectLines() { if(width < 2 || ySections == 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, 0, next, 0, width * ySections); for (int a = 0; a < ySections; a++) { next[a] |= ((data[a] << 1) & (data[a] >>> 1)) | data[a+ySections] | (data[a+ySections] << 1) | (data[a+ySections] >>> 1); next[(width-1)*ySections+a] |= ((data[(width-1)*ySections+a] << 1) & (data[(width-1)*ySections+a] >>> 1)) | data[(width-2) *ySections+a] | (data[(width-2)*ySections+a] << 1) | (data[(width-2)*ySections+a] >>> 1); for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] |= ((data[i] << 1) & (data[i] >>> 1)) | (data[i - ySections] & data[i + ySections]) | (((data[i - ySections] << 1) & (data[i + ySections] >>> 1)) ^ ((data[i + ySections] << 1) & (data[i - ySections] >>> 1))); } if(a > 0) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= ((data[i - 1] & 0x8000000000000000L) >>> 63 & (data[i] >>> 1)) | (((data[i - ySections - 1] & 0x8000000000000000L) >>> 63 & (data[i + ySections] >>> 1)) ^ ((data[i + ySections - 1] & 0x8000000000000000L) >>> 63 & (data[i - ySections] >>> 1))); } } else { for (int i = ySections; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i] >>> 1 & 1L) | (data[i - ySections] >>> 1 & 1L) | (data[i + ySections] >>> 1 & 1L); } } if(a < ySections - 1) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= ((data[i + 1] & 1L) << 63 & (data[i] << 1)) | (((data[i - ySections + 1] & 1L) << 63 & (data[i + ySections] << 1)) ^ ((data[i + ySections + 1] & 1L) << 63 & (data[i - ySections] << 1))); } } else { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i] << 1 & 0x8000000000000000L) | (data[i - ySections] << 1 & 0x8000000000000000L) | (data[i + ySections] << 1 & 0x8000000000000000L); } } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } /** * Like {@link #retract()}, this reduces the width of thick areas of this GreasedRegion, but thin() will not remove * areas that would be identical in a subsequent call to retract(), such as if the area would be eliminated. This * is useful primarily for adjusting areas so they do not exceed a width of 2 cells, though their length (the longer * of the two dimensions) will be unaffected by this. Especially wide, irregularly-shaped areas may have unintended * appearances if you call this repeatedly or use {@link #thinFully()}; consider using this sparingly, or primarily * when an area has just gotten thicker than desired. * @return this for chaining */ public GreasedRegion thin() { if(width <= 2 || ySections <= 0) return this; GreasedRegion c1 = new GreasedRegion(this).retract8way(), c2 = new GreasedRegion(c1).expand8way().xor(this).expand8way().and(this); remake(c1).or(c2); /* System.out.println("\n\nc1:\n" + c1.toString() + "\n"); System.out.println("\n\nc2:\n" + c2.toString() + "\n"); System.out.println("\n\nthis:\n" + toString() + "\n"); */ return this; } /** * Calls {@link #thin()} repeatedly, until the result is unchanged from the last call. Consider using the idiom * {@code expand8way().retract().thinFully()} to help change a possibly-strange appearance when the GreasedRegion * this is called on touches the edges of the grid. In general, this method is likely to go too far when it tries to * thin a round or irregular area, and this often results in many diagonal lines spanning the formerly-thick area. * @return this for chaining */ public GreasedRegion thinFully() { while (size() != thin().size()); return this; } /** * Removes "on" cells that are orthogonally adjacent to other "on" cells, keeping at least one cell in a group "on." * Uses a "checkerboard" pattern to determine which cells to turn off, with all cells that would be black on a * checkerboard turned off and all others kept as-is. * @return this for chaining */ public GreasedRegion disperse() { if(width < 1 || ySections <= 0) return this; long mask = 0x5555555555555555L; for (int i = 0; i < width; i++) { for (int j = 0; j < ySections; j++) { data[j] &= mask; } mask = ~mask; } return this; } /** * Removes "on" cells that are 8-way adjacent to other "on" cells, keeping at least one cell in a group "on." * Uses a "grid-like" pattern to determine which cells to turn off, with all cells with even x and even y kept as-is * but all other cells (with either or both odd x or odd y) turned off. * @return this for chaining */ public GreasedRegion disperse8way() { if(width < 1 || ySections <= 0) return this; int len = data.length; long mask = 0x5555555555555555L; for (int j = 0; j < len - 1; j += 2) { data[j] &= mask; data[j+1] = 0; } return this; } /** * Removes "on" cells that are nearby other "on" cells, with a random factor to which bits are actually turned off * that still ensures exactly half of the bits are kept as-is (the one exception is when height is an odd number, * which makes the bottom row slightly random). * @param random the RNG used for a random factor * @return this for chaining */ public GreasedRegion disperseRandom(RNG random) { if(width < 1 || ySections <= 0) return this; int len = data.length; for (int j = 0; j < len; j++) { data[j] &= random.randomInterleave(); } return this; } /** * Takes the "on" cells in this GreasedRegion and expands them by one cell in the 4 orthogonal directions, making * each "on" cell take up a plus-shaped area that may overlap with other "on" cells (which is just a normal "on" * cell then). * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return this for chaining */ public GreasedRegion expand() { if(width < 2 || ySections == 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, 0, next, 0, width * ySections); for (int a = 0; a < ySections; a++) { next[a] |= (data[a] << 1) | (data[a] >>> 1) | data[a+ySections]; next[(width-1)*ySections+a] |= (data[(width-1)*ySections+a] << 1) | (data[(width-1)*ySections+a] >>> 1) | data[(width-2) *ySections+a]; for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] |= (data[i] << 1) | (data[i] >>> 1) | data[i - ySections] | data[i + ySections]; } if(a > 0) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i - 1] & 0x8000000000000000L) >>> 63; } } if(a < ySections - 1) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= (data[i + 1] & 1L) << 63; } } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } /** * Takes the "on" cells in this GreasedRegion and expands them by amount cells in the 4 orthogonal directions, * making each "on" cell take up a plus-shaped area that may overlap with other "on" cells (which is just a normal * "on" cell then). * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return this for chaining */ @Override public GreasedRegion expand(int amount) { for (int i = 0; i < amount; i++) { expand(); } return this; } /** * Takes the "on" cells in this GreasedRegion and produces amount GreasedRegions, each one expanded by 1 cell in * the 4 orthogonal directions relative to the previous GreasedRegion, making each "on" cell take up a plus-shaped * area that may overlap with other "on" cells (which is just a normal "on" cell then). This returns an array of * GreasedRegions with progressively greater expansions, and does not modify this GreasedRegion. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return an array of new GreasedRegions, length == amount, where each one is expanded by 1 relative to the last */ public GreasedRegion[] expandSeries(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); for (int i = 0; i < amount; i++) { regions[i] = new GreasedRegion(temp.expand()); } return regions; } public ArrayList<GreasedRegion> expandSeriesToLimit() { ArrayList<GreasedRegion> regions = new ArrayList<>(); GreasedRegion temp = new GreasedRegion(this); while (temp.size() != temp.expand().size()) { regions.add(new GreasedRegion(temp)); } return regions; } /** * Takes the "on" cells in this GreasedRegion and expands them by one cell in the 4 orthogonal directions, producing * a diamoond shape, then removes the original area before expansion, producing only the cells that were "off" in * this and within 1 cell (orthogonal-only) of an "on" cell. This method is similar to {@link #surface()}, but * surface finds cells inside the current GreasedRegion, while fringe finds cells outside it. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. The surface and fringe methods do allocate one * temporary GreasedRegion to store the original before modification, but the others generally don't. * @return this for chaining */ public GreasedRegion fringe() { GreasedRegion cpy = new GreasedRegion(this); expand(); return andNot(cpy); } /** * Takes the "on" cells in this GreasedRegion and expands them by amount cells in the 4 orthogonal directions * (iteratively, producing a diamond shape), then removes the original area before expansion, producing only the * cells that were "off" in this and within amount cells (orthogonal-only) of an "on" cell. This method is similar * to {@link #surface()}, but surface finds cells inside the current GreasedRegion, while fringe finds cells outside * it. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. The surface and fringe methods do allocate one * temporary GreasedRegion to store the original before modification, but the others generally don't. * @return this for chaining */ public GreasedRegion fringe(int amount) { GreasedRegion cpy = new GreasedRegion(this); expand(amount); return andNot(cpy); } /** * Takes the "on" cells in this GreasedRegion and produces amount GreasedRegions, each one expanded by 1 cell in * the 4 orthogonal directions relative to the previous GreasedRegion, making each "on" cell take up a diamond- * shaped area. After producing the expansions, this removes the previous GreasedRegion from the next GreasedRegion * in the array, making each "fringe" in the series have 1 "thickness," which can be useful for finding which layer * of expansion a cell lies in. This returns an array of GreasedRegions with progressively greater expansions * without the cells of this GreasedRegion, and does not modify this GreasedRegion. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return an array of new GreasedRegions, length == amount, where each one is a 1-depth fringe pushed further out from this */ public GreasedRegion[] fringeSeries(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); regions[0] = new GreasedRegion(temp); for (int i = 1; i < amount; i++) { regions[i] = new GreasedRegion(temp.expand()); } for (int i = 0; i < amount - 1; i++) { regions[i].xor(regions[i + 1]); } regions[amount - 1].fringe(); return regions; } public ArrayList<GreasedRegion> fringeSeriesToLimit() { ArrayList<GreasedRegion> regions = expandSeriesToLimit(); for (int i = regions.size() - 1; i > 0; i--) { regions.get(i).xor(regions.get(i-1)); } regions.get(0).xor(this); return regions; } /** * Takes the "on" cells in this GreasedRegion and retracts them by one cell in the 4 orthogonal directions, * making each "on" cell that was orthogonally adjacent to an "off" cell into an "off" cell. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return this for chaining */ public GreasedRegion retract() { if(width <= 2 || ySections <= 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, ySections, next, ySections, (width - 2) * ySections); for (int a = 0; a < ySections; a++) { if(a > 0 && a < ySections - 1) { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= ((data[i] << 1) | ((data[i - 1] & 0x8000000000000000L) >>> 63)) & ((data[i] >>> 1) | ((data[i + 1] & 1L) << 63)) & data[i - ySections] & data[i + ySections]; } } else if(a > 0) { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= ((data[i] << 1) | ((data[i - 1] & 0x8000000000000000L) >>> 63)) & (data[i] >>> 1) & data[i - ySections] & data[i + ySections]; } } else if(a < ySections - 1) { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= (data[i] << 1) & ((data[i] >>> 1) | ((data[i + 1] & 1L) << 63)) & data[i - ySections] & data[i + ySections]; } } else // only the case when ySections == 1 { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= (data[i] << 1) & (data[i] >>> 1) & data[i - ySections] & data[i + ySections]; } } } if(yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } /** * Takes the "on" cells in this GreasedRegion and retracts them by one cell in the 4 orthogonal directions, doing * this iteeratively amount times, making each "on" cell that was within amount orthogonal distance to an "off" cell * into an "off" cell. * <br> * This method is very efficient due to how the class is implemented, and the various spatial increase/decrease * methods (including {@link #expand()}, {@link #retract()}, {@link #fringe()}, and {@link #surface()}) all perform * very well by operating in bulk on up to 64 cells at a time. * @return this for chaining */ public GreasedRegion retract(int amount) { for (int i = 0; i < amount; i++) { retract(); } return this; } public GreasedRegion[] retractSeries(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); for (int i = 0; i < amount; i++) { regions[i] = new GreasedRegion(temp.retract()); } return regions; } public ArrayList<GreasedRegion> retractSeriesToLimit() { ArrayList<GreasedRegion> regions = new ArrayList<>(); GreasedRegion temp = new GreasedRegion(this); while (!temp.retract().isEmpty()) { regions.add(new GreasedRegion(temp)); } return regions; } public GreasedRegion surface() { GreasedRegion cpy = new GreasedRegion(this).retract(); return xor(cpy); } public GreasedRegion surface(int amount) { GreasedRegion cpy = new GreasedRegion(this).retract(amount); return xor(cpy); } public GreasedRegion[] surfaceSeries(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); regions[0] = new GreasedRegion(temp); for (int i = 1; i < amount; i++) { regions[i] = new GreasedRegion(temp.retract()); } for (int i = 0; i < amount - 1; i++) { regions[i].xor(regions[i + 1]); } regions[amount - 1].surface(); return regions; } public ArrayList<GreasedRegion> surfaceSeriesToLimit() { ArrayList<GreasedRegion> regions = retractSeriesToLimit(); if(regions.isEmpty()) return regions; regions.add(0, regions.get(0).copy().xor(this)); for (int i = 1; i < regions.size() - 1; i++) { regions.get(i).xor(regions.get(i+1)); } return regions; } public GreasedRegion expand8way() { if(width < 2 || ySections <= 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, 0, next, 0, width * ySections); for (int a = 0; a < ySections; a++) { next[a] |= (data[a] << 1) | (data[a] >>> 1) | data[a+ySections] | (data[a+ySections] << 1) | (data[a+ySections] >>> 1); next[(width-1)*ySections+a] |= (data[(width-1)*ySections+a] << 1) | (data[(width-1)*ySections+a] >>> 1) | data[(width-2) *ySections+a] | (data[(width-2)*ySections+a] << 1) | (data[(width-2)*ySections+a] >>> 1); for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] |= (data[i] << 1) | (data[i] >>> 1) | data[i - ySections] | (data[i - ySections] << 1) | (data[i - ySections] >>> 1) | data[i + ySections] | (data[i + ySections] << 1) | (data[i + ySections] >>> 1); } if(a > 0) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= ((data[i - 1] & 0x8000000000000000L) >>> 63) | ((data[i - ySections - 1] & 0x8000000000000000L) >>> 63) | ((data[i + ySections - 1] & 0x8000000000000000L) >>> 63); } } if(a < ySections - 1) { for (int i = ySections+a; i < (width-1) * ySections; i+= ySections) { next[i] |= ((data[i + 1] & 1L) << 63) | ((data[i - ySections + 1] & 1L) << 63) | ((data[i + ySections+ 1] & 1L) << 63); } } } if(ySections > 0 && yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } @Override public GreasedRegion expand8way(int amount) { for (int i = 0; i < amount; i++) { expand8way(); } return this; } public GreasedRegion[] expandSeries8way(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); for (int i = 0; i < amount; i++) { regions[i] = new GreasedRegion(temp.expand8way()); } return regions; } public ArrayList<GreasedRegion> expandSeriesToLimit8way() { ArrayList<GreasedRegion> regions = new ArrayList<>(); GreasedRegion temp = new GreasedRegion(this); while (temp.size() != temp.expand8way().size()) { regions.add(new GreasedRegion(temp)); } return regions; } public GreasedRegion fringe8way() { GreasedRegion cpy = new GreasedRegion(this); expand8way(); return andNot(cpy); } public GreasedRegion fringe8way(int amount) { GreasedRegion cpy = new GreasedRegion(this); expand8way(amount); return andNot(cpy); } public GreasedRegion[] fringeSeries8way(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); regions[0] = new GreasedRegion(temp); for (int i = 1; i < amount; i++) { regions[i] = new GreasedRegion(temp.expand8way()); } for (int i = 0; i < amount - 1; i++) { regions[i].xor(regions[i + 1]); } regions[amount - 1].fringe8way(); return regions; } public ArrayList<GreasedRegion> fringeSeriesToLimit8way() { ArrayList<GreasedRegion> regions = expandSeriesToLimit8way(); for (int i = regions.size() - 1; i > 0; i--) { regions.get(i).xor(regions.get(i-1)); } regions.get(0).xor(this); return regions; } public GreasedRegion retract8way() { if(width <= 2 || ySections <= 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, ySections, next, ySections, (width - 2) * ySections); for (int a = 0; a < ySections; a++) { if(a > 0 && a < ySections - 1) { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= ((data[i] << 1) | ((data[i - 1] & 0x8000000000000000L) >>> 63)) & ((data[i] >>> 1) | ((data[i + 1] & 1L) << 63)) & data[i - ySections] & data[i + ySections] & ((data[i - ySections] << 1) | ((data[i - 1 - ySections] & 0x8000000000000000L) >>> 63)) & ((data[i + ySections] << 1) | ((data[i - 1 + ySections] & 0x8000000000000000L) >>> 63)) & ((data[i - ySections] >>> 1) | ((data[i + 1 - ySections] & 1L) << 63)) & ((data[i + ySections] >>> 1) | ((data[i + 1 + ySections] & 1L) << 63)); } } else if(a > 0) { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= ((data[i] << 1) | ((data[i - 1] & 0x8000000000000000L) >>> 63)) & (data[i] >>> 1) & data[i - ySections] & data[i + ySections] & ((data[i - ySections] << 1) | ((data[i - 1 - ySections] & 0x8000000000000000L) >>> 63)) & ((data[i + ySections] << 1) | ((data[i - 1 + ySections] & 0x8000000000000000L) >>> 63)) & (data[i - ySections] >>> 1) & (data[i + ySections] >>> 1); } } else if(a < ySections - 1) { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= (data[i] << 1) & ((data[i] >>> 1) | ((data[i + 1] & 1L) << 63)) & data[i - ySections] & data[i + ySections] & (data[i - ySections] << 1) & (data[i + ySections] << 1) & ((data[i - ySections] >>> 1) | ((data[i + 1 - ySections] & 1L) << 63)) & ((data[i + ySections] >>> 1) | ((data[i + 1 + ySections] & 1L) << 63)); } } else // only the case when ySections == 1 { for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= (data[i] << 1) & (data[i] >>> 1) & data[i - ySections] & data[i + ySections] & (data[i - ySections] << 1) & (data[i + ySections] << 1) & (data[i - ySections] >>> 1) & (data[i + ySections] >>> 1); } } } if(yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } public GreasedRegion retract8way(int amount) { for (int i = 0; i < amount; i++) { retract8way(); } return this; } public GreasedRegion[] retractSeries8way(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); for (int i = 0; i < amount; i++) { regions[i] = new GreasedRegion(temp.retract8way()); } return regions; } public ArrayList<GreasedRegion> retractSeriesToLimit8way() { ArrayList<GreasedRegion> regions = new ArrayList<>(); GreasedRegion temp = new GreasedRegion(this); while (!temp.retract8way().isEmpty()) { regions.add(new GreasedRegion(temp)); } return regions; } public GreasedRegion surface8way() { GreasedRegion cpy = new GreasedRegion(this).retract8way(); return xor(cpy); } public GreasedRegion surface8way(int amount) { GreasedRegion cpy = new GreasedRegion(this).retract8way(amount); return xor(cpy); } public GreasedRegion[] surfaceSeries8way(int amount) { if(amount <= 0) return new GreasedRegion[0]; GreasedRegion[] regions = new GreasedRegion[amount]; GreasedRegion temp = new GreasedRegion(this); regions[0] = new GreasedRegion(temp); for (int i = 1; i < amount; i++) { regions[i] = new GreasedRegion(temp.retract8way()); } for (int i = 0; i < amount - 1; i++) { regions[i].xor(regions[i + 1]); } regions[amount - 1].surface8way(); return regions; } public ArrayList<GreasedRegion> surfaceSeriesToLimit8way() { ArrayList<GreasedRegion> regions = retractSeriesToLimit8way(); if(regions.isEmpty()) return regions; regions.add(0, regions.get(0).copy().xor(this)); for (int i = 1; i < regions.size() - 1; i++) { regions.get(i).xor(regions.get(i+1)); } return regions; } public GreasedRegion flood(GreasedRegion bounds) { if(width < 2 || ySections <= 0 || bounds == null || bounds.width < 2 || bounds.ySections <= 0) return this; final long[] next = new long[width * ySections]; for (int a = 0; a < ySections && a < bounds.ySections; a++) { next[a] |= (data[a] |(data[a] << 1) | (data[a] >>> 1) | data[a+ySections]) & bounds.data[a]; next[(width-1)*ySections+a] |= (data[(width-1)*ySections+a] | (data[(width-1)*ySections+a] << 1) | (data[(width-1)*ySections+a] >>> 1) | data[(width-2) *ySections+a]) & bounds.data[(width-1)*bounds.ySections+a]; for (int i = ySections+a, j = bounds.ySections+a; i < (width - 1) * ySections && j < (bounds.width - 1) * bounds.ySections; i+= ySections, j+= bounds.ySections) { next[i] |= (data[i] | (data[i] << 1) | (data[i] >>> 1) | data[i - ySections] | data[i + ySections]) & bounds.data[j]; } if(a > 0) { for (int i = ySections+a, j = bounds.ySections+a; i < (width-1) * ySections && j < (bounds.width-1) * bounds.ySections; i+= ySections, j += bounds.ySections) { next[i] |= (data[i] | ((data[i - 1] & 0x8000000000000000L) >>> 63)) & bounds.data[j]; } } if(a < ySections - 1 && a < bounds.ySections - 1) { for (int i = ySections+a, j = bounds.ySections+a; i < (width-1) * ySections && j < (bounds.width-1) * bounds.ySections; i+= ySections, j += bounds.ySections) { next[i] |= (data[i] | ((data[i + 1] & 1L) << 63)) & bounds.data[j]; } } } if(yEndMask != -1 && bounds.yEndMask != -1) { if(ySections == bounds.ySections) { long mask = ((yEndMask >>> 1) <= (bounds.yEndMask >>> 1)) ? yEndMask : bounds.yEndMask; for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= mask; } } else if(ySections < bounds.ySections) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } else { for (int a = bounds.ySections - 1; a < next.length; a += ySections) { next[a] &= bounds.yEndMask; } } } data = next; return this; } public GreasedRegion flood(GreasedRegion bounds, int amount) { int ct = size(), ct2; for (int i = 0; i < amount; i++) { flood(bounds); if(ct == (ct2 = size())) break; else ct = ct2; } return this; } public GreasedRegion[] floodSeries(GreasedRegion bounds, int amount) { if(amount <= 0) return new GreasedRegion[0]; int ct = size(), ct2; GreasedRegion[] regions = new GreasedRegion[amount]; boolean done = false; GreasedRegion temp = new GreasedRegion(this); for (int i = 0; i < amount; i++) { if(done) { regions[i] = new GreasedRegion(temp); } else { regions[i] = new GreasedRegion(temp.flood(bounds)); if (ct == (ct2 = temp.size())) done = true; else ct = ct2; } } return regions; } public ArrayList<GreasedRegion> floodSeriesToLimit(GreasedRegion bounds) { int ct = size(), ct2; ArrayList<GreasedRegion> regions = new ArrayList<>(); GreasedRegion temp = new GreasedRegion(this); while (true) { temp.flood(bounds); if (ct == (ct2 = temp.size())) return regions; else { ct = ct2; regions.add(new GreasedRegion(temp)); } } } public GreasedRegion flood8way(GreasedRegion bounds) { if(width < 2 || ySections <= 0 || bounds == null || bounds.width < 2 || bounds.ySections <= 0) return this; final long[] next = new long[width * ySections]; for (int a = 0; a < ySections && a < bounds.ySections; a++) { next[a] |= (data[a] | (data[a] << 1) | (data[a] >>> 1) | data[a+ySections] | (data[a+ySections] << 1) | (data[a+ySections] >>> 1)) & bounds.data[a]; next[(width-1)*ySections+a] |= (data[(width-1)*ySections+a] | (data[(width-1)*ySections+a] << 1) | (data[(width-1)*ySections+a] >>> 1) | data[(width-2) *ySections+a] | (data[(width-2)*ySections+a] << 1) | (data[(width-2)*ySections+a] >>> 1)) & bounds.data[(width-1)*bounds.ySections+a]; for (int i = ySections+a, j = bounds.ySections+a; i < (width - 1) * ySections && j < (bounds.width - 1) * bounds.ySections; i+= ySections, j+= bounds.ySections) { next[i] |= (data[i] | (data[i] << 1) | (data[i] >>> 1) | data[i - ySections] | (data[i - ySections] << 1) | (data[i - ySections] >>> 1) | data[i + ySections] | (data[i + ySections] << 1) | (data[i + ySections] >>> 1)) & bounds.data[j]; } if(a > 0) { for (int i = ySections+a, j = bounds.ySections+a; i < (width-1) * ySections && j < (bounds.width-1) * bounds.ySections; i+= ySections, j += bounds.ySections) { next[i] |= (data[i] | ((data[i - 1] & 0x8000000000000000L) >>> 63) | ((data[i - ySections - 1] & 0x8000000000000000L) >>> 63) | ((data[i + ySections - 1] & 0x8000000000000000L) >>> 63)) & bounds.data[j]; } } if(a < ySections - 1 && a < bounds.ySections - 1) { for (int i = ySections+a, j = bounds.ySections+a; i < (width-1) * ySections && j < (bounds.width-1) * bounds.ySections; i+= ySections, j += bounds.ySections) { next[i] |= (data[i] | ((data[i + 1] & 1L) << 63) | ((data[i - ySections + 1] & 1L) << 63) | ((data[i + ySections+ 1] & 1L) << 63)) & bounds.data[j]; } } } if(yEndMask != -1 && bounds.yEndMask != -1) { if(ySections == bounds.ySections) { long mask = ((yEndMask >>> 1) <= (bounds.yEndMask >>> 1)) ? yEndMask : bounds.yEndMask; for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= mask; } } else if(ySections < bounds.ySections) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } else { for (int a = bounds.ySections - 1; a < next.length; a += ySections) { next[a] &= bounds.yEndMask; } } } data = next; return this; } public GreasedRegion flood8way(GreasedRegion bounds, int amount) { int ct = size(), ct2; for (int i = 0; i < amount; i++) { flood8way(bounds); if(ct == (ct2 = size())) break; else ct = ct2; } return this; } public GreasedRegion[] floodSeries8way(GreasedRegion bounds, int amount) { if(amount <= 0) return new GreasedRegion[0]; int ct = size(), ct2; GreasedRegion[] regions = new GreasedRegion[amount]; boolean done = false; GreasedRegion temp = new GreasedRegion(this); for (int i = 0; i < amount; i++) { if(done) { regions[i] = new GreasedRegion(temp); } else { regions[i] = new GreasedRegion(temp.flood8way(bounds)); if (ct == (ct2 = temp.size())) done = true; else ct = ct2; } } return regions; } public ArrayList<GreasedRegion> floodSeriesToLimit8way(GreasedRegion bounds) { int ct = size(), ct2; ArrayList<GreasedRegion> regions = new ArrayList<>(); GreasedRegion temp = new GreasedRegion(this); while (true) { temp.flood8way(bounds); if (ct == (ct2 = temp.size())) return regions; else { ct = ct2; regions.add(new GreasedRegion(temp)); } } } public GreasedRegion spill(GreasedRegion bounds, int volume, RNG rng) { if(width < 2 || ySections <= 0 || bounds == null || bounds.width < 2 || bounds.ySections <= 0) return this; int current = size(); if(current >= volume) return this; GreasedRegion t = new GreasedRegion(this); Coord c = Coord.get(-1, -1); for (int i = current; i < volume; i++) { insert(t.remake(this).fringe().and(bounds).singleRandom(rng)); } return this; } public GreasedRegion removeCorners() { if(width <= 2 || ySections <= 0) return this; final long[] next = new long[width * ySections]; System.arraycopy(data, 0, next, 0, width * ySections); for (int a = 0; a < ySections; a++) { if(a > 0 && a < ySections - 1) { next[a] &= (((data[a] << 1) | ((data[a - 1] & 0x8000000000000000L) >>> 63)) & ((data[a] >>> 1) | ((data[a + 1] & 1L) << 63))); next[(width - 1) * ySections + a] &= (((data[(width - 1) * ySections + a] << 1) | ((data[(width - 1) * ySections + a - 1] & 0x8000000000000000L) >>> 63)) & ((data[(width - 1) * ySections + a] >>> 1) | ((data[(width - 1) * ySections + a + 1] & 1L) << 63))); for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= (((data[i] << 1) | ((data[i - 1] & 0x8000000000000000L) >>> 63)) & ((data[i] >>> 1) | ((data[i + 1] & 1L) << 63))) | (data[i - ySections] & data[i + ySections]); } } else if(a > 0) { next[a] &= (((data[a] << 1) | ((data[a - 1] & 0x8000000000000000L) >>> 63)) & (data[a] >>> 1)); next[(width - 1) * ySections + a] &= (((data[(width - 1) * ySections + a] << 1) | ((data[(width - 1) * ySections + a - 1] & 0x8000000000000000L) >>> 63)) & (data[(width - 1) * ySections + a] >>> 1)); for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= (((data[i] << 1) | ((data[i - 1] & 0x8000000000000000L) >>> 63)) & (data[i] >>> 1)) | (data[i - ySections] & data[i + ySections]); } } else if(a < ySections - 1) { next[a] &= ((data[a] << 1) & ((data[a] >>> 1) | ((data[a + 1] & 1L) << 63))); next[(width - 1) * ySections + a] &= ((data[(width - 1) * ySections + a] << 1) & ((data[(width - 1) * ySections + a] >>> 1) | ((data[(width - 1) * ySections + a + 1] & 1L) << 63))); for (int i = ySections+a; i < (width - 1) * ySections; i+= ySections) { next[i] &= ((data[i] << 1) & ((data[i] >>> 1) | ((data[i + 1] & 1L) << 63))) | (data[i - ySections] & data[i + ySections]); } } else // only the case when ySections == 1 { next[0] &= (data[0] << 1) & (data[0] >>> 1); next[width-1] &= (data[width-1] << 1) & (data[width-1] >>> 1); for (int i = 1+a; i < (width - 1); i++) { next[i] &= ((data[i] << 1) & (data[i] >>> 1)) | (data[i - ySections] & data[i + ySections]); } } } if(yEndMask != -1) { for (int a = ySections - 1; a < next.length; a += ySections) { next[a] &= yEndMask; } } data = next; return this; } /** * If this GreasedRegion stores 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 GreasedRegion, with one GreasedRegion per isolated area. Not to be confused with * {@link #split8way()}, which considers diagonally-adjacent cells as part of one region, while this method requires * cells to be orthogonally adjacent. * <br> * 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 using: * <br> * {@code GreasedRegion floors = new GreasedRegion(map, '.'), * rooms = floors.copy().retract8way().flood(floors, 2), * corridors = floors.copy().andNot(rooms), * doors = rooms.copy().and(corridors.copy().fringe());} * <br> * You can then get all rooms as separate regions with {@code List<GreasedRegion> 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 GreasedRegion * @return an ArrayList containing each unconnected area from packed as a GreasedRegion element */ public ArrayList<GreasedRegion> split() { ArrayList<GreasedRegion> scattered = new ArrayList<>(32); Coord fst = first(); GreasedRegion remaining = new GreasedRegion(this); while (fst.x >= 0) { GreasedRegion filled = new GreasedRegion(fst, width, height).flood(remaining, width * height); scattered.add(filled); remaining.andNot(filled); fst = remaining.first(); } return scattered; } /** * If this GreasedRegion stores multiple unconnected "on" areas, this finds each isolated area (areas that * are only adjacent diagonally are considered <b>one area</b> with this) and returns it as an element in an * ArrayList of GreasedRegion, with one GreasedRegion per isolated area. This should not be confused with * {@link #split()}, which is almost identical except that split() considers only orthogonal connections, while this * method considers both orthogonal and diagonal connections between cells as joining an area. * <br> * 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 using: * <br> * {@code GreasedRegion floors = new GreasedRegion(map, '.'), * rooms = floors.copy().retract8way().flood(floors, 2), * corridors = floors.copy().andNot(rooms), * doors = rooms.copy().and(corridors.copy().fringe());} * <br> * You can then get all rooms as separate regions with {@code List<GreasedRegion> 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 GreasedRegion * @return an ArrayList containing each unconnected area from packed as a GreasedRegion element */ public ArrayList<GreasedRegion> split8way() { ArrayList<GreasedRegion> scattered = new ArrayList<>(32); Coord fst = first(); GreasedRegion remaining = new GreasedRegion(this); while (fst.x >= 0) { GreasedRegion filled = new GreasedRegion(fst, width, height).flood8way(remaining, width * height); scattered.add(filled); remaining.andNot(filled); fst = remaining.first(); } return scattered; } public GreasedRegion removeIsolated() { Coord fst = first(); GreasedRegion remaining = new GreasedRegion(this), filled = new GreasedRegion(this); while (fst.x >= 0) { filled.empty().insert(fst).flood(remaining, 8); if(filled.size() <= 4) andNot(filled); remaining.andNot(filled); fst = remaining.first(); } return this; } public boolean intersects(GreasedRegion other) { for (int x = 0; x < width && x < other.width; x++) { for (int y = 0; y < ySections && y < other.ySections; y++) { if((data[x * ySections + y] & other.data[x * ySections + y]) != 0) return true; } } return false; } public static OrderedSet<GreasedRegion> whichContain(int x, int y, GreasedRegion ... packed) { OrderedSet<GreasedRegion> found = new OrderedSet<>(packed.length); GreasedRegion tmp; for (int i = 0; i < packed.length; i++) { if((tmp = packed[i]) != null && tmp.contains(x, y)) found.add(tmp); } return found; } public static OrderedSet<GreasedRegion> whichContain(int x, int y, Collection<GreasedRegion> packed) { OrderedSet<GreasedRegion> found = new OrderedSet<>(packed.size()); for (GreasedRegion tmp : packed) { if(tmp != null && tmp.contains(x, y)) found.add(tmp); } return found; } public int size() { int c = 0; for (int i = 0; i < width * ySections; i++) { c += Long.bitCount(data[i]); } return c; } public Coord fit(double xFraction, double yFraction) { int tmp, xTotal = 0, yTotal = 0, xTarget, yTarget, bestX = -1; long t; int[] xCounts = new int[width]; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { t = data[x * ySections + s]; if (t != 0) { tmp = Long.bitCount(t); xCounts[x] += tmp; xTotal += tmp; } } } xTarget = (int)(xTotal * xFraction); for (int x = 0; x < width; x++) { if((xTarget -= xCounts[x]) < 0) { bestX = x; yTotal = xCounts[x]; break; } } if(bestX < 0) { return Coord.get(-1, -1); } yTarget = (int)(yTotal * yFraction); for (int s = 0, y = 0; s < ySections; s++) { t = data[bestX * ySections + s]; for (long cy = 1; cy != 0 && y < height; y++, cy <<= 1) { if((t & cy) != 0 && --yTarget < 0) { return Coord.get(bestX, y); } } } return Coord.get(-1, -1); } public int[][] fit(int[][] basis, int defaultValue) { int[][] next = ArrayTools.fill(defaultValue, width, height); if(basis == null || basis.length <= 0 || basis[0] == null || basis[0].length <= 0) return next; int tmp, xTotal = 0, yTotal = 0, xTarget, yTarget, bestX = -1, oX = basis.length, oY = basis[0].length, ao; long t; int[] xCounts = new int[width]; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { t = data[x * ySections + s]; if (t != 0) { tmp = Long.bitCount(t); xCounts[x] += tmp; xTotal += tmp; } } } if(xTotal <= 0) return next; for (int aX = 0; aX < oX; aX++) { CELL_WISE: for (int aY = 0; aY < oY; aY++) { if((ao = basis[aX][aY]) == defaultValue) continue; xTarget = xTotal * aX / oX; for (int x = 0; x < width; x++) { if((xTarget -= xCounts[x]) < 0) { bestX = x; yTotal = xCounts[x]; yTarget = yTotal * aY / oY; for (int s = 0, y = 0; s < ySections; s++) { t = data[bestX * ySections + s]; for (long cy = 1; cy != 0 && y < height; y++, cy <<= 1) { if((t & cy) != 0 && --yTarget < 0) { next[bestX][y] = ao; continue CELL_WISE; } } } continue CELL_WISE; } } } } return next; } /* public int[][] edgeFit(int[][] basis, int defaultValue) { int[][] next = GwtCompatibility.fill(defaultValue, width, height); if(basis == null || basis.length <= 0 || basis[0] == null || basis[0].length <= 0) return next; return next; } */ public Coord[] separatedPortion(double fraction) { if(fraction < 0) return new Coord[0]; if(fraction > 1) fraction = 1; int ct, tmp, xTotal = 0, yTotal = 0, xTarget, yTarget, bestX = -1; long t; int[] xCounts = new int[width]; for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { t = data[x * ySections + s]; if (t != 0) { tmp = Long.bitCount(t); xCounts[x] += tmp; xTotal += tmp; } } } Coord[] vl = new Coord[ct = (int)(fraction * xTotal)]; double[] vec = new double[2]; sobol.skipTo(1337); EACH_SOBOL: for (int i = 0; i < ct; i++) { sobol.fillVector(vec); xTarget = (int) (xTotal * vec[0]); for (int x = 0; x < width; x++) { if ((xTarget -= xCounts[x]) < 0) { bestX = x; yTotal = xCounts[x]; break; } } yTarget = (int) (yTotal * vec[1]); for (int s = 0, y = 0; s < ySections; s++) { t = data[bestX * ySections + s]; for (long cy = 1; cy != 0 && y < height; y++, cy <<= 1) { if ((t & cy) != 0 && --yTarget < 0) { vl[i] = Coord.get(bestX, y); continue EACH_SOBOL; } } } } return vl; } public Coord[] randomSeparated(double fraction, RNG rng) { return randomSeparated(fraction, rng, -1); } public Coord[] randomSeparated(double fraction, RNG rng, int limit) { if(fraction < 0) return new Coord[0]; if(fraction > 1) fraction = 1; int ct, tmp, xTotal = 0, yTotal = 0, xTarget, yTarget, bestX = -1; long t; int[] xCounts = new int[width]; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { t = data[x * ySections + s]; if (t != 0) { tmp = Long.bitCount(t); xCounts[x] += tmp; xTotal += tmp; } } } ct = (int)(fraction * xTotal); if(limit >= 0 && limit < ct) ct = limit; Coord[] vl = new Coord[ct]; double[] vec = new double[2]; sobol.skipTo(rng.between(1000, 65000)); EACH_SOBOL: for (int i = 0; i < ct; i++) { sobol.fillVector(vec); xTarget = (int) (xTotal * vec[0]); for (int x = 0; x < width; x++) { if ((xTarget -= xCounts[x]) < 0) { bestX = x; yTotal = xCounts[x]; break; } } yTarget = (int) (yTotal * vec[1]); for (int s = 0, y = 0; s < ySections; s++) { t = data[bestX * ySections + s]; for (long cy = 1; cy != 0 && y < height; y++, cy <<= 1) { if ((t & cy) != 0 && --yTarget < 0) { vl[i] = Coord.get(bestX, y); continue EACH_SOBOL; } } } } return vl; } /** * Gets a Coord array from the "on" contents of this GreasedRegion, using a quasi-random scattering of chosen cells * with a count that matches the given {@code fraction} of the total amount of "on" cells in this. This is quasi- * random instead of pseudo-random because it is somewhat less likely to produce nearby cells in the result. If you * request too many cells (too high of a value for fraction), it will start to overlap, however. * Does not restrict the size of the returned array other than only using up to {@code fraction * size()} cells. * @param fraction the fraction of "on" cells to randomly select, between 0.0 and 1.0 * @return a freshly-allocated Coord array containing the quasi-random cells */ public Coord[] quasiRandomSeparated(double fraction) { return quasiRandomSeparated(fraction, -1); } /** * Gets a Coord array from the "on" contents of this GreasedRegion, using a quasi-random scattering of chosen cells * with a count that matches the given {@code fraction} of the total amount of "on" cells in this. This is quasi- * random instead of pseudo-random because it is somewhat less likely to produce nearby cells in the result. If you * request too many cells (too high of a value for fraction), it will start to overlap, however. * Restricts the total size of the returned array to a maximum of {@code limit} (minimum is 0 if no cells are "on"). * If limit is negative, this will not restrict the size. * @param fraction the fraction of "on" cells to randomly select, between 0.0 and 1.0 * @param limit the maximum size of the array to return * @return a freshly-allocated Coord array containing the quasi-random cells */ public Coord[] quasiRandomSeparated(double fraction, int limit) { if(fraction < 0) return new Coord[0]; if(fraction > 1) fraction = 1; int ct = 0, tmp, total, ic; long t, w; int[] counts = new int[width * ySections]; for (int i = 0; i < width * ySections; i++) { tmp = Long.bitCount(data[i]); counts[i] = tmp == 0 ? -1 : (ct += tmp); } total = ct; ct *= fraction;// (int)(fraction * ct); if(limit >= 0 && limit < ct) ct = limit; Coord[] vl = new Coord[ct]; EACH_QUASI: for (int i = 0; i < ct; i++) { tmp = (int)(VanDerCorputQRNG.weakDetermine(i) * total); for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if ((ic = counts[x * ySections + s]) > tmp) { t = data[x * ySections + s]; w = Long.lowestOneBit(t); for (--ic; w != 0; ic--) { if (ic == tmp) { vl[i] = Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); continue EACH_QUASI; } t ^= w; w = Long.lowestOneBit(t); } } } } } return vl; } /** * Modifies this GreasedRegion so it contains a quasi-random subset of its previous contents, choosing cells so that * the {@link #size()} matches the given {@code fraction} of the total amount of "on" cells in this. This is quasi- * random instead of pseudo-random because it is somewhat less likely to produce nearby cells in the result. If you * request too many cells (too high of a value for fraction), it will start to overlap, however. * Does not restrict the count of "on" cells after this returns other than by only using up to * {@code fraction * size()} cells. * <br> * Uses a different algorithm than {@link #quasiRandomSeparated(double)}, hoping that it will be faster. * @param fraction the fraction of "on" cells to randomly select, between 0.0 and 1.0 * @return this for chaining */ public GreasedRegion quasiRandomRegion(double fraction) { return quasiRandomRegion(fraction, -1); } /** * Modifies this GreasedRegion so it contains a quasi-random subset of its previous contents, choosing cells so that * the {@link #size()} matches the given {@code fraction} of the total amount of "on" cells in this. This is quasi- * random instead of pseudo-random because it is somewhat less likely to produce nearby cells in the result. If you * request too many cells (too high of a value for fraction), it will start to overlap, however. * Restricts the total count of "on" cells after this returns to a maximum of {@code limit} (minimum is 0 if no * cells are "on"). If limit is negative, this will not restrict the count. * <br> * Used a different algorithm than {@link #quasiRandomSeparated(double)}, but now that method changed to match the * algorithm this uses ({@link VanDerCorputQRNG#weakDetermine(int)}). * @param fraction the fraction of "on" cells to randomly select, between 0.0 and 1.0 * @param limit the maximum count of "on" cells to keep * @return this for chaining */ public GreasedRegion quasiRandomRegion(double fraction, int limit) { int ct = 0, idx, run = 0; for (int i = 0; i < width * ySections; i++) { ct += Long.bitCount(data[i]); } if (ct <= limit) return this; if (ct <= 0) return empty(); if (limit < 0) limit = (int) (fraction * ct); if(limit <= 0) return empty(); int[] order = new int[limit]; for (int i = 0, m = 0; i < limit; i++, m++) { idx = (int) (VanDerCorputQRNG.weakDetermine(m) * ct); BIG: while (true) { for (int j = 0; j < i; j++) { if (order[j] == idx) { idx = (int) (VanDerCorputQRNG.weakDetermine(++m) * ct); continue BIG; } } break; } order[i] = idx; } idx = 0; Arrays.sort(order); long t, w; ALL: for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if ((t = data[x * ySections + s]) != 0) { w = Long.lowestOneBit(t); while (w != 0) { if (run++ == order[idx]) { if (++idx >= limit) { data[x * ySections + s] &= (w<<1)-1; for (int rx = x+1; rx < width; rx++) { data[rx * ySections + s] = 0; } for (int rs = s+1; rs < ySections; rs++) { for (int rx = 0; rx < width; rx++) { data[rx * ySections + rs] = 0; } } break ALL; } } else { data[x * ySections + s] ^= w; } t ^= w; w = Long.lowestOneBit(t); } } } } return this; } public double rateDensity() { double sz = height * width; if(sz == 0) return 0; double onAmount = sz - size(), retractedOn = sz - copy().retract().size(); return (onAmount + retractedOn) / (sz * 2.0); } public double rateRegularity() { GreasedRegion me2 = copy().surface8way(); double irregularCount = me2.size(); if(irregularCount == 0) return 0; return me2.remake(this).surface().size() / irregularCount; } /* // This showed a strong x-y correlation because it didn't have a way to use a non-base-2 van der Corput sequence. // It also produced very close-together points, unfortunately. public static double quasiRandomX(int idx) { return atVDCSequence(26 + idx * 5); } public static double quasiRandomY(int idx) { return atVDCSequence(19 + idx * 3); } private static double atVDCSequence(int idx) { int leading = Integer.numberOfLeadingZeros(idx); return (Integer.reverse(idx) >>> leading) / (1.0 * (1 << (32 - leading))); } */ public Coord[] asCoords() { int ct = 0, idx = 0; for (int i = 0; i < width * ySections; i++) { ct += Long.bitCount(data[i]); } Coord[] points = new Coord[ct]; long t, w; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { if((t = data[x * ySections + s]) != 0) { w = Long.lowestOneBit(t); while (w != 0) { points[idx++] = Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); t ^= w; w = Long.lowestOneBit(t); } } } } return points; } public int[] asEncoded() { int ct = 0, idx = 0; for (int i = 0; i < width * ySections; i++) { ct += Long.bitCount(data[i]); } int[] points = new int[ct]; long t, w; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { if((t = data[x * ySections + s]) != 0) { w = Long.lowestOneBit(t); while (w != 0) { points[idx++] = Coord.pureEncode(x, (s << 6) | Long.numberOfTrailingZeros(w)); t ^= w; w = Long.lowestOneBit(t); } } } } return points; } public int[] asTightEncoded() { int ct = 0, idx = 0; for (int i = 0; i < width * ySections; i++) { ct += Long.bitCount(data[i]); } int[] points = new int[ct]; long t, w; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { if((t = data[x * ySections + s]) != 0) { w = Long.lowestOneBit(t); while (w != 0) { points[idx++] = ((s << 6) | Long.numberOfTrailingZeros(w)) * width + x; t ^= w; w = Long.lowestOneBit(t); } } } } return points; } /** * @return All cells in this zone. */ @Override public List<Coord> getAll() { ArrayList<Coord> points = new ArrayList<>(); long t, w; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { if((t = data[x * ySections + s]) != 0) { w = Long.lowestOneBit(t); while (w != 0) { points.add(Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w))); t ^= w; w = Long.lowestOneBit(t); } } } } return points; } public Coord first() { long w; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { if ((w = Long.lowestOneBit(data[x * ySections + s])) != 0) { return Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); } } } return Coord.get(-1, -1); } public Coord nth(final int index) { int ct = 0, tmp; int[] counts = new int[width * ySections]; for (int i = 0; i < width * ySections; i++) { tmp = Long.bitCount(data[i]); counts[i] = tmp == 0 ? -1 : (ct += tmp); } if(index >= ct) return Coord.get(-1, -1); long t, w; for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if ((ct = counts[x * ySections + s]) > index) { t = data[x * ySections + s]; w = Long.lowestOneBit(t); for (--ct; w != 0; ct--) { if (ct == index) return Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); t ^= w; w = Long.lowestOneBit(t); } } } } return Coord.get(-1, -1); } public Coord atFraction(final double fraction) { int ct = 0, tmp; int[] counts = new int[width * ySections]; for (int i = 0; i < width * ySections; i++) { tmp = Long.bitCount(data[i]); counts[i] = tmp == 0 ? -1 : (ct += tmp); } tmp = Math.abs((int)(fraction * ct) % ct); long t, w; for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if ((ct = counts[x * ySections + s]) > tmp) { t = data[x * ySections + s]; w = Long.lowestOneBit(t); for (--ct; w != 0; ct--) { if (ct == tmp) return Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); t ^= w; w = Long.lowestOneBit(t); } } } } return Coord.get(-1, -1); } public int atFractionTight(final double fraction) { int ct = 0, tmp; int[] counts = new int[width * ySections]; for (int i = 0; i < width * ySections; i++) { tmp = Long.bitCount(data[i]); counts[i] = tmp == 0 ? -1 : (ct += tmp); } if(ct <= 0) return -1; tmp = Math.abs((int)(fraction * ct) % ct); long t, w; for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { if ((ct = counts[x * ySections + s]) > tmp) { t = data[x * ySections + s]; w = Long.lowestOneBit(t); for (--ct; w != 0; ct--) { if (ct == tmp) return ((s << 6) | Long.numberOfTrailingZeros(w)) * width + x; t ^= w; w = Long.lowestOneBit(t); } } } } return -1; } public Coord singleRandom(RNG rng) { int ct = 0, tmp; int[] counts = new int[width * ySections]; for (int i = 0; i < width * ySections; i++) { tmp = Long.bitCount(data[i]); counts[i] = tmp == 0 ? -1 : (ct += tmp); } tmp = rng.nextInt(ct); long t, w; for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if ((ct = counts[x * ySections + s]) > tmp) { t = data[x * ySections + s]; w = Long.lowestOneBit(t); for (--ct; w != 0; ct--) { if (ct == tmp) return Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); t ^= w; w = Long.lowestOneBit(t); } } } } return Coord.get(-1, -1); } public int singleRandomTight(RNG rng) { int ct = 0, tmp; int[] counts = new int[width * ySections]; for (int i = 0; i < width * ySections; i++) { tmp = Long.bitCount(data[i]); counts[i] = tmp == 0 ? -1 : (ct += tmp); } tmp = rng.nextInt(ct); long t, w; for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if ((ct = counts[x * ySections + s]) > tmp) { t = data[x * ySections + s]; w = Long.lowestOneBit(t); for (--ct; w != 0; ct--) { if (ct == tmp) return ((s << 6) | Long.numberOfTrailingZeros(w)) * width + x; t ^= w; w = Long.lowestOneBit(t); } } } } return -1; } public Coord[] randomPortion(RNG rng, int size) { int ct = 0, idx = 0, run = 0; for (int i = 0; i < width * ySections; i++) { ct += Long.bitCount(data[i]); } if(ct <= 0 || size <= 0) return new Coord[0]; if(ct <= size) return asCoords(); Coord[] points = new Coord[size]; int[] order = rng.randomOrdering(ct); Arrays.sort(order, 0, size); long t, w; ALL: for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if((t = data[x * ySections + s]) != 0) { w = Long.lowestOneBit(t); while (w != 0) { if (run++ == order[idx]) { points[idx++] = Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); if (idx >= size) break ALL; } t ^= w; w = Long.lowestOneBit(t); } } } } return points; } public GreasedRegion randomRegion(RNG rng, int size) { int ct = 0, idx = 0, run = 0; for (int i = 0; i < width * ySections; i++) { ct += Long.bitCount(data[i]); } if(ct <= 0 || size <= 0) return empty(); if(ct <= size) return this; int[] order = rng.randomOrdering(ct); Arrays.sort(order, 0, size); long t, w; ALL: for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if((t = data[x * ySections + s]) != 0) { w = Long.lowestOneBit(t); while (w != 0) { if (run++ == order[idx]) { if(++idx >= size) break ALL; } else { data[x * ySections + s] &= ~(1L << Long.numberOfTrailingZeros(w)); } t ^= w; w = Long.lowestOneBit(t); } } } } return this; } @Override public boolean contains(int x, int y) { return x >= 0 && y >= 0 && x < width && y < height && ySections > 0 && ((data[x * ySections + (y >> 6)] & (1L << (y & 63))) != 0); } /** * @return Whether this zone is empty. */ @Override public boolean isEmpty() { for (int i = 0; i < data.length; i++) { if(data[i] != 0L) return false; } return true; } /** * Generates a 2D int array from an array or vararg of GreasedRegions, starting at all 0 and adding 1 to the int at * a position once for every GreasedRegion that has that cell as "on." This means if you give 8 GreasedRegions to * this method, it can produce any number between 0 and 8 in a cell; if you give 16 GreasedRegions, then it can * produce number between 0 and 16 in a cell. * @param regions an array or vararg of GreasedRegions; must all have the same width and height * @return a 2D int array with the same width and height as the regions, where an int cell equals the number of given GreasedRegions that had an "on" cell at that position */ public static int[][] sum(GreasedRegion... regions) { if(regions == null || regions.length <= 0) return new int[0][0]; int w = regions[0].width, h = regions[0].height, l = Math.min(32, regions.length), ys = regions[0].ySections; int[][] numbers = new int[w][h]; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { for (int i = 0; i < l; i++) { numbers[x][y] += (regions[i].data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 : 0; } } } return numbers; } /** * Generates a 2D int array from an array or vararg of GreasedRegions, starting at all 0 and adding 1 to the int at * a position once for every GreasedRegion that has that cell as "on." This means if you give 8 GreasedRegions to * this method, it can produce any number between 0 and 8 in a cell; if you give 16 GreasedRegions, then it can * produce number between 0 and 16 in a cell. * @param regions an array or vararg of GreasedRegions; must all have the same width and height * @return a 2D int array with the same width and height as the regions, where an int cell equals the number of given GreasedRegions that had an "on" cell at that position */ public static int[][] sum(List<GreasedRegion> regions) { if(regions == null || regions.isEmpty()) return new int[0][0]; GreasedRegion t = regions.get(0); int w = t.width, h = t.height, l = regions.size(), ys = t.ySections; int[][] numbers = new int[w][h]; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { for (int i = 0; i < l; i++) { numbers[x][y] += (regions.get(i).data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 : 0; } } } return numbers; } public static double[][] dijkstraScan(char[][] map, Coord... goals) { if(map == null || map.length <= 0 || map[0].length <= 0 || goals == null || goals.length <= 0) return new double[0][0]; int w = map.length, h = map[0].length, ys = (h + 63) >>> 6; double[][] numbers = new double[w][h]; GreasedRegion walls = new GreasedRegion(map, '#'), floors = new GreasedRegion(walls).not(), middle = new GreasedRegion(w, h, goals).and(floors); ArrayList<GreasedRegion> regions = middle.floodSeriesToLimit(floors); int l = regions.size(); for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { for (int i = 0; i < l; i++) { numbers[x][y] += (regions.get(i).data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 : 0; } } } return numbers; } public static double[][] dijkstraScan8way(char[][] map, Coord... goals) { if(map == null || map.length <= 0 || map[0].length <= 0 || goals == null || goals.length <= 0) return new double[0][0]; int w = map.length, h = map[0].length, ys = (h + 63) >>> 6; double[][] numbers = new double[w][h]; GreasedRegion walls = new GreasedRegion(map, '#'), floors = new GreasedRegion(walls).not(), middle = new GreasedRegion(w, h, goals).and(floors); ArrayList<GreasedRegion> regions = middle.floodSeriesToLimit8way(floors); int l = regions.size(); for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { for (int i = 0; i < l; i++) { numbers[x][y] += (regions.get(i).data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 : 0; } } } return numbers; } /** * Generates a 2D int array from an array or vararg of GreasedRegions, treating each cell in the nth region as the * nth bit of the int at the corresponding x,y cell in the int array. This means if you give 8 GreasedRegions to * this method, it can produce any 8-bit number in a cell (0-255); if you give 16 GreasedRegions, then it can * produce any 16-bit number (0-65535). * @param regions an array or vararg of GreasedRegions; must all have the same width and height * @return a 2D int array with the same width and height as the regions, with bits per int taken from the regions */ public static int[][] bitSum(GreasedRegion... regions) { if(regions == null || regions.length <= 0) return new int[0][0]; int w = regions[0].width, h = regions[0].height, l = Math.min(32, regions.length), ys = regions[0].ySections; int[][] numbers = new int[w][h]; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { for (int i = 0; i < l; i++) { numbers[x][y] |= (regions[i].data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 << i : 0; } } } return numbers; } /* public static int[][] selectiveNegate(int[][] numbers, GreasedRegion region, int mask) { if(region == null) return numbers; int w = region.width, h = region.height, ys = region.ySections; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { if((region.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0) numbers[x][y] = (~numbers[x][y] & mask); } } return numbers; } */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GreasedRegion that = (GreasedRegion) o; if (height != that.height) return false; if (width != that.width) return false; if (ySections != that.ySections) return false; if (yEndMask != that.yEndMask) return false; return Arrays.equals(data, that.data); } @Override public int hashCode() { /* int result = CrossHash.Lightning.hash(data); result = 31 * result + height; result = 31 * result + width; result = 31 * result + ySections; //not needed; purely dependent on height result = 31 * result + (int) (yEndMask ^ (yEndMask >>> 32)); //not needed; purely dependent on height return result; */ /* long z = 0x632BE59BD9B4E019L, result = 1L; for (int i = 0; i < data.length; i++) { result ^= (z += (data[i] + 0x9E3779B97F4A7C15L) * 0xD0E89D2D311E289FL) * 0xC6BC279692B5CC83L; } result ^= (z += (height + 0x9E3779B97F4A7C15L) * 0xD0E89D2D311E289FL) * 0xC6BC279692B5CC83L; result ^= (z += (width + 0x9E3779B97F4A7C15L) * 0xD0E89D2D311E289FL) * 0xC6BC279692B5CC83L; return (int) ((result ^= Long.rotateLeft((z * 0xC6BC279692B5CC83L ^ result * 0x9E3779B97F4A7C15L) + 0x632BE59BD9B4E019L, (int) (z >>> 58))) ^ (result >>> 32)); */ long result = 0x9E3779B97F4A7C94L, a = 0x632BE59BD9B4E019L; final int len = data.length; for (int i = 0; i < len; i++) { result += (a ^= 0x8329C6EB9E6AD3E3L * data[i]); } result += (a ^= 0x8329C6EB9E6AD3E3L * height); result += (a ^= 0x8329C6EB9E6AD3E3L * width); return (int)((result = (result * (a | 1L) ^ (result >>> 27 | result << 37))) ^ (result >>> 32)); } public String serializeToString() { return width + "," + height + "," + StringKit.join(",",data); } public static GreasedRegion deserializeFromString(String s) { if(s == null || s.isEmpty()) return null; int gap = s.indexOf(','), w = Integer.parseInt(s.substring(0, gap)), gap2 = s.indexOf(',', gap+1), h = Integer.parseInt(s.substring(gap+1, gap2)); String[] splits = StringKit.split(s.substring(gap2+1), ","); long[] data = new long[splits.length]; for (int i = 0; i < splits.length; i++) { data[i] = Long.parseLong(splits[i]); } return new GreasedRegion(data, w, h); } /** * Constructs a GreasedRegion using a vararg for data. Primarily meant for generated code, since * {@link #serializeToString()} produces a String that happens to be a valid parameter list for this method. * @param width width of the GreasedRegion to produce * @param height height of the GreasedRegion to produce * @param data array or vararg of long containing the exact data, probably from an existing GreasedRegion * @return a new GreasedRegion with the given width, height, and data */ public static GreasedRegion of(final int width, final int height, final long... data) { return new GreasedRegion(data, width, height); } @Override public boolean contains(Object o) { if(o instanceof Coord) return contains((Coord)o); return false; } @Override public Iterator<Coord> iterator() { return new GRIterator(); } @Override public Object[] toArray() { return new Object[0]; } @SuppressWarnings("unchecked") @Override public <T> T[] toArray(T[] a) { if(a instanceof Coord[]) return (a = (T[])asCoords()); return a; } @Override public boolean add(Coord coord) { if(contains(coord)) return false; insert(coord); return true; } @Override public void clear() { Arrays.fill(data, 0L); } @Override public boolean remove(Object o) { if(o instanceof Coord) { if(contains((Coord)o)) { remove((Coord)o); return true; } return false; } return false; } @Override public boolean containsAll(Collection<?> c) { for(Object o : c) { if(!contains(o)) return false; } return true; } @Override public boolean addAll(Collection<? extends Coord> c) { boolean changed = false; for(Coord co : c) { changed |= add(co); } return changed; } @Override public boolean removeAll(Collection<?> c) { boolean changed = false; for(Object o : c) { changed |= remove(o); } return changed; } @Override public boolean retainAll(Collection<?> c) { GreasedRegion g2 = new GreasedRegion(width, height); for(Object o : c) { if(contains(o) && o instanceof Coord) { g2.add((Coord)o); } } boolean changed = equals(g2); remake(g2); return changed; } /** * Randomly removes points from a GreasedRegion, with larger values for preservation keeping more of the existing * shape intact. If preservation is 1, roughly 1/2 of all points will be removed; if 2, roughly 1/4, if 3, roughly * 1/8, and so on, so that preservation can be thought of as a negative exponent of 2. * @param rng used to determine random factors * @param preservation roughly what degree of points to remove (higher keeps more); removes about {@code 1/(2^preservation)} points * @return a randomly modified change to this GreasedRegion */ public GreasedRegion deteriorate(RNG rng, int preservation) { if(rng == null || width <= 2 || ySections <= 0 || preservation <= 0) return this; long mash; for (int i = 0; i < width * ySections; i++) { mash = rng.nextLong(); for (int j = i; j < preservation; j++) { mash |= rng.nextLong(); } data[i] &= mash; } return this; } /** * Randomly removes points from a GreasedRegion, with preservation as a fraction between 1.0 (keep all) and 0.0 * (remove all). If preservation is 0.5, roughly 1/2 of all points will be removed; if 0.25, roughly 3/4 will be * removed (roughly 0.25 will be _kept_), if 0.8, roughly 1/5 will be removed (and about 0.8 will be kept), and so * on. Preservation must be between 0.0 and 1.0 for this to have the intended behavior; 1.0 or higher will keep all * points without change (returning this GreasedRegion), while anything less than 0.015625 (1.0/64) will empty this * GreasedRegion (using {@link #empty()}) and then return it. * @param rng used to determine random factors * @param preservation the rough fraction of points to keep, between 0.0 and 1.0 * @return a randomly modified change to this GreasedRegion */ public GreasedRegion deteriorate(final RNG rng, final double preservation) { if(rng == null || width <= 2 || ySections <= 0 || preservation >= 1) return this; if(preservation <= 0) return empty(); int bitCount = (int) (preservation * 64); for (int i = 0; i < width * ySections; i++) { data[i] &= rng.approximateBits(bitCount); } return this; } /** * Inverts the on/off state of the cell with the given x and y. * @param x the x position of the cell to flip * @param y the y position of the cell to flip * @return this for chaining, modified */ public GreasedRegion flip(int x, int y) { if(x >= 0 && y >= 0 && x < width && y < height && ySections > 0) data[x * ySections + (y >> 6)] ^= (1L << (y & 63)); return this; } public class GRIterator implements Iterator<Coord> { public int index = 0; private int[] counts; private int limit; private long t, w; public GRIterator() { limit = 0; counts = new int[width * ySections]; int tmp; for (int i = 0; i < width * ySections; i++) { tmp = Long.bitCount(data[i]); counts[i] = tmp == 0 ? -1 : (limit += tmp); } } @Override public boolean hasNext() { return index < limit; } @Override public Coord next() { int ct; if(index >= limit) return null; for (int s = 0; s < ySections; s++) { for (int x = 0; x < width; x++) { if ((ct = counts[x * ySections + s]) > index) { t = data[x * ySections + s]; w = Long.lowestOneBit(t); for (--ct; w != 0; ct--) { if (ct == index) { if(index++ < limit) return Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); else return null; } t ^= w; w = Long.lowestOneBit(t); } } } } return null; /* for (int x = 0; x < width; x++) { for (int s = 0; s < ySections; s++) { if ((w = Long.lowestOneBit(data[x * ySections + s])) != 0 && i++ >= index) { if(index++ < limit) return Coord.get(x, (s << 6) | Long.numberOfTrailingZeros(w)); else return null; } } } */ } @Override public void remove() { throw new UnsupportedOperationException("remove() is not supported on this Iterator."); } } }