commit
f9500f955f
@ -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