From c7dc73e85ffd0f24dec0ee47c0cea31446b4656d Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sun, 7 Feb 2016 14:58:30 +0100 Subject: [PATCH] Bugfix: fixed incorrect faces' triangulation. --- .../scene/plugins/blender/meshes/Edge.java | 102 +++++++++++--- .../scene/plugins/blender/meshes/Face.java | 133 +++++++++++------- 2 files changed, 165 insertions(+), 70 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java index 2291b5e56..1d76fc02f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java @@ -10,6 +10,7 @@ import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.math.Vector3d; import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate; /** @@ -24,6 +25,8 @@ public class Edge { /** The vertices indexes. */ private int index1, index2; + /** The vertices that can be set if we need and abstract edge outside the mesh (for computations). */ + private Vector3f v1, v2; /** The weight of the edge. */ private float crease; /** A variable that indicates if this edge belongs to any face or not. */ @@ -31,6 +34,13 @@ public class Edge { /** The mesh that owns the edge. */ private TemporalMesh temporalMesh; + public Edge(Vector3f v1, Vector3f v2) { + this.v1 = v1 == null ? new Vector3f() : v1; + this.v2 = v2 == null ? new Vector3f() : v2; + index1 = 0; + index2 = 1; + } + /** * This constructor only stores the indexes of the vertices. The position vertices should be stored * outside this class. @@ -74,14 +84,14 @@ public class Edge { * @return the first vertex of the edge */ public Vector3f getFirstVertex() { - return temporalMesh.getVertices().get(index1); + return temporalMesh == null ? v1 : temporalMesh.getVertices().get(index1); } /** * @return the second vertex of the edge */ public Vector3f getSecondVertex() { - return temporalMesh.getVertices().get(index2); + return temporalMesh == null ? v2 : temporalMesh.getVertices().get(index2); } /** @@ -188,28 +198,82 @@ public class Edge { * @return true if the edges cross and false otherwise */ public boolean cross(Edge edge) { - Vector3f P1 = this.getFirstVertex(); - Vector3f P2 = edge.getFirstVertex(); - Vector3f u = this.getSecondVertex().subtract(P1); - Vector3f v = edge.getSecondVertex().subtract(P2); - float t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y); - float t1 = (P2.x - P1.x + v.x * t2) / u.x; - Vector3f p1 = P1.add(u.mult(t1)); - Vector3f p2 = P2.add(v.mult(t2)); + return this.getCrossPoint(edge) != null; + } + + /** + * The method computes the crossing pint of this edge and another edge. If + * there is no crossing then null is returned. + * + * @param edge + * the edge to compute corss point with + * @return cross point on null if none exist + */ + public Vector3f getCrossPoint(Edge edge) { + return this.getCrossPoint(edge, false, false); + } + + /** + * The method computes the crossing pint of this edge and another edge. If + * there is no crossing then null is returned. This method also allows to + * get the crossing point of the straight lines that contain these edges if + * you set the 'extend' parameter to true. + * + * @param edge + * the edge to compute corss point with + * @param extendThisEdge + * set to true to find a crossing point along the whole + * straight that contains the current edge + * @param extendSecondEdge + * set to true to find a crossing point along the whole + * straight that contains the given edge + * @return cross point on null if none exist + */ + public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) { + Vector3d P1 = new Vector3d(this.getFirstVertex()); + Vector3d P2 = new Vector3d(edge.getFirstVertex()); + Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal(); + Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal(); + + double t1 = 0, t2 = 0; + if(u.x == 0 && v.x == 0) { + t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y); + t1 = (P2.z - P1.z + v.z * t2) / u.z; + } else if(u.y == 0 && v.y == 0) { + t2 = (u.x * (P2.z - P1.z) - u.z * (P2.x - P1.x)) / (u.z * v.x - u.x * v.z); + t1 = (P2.x - P1.x + v.x * t2) / u.x; + } else if(u.z == 0 && v.z == 0) { + t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y); + t1 = (P2.x - P1.x + v.x * t2) / u.x; + } else { + t2 = (P1.y * u.x - P1.x * u.y + P2.x * u.y - P2.y * u.x) / (v.y * u.x - u.y * v.x); + t1 = (P2.x - P1.x + v.x * t2) / u.x; + if(Math.abs(P1.z - P2.z + u.z * t1 - v.z * t2) > FastMath.FLT_EPSILON) { + return null; + } + } + Vector3d p1 = P1.add(u.mult(t1)); + Vector3d p2 = P2.add(v.mult(t2)); - if (p1.distance(p2) <= FastMath.FLT_EPSILON) { - // the lines cross, check if p1 and p2 are within the edges - Vector3f p = p1.subtract(P1); - float cos = p.dot(u) / (p.length() * u.length()); - if (cos > 0 && p.length() <= u.length()) { + if (p1.distance(p2) <= FastMath.FLT_EPSILON) { + if(extendThisEdge && extendSecondEdge) { + return p1.toVector3f(); + } + // the lines cross, check if p1 and p2 are within the edges + Vector3d p = p1.subtract(P1); + double cos = p.dot(u) / p.length(); + if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= this.getLength()) { // p1 is inside the first edge, lets check the other edge now p = p2.subtract(P2); - cos = p.dot(v) / (p.length() * v.length()); - return cos > 0 && p.length() <= u.length(); + cos = p.dot(v) / p.length(); + if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= edge.getLength()) { + return p1.toVector3f(); + } } } - return false; - } + + return null; + } @Override public String toString() { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java index 34db4598d..a41d58ff0 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java @@ -276,30 +276,45 @@ public class Face implements Comparator { List facesToTriangulate = new ArrayList(Arrays.asList(this.clone())); while (facesToTriangulate.size() > 0) { Face face = facesToTriangulate.remove(0); - int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1; - while (face.vertexCount() > 0) { - indexes[0] = face.getIndex(0); - indexes[1] = face.findClosestVertex(indexes[0], -1); - indexes[2] = face.findClosestVertex(indexes[0], indexes[1]); - - LOGGER.finer("Veryfying improper triangulation of the temporal mesh."); - if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) { - throw new BlenderFileException("Unable to find two closest vertices while triangulating face in mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!"); - } - if (previousIndex1 == indexes[0] && previousIndex2 == indexes[1] && previousIndex3 == indexes[2]) { - throw new BlenderFileException("Infinite loop detected during triangulation of mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!"); - } - previousIndex1 = indexes[0]; - previousIndex2 = indexes[1]; - previousIndex3 = indexes[2]; + // two special cases will improve the computations speed + if(face.getIndexes().size() == 3) { + triangulatedFaces.add(face.getIndexes().clone()); + } else if(face.getIndexes().size() == 4) { + // in case face has 4 verts we use the plain triangulation + indexes[0] = face.getIndex(0); + indexes[1] = face.getIndex(1); + indexes[2] = face.getIndex(2); + triangulatedFaces.add(new IndexesLoop(indexes)); + + indexes[1] = face.getIndex(2); + indexes[2] = face.getIndex(3); + triangulatedFaces.add(new IndexesLoop(indexes)); + } else { + int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1; + while (face.vertexCount() > 0) { + indexes[0] = face.getIndex(0); + indexes[1] = face.findClosestVertex(indexes[0], -1); + indexes[2] = face.findClosestVertex(indexes[0], indexes[1]); + + LOGGER.finer("Veryfying improper triangulation of the temporal mesh."); + if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) { + throw new BlenderFileException("Unable to find two closest vertices while triangulating face in mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!"); + } + if (previousIndex1 == indexes[0] && previousIndex2 == indexes[1] && previousIndex3 == indexes[2]) { + throw new BlenderFileException("Infinite loop detected during triangulation of mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!"); + } + previousIndex1 = indexes[0]; + previousIndex2 = indexes[1]; + previousIndex3 = indexes[2]; - Arrays.sort(indexes, this); - facesToTriangulate.addAll(face.detachTriangle(indexes)); - triangulatedFaces.add(new IndexesLoop(indexes)); + Arrays.sort(indexes, this); + facesToTriangulate.addAll(face.detachTriangle(indexes)); + triangulatedFaces.add(new IndexesLoop(indexes)); + } } } } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Errors occured during face triangulation: {0}. The face will be triangulated with the most direct algorithm, " + "but the results might not be identical to blender.", e.getLocalizedMessage()); + LOGGER.log(Level.WARNING, "Errors occured during face triangulation: {0}. The face will be triangulated with the most direct algorithm, but the results might not be identical to blender.", e.getLocalizedMessage()); indexes[0] = this.getIndex(0); for (int i = 1; i < this.vertexCount() - 1; ++i) { indexes[1] = this.getIndex(i); @@ -308,7 +323,7 @@ public class Face implements Comparator { } } } - + /** * @return true if the face is smooth and false otherwise */ @@ -382,11 +397,9 @@ public class Face implements Comparator { int index2 = edge.getSecondIndex(); // check if the line between the vertices is not a border edge of the face if (!indexes.areNeighbours(index1, index2)) { - List vertices = temporalMesh.getVertices(); - for (int i = 0; i < indexes.size(); ++i) { - int i1 = this.getIndex(i); - int i2 = this.getIndex(i + 1); + int i1 = this.getIndex(i - 1); + int i2 = this.getIndex(i); // check if the edges have no common verts (because if they do, they cannot cross) if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) { if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) { @@ -395,35 +408,53 @@ public class Face implements Comparator { } } - // the edge does NOT cross any of other edges, so now we need to verify if it is inside the face or outside - // we check it by comparing the angle that is created by vertices: [index1 - 1, index1, index1 + 1] - // with the one creaded by vertices: [index1 - 1, index1, index2] - // if the latter is greater than it means that the edge is outside the face - // IMPORTANT: we assume that all vertices are in one plane (this should be ensured before creating the Face) - int indexOfIndex1 = indexes.indexOf(index1); - int indexMinus1 = this.getIndex(indexOfIndex1 - 1);// indexOfIndex1 == 0 ? indexes.get(indexes.size() - 1) : indexes.get(indexOfIndex1 - 1); - int indexPlus1 = this.getIndex(indexOfIndex1 + 1);// indexOfIndex1 == indexes.size() - 1 ? 0 : indexes.get(indexOfIndex1 + 1); - - Vector3f edge1 = vertices.get(indexMinus1).subtract(vertices.get(index1)).normalizeLocal(); - Vector3f edge2 = vertices.get(indexPlus1).subtract(vertices.get(index1)).normalizeLocal(); - Vector3f newEdge = vertices.get(index2).subtract(vertices.get(index1)).normalizeLocal(); - - // verify f the later computed angle is inside or outside the face - Vector3f direction1 = edge1.cross(edge2).normalizeLocal(); - Vector3f direction2 = edge1.cross(newEdge).normalizeLocal(); - Vector3f normal = temporalMesh.getNormals().get(index1); - - boolean isAngle1Interior = normal.dot(direction1) < 0; - boolean isAngle2Interior = normal.dot(direction2) < 0; - - float angle1 = isAngle1Interior ? edge1.angleBetween(edge2) : FastMath.TWO_PI - edge1.angleBetween(edge2); - float angle2 = isAngle2Interior ? edge1.angleBetween(newEdge) : FastMath.TWO_PI - edge1.angleBetween(newEdge); - - return angle1 >= angle2; + // computing the edge's middle point + Vector3f edgeMiddlePoint = edge.computeCentroid(); + // computing the edge that is perpendicular to the given edge and has a length of 1 (length actually does not matter) + Vector3f edgeVector = edge.getSecondVertex().subtract(edge.getFirstVertex()); + Vector3f edgeNormal = temporalMesh.getNormals().get(index1).cross(edgeVector).normalizeLocal(); + Edge e = new Edge(edgeMiddlePoint, edgeNormal.add(edgeMiddlePoint)); + // compute the vectors from the middle point to the crossing between the extended edge 'e' and other edges of the face + List crossingVectors = new ArrayList(); + for (int i = 0; i < indexes.size(); ++i) { + int i1 = this.getIndex(i); + int i2 = this.getIndex(i + 1); + Vector3f crossPoint = e.getCrossPoint(new Edge(i1, i2, 0, false, temporalMesh), true, false); + if(crossPoint != null) { + crossingVectors.add(crossPoint.subtractLocal(edgeMiddlePoint)); + } + } + if(crossingVectors.size() == 0) { + return false;// edges do not cross + } + + // use only distinct vertices (doubles may appear if the crossing point is a vertex) + List distinctCrossingVectors = new ArrayList(); + for(Vector3f cv : crossingVectors) { + double minDistance = Double.MAX_VALUE; + for(Vector3f dcv : distinctCrossingVectors) { + minDistance = Math.min(minDistance, dcv.distance(cv)); + } + if(minDistance > FastMath.FLT_EPSILON) { + distinctCrossingVectors.add(cv); + } + } + + if(distinctCrossingVectors.size() == 0) { + throw new IllegalStateException("There MUST be at least 2 crossing vertices!"); + } + // checking if all crossing vectors point to the same direction (if yes then the edge is outside the face) + float direction = Math.signum(distinctCrossingVectors.get(0).dot(edgeNormal));// if at least one vector has different direction that this - it means that the edge is inside the face + for(int i=1;i