/*
* 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.ArrayList;
/**
* Applies an algorithm to contours which results in null-region edges
* following the original detail source geometry edge more closely.
*
* @see <a href="http://www.critterai.org/nmgen_contourgen#nulledgesimple"
* target="_parent">Visualizations</a>
*/
public final class MatchNullRegionEdges implements IContourAlgorithm {
/*
* Recast Reference: simplifyContour() in RecastContour.cpp
*/
private static final int NULL_REGION = OpenHeightSpan.NULL_REGION;
/**
* The maximum distance the edge of the contour may deviate from the source
* geometry.
*/
private final float mThreshold;
/**
* Constructor.
*
* @param threshold
* The maximum distance the edge of the contour may
* deviate from the source geometry.
* <p>
* Setting this lower will result in the navmesh edges following the
* geometry contour more accurately at the expense of an increased
* vertex count.
* </p>
* <p>
* Setting the value to zero is not recommended since it can result
* in a large increase in the number of vertices at a high processing
* cost.
* </p>
* <p>
* Constraints: >= 0
* </p>
*/
public MatchNullRegionEdges(final float threshold) {
this.mThreshold = Math.max(threshold, 0);
}
/**
* {@inheritDoc}
* <p>
* Adds vertices from the source list to the result list such that if any
* null region vertices are compared against the result list, none of the
* vertices will be further from the null region edges than the allowed
* threshold.
* </p>
* <p>
* Only null-region edges are operated on. All other edges are ignored.
* </p>
* <p>
* The result vertices is expected to be seeded with at least two source
* vertices.
* </p>
*/
@Override
public void apply(final ArrayList<Integer> sourceVerts,
final ArrayList<Integer> inoutResultVerts) {
if ((sourceVerts == null) || (inoutResultVerts == null)) {
return;
}
final int sourceVertCount = sourceVerts.size() / 4;
int simplifiedVertCount = inoutResultVerts.size() / 4; // Will change.
int iResultVertA = 0;
/*
* Loop through all edges in this contour.
*
* NOTE: The simplifiedVertCount in the loop condition
* increases over iterations. That is what keeps the loop going beyond
* the initial vertex count.
*/
while (iResultVertA < simplifiedVertCount) {
final int iResultVertB = (iResultVertA + 1) % simplifiedVertCount;
// The line segment's beginning vertex.
final int ax = inoutResultVerts.get(iResultVertA * 4);
final int az = inoutResultVerts.get((iResultVertA * 4) + 2);
final int iVertASource = inoutResultVerts.get((iResultVertA * 4) + 3);
// The line segment's ending vertex.
final int bx = inoutResultVerts.get(iResultVertB * 4);
final int bz = inoutResultVerts.get((iResultVertB * 4) + 2);
final int iVertBSource = inoutResultVerts.get((iResultVertB * 4) + 3);
// The source index of the next vertex to test. (The vertex just
// after the current vertex in the source vertex list.)
int iTestVert = (iVertASource + 1) % sourceVertCount;
float maxDeviation = 0;
// Default to no index. No new vert to add.
int iVertToInsert = -1;
if (sourceVerts.get((iTestVert * 4) + 3) == NULL_REGION) {
/*
* This test vertex is part of a null region edge.
* Loop through the source vertices until the end vertex
* is found, searching for the vertex that is farthest from
* the line segment formed by the begin/end vertices.
*
* Visualizations:
* http://www.critterai.org/nmgen_contourgen#nulledgesimple
*/
while (iTestVert != iVertBSource) {
final float deviation =
Geometry.getPointSegmentDistanceSq(sourceVerts.get(iTestVert * 4),
sourceVerts.get((iTestVert * 4) + 2), ax, az, bx, bz);
if (deviation > maxDeviation) {
// A new maximum deviation was detected.
maxDeviation = deviation;
iVertToInsert = iTestVert;
}
// Move to the next vertex.
iTestVert = (iTestVert + 1) % sourceVertCount;
}
}
if ((iVertToInsert != -1) && (maxDeviation > (this.mThreshold * this.mThreshold))) {
// A vertex was found that is further than allowed from the
// current edge. Add this vertex to the contour.
inoutResultVerts.add((iResultVertA + 1) * 4, sourceVerts.get(iVertToInsert * 4));
inoutResultVerts.add(((iResultVertA + 1) * 4) + 1,
sourceVerts.get((iVertToInsert * 4) + 1));
inoutResultVerts.add(((iResultVertA + 1) * 4) + 2,
sourceVerts.get((iVertToInsert * 4) + 2));
inoutResultVerts.add(((iResultVertA + 1) * 4) + 3, iVertToInsert);
// Update the vertex count since a new vertex was added.
simplifiedVertCount = inoutResultVerts.size() / 4;
// Not incrementing the vertex since we need to test the edge
// formed by vertA and this this new vertex on the next
// iteration of the loop.
} else {
// This edge segment does not need to be altered. Move to
// the next vertex.
iResultVertA++;
}
}
}
}