src.game.common.Spacing.java Source code

Java tutorial

Introduction

Here is the source code for src.game.common.Spacing.java

Source

/**  
  *  Written by Morgan Allen.
  *  I intend to slap on some kind of open-source license here in a while, but
  *  for now, feel free to poke around for non-commercial purposes.
  */

package src.game.common;

//import src.game.actors.Actor;
import src.game.building.*;
import src.util.*;
import org.apache.commons.math3.util.FastMath;

public final class Spacing implements TileConstants {

    private Spacing() {
    }

    final static int CLUSTER_SIZE = 8;

    private final static Vec3D pA = new Vec3D(), pB = new Vec3D();
    private final static Box2D tA = new Box2D(), tB = new Box2D();

    public final static Tile tempT4[] = new Tile[4], tempT8[] = new Tile[8], tempT9[] = new Tile[9];
    public final static Boardable tempB4[] = new Boardable[4], tempB8[] = new Boardable[8];

    final static Tile PERIM_ARRAYS[][] = { new Tile[8], new Tile[12], new Tile[16], new Tile[20] };
    final static Element NEAR_ARRAYS[][] = { new Element[8], new Element[12], new Element[16], new Element[20] };

    public static void wipeTempArrays() {
        Visit.wipe(tempT4);
        Visit.wipe(tempT8);
        Visit.wipe(tempT9);
        Visit.wipe(tempB4);
        Visit.wipe(tempB8);
        for (Tile t[] : PERIM_ARRAYS)
            Visit.wipe(t);
        for (Element e[] : NEAR_ARRAYS)
            Visit.wipe(e);
    }

    //
    //  Method for getting all tiles around the perimeter of a venue/area.
    public static Tile[] perimeter(Box2D area, World world) {
        final int minX = (int) Math.floor(area.xpos()), minY = (int) Math.floor(area.ypos()),
                maxX = (int) (minX + area.xdim() + 1), maxY = (int) (minY + area.ydim() + 1),
                wide = 1 + maxX - minX, high = 1 + maxY - minY;
        ///if (wide < 3) I.say("WIDE IS: "+wide+", area: "+area) ;
        final Tile perim[];
        if (wide == high && wide <= 6)
            perim = PERIM_ARRAYS[wide - 3];
        else
            perim = new Tile[(wide + high - 2) * 2];
        int tX, tY, pI = 0;
        for (tX = minX; tX++ < maxX;)
            perim[pI++] = world.tileAt(tX, minY);
        for (tY = minY; tY++ < maxY;)
            perim[pI++] = world.tileAt(maxX, tY);
        for (tX = maxX; tX-- > minX;)
            perim[pI++] = world.tileAt(tX, maxY);
        for (tY = maxY; tY-- > minY;)
            perim[pI++] = world.tileAt(minX, tY);
        return perim;
    }

    public static Tile[] under(Box2D area, World world) {
        final Batch<Tile> under = new Batch<Tile>();
        for (Tile t : world.tilesIn(area, true))
            under.add(t);
        return (Tile[]) under.toArray(Tile.class);
    }

    public static int[] entranceCoords(int xdim, int ydim, float face) {
        if (face == Venue.ENTRANCE_NONE)
            return new int[] { 0, 0 };
        face = (face + 0.5f) % Venue.NUM_SIDES;
        float edgeVal = face % 1;
        ///I.say("Face/edge-val: "+face+"/"+edgeVal) ;

        int enterX = 1, enterY = -1;
        if (face < Venue.ENTRANCE_EAST) {
            //  This is the north edge.
            enterX = (int) (xdim * edgeVal);
            enterY = ydim;
        } else if (face < Venue.ENTRANCE_SOUTH) {
            //  This is the east edge.
            enterX = xdim;
            enterY = (int) (ydim * (1 - edgeVal));
        } else if (face < Venue.ENTRANCE_WEST) {
            //  This is the south edge.
            enterX = (int) (xdim * (1 - edgeVal));
            enterY = -1;
        } else {
            //  This is the west edge.
            enterX = -1;
            enterY = (int) (ydim * edgeVal);
        }
        return new int[] { enterX, enterY };
    }

