/* * 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; /** * Adds vertices to a contour such that no null region edge segment exceeds * the allowed edge length. * <p> * Only null region edges are operated on. * </p> * <p> * <a href= * "http://www.critterai.org/projects/nmgen/images/main_maxedge_off.png" * target="_parent"> <img class="insert" height="464" src= * "http://www.critterai.org/projects/nmgen/images/main_maxedge_off.jpg" * width="620" /> </a> * </p> * <p> * <a href= "http://www.critterai.org/projects/nmgen/images/main_maxedge_on.png" * target="_parent"> <img class="insert" height="464" src= * "http://www.critterai.org/projects/nmgen/images/main_maxedge_on.jpg" * width="620" /> </a> * </p> */ public final class NullRegionMaxEdge implements IContourAlgorithm { private static final int NULL_REGION = OpenHeightSpan.NULL_REGION; /** * The maximum length allowed for line segments that are part of a null * region edge. */ private final int mMaxEdgeLength; /** * Constructor * * @param maxEdgeLength * The maximum length of polygon edges that * represent the border of the navmesh. * <p> * More vertices will be added to navmesh border edges if this value * is exceeded for a particular edge. In certain cases this will * reduce the number of thin, long triangles in the navmesh. * </p> * <p> * A value of zero will disable this feature. * </p> * <p> * Constraints: >= 0 * </p> */ public NullRegionMaxEdge(final int maxEdgeLength) { this.mMaxEdgeLength = Math.max(maxEdgeLength, 0); } /** * {@inheritDoc} * <p> * The resultVerts argument is expected to be seeded with at least one edge. * </p> */ @Override public void apply(final ArrayList<Integer> sourceVerts, final ArrayList<Integer> resultVerts) { // See the interface documentation for details on what the argument // lists contain. if (this.mMaxEdgeLength <= 0) { return; } final int sourceVertCount = sourceVerts.size() / 4; int resultVertCount = resultVerts.size() / 4; int iVertA = 0; /* * Insert verts into null-region edges that are two long. * * The basic process is to look at each edge in the result list. * If it connects to the null region and exceeds the allowed length * then insert a vertex from the source vertices list that is * closest to the middle of the current edge, splitting it in half. * * Note: The number of result vertices may increase, which is why a * while loop is being used. */ while (iVertA < resultVertCount) { // Get vertices for the current edge. // Wrap if necessary. final int iVertB = (iVertA + 1) % resultVertCount; final int ax = resultVerts.get(iVertA * 4); final int az = resultVerts.get((iVertA * 4) + 2); final int iVertASource = resultVerts.get((iVertA * 4) + 3); final int bx = resultVerts.get(iVertB * 4); final int bz = resultVerts.get((iVertB * 4) + 2); final int iVertBSource = resultVerts.get((iVertB * 4) + 3); int iNewVert = -1; // -1 indicates no need to add new vertex. // Wrap if necessary. final int iTestVert = (iVertASource + 1) % sourceVertCount; // Find maximum deviation from the edge. if (sourceVerts.get((iTestVert * 4) + 3) == NULL_REGION) { // This is a null-region edge. Check its length against // the maximum allowed. final int dx = bx - ax; final int dz = bz - az; if (((dx * dx) + (dz * dz)) > (this.mMaxEdgeLength * this.mMaxEdgeLength)) { // The current edge is too long and needs to be split. // Find original number of vertices between the vertA // and vertB. final int indexDistance = iVertBSource < iVertASource ? (iVertBSource + (sourceVertCount - iVertASource)) : (iVertBSource - iVertASource); // Choose the vertex that is half way between vertA // and vertB. Not distance wise, but step wise. // More wrapping. iNewVert = (iVertASource + (indexDistance / 2)) % sourceVertCount; } } if (iNewVert != -1) { // A new vertex needs to be inserted. Do it. resultVerts.add((iVertA + 1) * 4, sourceVerts.get(iNewVert * 4)); resultVerts.add(((iVertA + 1) * 4) + 1, sourceVerts.get((iNewVert * 4) + 1)); resultVerts.add(((iVertA + 1) * 4) + 2, sourceVerts.get((iNewVert * 4) + 2)); resultVerts.add(((iVertA + 1) * 4) + 3, iNewVert); // Update the vertex count since a new vertex was added. resultVertCount = resultVerts.size() / 4; // The vertex index is not being incremented because on the // next loop we want to perform the check from iVertA to // the newly inserted vertex. } else { // This edge is finished. Move to the next vertex. iVertA++; } } } }