/* * 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; import java.util.Hashtable; import java.util.Iterator; import java.util.NoSuchElementException; /** * Provides a representation of the open (unobstructed) space above solid * surfaces in a voxel field. * <p> * For this type of heightfield, spans represent the floor and ceiling of the * open spaces. * </p> * <p> * WARNING: This class has very little protections build into it. It is * basically an uncontrolled data structure with convenience functions. * </p> * <p> * <a href= "http://www.critterai.org/projects/nmgen/images/hf_07_openfield.png" * target="_parent"> <img alt="" height="449" src= * "http://www.critterai.org/projects/nmgen/images/hf_07_openfield.jpg" * width="620" /> </a> * </p> * * @see <a href="http://www.critterai.org/?q=nmgen_hfintro" * target="_parent">Introduction to Height Fields</a> */ public final class OpenHeightfield extends BoundedField { /* * Recast Reference: rcCompactHeightfield in Recast.h * The internal structure of this class is very different from Recast. * See: http://www.critterai.org/nmgen_diffs for the reason. */ /** * An iterator that will iterate through all spans within a * height field. (Not just the base spans.) * <p> * Behavior of the iterator is undefined if the interator's source is * changed during iteration. * </p> * The iterator returned by {@link OpenHeightfield#dataIterator()}. */ public final class OpenHeightFieldIterator implements Iterator<OpenHeightSpan> { // See reset() for initialization information. private int mNextWidth; private int mNextDepth; private OpenHeightSpan mNext; private int mLastWidth; private int mLastDepth; /** * Constructor. */ private OpenHeightFieldIterator() { reset(); } /** * The depth index of the last span returned by {@link #next()} * * @return The depth index of the last span returned by {@link #next()} */ public int depthIndex() { return this.mLastDepth; } /** * {@inheritDoc} */ @Override public boolean hasNext() { return (this.mNext != null); } /** * {@inheritDoc} */ @Override public OpenHeightSpan next() { if (this.mNext == null) { throw new NoSuchElementException(); } // Select value cursor. final OpenHeightSpan next = this.mNext; this.mLastWidth = this.mNextWidth; this.mLastDepth = this.mNextDepth; // Move the cursor to the next value. moveToNext(); return next; } /** * {@inheritDoc} This operation is not supported. */ @Override public void remove() { throw new UnsupportedOperationException(); } /** * Resets the iterator so that it can be re-used. */ public void reset() { this.mNextWidth = 0; this.mNextDepth = 0; this.mNext = null; this.mLastWidth = 0; this.mLastDepth = 0; moveToNext(); } /** * The width index of the last span returned by {@link #next()} * * @return The width index of the last span returned by {@link #next()} */ public int widthIndex() { return this.mLastWidth; } /** * This operation is called in order to move the * cursor to the next span after the {@link #next()} operation * has selected its span to return. */ private void moveToNext() { if (this.mNext != null) { // There is a current cell selected. if (this.mNext.next() != null) { // The current cell has a next. Select it this.mNext = this.mNext.next(); return; } else { this.mNextWidth++; // Move to next cell. } } // Need to find the next grid location that contains a span. // Loop until one is found or no more are available. for (int depthIndex = this.mNextDepth; depthIndex < depth(); depthIndex++) { for (int widthIndex = this.mNextWidth; widthIndex < width(); widthIndex++) { final OpenHeightSpan span = OpenHeightfield.this.mSpans.get(gridIndex(widthIndex, depthIndex)); if (span != null) { // A span was found. Set the cursor to it. this.mNext = span; this.mNextWidth = widthIndex; this.mNextDepth = depthIndex; return; } } this.mNextWidth = 0; } // If got here then there are no more spans. // Set values to indicate the end of iteration. this.mNext = null; this.mNextDepth = -1; this.mNextWidth = -1; } } /** * Indicates that a value is unknown and need to be derived. */ private static final int UNKNOWN = -1; private int mSpanCount = 0; // These next fields are derived only when the value is needed. private int mRegionCount = 0; /** * The maximum distance a span is from a border span. */ private int mMaxBorderDistance = UNKNOWN; /** * The minimum distance a span is from a border span. */ private int mMinBorderDistance = UNKNOWN; /** * Key = Grid index from {@link #gridIndex(int, int)}. * Value = The first (lowest) span in the grid column. */ private final Hashtable<Integer, OpenHeightSpan> mSpans = new Hashtable<Integer, OpenHeightSpan>(); /** * Constructor * * @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 OpenHeightfield(final float[] gridBoundsMin, final float[] gridBoundsMax, final float cellSize, final float cellHeight) throws IllegalArgumentException { super(gridBoundsMin, gridBoundsMax, cellSize, cellHeight); } /** * Puts the span at the grid location, replacing any spans already at the * location. The added span becomes the new base span for the location. * <p> * WARNING: The span count must be manually updated to reflect changes in * span count * </p> * <p> * Behavior is undefined if the indices are invalid. * </p> * * @param widthIndex * The width index of the grid location to add the * span to. (0 <= value < {@link #width()}) * @param depthIndex * The depth index of the grid location to add the * span to. (0 <= value < {@link #depth()}) * @param span * The span to put at the grid location. * @return The original base span that was in the grid location, or null * if there was no pre-existing span in the location. */ public OpenHeightSpan addData(final int widthIndex, final int depthIndex, final OpenHeightSpan span) { return this.mSpans.put(gridIndex(widthIndex, depthIndex), span); } /** * Resets the border distance values so they will * be recacluated the next time they are needed. */ public void clearBorderDistanceBounds() { this.mMaxBorderDistance = UNKNOWN; this.mMinBorderDistance = UNKNOWN; } /** * An iterator for the heightfields spans. * The returned iterator does not support the {@link Iterator#remove()} * operation. */ public OpenHeightFieldIterator dataIterator() { return this.new OpenHeightFieldIterator(); } /** * Retrieves the base (lowest) grid for the specified grid location. * <p> * Behavior is undefined if the indices are invalid. * </p> * * @param widthIndex * The width index of the grid location the span is * located in. (0 <= value < {@link #width()}) * @param depthIndex * The depth index of the grid location the span is * located in. (0 <= value < {@link #depth()}) * @return The base (lowest) span for the specified grid location. Null * if there is no data for the grid location. */ public OpenHeightSpan getData(final int widthIndex, final int depthIndex) { return this.mSpans.get(gridIndex(widthIndex, depthIndex)); } /** * Increments the span count. * <p> * IMPORTANT: There is no automatic span count updates. Span count must be * managed manually. * </p> * * @return The new span count. */ public int incrementSpanCount() { return ++this.mSpanCount; } /** * The maximum distance a span in the heightfield is from its * nearest border. * * @return The maximum distance a span in the heightfield is from * its nearest border. */ public int maxBorderDistance() { if (this.mMaxBorderDistance == UNKNOWN) { calcBorderDistanceBounds(); } return this.mMaxBorderDistance; } /** * The minimum distance a span in the height field is from its nearest * border. (Usually zero. But can depend on the generation method.) * * @return The minimum distance a span in the height field is from its * nearest border. */ public int minBorderDistance() { if (this.mMinBorderDistance == UNKNOWN) { calcBorderDistanceBounds(); } return this.mMinBorderDistance; } /** * Sends a tab delimited table of the distance field values to * standard out. * <p> * Only the lowest spans in the field are output. So this operation is * really only suitable for simple tests on fields that don't contain * overlapping spans. * </p> * <p> * Columns: Width<br/> * Rows: Depth * </p> */ public void printDistanceField() { System.out.println("Distance Field (Spans: " + this.mSpanCount + ")"); final OpenHeightFieldIterator iter = new OpenHeightFieldIterator(); int depth = -1; System.out.print("\t"); for (int width = 0; width < width(); width++) { System.out.print(width + "\t"); } while (iter.hasNext()) { final OpenHeightSpan span = iter.next(); if (iter.depthIndex() != depth) { System.out.print("\n" + ++depth + "\t"); } System.out.print(span.distanceToBorder() + "\t"); } System.out.println(); } /** * Sends a tab delimited table of the region ID values to standard out. * <p> * Only the lowest spans in the field are output. So this operation is * really only suitable for simple tests on fields that don't contain * overlapping spans. * </p> * <p> * Columns: Width<br/> * Rows: Depth * </p> */ public void printRegionField() { System.out.println("Distance Field (Spans: " + this.mSpanCount + ", Regions: " + this.mRegionCount + ")"); System.out.print("\t"); for (int width = 0; width < width(); width++) { System.out.print(width + "\t"); } for (int iDepth = 0; iDepth < depth(); iDepth++) { System.out.print("\n" + iDepth + "\t"); for (int iWidth = 0; iWidth < width(); iWidth++) { final OpenHeightSpan span = getData(iWidth, iDepth); if (span == null) { System.out.print(" \t"); } else { System.out.print(span.regionID() + "\t"); } } } System.out.println(); } /** * The number of regions in the height field. * <p> * Includes the null region. So unless all spans are in the null region, * this value will be >= 2. (E.g. The null region and at least one other * region.) * </p> */ public int regionCount() { return this.mRegionCount; } /** * Sets the region count. * <p> * The region count is expected to be zero based with the zero region * representing the null region. So the region count is expected to be one * more that the maximum region value. E.g. If the highest region value is * 14, then the region count is 15. * </p> * <p> * IMPORTANT: There is no automatic region count management. Region count * must be managed manually. * </p> * * @param value * The new region count. */ public void setRegionCount(final int value) { this.mRegionCount = value; } /** * The number of spans in the heightfield. * <p> * A value of zero indicates an empty heightfield. * </p> */ public int spanCount() { return this.mSpanCount; } /** * Calculates the min/max distance a span in the field is from it * nearest border. Allows on-demand calculation of this information. */ private void calcBorderDistanceBounds() { if (this.mSpanCount == 0) { return; } // Default the values. this.mMinBorderDistance = Integer.MAX_VALUE; this.mMaxBorderDistance = UNKNOWN; // Iterate through all spans and reset the values if new min/max's are // found. final OpenHeightFieldIterator iter = this.new OpenHeightFieldIterator(); while (iter.hasNext()) { final OpenHeightSpan span = iter.next(); this.mMinBorderDistance = Math.min(this.mMinBorderDistance, span.distanceToBorder()); this.mMaxBorderDistance = Math.max(this.mMaxBorderDistance, span.distanceToBorder()); } if (this.mMinBorderDistance == Integer.MAX_VALUE) { // There ware a problem locating the maximum. // So set it back to unknown. this.mMinBorderDistance = UNKNOWN; } } }