    /**  Methods for assisting placement-viability checks:
      */
    //
    //  This method checks whether the placement of the given element in this
    //  location would create secondary 'gaps' along it's perimeter that might
    //  lead to the existence of inaccessible 'pockets' of terrain- that would
    //  cause pathing problems.
    public static boolean perimeterFits(Element element) {
        final Box2D area = element.area(tA);
        final Tile perim[] = perimeter(area, element.origin().world);
        //
        //  Here, we check the first perimeter.  First, determine where the first
        //  taken (reserved) tile after a contiguous gap is-
        boolean inClearSpace = false;
        int index = perim.length - 1;
        while (index >= 0) {
            final Tile t = perim[index];
            if (t == null || t.owningType() >= element.owningType()) {
                if (inClearSpace)
                    break;
            } else {
                inClearSpace = true;
            }
            index--;
        }
        //
        //  Then, starting from that point, and scanning in the other direction,
        //  ensure there's no more than a single contiguous clear space-
        final int firstTaken = (index + perim.length) % perim.length, firstClear = (firstTaken + 1) % perim.length;
        inClearSpace = false;
        int numSpaces = 0;
        for (index = firstClear; index != firstTaken;) {
            final Tile t = perim[index];
            if (t == null || t.owningType() >= element.owningType()) {
                inClearSpace = false;
            } else if (!inClearSpace) {
                inClearSpace = true;
                numSpaces++;
            }
            index = (index + 1) % perim.length;
        }
        if (numSpaces > 1)
            return false;
        return true;
    }

    public static int numNeighbours(Element element, World world) {
        if (element.xdim() > 4 || element.xdim() != element.ydim()) {
            I.complain("This method is intended only for small, regular elements.");
        }
        final int size = element.xdim() - 1;
        int numNeighbours = 0;
        final Element near[] = NEAR_ARRAYS[size];
        final Tile perim[] = (size == 0) ? element.origin().allAdjacent(PERIM_ARRAYS[0])
                : Spacing.perimeter(element.area(tA), world);

        for (Tile t : perim)
            if (t != null) {
                final Element o = t.owner();
                if (o != null && o.flaggedWith() == null) {
                    near[numNeighbours++] = o;
                    o.flagWith(element);
                }
            }

        for (int i = numNeighbours; i-- > 0;)
            near[i].flagWith(null);
        return numNeighbours;
    }

    public static boolean isEntrance(Tile t) {
        for (Tile n : t.edgeAdjacent(tempT4)) {
            if (n == null || !(n.owner() instanceof Boardable))
                continue;
            for (Boardable b : ((Boardable) n.owner()).canBoard(tempB4)) {
                if (b == t)
                    return true;
            }
        }
        return false;
    }

    public static Batch<Element> entranceFor(Tile t) {
        final Batch<Element> batch = new Batch<Element>();
        for (Tile n : t.edgeAdjacent(tempT4)) {
            if (n == null || !(n.owner() instanceof Boardable))
                continue;
            for (Boardable b : ((Boardable) n.owner()).canBoard(tempB4)) {
                if (b == t)
                    batch.add((Element) n.owner());
            }
        }
        return batch;
    }

    /**  Used to ensure that diagonally-adjacent tiles are fully accessible.
      */
    public static void cullDiagonals(Object batch[]) {
        for (int i : Tile.N_DIAGONAL)
            if (batch[i] != null) {
                if (batch[(i + 7) % 8] == null)
                    batch[i] = null;
                if (batch[(i + 1) % 8] == null)
                    batch[i] = null;
            }
    }

    /**  Proximity methods-
      */
    public static Target nearest(Series<? extends Target> targets, final Target client) {
        final Visit<Target> v = new Visit<Target>() {
            public float rate(Target t) {
                return 0 - distance(t, client);
            }
        };
        return v.pickBest((Series) targets);
    }

    public static Tile nearestOpenTile(Box2D area, Target client, World world) {
        final Vec3D p = client.position(pA);
        final Tile o = world.tileAt(p.x, p.y);
        float minDist = Float.POSITIVE_INFINITY;
        Tile nearest = null;
        int numTries = 0;
        while (nearest == null && numTries++ < (CLUSTER_SIZE / 2)) {
            for (Tile t : perimeter(area, world)) {
                if (t == null || t.blocked())
                    continue;
                if (t != o && t.inside().size() > 0)
                    continue;
                final float dist = distance(o, t);
                if (dist < minDist) {
                    minDist = dist;
                    nearest = t;
                }
            }
            area.expandBy(1);
        }
        return nearest;
    }

    public static Tile nearestOpenTile(Target t, Target client) {
        final Tile under = client.world().tileAt(t);
        return nearestOpenTile(under, client);
    }

    public static Tile nearestOpenTile(Tile tile, Target client) {
        if (tile == null)
            return null;
        if (!tile.blocked())
            return tile;
        return nearestOpenTile(tile.area(tB), client, tile.world);
    }

    public static Tile nearestOpenTile(Element element, Target client, World world) {
        if (element.pathType() >= Tile.PATH_HINDERS) {
            return nearestOpenTile(element.area(tA), client, world);
        } else {
            final Vec3D p = element.position(pA);
            return world.tileAt(p.x, p.y);
        }
    }

