/* * Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.model.structures; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * A selection of block positions, which may be relative (within a BlockCollection) or absolute (placed in a world) * Useful for tracking meta-block objects like Portals, Doors, Trees, etc * In other words simply a central wrapper around a Set of BlockPositions in case we change Set later or add utility * * @author Rasmus 'Cervator' Praestholm <cervator@gmail.com> */ public class BlockSelection { private static Logger logger = Logger.getLogger(BlockSelection.class.getName()); protected HashSet<BlockPosition> _positions = new HashSet<BlockPosition>(); /** * Default constructor for an empty selection */ public BlockSelection() { } /** * Constructor useful to take the Set<BlockPosition> you get back via keySet() on Maps, etc * * @param positions The initial BlockPositions this selection should contain */ public BlockSelection(Set<BlockPosition> positions) { _positions = new HashSet<BlockPosition>(positions); } /** * Simple check for being empty * * @return boolean empty or not * //TODO: See if there's anywhere this should be used yet? */ public boolean isEmpty() { return _positions.isEmpty(); } /** * Forward the position addition to the internal Set * * @param pos BlockPosition to add to this selection * @return true if this set did not already contain the position */ public boolean add(BlockPosition pos) { return _positions.add(pos); } /** * Returns the Set containing the selection of positions * * @return The positions in a Set */ public Set<BlockPosition> positions() { return _positions; } /** * Calculates the highest X (right) value of any position in this BlockSelection, or Integer.MIN_VALUE if empty * * @return highest X or Integer.MIN_VALUE if empty TODO: Make this throw exceptions instead! */ public int calcMaxX() { int xMax = Integer.MIN_VALUE; for (BlockPosition pos : _positions) { if (pos.x > xMax) { xMax = pos.x; } } return xMax; } /** * Calculates the lowest X (left) value of any position in this BlockSelection, or Integer.MAX_VALUE if empty * * @return lowest X or Integer.MAX_VALUE if empty */ public int calcMinX() { int xMin = Integer.MAX_VALUE; for (BlockPosition pos : _positions) { if (pos.x < xMin) { xMin = pos.x; } } return xMin; } /** * Returns the total width (x) of the BlockSelection's widest point (horizontally measured, not diagonally) * * @return int holding calculated width or -1 if there are no elements */ public int calcWidth() { if (_positions.isEmpty()) return -1; // Calculate the total width + 1 (a one-block wide construct would otherwise calculate to 0) return Math.abs(calcMaxX() - calcMinX()) + 1; } /** * Calculates the highest Y value of any position in this BlockSelection, or Integer.MIN_VALUE if empty * * @return highest Y or Integer.MIN_VALUE if empty */ public int calcMaxY() { int yMax = Integer.MIN_VALUE; for (BlockPosition pos : _positions) { if (pos.y > yMax) { yMax = pos.y; } } return yMax; } /** * Calculates the lowest Y value of any position in this BlockSelection, or Integer.MAX_VALUE if empty * * @return lowest Y or Integer.MAX_VALUE if empty */ public int calcMinY() { int yMin = Integer.MAX_VALUE; for (BlockPosition pos : _positions) { if (pos.y < yMin) { yMin = pos.y; } } return yMin; } /** * Returns the total height (y) from the BlockSelection's highest point to the lowest (vertically measured, not diagonally) * * @return int holding calculated height or -1 if there are no elements */ public int calcHeight() { if (_positions.isEmpty()) return -1; // Calculate the total height + 1 (a one-block tall construct would otherwise calculate to 0) return Math.abs(calcMaxY() - calcMinY()) + 1; } /** * Calculates the highest Z value of any position in this BlockSelection, or Integer.MIN_VALUE if empty * * @return highest Z or Integer.MIN_VALUE if empty */ public int calcMaxZ() { int zMax = Integer.MIN_VALUE; for (BlockPosition pos : _positions) { if (pos.z > zMax) { zMax = pos.z; } } return zMax; } /** * Calculates the lowest Z value of any position in this BlockSelection, or Integer.MAX_VALUE if empty * * @return lowest Z or Integer.MAX_VALUE if empty */ public int calcMinZ() { int zMin = Integer.MAX_VALUE; for (BlockPosition pos : _positions) { if (pos.z < zMin) { zMin = pos.z; } } return zMin; } /** * Returns the total depth (z) of the BlockSelection's closest point to the deepest (horizontally measured, not diagonally) * * @return int holding calculated depth or -1 if there are no elements */ public int calcDepth() { if (_positions.isEmpty()) return -1; // Calculate the total depth + 1 (a one-block deep construct would otherwise calculate to 0) return Math.abs(calcMaxZ() - calcMinZ()) + 1; } /** * Compare this BlockSelection's positions vs a given BlockSelection and see if there is any overlap at all * * @param otherSelection the other selection of positions * @return boolean for overlap or not */ public boolean overlaps(BlockSelection otherSelection) { if (!possiblyOverlaps(otherSelection)) { return false; } // If we're still here then the BlockSelections possibly overlap, but we'll need to test per position to be sure (long hard way to test) for (BlockPosition myPos : _positions) { for (BlockPosition targetPos : otherSelection.positions()) { if (myPos.equals(targetPos)) { logger.log(Level.INFO, "Selections overlap at " + myPos + ", maybe more, returning true"); return true; } } } return false; } /** * Compare this BlockSelection's positions vs a given BlockSelection and see if this one entirely contains the other * * @param otherSelection the other selection of positions * @return boolean for full containment or not */ public boolean contains(BlockSelection otherSelection) { // TODO: Not sure this makes performance sense here? Since the slightest lack of containment means we know the answer if (!possiblyOverlaps(otherSelection)) { return false; } // If we're still here then the BlockSelections possibly overlap, but we'll need to test for full containment for (BlockPosition otherPos : otherSelection.positions()) { boolean contained = false; for (BlockPosition myPos : _positions) { if (otherPos.equals(myPos)) { contained = true; break; } } if (!contained) { logger.log(Level.INFO, "Position " + otherPos + "was not contained in this selection, returning false"); return false; } } return true; } /** * Checks to see if the given BlockSelection can possibly overlap this one, which is figured out by checking bounds * * @param otherSelection The other BlockSelection to check with * @return boolean for possible overlap or not */ public boolean possiblyOverlaps(BlockSelection otherSelection) { // See if the entirety of the BlockSelections are completely out of bounds versus each other, nice short way to test if (calcMinX() > otherSelection.calcMaxX() || otherSelection.calcMinX() > calcMaxX()) { logger.log(Level.INFO, "Selections have no overlap at all along X axis, returning false"); return false; } if (calcMinY() > otherSelection.calcMaxY() || otherSelection.calcMinY() > calcMaxY()) { logger.log(Level.INFO, "Selections have no overlap at all along Y axis, returning false"); return false; } if (calcMinZ() > otherSelection.calcMaxZ() || otherSelection.calcMinZ() > calcMaxZ()) { logger.log(Level.INFO, "Selections have no overlap at all along Z axis, returning false"); return false; } return true; } @Override public String toString() { String result = "["; for (BlockPosition pos : _positions) { result += "[" + pos + "],"; } return result.substring(0, result.length() - 1) + "]"; } }