/* * Copyright (c) 2010 Stephen A. Pratt * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.critterai.nmgen; /** * Defines an axis aligned bounding box containing a grid based field. * <p> * There are no enforced usage models for this class. But within this package * the following standards apply: * </p> * <ul> * <li>The base (width/depth) of the field is defined by a grid of cells with a * size of {@link #cellSize()}.</li> * <li>Width of the field is associated with the x-axis and depth with the * z-axis. So the width and depth of the field lies on the xz-plane.</p> * <li>The position of a cell is defined by an aggregate width/depth index * obtained from {@link #gridIndex(int, int) gridIndex()}.</li> * <li>The grid indices originate at the minimum bounds of the field.</li> * <li>The height increment within each grid location is defined by the value of * {@link #cellHeight()} and also originates at the field's minimum bounds.</li> * <li>Sizing of the bounds and the values of cell size/height will be such that * the bounds will not contain partial cells.</li> * </ul> * <p> * Warning: Behavior is undefined if the minimum bounds is set to be greater * than the maximum bounds. * </p> * * @see <a href="http://www.critterai.org/nmgen_hfintro" * target="_parent">Introduction to Height Fields</a> */ public class BoundedField { /* * Design notes: * * In most cases, performance trumps object safety. For example, the * bounds getters return a reference to the internal bounds arrays in * order to save on object creation costs. * * Setters are protected rather than public in order to allow implementing * classes to control mutability. * * Recast Reference: None */ private int mWidth; private int mDepth; private final float[] mBoundsMin = new float[3]; private final float[] mBoundsMax = new float[3]; private float mCellSize; private float mCellHeight; /** * Constructor - Default * <p> * The bounds of the field will default to min(0, 0, 0) and max(1, 1, 1). * <p> * <p> * The cell size and height will default to 0.1. * </p> */ public BoundedField() { resetBounds(); resetCellInfo(); // Must call this after the bounds call. } /** * Constructor - Partial * <p> * The bounds of the field will default to min(0, 0, 0) and max(1, 1, 1). * </p> * <p> * The cell size and height values are auto-clamped to * {@link Float#MIN_VALUE}. * </p> * * @param cellSize * The size of the cells. (The grid that forms the * base of the field.) * @param cellHeight * The height increment of the field. */ public BoundedField(final float cellSize, final float cellHeight) { this.mCellSize = Math.max(cellSize, Float.MIN_VALUE); this.mCellHeight = Math.max(cellHeight, Float.MIN_VALUE); calculateWidthDepth(); } /** * Constructor - Full * <p> * The cell size and height values are auto-clamped to * {@link Float#MIN_VALUE}. * </p> * <p> * Warning: Behavior is undefined if the minimum bounds is set to be greater * than the maximum bounds. * </p> * * @param gridBoundsMin * The minimum bounds of the field in the form * (minX, minY, minZ). * @param gridBoundsMax * The maximum bounds of the field in the form * (maxX, maxY, maxZ). * @param cellSize * The size of the cells. (The grid that forms the * base of the field.) * @param cellHeight * The height increment of the field. * @throws IllegalArgumentException * If the bounds are null or the * wrong size. */ public BoundedField(final float[] gridBoundsMin, final float[] gridBoundsMax, final float cellSize, final float cellHeight) throws IllegalArgumentException { if ((gridBoundsMax == null) || (gridBoundsMin == null) || (gridBoundsMax.length != 3) || (gridBoundsMin.length != 3)) { throw new IllegalArgumentException("One or both bounds are invalid."); } System.arraycopy(gridBoundsMin, 0, this.mBoundsMin, 0, 3); System.arraycopy(gridBoundsMax, 0, this.mBoundsMax, 0, 3); this.mCellSize = Math.max(cellSize, Float.MIN_VALUE); this.mCellHeight = Math.max(cellHeight, Float.MIN_VALUE); calculateWidthDepth(); } /** * The maximum bounds of the field in world units. * <p> * Warning: A reference to the internal array is being returned, not a new * array. * </p> * <p> * Form: (maxX, maxY, maxZ) * </p> */ public final float[] boundsMax() { return this.mBoundsMax; } /** * The minimum bounds of the field in world units. * <p> * Warning: A reference to the internal array is being returned, not a new * array. * </p> * <p> * Form: (minX, minY, minZ) * </p> * <p> * Considered the origin of the field. * <p> */ public final float[] boundsMin() { return this.mBoundsMin; } /** * The height increment of the field. */ public final float cellHeight() { return this.mCellHeight; } /** * The size of the cells. (The grid that forms the base of the field.) */ public final float cellSize() { return this.mCellSize; } /** * Depth of the field in voxels. * <p> * The maximum depth index for the field is equal to(depth - 1). */ public final int depth() { return this.mDepth; } /** * Indicates whether or not the provided index values represent a * valid cell location within * the field. * * @param widthIndex * The width index. * @param depthIndex * The depth index. * @return TRUE if the width and depth indices are valid for the field. * Otherwise FALSE. */ public final boolean isInBounds(final int widthIndex, final int depthIndex) { return ((widthIndex >= 0) && (depthIndex >= 0) && (widthIndex < this.mWidth) && (depthIndex < this.mDepth)); } /** * Indicates whether or not the provided bounds overlaps the bounds of * the current field. * <p> * All tests are inclusive. So if there is an edge match, then the bounds * overlap. * </p> * * @param boundsMin * The minimum bounds of the field to test in the * form (minX, minY, minZ). * @param boundsMax * The maximum bounds of the field to test in the * form (maxX, maxY, maxZ). * @return TRUE if the provided bounds overlaps the bounds of the * current field. Otherwise FALSE. */ public final boolean overlaps(final float[] boundsMin, final float[] boundsMax) { boolean overlaps = true; if ((boundsMin == null) || (boundsMax == null) || (boundsMin.length != 3) || (boundsMax.length != 3)) { return false; } // Keep the value of TRUE unless a non-overlap condition is found. overlaps = ((this.mBoundsMin[0] > boundsMax[0]) || (this.mBoundsMax[0] < boundsMin[0])) ? false : overlaps; overlaps = ((this.mBoundsMin[1] > boundsMax[1]) || (this.mBoundsMax[1] < boundsMin[1])) ? false : overlaps; overlaps = ((this.mBoundsMin[2] > boundsMax[2]) || (this.mBoundsMax[2] < boundsMin[2])) ? false : overlaps; return overlaps; } /** * Width of the field in voxels. * <p> * The maximum width index for the field is equal to (width - 1). * <p> */ public final int width() { return this.mWidth; } /** * Generates a standardized grid index suitable for use in flattened * storage arrays. * <p> * Results in depth adjacent storage. (Cells at depth 0, Cells at depth 1, * etc.) * </p> * * @param widthIndex * The width index. * @param depthIndex * The depth index. * @return A standardized grid index for the cell identified by the * width and depth * indices. If the width and depth combination is invalid for the * field, a value of -1 will be returned. */ protected final int gridIndex(final int widthIndex, final int depthIndex) { /* * Design notes: * * It is not uncommon during iteration processes, especially * processes which are iterating on the edge of the bounds, that the * width and depth indices may go out of range. While there would be * a slight performance gain by leaving out the below argument * validation, and require that callers be responsible for passing * good values, experience indicates that this causes hard to find * bugs. So instead, the validation is being performed here for * all calls. * * Compare this algorithm to flattened vertex storage. * This only involves different naming conventions. * vertPointer = vertIndex * 3 + offset. * Where: 3 is the number of values per vertex (the dimension) * and 0 <= offset < vertex dimensions. */ if ((widthIndex < 0) || (depthIndex < 0) || (widthIndex >= this.mWidth) || (depthIndex >= this.mDepth)) { return -1; } return (widthIndex * this.mDepth) + depthIndex; } /** * Resets the bounds to min(0, 0, 0) and max(1, 1, 1). */ protected final void resetBounds() { this.mBoundsMin[0] = 0; this.mBoundsMin[1] = 0; this.mBoundsMin[2] = 0; this.mBoundsMax[0] = 1; this.mBoundsMax[1] = 1; this.mBoundsMax[2] = 1; calculateWidthDepth(); } /** * Reset the cell size and height values to 0.1. */ protected final void resetCellInfo() { this.mCellSize = 0.1f; this.mCellHeight = 0.1f; calculateWidthDepth(); } /** * Sets the bounds of the field. * <p> * Warning: Behavior is undefined if the minimum bounds is greater than the * maximum bounds. * </p> * * @param xmin * The x-value for the minimum bounds. * @param ymin * The y-value for the minimum bounds. * @param zmin * The z-value for the minimum bounds. * @param xmax * The x-value for the maximum bounds. * @param ymax * The y-value for the maximum bounds. * @param zmax * The z-value for the maximum bounds. */ protected final void setBounds(final float xmin, final float ymin, final float zmin, final float xmax, final float ymax, final float zmax) { this.mBoundsMin[0] = xmin; this.mBoundsMin[1] = ymin; this.mBoundsMin[2] = zmin; this.mBoundsMax[0] = xmax; this.mBoundsMax[1] = ymax; this.mBoundsMax[2] = zmax; calculateWidthDepth(); } /** * Sets the bounds of the field. * <p> * Null values and arrays of length other than 3 are ignored. * </p> * <p> * Warning: Behavior is undefined if the minimum bounds is greater than the * maximum bounds. * </p> * * @param min * The minimum bounds in the form (minX, minY, minZ). * @param max * The maximum bounds in the form (maxX, maxY, maxZ). */ protected final void setBounds(final float[] min, final float[] max) { if ((min == null) || (max == null) || (min.length != 3) || (max.length != 3)) { return; } System.arraycopy(min, 0, this.mBoundsMin, 0, 3); System.arraycopy(max, 0, this.mBoundsMax, 0, 3); calculateWidthDepth(); } /** * Set the maximum bounds of the field. * <p> * Null values and arrays of length other than 3 are ignored. * </p> * <p> * Warning: Behavior is undefined if the new maximum bounds is less than the * current minimum bounds. * </p> * * @param value * The maximum bounds in the form (maxX, maxY, maxZ). */ protected final void setBoundsMax(final float[] value) { if ((value == null) || (value.length != 3)) { return; } System.arraycopy(value, 0, this.mBoundsMax, 0, 3);; calculateWidthDepth(); } /** * Set the minimum bounds of the field. * <p> * Null values and arrays of length other than 3 are ignored. * </p> * <p> * Warning: Behavior is undefined if the new minimum bounds is greater than * the current maximum bounds. * </p> * * @param value * The minimum bounds in the form (minX, minY, minZ). */ protected final void setBoundsMin(final float[] value) { if ((value == null) || (value.length != 3)) { return; } System.arraycopy(value, 0, this.mBoundsMin, 0, 3); calculateWidthDepth(); } /** * Set the cell height. * <p> * The cell height value is clamped to {@link Float#MIN_VALUE}. * </p> * * @param value * The new cell height. */ protected final void setCellHeight(final float value) { this.mCellHeight = Math.max(value, Float.MIN_VALUE); } /** * Set the cell size. * <p> * The cell size value is clamped to {@link Float#MIN_VALUE}. * </p> * * @param value * The new cell size. */ protected final void setCellSize(final float value) { this.mCellSize = Math.max(value, Float.MIN_VALUE); calculateWidthDepth(); } /** * Sets the width and depth fields based on the current * bounds and cell size. */ private void calculateWidthDepth() { this.mWidth = (int) (((this.mBoundsMax[0] - this.mBoundsMin[0]) / this.mCellSize) + 0.5f); this.mDepth = (int) (((this.mBoundsMax[2] - this.mBoundsMin[2]) / this.mCellSize) + 0.5f); } /** * When used in conjunction with {@link #getDirOffsetWidth(int)}, gets a * standard axis-neighbor direction offset that can be used for * searching adjacent grid locations. * <p> * The combined offset will be in the clockwise direction for direction 0 to * 3, starting at (-1, 0). * </p> * <p> * For example, if a direction value of 3 is passed to both this operation * and {@link #getDirOffsetWidth(int)}, then the combined width/depth offset * will be (0, -1). * </p> * * @param dir * A value representing the direction to get the offset for. * The value will be automatically constrained to a valid value * between * 0 and 3 inclusive using wrapping. E.g. A value of 4 will be * automatically wrapped to 0, a value of 9 will be automatically * wrapped to 1, etc. * @return The standard offset for the provided direction. * @see <a href="http://www.critterai.org/?q=nmgen_hfintro#nsearch" * target="_parent">Neighbor Searches</a> */ public static int getDirOffsetDepth(final int dir) { final int offset[] = { 0, 1, 0, -1 }; return offset[dir & 0x03]; } /** * When used in conjunction with {@link #getDirOffsetDepth(int)}, gets a * standard axis-neighbor direction offset that can be used for * searching adjacent grid locations within the field. * <p> * The combined offset will be in the clockwise direction for direction 0 to * 3, starting at (-1, 0). * </p> * <p> * For example, if a direction value of 3 is passed to both this operation * and {@link #getDirOffsetDepth(int)}, then the combined width/depth offset * will be (0, -1). * </p> * * @param dir * A value representing the direction to get the offset for. * The value will be automatically constrained to a valid value * between * 0 and 3 inclusive, using wrapping. E.g. A value of 4 will be * automatically wrapped to 0, a value of 9 will be automatically * wrapped * to 1, etc. * @return The standard offset for the provided direction. * @see <a href="http://www.critterai.org/?q=nmgen_hfintro#nsearch" * target="_parent">Neighbor Searches</a> */ public static int getDirOffsetWidth(final int dir) { final int offset[] = { -1, 0, 1, 0 }; // All bits above 3 are discarded, constraining argument to 0 - 3; return offset[dir & 0x03]; } }