    /**  Returns a semi-random unblocked tile around the given element.
      */
    //  TODO:  Move this to the Placement class.
    public static Tile pickFreeTileAround(Target t, Element client) {
        final Tile perim[];
        if (t instanceof Tile) {
            perim = ((Tile) t).allAdjacent(null);
        } else if (t instanceof Element) {
            final Element e = (Element) t;
            perim = perimeter(e.area(tA), client.world());
        } else
            return null;
        //
        //  We want to avoid any tiles that are obstructed, including by other
        //  actors, and probably to stay in the same spot if possible.
        final Tile l = client.origin();
        final boolean inPerim = Visit.arrayIncludes(perim, l);
        ///if (inPerim && Rand.num() > 0.2f) return l ;

        final float weights[] = new float[perim.length];
        float sumWeights = 0;
        int index = -1;
        for (Tile p : perim) {
            index++;
            if (p == null || p.blocked())
                continue;
            if (p.inside().size() > 0)
                continue;
            final float dist = Spacing.distance(p, l);
            if (inPerim && dist > 4)
                continue;
            final float weight = 1f / (1 + dist);
            weights[index] = weight;
            sumWeights += weight;
        }

        float roll = Rand.num() * sumWeights;
        sumWeights = 0;
        for (int n = 0; n < perim.length; n++) {
            if (roll < weights[n])
                return perim[n];
            sumWeights += weights[n];
        }
        return nearestOpenTile(t, client);
    }

    public static Tile pickRandomTile(Target t, float range, World world) {
        final double angle = Rand.num() * Math.PI * 2;
        final float dist = Rand.num() * range, max = world.size - 1;
        final Vec3D o = t.position(pA);
        return world.tileAt(Visit.clamp(o.x + (float) (Math.cos(angle) * dist), 0, max),
                Visit.clamp(o.y + (float) (Math.sin(angle) * dist), 0, max));
    }

    /**  Distance calculation methods-
      */
    final public static float distance(final Target a, final Target b) {
        final float dist = innerDistance(a, b) - (a.radius() + b.radius());
        return (dist < 0) ? 0 : dist;
    }

    final public static float innerDistance(final Target a, final Target b) {
        a.position(pA);
        b.position(pB);
        final float xd = pA.x - pB.x, yd = pA.y - pB.y;
        return (float) FastMath.sqrt((xd * xd) + (yd * yd));
    }

    final public static int outerDistance(final Target a, final Target b) {
        final float dist = innerDistance(a, b);
        return (int) FastMath.ceil(dist + a.radius() + b.radius());
    }

    final public static float distance(final Tile a, final Tile b) {
        final int xd = a.x - b.x, yd = a.y - b.y;
        return (float) FastMath.sqrt((xd * xd) + (yd * yd));
    }

    public static int maxAxisDist(Tile a, Tile b) {
        final int xd = Math.abs(a.x - b.x), yd = Math.abs(a.y - b.y);
        return Math.max(xd, yd);
    }

    public static int sumAxisDist(Tile a, Tile b) {
        final int xd = Math.abs(a.x - b.x), yd = Math.abs(a.y - b.y);
        return xd + yd;
    }

    public static boolean adjacent(Tile t, Element e) {
        if (t == null || e == null)
            return false;
        e.area(tA);
        tA.expandBy(1);
        return tA.contains(t.x, t.y);
    }

    public static boolean edgeAdjacent(Tile a, Tile b) {
        if (a.x == b.x)
            return a.y == b.y + 1 || a.y == b.y - 1;
        if (a.y == b.y)
            return a.x == b.x + 1 || a.x == b.x - 1;
        return false;
    }

    public static boolean adjacent(Element a, Element b) {
        if (a == null || b == null)
            return false;
        a.area(tA);
        b.area(tB);
        return tA.intersects(tB);
    }
}

/*
public static Tile[] traceSurrounding(Element element, int maxLen) {
  final Tile perim[] = perimeter(element.area(), element.world()) ;
  Tile temp[] = new Tile[8] ;
  Tile lastClear = null, lastBlock = null ;
      
  for (Tile t : perim) if (! t.blocked()) { lastClear = t ; break ; }
  if (lastClear != null) for (Tile t : lastClear.edgeAdjacent(temp)) {
if (t.blocked()) { lastBlock = t ; break ; }
  }
  if (lastClear == null || lastBlock == null) return new Tile[0] ;
      
      
  final Batch <Tile> clear = new Batch <Tile> () ;
  Tile nextClear, nextBlock ;
      
  //  TODO:  Figure out how this works.
      
  return (Tile[]) clear.toArray(Tile.class) ;
}
//*/

/*
private static boolean checkClustering(Element a, Tile t, boolean checkType) {
  final Element b = t.owner() ;
  if (checkType && (b == null || b.owningType() < a.owningType()))
return true ;
  final Tile oA = a.origin(), oB = b.origin() ;
  return
((oA.x / CLUSTER_SIZE) != (oB.x / CLUSTER_SIZE)) ||
((oA.y / CLUSTER_SIZE) != (oB.y / CLUSTER_SIZE)) ;
}
//*/