/*
* 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;
}
}