parent
18db26292f
commit
f9ce9e246c
@ -0,0 +1,241 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.triangulator; |
||||||
|
|
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Matrix3f; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.IrPolygon; |
||||||
|
import com.jme3.scene.plugins.IrVertex; |
||||||
|
import java.util.ArrayList; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implemented according to |
||||||
|
* <ul> |
||||||
|
* <li>http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf</li>
|
||||||
|
* <li>http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html</li>
|
||||||
|
* </ul> |
||||||
|
*/ |
||||||
|
public final class EarClippingTriangulator { |
||||||
|
|
||||||
|
private static enum VertexType { |
||||||
|
Convex, |
||||||
|
Reflex, |
||||||
|
Ear; |
||||||
|
} |
||||||
|
|
||||||
|
private final ArrayList<Integer> indices = new ArrayList<Integer>(); |
||||||
|
private final ArrayList<VertexType> types = new ArrayList<VertexType>(); |
||||||
|
private final ArrayList<Vector2f> positions = new ArrayList<Vector2f>(); |
||||||
|
|
||||||
|
public EarClippingTriangulator() { |
||||||
|
} |
||||||
|
|
||||||
|
private static int ccw(Vector2f p0, Vector2f p1, Vector2f p2) { |
||||||
|
float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x); |
||||||
|
if (result > 0) { |
||||||
|
return 1; |
||||||
|
} else if (result < 0) { |
||||||
|
return -1; |
||||||
|
} else { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean pointInTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) { |
||||||
|
float d = ((t1.y - t2.y) * (t0.x - t2.x) + (t2.x - t1.x) * (t0.y - t2.y)); |
||||||
|
float a = ((t1.y - t2.y) * (p.x - t2.x) + (t2.x - t1.x) * (p.y - t2.y)) / d; |
||||||
|
float b = ((t2.y - t0.y) * (p.x - t2.x) + (t0.x - t2.x) * (p.y - t2.y)) / d; |
||||||
|
float c = 1 - a - b; |
||||||
|
return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1; |
||||||
|
} |
||||||
|
|
||||||
|
private static Matrix3f normalToMatrix(Vector3f norm) { |
||||||
|
Vector3f tang1 = norm.cross(Vector3f.UNIT_X); |
||||||
|
if (tang1.lengthSquared() < FastMath.ZERO_TOLERANCE) { |
||||||
|
tang1 = norm.cross(Vector3f.UNIT_Y); |
||||||
|
} |
||||||
|
tang1.normalizeLocal(); |
||||||
|
Vector3f tang2 = norm.cross(tang1).normalizeLocal(); |
||||||
|
|
||||||
|
return new Matrix3f( |
||||||
|
tang1.x, tang1.y, tang1.z, |
||||||
|
tang2.x, tang2.y, tang2.z, |
||||||
|
norm.x, norm.y, norm.z); |
||||||
|
} |
||||||
|
|
||||||
|
private int prev(int index) { |
||||||
|
if (index == 0) { |
||||||
|
return indices.size() - 1; |
||||||
|
} else { |
||||||
|
return index - 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private int next(int index) { |
||||||
|
if (index == indices.size() - 1) { |
||||||
|
return 0; |
||||||
|
} else { |
||||||
|
return index + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private VertexType calcType(int index) { |
||||||
|
int prev = prev(index); |
||||||
|
int next = next(index); |
||||||
|
|
||||||
|
Vector2f p0 = positions.get(prev); |
||||||
|
Vector2f p1 = positions.get(index); |
||||||
|
Vector2f p2 = positions.get(next); |
||||||
|
|
||||||
|
if (ccw(p0, p1, p2) <= 0) { |
||||||
|
return VertexType.Reflex; |
||||||
|
} else { |
||||||
|
for (int i = 0; i < positions.size() - 3; i++) { |
||||||
|
int testIndex = (index + 2 + i) % positions.size(); |
||||||
|
if (types.get(testIndex) != VertexType.Reflex) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
Vector2f p = positions.get(testIndex); |
||||||
|
if (pointInTriangle(p0, p1, p2, p)) { |
||||||
|
return VertexType.Convex; |
||||||
|
} |
||||||
|
} |
||||||
|
return VertexType.Ear; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void updateType(int index) { |
||||||
|
if (types.get(index) == VertexType.Convex) { |
||||||
|
return; |
||||||
|
} |
||||||
|
types.set(index, calcType(index)); |
||||||
|
} |
||||||
|
|
||||||
|
private void loadVertices(IrVertex[] vertices) { |
||||||
|
indices.ensureCapacity(vertices.length); |
||||||
|
types.ensureCapacity(vertices.length); |
||||||
|
positions.ensureCapacity(vertices.length); |
||||||
|
|
||||||
|
Vector3f normal = FastMath.computeNormal( |
||||||
|
vertices[0].pos, |
||||||
|
vertices[1].pos, |
||||||
|
vertices[2].pos); |
||||||
|
|
||||||
|
Matrix3f transform = normalToMatrix(normal); |
||||||
|
|
||||||
|
for (int i = 0; i < vertices.length; i++) { |
||||||
|
Vector3f projected = transform.mult(vertices[i].pos); |
||||||
|
indices.add(i); |
||||||
|
positions.add(new Vector2f(projected.x, projected.y)); |
||||||
|
types.add(VertexType.Reflex); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < vertices.length; i++) { |
||||||
|
types.set(i, calcType(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private IrPolygon createTriangle(IrPolygon polygon, int prev, int index, int next) { |
||||||
|
int p0 = indices.get(prev); |
||||||
|
int p1 = indices.get(index); |
||||||
|
int p2 = indices.get(next); |
||||||
|
IrPolygon triangle = new IrPolygon(); |
||||||
|
triangle.vertices = new IrVertex[] { |
||||||
|
polygon.vertices[p0], |
||||||
|
polygon.vertices[p1], |
||||||
|
polygon.vertices[p2], |
||||||
|
}; |
||||||
|
return triangle; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Triangulates the given polygon. |
||||||
|
* |
||||||
|
* Five or more vertices are required, if less are given, an exception |
||||||
|
* is thrown. |
||||||
|
* |
||||||
|
* @param polygon The polygon to triangulate. |
||||||
|
* @return N - 2 triangles, where N is the number of vertices in the polygon. |
||||||
|
* |
||||||
|
* @throws IllegalArgumentException If the polygon has less than 5 vertices. |
||||||
|
*/ |
||||||
|
public IrPolygon[] triangulate(IrPolygon polygon) { |
||||||
|
if (polygon.vertices.length < 5) { |
||||||
|
throw new IllegalArgumentException("Only polygons with 5 or more vertices are supported"); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
int numTris = 0; |
||||||
|
IrPolygon[] triangles = new IrPolygon[polygon.vertices.length - 2]; |
||||||
|
|
||||||
|
loadVertices(polygon.vertices); |
||||||
|
|
||||||
|
int index = 0; |
||||||
|
while (types.size() > 3) { |
||||||
|
if (types.get(index) == VertexType.Ear) { |
||||||
|
int prev = prev(index); |
||||||
|
int next = next(index); |
||||||
|
|
||||||
|
triangles[numTris++] = createTriangle(polygon, prev, index, next); |
||||||
|
|
||||||
|
indices.remove(index); |
||||||
|
types.remove(index); |
||||||
|
positions.remove(index); |
||||||
|
|
||||||
|
next = next(prev); |
||||||
|
updateType(prev); |
||||||
|
updateType(next); |
||||||
|
|
||||||
|
index = next(next); |
||||||
|
} else { |
||||||
|
index = next(index); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (types.size() == 3) { |
||||||
|
triangles[numTris++] = createTriangle(polygon, 0, 1, 2); |
||||||
|
} |
||||||
|
|
||||||
|
if (numTris != triangles.length) { |
||||||
|
throw new AssertionError("Triangulation failed to generate enough triangles"); |
||||||
|
} |
||||||
|
|
||||||
|
return triangles; |
||||||
|
} finally { |
||||||
|
indices.clear(); |
||||||
|
positions.clear(); |
||||||
|
types.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 jMonkeyEngine |
||||||
|
* All rights reserved. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are |
||||||
|
* met: |
||||||
|
* |
||||||
|
* * Redistributions of source code must retain the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer. |
||||||
|
* |
||||||
|
* * Redistributions in binary form must reproduce the above copyright |
||||||
|
* notice, this list of conditions and the following disclaimer in the |
||||||
|
* documentation and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||||
|
* may be used to endorse or promote products derived from this software |
||||||
|
* without specific prior written permission. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
package com.jme3.scene.plugins.triangulator; |
||||||
|
|
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.IrPolygon; |
||||||
|
import com.jme3.scene.plugins.IrVertex; |
||||||
|
import junit.framework.TestCase; |
||||||
|
|
||||||
|
public class TriangulatorTest extends TestCase { |
||||||
|
|
||||||
|
public void testTriangulator() { |
||||||
|
Vector3f[] dataSet = new Vector3f[]{ |
||||||
|
new Vector3f(0.75f, 0.3f, 1.2f), |
||||||
|
new Vector3f(0.75f, 0.3f, 0.0f), |
||||||
|
new Vector3f(0.75f, 0.17f, 0.0f), |
||||||
|
new Vector3f(0.75000095f, 0.17f, 1.02f), |
||||||
|
new Vector3f(0.75f, -0.17f, 1.02f), |
||||||
|
new Vector3f(0.75f, -0.17f, 0.0f), |
||||||
|
new Vector3f(0.75f, -0.3f, 0.0f), |
||||||
|
new Vector3f(0.75f, -0.3f, 1.2f) |
||||||
|
}; |
||||||
|
|
||||||
|
IrPolygon poly = new IrPolygon(); |
||||||
|
poly.vertices = new IrVertex[dataSet.length]; |
||||||
|
for (int i = 0; i < dataSet.length; i++) { |
||||||
|
poly.vertices[i] = new IrVertex(); |
||||||
|
poly.vertices[i].pos = dataSet[i]; |
||||||
|
} |
||||||
|
|
||||||
|
EarClippingTriangulator triangulator = new EarClippingTriangulator(); |
||||||
|
triangulator.triangulate(poly); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue