/* * 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; /** * Contains data that represents the obstructed (solid) area of a bounded * field of voxels. * <p> * The data is stored within spans which represent a vertically contiguous group * of solid voxels. A reference to the lowest span within a grid location is * stored at that grid location's width/depth index. Height spans further up in * the grid's column can be accessed via {@link HeightSpan#next()} on the base * span. (I.e. By climbing up the links.) * </p> * * @see SolidHeightfieldBuilder * @see <a href="http://www.critterai.org/nmgen_voxel" target="_parent">The * Voxelization Process</a> * @see <a href="http://www.critterai.org/nmgen_hfintro" * target="_parent">Introduction to Height Fields</a> */ public final class SolidHeightfield extends BoundedField { /* * Recast Reference: reHeightfield in Recast.h */ /** * Implements 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> */ public class SolidHeightFieldIterator implements Iterator<HeightSpan> { private int mNextWidth = 0; private int mNextDepth = 0; private HeightSpan mNext = null; private int mLastWidth = 0; private int mLastDepth = 0; private SolidHeightFieldIterator() { moveToNext(); } /** * 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 HeightSpan next() { if (this.mNext == null) { throw new NoSuchElementException(); } final HeightSpan next = this.mNext; this.mLastWidth = this.mNextWidth; this.mLastDepth = this.mNextDepth; 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; } /** * Move to the next span in the data set. */ private void moveToNext() { if (this.mNext != null) { // There is a current span selected. if (this.mNext.next() != null) { // The current span has a next. // Move to it. this.mNext = this.mNext.next(); return; } else { // No more spans in the column of the current grid location. // Move to next grid location. this.mNextWidth++; } } // Search through the grid until a new base span is found. for (int depthIndex = this.mNextDepth; depthIndex < depth(); depthIndex++) { for (int widthIndex = this.mNextWidth; widthIndex < width(); widthIndex++) { final HeightSpan span = SolidHeightfield.this.mSpans.get(gridIndex(widthIndex, depthIndex)); if (span != null) { // A new base span was found. Select it. this.mNext = span; this.mNextWidth = widthIndex; this.mNextDepth = depthIndex; return; } } this.mNextWidth = 0; } // If got here, then there are no more spans. this.mNext = null; this.mNextDepth = -1; this.mNextWidth = -1; } } /** * Contains the spans within the heightfield's grid. * <p> * Key: Grid index obtained via {@link #gridIndex(int, int)}.<br/> * Value: The lowest span at the grid location, or null if there are no * spans at the grid location. * </p> */ private final Hashtable<Integer, HeightSpan> mSpans = new Hashtable<Integer, HeightSpan>(); /** * Constructor * <p> * The bounds of the field will default to min(0, 0, 0) and max(1, 1, 1). * </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 SolidHeightfield(final float cellSize, final float cellHeight) { super(cellSize, cellHeight); } /** * Adds span data to the heightfield. New span data is either merged * into existing spans or a new span is created. * <p> * Only the following validations are peformed: * </p> * <ul> * <li>The bounds of the width and depth indices.</li> * <li>The lower bounds of the height indices. (>=0)</li> * <li>Height min <= max.</li> * </ul> * <p> * No check that the height maximum is within bounds is performed. * </p> * <p> * Flags are set as follows: * </p> * <ul> * <li>If the maximum of the new data coincides with the maximum of an * existing span, the old and new flags are merged.</li> * <li>If the new data represents a new maximum (new span or new maximum for * an existing span), the flags for the new data is used exclusively.</li> * <li>Otherwise, the new data's flags are ignored.</li> * </ul> * <p> * Basically, only the flags at the top of a span are considered to matter. * </p> * * @param widthIndex * The width index of the column that contains the * new data. * @param depthIndex * The depth index of the column that contains the * new data. * @param heightIndexMin * The solid span's minimum. The minimum of the * obstructed space. (In zero-based height increments based on * {@link #cellHeight()}.) * @param heightIndexMax * The solid span's maximum. The maximum of the * obstructed space. (In zero-based height increments based on * {@link #cellHeight()}.) * @param flags * The flags for the new data. * @return TRUE if the data was successfully added. Otherwise FALSE. * The only time this operation will fail is if the argument data is * invalid in some way. */ public boolean addData(final int widthIndex, final int depthIndex, final int heightIndexMin, final int heightIndexMax, final int flags) { if ((widthIndex < 0) || (widthIndex >= width()) || (depthIndex < 0) || (depthIndex >= depth())) { // Outside of grid bounds. return false; } if ((heightIndexMin < 0) || (heightIndexMax < 0) || (heightIndexMin > heightIndexMax)) { // Invalid height values. return false; } // Find the grid location of the span and get existing data for the // location. final int gridIndex = gridIndex(widthIndex, depthIndex); HeightSpan currentSpan = this.mSpans.get(gridIndex); if (currentSpan == null) { // This is the first span for this grid location. // Generate a new span. this.mSpans.put(gridIndex, new HeightSpan(heightIndexMin, heightIndexMax, flags)); return true; } // Span data already exists at this location. Search the spans in // this column to see which one should contain this span. Or if a // new span should be created. HeightSpan previousSpan = null; while (currentSpan != null) { /* * Note: The way the spans are built, separate spans are always * guaranteed to have a gap between them. The minimum gap will * be the cell height increment. */ if (currentSpan.min() > (heightIndexMax + 1)) { /* * The new span is below the current span and NOT adjacent. * Due to the structure of the data, the new span is * guaranteed to fit below the current span. * * Create a new span. */ final HeightSpan newSpan = new HeightSpan(heightIndexMin, heightIndexMax, flags); // Insert this span below the current span. newSpan.setNext(currentSpan); if (previousSpan == null) { // The new span is the new first span in this column. // Insert it at the base of this column. this.mSpans.put(gridIndex, newSpan); } else { // The new span is between two spans. // Link the previous span to the new span. previousSpan.setNext(newSpan); } return true; } else if (currentSpan.max() < (heightIndexMin - 1)) { // Current span is below the new span and NOT adjacent. if (currentSpan.next() == null) { // The new span is the final span. // Insert it above the current span. currentSpan.setNext(new HeightSpan(heightIndexMin, heightIndexMax, flags)); return true; } // Continue searching up the span's in this column. previousSpan = currentSpan; currentSpan = currentSpan.next(); } else { /* * There is either overlap or adjacency between the current * span and the new span. * Need to perform a merge of some type. * Will always return after the merge is complete. * Get easy stuff out of the way first. */ if (heightIndexMin < currentSpan.min()) { // This span will result in a new minimum for the current // span. Adjust the current span's minimum. currentSpan.setMin(heightIndexMin); } if (heightIndexMax == currentSpan.max()) { // The new span ends at same height as current span. // Merge flags. currentSpan.setFlags((byte) (currentSpan.flags() | flags)); return true; } if (currentSpan.max() > heightIndexMax) { // The top of the current span is higher than the new span. // So discard the new span's flag. return true; } // The new spans's maximum height is higher than the current // span's maximum height. // Need to search up the spans to find where the merge ends. HeightSpan nextSpan = currentSpan.next(); while (true) { if ((nextSpan == null) || (nextSpan.min() > (heightIndexMax + 1))) { /* * There are no spans above the current span, or the * height increase caused by this span will not touch * the next span. Can just expand the current span * upward. */ currentSpan.setMax(heightIndexMax); // New span is new "top", so its flags replace current // span's flags. currentSpan.setFlags(flags); if (nextSpan == null) { // The current span is at the top of the column. // Get rid of any links it may have had. currentSpan.setNext(null); } else { // Take care of re-pointing. (Some spans may have // been encompassed.) currentSpan.setNext(nextSpan); } // Finished. return true; } // The new height of the current span will touch the next // span in some manner. Merging is needed. if ((nextSpan.min() == (heightIndexMax + 1)) || (heightIndexMax <= nextSpan.max())) { // No gap between current and next spans, but no // overlap with next span. (Spans abut each other.) // Encompass the next span. currentSpan.setMax(nextSpan.max()); // Set the current span to point the the encompassed // span's next span. currentSpan.setNext(nextSpan.next()); // Take the flags of the next span since we know the // next span's max is higher than the current span. currentSpan.setFlags(nextSpan.flags()); if (heightIndexMax == currentSpan.max()) { // New span ends at same height as merged span. // Merge flags. currentSpan.setFlags(currentSpan.flags() | flags); return true; } return true; } // The current span overlaps with the next span. // Need to continue up the column to see if the next span // will be fully engulfed. nextSpan = nextSpan.next(); } } } // Will only ever get here if there is a code logic error. return false; } /** * Provides an iterator that iterates all spans in the field. * <p> * Unlike {@link #getData(int, int)}, this iterator will iterate through all * spans, not just the base spans. So their is no need to use * {@link HeightSpan#next()} to climb the span structure. * </p> */ public SolidHeightFieldIterator dataIterator() { return this.new SolidHeightFieldIterator(); } /** * Gets the lowest span at the grid location, or null if there are no * spans at the location. * <p> * The data will be the lowest span at the grid location. * </p> * * @return The lowest span at the grid location. */ public HeightSpan getData(final int widthIndex, final int depthIndex) { return this.mSpans.get(gridIndex(widthIndex, depthIndex)); } /** * Indicates whether or not the field contains any spans. If FALSE is * returned, then the field does not contain any obstructed space. * * @return TRUE if the field contains spans. Otherwise FALSE. */ public boolean hasSpans() { return (this.mSpans.size() > 0); } }