/* * 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; /** * Represents the open space above a solid span within the cell column of * a heightfield. * <p> * <a href= "http://www.critterai.org/projects/nmgen/images/hf_07_openfield.png" * target="_blank"> <img alt="" height="449" src= * "http://www.critterai.org/projects/nmgen/images/hf_07_openfield.jpg" * width="620" /> </a> * </p> * <p> * </p> * * @see <a href="http://www.critterai.org/?q=nmgen_hfintro" * target="_parent">Introduction to Heightfields</a> * @see HeightSpan */ public final class OpenHeightSpan { /* * Design Notes: * * Structurally this class is so similar with HeightSpan that it can * extend HeightSpan. But it is being kept separate for clarity. * As an independent class it can use the floor and ceiling nomenclature * that makes reading code much easier. */ /** * A value representing a span in the null region. Spans in the null * are considered not traversable. * <p> * Spans in the null-region are often skipped during processing. Other * processing is only applied when the null-region is involved. * </p> */ public static final int NULL_REGION = 0; /** * Temporary flags associated with the span. * <p> * The value is meaningless outside the operation in which the flags are * needed. Since various operations may use this flag for their own purpose, * always reset the flag after use. * </p> * <p> * The contract is that an operation expects the flag to be zero when it * receives the span data. * </p> */ public int flags = 0; private int mRegionID = 0; private int mDistanceToRegionCore = 0; private int mDistanceToBorder = 0; private final int mFloor; private final int mHeight; private OpenHeightSpan mNext = null; private OpenHeightSpan mNeighborConnection0 = null; private OpenHeightSpan mNeighborConnection1 = null; private OpenHeightSpan mNeighborConnection2 = null; private OpenHeightSpan mNeighborConnection3 = null; /** * Constructor * * @param floor * The base height of the span. * @param height * The height of the unobstructed space above the floor. * {@link Integer#MAX_VALUE} is generally used to indicate no * obstructions * exist above the floor. * @throws IllegalArgumentException * If the floor is below zero or the * height is less than 1. */ public OpenHeightSpan(final int floor, final int height) throws IllegalArgumentException { if (floor < 0) { throw new IllegalArgumentException("Floor is less than zero."); } if (height < 1) { throw new IllegalArgumentException("Height is less than one."); } this.mFloor = floor; this.mHeight = height; } /** * The height of the ceiling. * * @return The height of the ceiling. */ public int ceiling() { return this.mFloor + this.mHeight; } /** * The distance this span is from the nearest border of the heightfield * it belongs to. * * @return The distance this span is from the nearest heightfield border. */ public int distanceToBorder() { return this.mDistanceToBorder; } /** * The distance this span is from the core of the heightfield region * it belongs to. * * @return The distance this span is from the core of the heightfield * region it belongs to. */ public int distanceToRegionCore() { return this.mDistanceToRegionCore; } /** * The base height of the span. * * @return The base height of the span. */ public int floor() { return this.mFloor; } /** * Populates an array with information on the regions a span's * 8-neighbors are assigned to. * <p> * If necessary, both of a diagonal neighbor's associated axis-neighbors * will be used to detect the diagonal neighbor. * </p> * <p> * Special case: Since, diagonal neighbors are detected through * axis-neighbors, if the span has no axis-neighbors in the direction of the * diagonal-neighbor, then the diagonal-neighbor will not be detected. * </p> * <p> * Neighbor order:</br> 0 - 3 : Standard axis-neighbor order. (E.g. Starting * at standard zero direction.)</br> 4 - 7 : Standard diagonal neighbors. * (E.g. Clockwise of associated axis-neighbor.)</br> So the standard * diagonal neigbor of an axis-neighbor can be found at * "axis-neighbor index + 4". * </p> * * @param out * An array of at least size 8. * @see <a href="http://critterai.org/nmgen_hfintro#nsearch" * target="_blank">Neighbor Searches</a> */ public void getDetailedRegionMap(final int[] out, final int insertIndex) { for (int i = 0; i < 8; i++) { out[insertIndex + i] = NULL_REGION; } OpenHeightSpan nSpan = null; OpenHeightSpan nnSpan = null; for (int dir = 0; dir < 4; dir++) { nSpan = getNeighbor(dir); if (nSpan != null) { out[insertIndex + dir] = nSpan.regionID(); nnSpan = nSpan.getNeighbor((dir + 1) & 0x3); if (nnSpan != null) { out[insertIndex + dir + 4] = nnSpan.regionID(); } nnSpan = nSpan.getNeighbor((dir + 3) & 0x3); if (nnSpan != null) { out[insertIndex + ((dir + 3) & 0x3) + 4] = nnSpan.regionID(); } } } } /** * Gets a reference to the span that is considered an axis-neighbor to * this span for the specified direction. Uses the standard direction * indices (0 through 3) where zero is the neighbor offset at (-1, 0) * and the search proceeds clockwise. * * @param direction * The direction to search. * @return A reference to the axis-neighbor in the specified direction. * Or null if there is no neighbor in the direction or the direction * index is invalid. * @see <a href="http://www.critterai.org/?q=nmgen_hfintro#nsearch" * target="_parent">Neighbor Searches</a> */ public OpenHeightSpan getNeighbor(final int direction) { switch (direction) { case 0: return this.mNeighborConnection0; case 1: return this.mNeighborConnection1; case 2: return this.mNeighborConnection2; case 3: return this.mNeighborConnection3; default: return null; } } /** * The height of the unobstructed space above the floor. * <p> * {@link Integer#MAX_VALUE} is generally used to indicate no obstructions * exist above the floor. * </p> * * @return The height of the unobstructed space above the floor. */ public int height() { return this.mHeight; } /** * The next span higher in the span's heightfield column. * <p> * The space between this span's ceiling and the next span's floor is * considered to be obstructed space. * </p> * * @return The next higher span in the span's heightfield column. * Or null if there is no heigher span. */ public OpenHeightSpan next() { return this.mNext; } /** * The heightfield region this span belongs to. * <p> * This value will never be less than {@link #NULL_REGION} for a finished, * properly constructed heightfield. * </p> * <p> * For a partially constructed heightfield the contract is that any region * ID less than or equal to {@link #NULL_REGION} belongs to the null region. * </p> * * @return The heightfield region this span belongs to. */ public int regionID() { return this.mRegionID; } /** * Set the distance this span is from the nearest border of the * heightfield it belongs to. * * @param value * The new distance. Auto-clamped at a minimum of zero. */ public void setDistanceToBorder(final int value) { this.mDistanceToBorder = Math.max(value, 0); } /** * Set the distance this span is from the core of the heightfield region * it belongs to. * * @param value * The new distance. Auto-clamped at a minimum of zero. */ public void setDistanceToRegionCore(final int value) { this.mDistanceToRegionCore = Math.max(value, 0); } /** * Sets the specified span at the neighbor of the current span. * <p> * Uses the standard direction indices (0 through 3) where Zero is the * neighbor offset at (-1, 0) and the search proceeds clockwise. * </p> * * @param direction * The direction of the neighbor. * @param neighbor * The neighbor of this span. * @see <a href="http://www.critterai.org/?q=nmgen_hfintro#nsearch" * target="_parent">Neighbor Searches</a> */ public void setNeighbor(final int direction, final OpenHeightSpan neighbor) { switch (direction) { case 0: this.mNeighborConnection0 = neighbor; break; case 1: this.mNeighborConnection1 = neighbor; break; case 2: this.mNeighborConnection2 = neighbor; break; case 3: this.mNeighborConnection3 = neighbor; break; } } /** * Set the next heigher span in the span's heightfield column. * * @param value * The new value. null is an acceptable value. */ public void setNext(final OpenHeightSpan value) { this.mNext = value; } /** * The heightfield region this span belongs to. * <p> * See {@link #regionID()} for important contract information. * </p> * * @param value * The new value. */ public void setRegionID(final int value) { this.mRegionID = value; } /** * {@inheritDoc} */ @Override public String toString() { return "Floor: " + this.mFloor + ", Ceiling: " + this.mHeight + ", Region: " + this.mRegionID; } }