/* Copyright 2006 by Sean Luke and George Mason University Licensed under the Academic Free License version 3.0 See the file "LICENSE" for more information */ package sim.field.grid; import sim.util.*; import java.util.*; /** A concrete implementation of the Grid3D methods; used by several subclasses. Note that you should avoid calling these methods from an object of type Grid3D; instead try to call them from something more concrete (AbstractGrid3D or SparseGrid3D). Otherwise they will not get inlined. For example, <pre><tt> Grid3D foo = ... ; foo.tx(4); // will not get inlined AbstractGrid3D bar = ...; bar.tx(4); // WILL get inlined </tt></pre> */ public abstract class AbstractGrid3D implements Grid3D { private static final long serialVersionUID = 1; // this should never change except via setTo protected int width; // this should never change except via setTo protected int height; // this should never change except via setTo protected int length; public final int getWidth() { return width; } public final int getHeight() { return height; } public final int getLength() { return length; } public Map buildMap(Map other) { return new HashMap(other); } public Map buildMap(int size) { if (size <= ANY_SIZE) return new HashMap(); else return new HashMap(size); } /* public final int tx(final int x) { final int width = this.width; if (x >= 0) return (x % width); final int width2 = (x % width) + height; if (width2 < width) return width2; return 0; } */ // slight revision for more efficiency public final int tx(int x) { final int width = this.width; if (x >= 0 && x < width) return x; // do clearest case first x = x % width; if (x < 0) x = x + width; return x; } /* public final int ty(final int y) { final int height = this.height; if (y >= 0) return (y % height); final int height2 = (y % height) + height; if (height2 < height) return height2; return 0; } */ // slight revision for more efficiency public final int ty(int y) { final int height = this.height; if (y >= 0 && y < height) return y; // do clearest case first y = y % height; if (y < 0) y = y + height; return y; } /* public final int tz(final int z) { final int length = this.length; if (z >= 0) return (z % length); final int length2 = (z % length) + length; if (length2 < length) return length2; return 0; } */ // slight revision for more efficiency public final int tz(int z) { final int length = this.length; if (z >= 0 && z < length) return z; // do clearest case first z = z % length; if (z < 0) z = z + height; return z; } public final int stx(final int x) { if (x >= 0) { if (x < width) return x; return x - width; } return x + width; } public final int sty(final int y) { if (y >= 0) { if (y < height) return y ; return y - height; } return y + height; } public final int stz(final int z) { if (z >= 0) { if (z < length) return z ; return z - length; } return z + length; } // faster version final int stx(final int x, final int width) { if (x >= 0) { if (x < width) return x; return x - width; } return x + width; } // faster version final int sty(final int y, final int height) { if (y >= 0) { if (y < height) return y ; return y - height; } return y + height; } // faster version public final int stz(final int z, final int length) { if (z >= 0) { if (z < length) return z ; return z - length; } return z + length; } // this internal version of tx is arranged to be 34 bytes. It first tries stx, then tx. int tx(int x, int width, int widthtimestwo, int xpluswidth, int xminuswidth) { if (x >= -width && x < widthtimestwo) { if (x < 0) return xpluswidth; if (x < width) return x; return xminuswidth; } return tx2(x, width); } // used internally by the internal version of tx above. Do not call directly. int tx2(int x, int width) { x = x % width; if (x < 0) x = x + width; return x; } // this internal version of ty is arranged to be 34 bytes. It first tries sty, then ty. int ty(int y, int height, int heighttimestwo, int yplusheight, int yminusheight) { if (y >= -height && y < heighttimestwo) { if (y < 0) return yplusheight; if (y < height) return y; return yminusheight; } return ty2(y, height); } // used internally by the internal version of ty above. Do not call directly. int ty2(int y, int height) { y = y % height; if (y < 0) y = y + height; return y; } // this internal version of tz is arranged to be 34 bytes. It first tries stz, then tz. int tz(int z, int length, int lengthtimestwo, int zpluslength, int zminuslength) { if (z >= -length && z < lengthtimestwo) { if (z < 0) return zpluslength; if (z < length) return z; return zminuslength; } return tz2(z, length); } // used internally by the internal version of ty above. Do not call directly. int tz2(int z, int length) { z = z % length; if (z < 0) z = z + length; return z; } protected void removeOrigin(int x, int y, int z, IntBag xPos, IntBag yPos, IntBag zPos) { int size = xPos.size(); for(int i = 0; i <size; i++) { if (xPos.get(i) == x && yPos.get(i) == y && zPos.get(i) == z) { xPos.remove(i); yPos.remove(i); zPos.remove(i); return; } } } // only removes the first occurence protected void removeOriginToroidal(int x, int y, int z, IntBag xPos, IntBag yPos, IntBag zPos) { int size = xPos.size(); x = tx(x, width, width*2, x+width, x-width); y = ty(y, height, height*2, y+height, y-height); z = tz(z, length, length*2, z+length, z-length); for(int i = 0; i <size; i++) { if (tx(xPos.get(i), width, width*2, x+width, x-width) == x && ty(yPos.get(i), height, height*2, y+height, y-height) == y && tz(zPos.get(i), length, length*2, z+length, z-length) == z) { xPos.remove(i); yPos.remove(i); zPos.remove(i); return; } } } /** @deprecated */ public void getNeighborsMaxDistance( final int x, final int y, final int z, final int dist, final boolean toroidal, IntBag xPos, IntBag yPos, IntBag zPos ) { getMooreLocations(x, y, z, dist, toroidal ? TOROIDAL : BOUNDED, true, xPos, yPos, zPos); } public void getMooreLocations( final int x, final int y, final int z, final int dist, int mode, boolean includeOrigin, IntBag xPos, IntBag yPos, IntBag zPos ) { boolean toroidal = (mode == TOROIDAL); boolean bounded = (mode == BOUNDED); if (mode != BOUNDED && mode != UNBOUNDED && mode != TOROIDAL) { throw new RuntimeException("Mode must be either Grid3D.BOUNDED, Grid3D.UNBOUNDED, or Grid3D.TOROIDAL"); } // won't work for negative distances if( dist < 0 ) { throw new RuntimeException( "Distance must be positive" ); } if( xPos == null || yPos == null || zPos == null) { throw new RuntimeException( "xPos and yPos and zPos should not be null" ); } xPos.clear(); yPos.clear(); zPos.clear(); // local variables are faster final int height = this.height; final int width = this.width; final int length = this.length; // for toroidal environments the code will be different because of wrapping arround if( toroidal ) { // compute xmin and xmax for the neighborhood int xmin = x - dist; int xmax = x + dist; // next: is xmax - xmin humongous? If so, no need to continue wrapping around if (xmax - xmin >= width) // too wide xmax = xmin + width - 1; // compute ymin and ymax for the neighborhood int ymin = y - dist; int ymax = y + dist; // next: is ymax - ymin humongous? If so, no need to continue wrapping around if (ymax - ymin >= height) // too wide ymax = ymin + height - 1; // compute zmin and zmax for the neighborhood int zmin = z - dist; int zmax = z + dist; // next: is zmax - zmin humongous? If so, no need to continue wrapping around if (zmax - zmin >= length) // too wide zmax = zmin + length - 1; for( int x0 = xmin; x0 <= xmax ; x0++ ) { final int x_0 = tx(x0, width, width*2, x0+width, x0-width); for( int y0 = ymin ; y0 <= ymax ; y0++ ) { final int y_0 = ty(y0, height, height*2, y0+height, y0-height); for( int z0 = zmin ; z0 <= zmax ; z0++ ) { final int z_0 = tz(z0, length, length*2, z0+length, z0-length); if( x_0 != x || y_0 != y || z_0 != z ) { xPos.add( x_0 ); yPos.add( y_0 ); zPos.add( z_0 ); } } } } if (!includeOrigin) removeOriginToroidal(x,y,z,xPos,yPos,zPos); } else // not toroidal { // compute xmin and xmax for the neighborhood such that they are within boundaries final int xmin = ((x-dist>=0 || !bounded)?x-dist:0); final int xmax =((x+dist<=width-1 || !bounded)?x+dist:width-1); // compute ymin and ymax for the neighborhood such that they are within boundaries final int ymin = ((y-dist>=0 || !bounded)?y-dist:0); final int ymax = ((y+dist<=height-1 || !bounded)?y+dist:height-1); final int zmin = ((z-dist>=0 || !bounded)?z-dist:0); final int zmax = ((z+dist<=length-1 || !bounded)?z+dist:length-1); for( int x0 = xmin ; x0 <= xmax ; x0++ ) { for( int y0 = ymin ; y0 <= ymax ; y0++ ) { for( int z0 = zmin ; z0 <= zmax ; z0++ ) { xPos.add( x0 ); yPos.add( y0 ); zPos.add( z0 ); } } } if (!includeOrigin) removeOrigin(x,y,z,xPos,yPos,zPos); } } /** @deprecated */ public void getNeighborsHamiltonianDistance( final int x, final int y, final int z, final int dist, final boolean toroidal, IntBag xPos, IntBag yPos, IntBag zPos ) { getVonNeumannLocations(x, y, z, dist, toroidal ? TOROIDAL : BOUNDED, true, xPos, yPos, zPos); } public void getVonNeumannLocations( final int x, final int y, final int z, final int dist, int mode, boolean includeOrigin, IntBag xPos, IntBag yPos, IntBag zPos ) { boolean toroidal = (mode == TOROIDAL); boolean bounded = (mode == BOUNDED); if (mode != BOUNDED && mode != UNBOUNDED && mode != TOROIDAL) { throw new RuntimeException("Mode must be either Grid3D.BOUNDED, Grid3D.UNBOUNDED, or Grid3D.TOROIDAL"); } // won't work for negative distances if( dist < 0 ) { throw new RuntimeException( "Distance must be positive" ); } if( xPos == null || yPos == null || zPos == null) { throw new RuntimeException( "xPos and yPos and zPos should not be null" ); } xPos.clear(); yPos.clear(); zPos.clear(); // local variables are faster final int height = this.height; final int width = this.width; final int length = this.length; // for toroidal environments the code will be different because of wrapping arround if( toroidal ) { // compute xmin and xmax for the neighborhood final int xmax = x+dist; final int xmin = x-dist; for( int x0 = xmin; x0 <= xmax ; x0++ ) { final int x_0 = tx(x0, width, width*2, x0+width, x0-width); // compute ymin and ymax for the neighborhood; they depend on the curreny x0 value final int ymax = y+(dist-((x0-x>=0)?x0-x:x-x0)); final int ymin = y-(dist-((x0-x>=0)?x0-x:x-x0)); for( int y0 = ymin; y0 <= ymax; y0++ ) { final int y_0 = ty(y0, height, height*2, y0+height, y0-height); final int zmax = z+(dist-((x0-x>=0)?x0-x:x-x0)-((y0-y>=0)?y0-y:y-y0)); final int zmin = z-(dist-((x0-x>=0)?x0-x:x-x0)-((y0-y>=0)?y0-y:y-y0)); for( int z0 = zmin; z0 <= zmax; z0++ ) { final int z_0 = tz(z0, length, length*2, z0+length, z0-length); if( x_0 != x || y_0 != y || z_0 != z ) { xPos.add( x_0 ); yPos.add( y_0 ); zPos.add( z_0 ); } } } } if (dist * 2 >= width || dist * 2 >= height || dist * 2 >= length) // too big, will have to remove duplicates { int sz = xPos.size(); Map map = buildMap(sz); for(int i = 0 ; i < sz; i++) { Double3D elem = new Double3D(xPos.get(i), yPos.get(i), zPos.get(i)); if (map.containsKey(elem)) // already there { xPos.remove(i); yPos.remove(i); zPos.remove(i); i--; sz--; } else { map.put(elem, elem); } } } if (!includeOrigin) removeOriginToroidal(x,y,z,xPos,yPos,zPos); } else // not toroidal { // compute xmin and xmax for the neighborhood such that they are within boundaries final int xmax = ((x+dist<=width-1 || !bounded)?x+dist:width-1); final int xmin = ((x-dist>=0 || !bounded)?x-dist:0); for( int x0 = xmin ; x0 <= xmax ; x0++ ) { final int x_0 = x0; // compute ymin and ymax for the neighborhood such that they are within boundaries // they depend on the curreny x0 value final int ymax = ((y+(dist-((x0-x>=0)?x0-x:x-x0))<=height-1 || !bounded)?y+(dist-((x0-x>=0)?x0-x:x-x0)):height-1); final int ymin = ((y-(dist-((x0-x>=0)?x0-x:x-x0))>=0 || !bounded)?y-(dist-((x0-x>=0)?x0-x:x-x0)):0); for( int y0 = ymin; y0 <= ymax; y0++ ) { final int y_0 = y0; final int zmin = ((z-(dist-((x0-x>=0)?x0-x:x-x0)-((y0-y>=0)?y0-y:y-y0))>=0 || !bounded)?z-(dist-((x0-x>=0)?x0-x:x-x0)-((y0-y>=0)?y0-y:y-y0)):0); final int zmax = ((z+(dist-((x0-x>=0)?x0-x:x-x0)-((y0-y>=0)?y0-y:y-y0))<=length-1 || !bounded)?z+(dist-((x0-x>=0)?x0-x:x-x0)-((y0-y>=0)?y0-y:y-y0)):length-1) ; for( int z0 = zmin; z0 <= zmax; z0++ ) { final int z_0 = z0; xPos.add( x_0 ); yPos.add( y_0 ); zPos.add( z_0 ); } } } if (!includeOrigin) removeOrigin(x,y,z,xPos,yPos,zPos); } } double ds(double d1x, double d1y, double d1z, double d2x, double d2y, double d2z) { return ((d1x - d2x) * (d1x - d2x) + (d1y - d2y) * (d1y - d2y) + (d1z - d2z) * (d1z - d2z)); } boolean within(double d1x, double d1y, double d1z, double d2x, double d2y, double d2z, double distanceSquared, boolean closed) { double d= ds(d1x, d1y, d1z, d2x, d2y, d2z); return (d < distanceSquared || (d == distanceSquared && closed)); } public void getRadialLocations( final int x, final int y, final int z, final double dist, int mode, boolean includeOrigin, IntBag xPos, IntBag yPos, IntBag zPos ) { getRadialLocations(x, y, z, dist, mode, includeOrigin, Grid3D.ANY, true, xPos, yPos, zPos); } public void getRadialLocations( final int x, final int y, final int z, final double dist, int mode, boolean includeOrigin, int measurementRule, boolean closed, IntBag xPos, IntBag yPos, IntBag zPos ) { boolean toroidal = (mode == TOROIDAL); // won't work for negative distances if( dist < 0 ) { throw new RuntimeException( "Distance must be positive" ); } if (measurementRule != Grid3D.ANY && measurementRule != Grid3D.ALL && measurementRule != Grid3D.CENTER) { throw new RuntimeException(" Measurement rule must be one of ANY, ALL, or CENTER" ); } // grab the rectangle getMooreLocations(x,y, z, (int) Math.ceil(dist + 0.5), mode, includeOrigin, xPos, yPos, zPos); int len = xPos.size(); double distsq = dist * dist; int width = this.width; int height = this.height; int length = this.length; int widthtimestwo = width * 2; int heighttimestwo = height * 2; int lengthtimestwo = length * 2; for(int i = 0; i < len; i++) { int xp = xPos.get(i); int yp = yPos.get(i); int zp = zPos.get(i); boolean remove = false; if (measurementRule == Grid3D.ANY) { // handle simple cases where there's at least one equality if (x==xp) { remove = AbstractGrid2D.removeForAny(y,z,yp,zp,dist,closed); } else if (y==yp) { remove = AbstractGrid2D.removeForAny(x,z,xp,zp,dist,closed); } else if (z==zp) { remove = AbstractGrid2D.removeForAny(x,y,xp,yp,dist,closed); } // off center -- check for nearest corner else if (z < zp) { if (x < xp) { if (y < yp) remove = !within(x,y,z,xp-0.5,yp-0.5,zp-0.5,distsq,closed); else // y > yp remove = !within(x,y,z,xp-0.5,yp+0.5,zp-0.5,distsq,closed); } else // x > xp { if (y < yp) remove = !within(x,y,z,xp+0.5,yp-0.5,zp-0.5,distsq,closed); else // y > yp remove = !within(x,y,z,xp+0.5,yp+0.5,zp-0.5,distsq,closed); } } else // z > zp { if (x < xp) { if (y < yp) remove = !within(x,y,z,xp-0.5,yp-0.5,zp+0.5,distsq,closed); else // y > yp remove = !within(x,y,z,xp-0.5,yp+0.5,zp+0.5,distsq,closed); } else // x > xp { if (y < yp) remove = !within(x,y,z,xp+0.5,yp-0.5,zp+0.5,distsq,closed); else // y > yp remove = !within(x,y,z,xp+0.5,yp+0.5,zp+0.5,distsq,closed); } } } else if (measurementRule == Grid3D.ALL) { if (z < zp) { if (x < xp) { if (y < yp) remove = !within(x,y,z,xp+0.5,yp+0.5,zp+0.5,distsq,closed); else remove = !within(x,y,z,xp+0.5,yp-0.5,zp+0.5,distsq,closed); } else { if (y < yp) remove = !within(x,y,z,xp-0.5,yp+0.5,zp+0.5,distsq,closed); else remove = !within(x,y,z,xp-0.5,yp-0.5,zp+0.5,distsq,closed); } } else { if (x < xp) { if (y < yp) remove = !within(x,y,z,xp+0.5,yp+0.5,zp-0.5,distsq,closed); else remove = !within(x,y,z,xp+0.5,yp-0.5,zp-0.5,distsq,closed); } else { if (y < yp) remove = !within(x,y,z,xp-0.5,yp+0.5,zp-0.5,distsq,closed); else remove = !within(x,y,z,xp-0.5,yp-0.5,zp-0.5,distsq,closed); } } } else // (measurementRule == Grid3D.CENTER) { remove = !within(x,y,z,xp,yp,zp,distsq,closed); } if (remove) { xPos.remove(i); yPos.remove(i); zPos.remove(i); i--; len--; } else if (toroidal) // need to convert to toroidal position { int _x = xPos.get(i); int _y = yPos.get(i); int _z = zPos.get(i); xPos.set(i, tx(_x, width, widthtimestwo, _x + width, _x - width)); yPos.set(i, ty(_y, height, heighttimestwo, _y + width, _y - width)); zPos.set(i, tz(_z, length, lengthtimestwo, _z + length, _z - length)); } } } protected void checkBounds(Grid3D other) { if (getHeight() != other.getHeight() || getWidth() != other.getWidth() || getLength() != other.getLength()) throw new IllegalArgumentException("Grids must be the same dimensions."); } }