Bugfix: fixed incorrect faces' triangulation.
This commit is contained in:
parent
a0261e78fb
commit
c7dc73e85f
@ -10,6 +10,7 @@ import com.jme3.math.Vector3f;
|
|||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
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;
|
import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,6 +25,8 @@ public class Edge {
|
|||||||
|
|
||||||
/** The vertices indexes. */
|
/** The vertices indexes. */
|
||||||
private int index1, index2;
|
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. */
|
/** The weight of the edge. */
|
||||||
private float crease;
|
private float crease;
|
||||||
/** A variable that indicates if this edge belongs to any face or not. */
|
/** 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. */
|
/** The mesh that owns the edge. */
|
||||||
private TemporalMesh temporalMesh;
|
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
|
* This constructor only stores the indexes of the vertices. The position vertices should be stored
|
||||||
* outside this class.
|
* outside this class.
|
||||||
@ -74,14 +84,14 @@ public class Edge {
|
|||||||
* @return the first vertex of the edge
|
* @return the first vertex of the edge
|
||||||
*/
|
*/
|
||||||
public Vector3f getFirstVertex() {
|
public Vector3f getFirstVertex() {
|
||||||
return temporalMesh.getVertices().get(index1);
|
return temporalMesh == null ? v1 : temporalMesh.getVertices().get(index1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the second vertex of the edge
|
* @return the second vertex of the edge
|
||||||
*/
|
*/
|
||||||
public Vector3f getSecondVertex() {
|
public Vector3f getSecondVertex() {
|
||||||
return temporalMesh.getVertices().get(index2);
|
return temporalMesh == null ? v2 : temporalMesh.getVertices().get(index2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,28 +198,82 @@ public class Edge {
|
|||||||
* @return <b>true</b> if the edges cross and false otherwise
|
* @return <b>true</b> if the edges cross and false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean cross(Edge edge) {
|
public boolean cross(Edge edge) {
|
||||||
Vector3f P1 = this.getFirstVertex();
|
return this.getCrossPoint(edge) != null;
|
||||||
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));
|
|
||||||
|
|
||||||
if (p1.distance(p2) <= FastMath.FLT_EPSILON) {
|
/**
|
||||||
// the lines cross, check if p1 and p2 are within the edges
|
* The method computes the crossing pint of this edge and another edge. If
|
||||||
Vector3f p = p1.subtract(P1);
|
* there is no crossing then null is returned.
|
||||||
float cos = p.dot(u) / (p.length() * u.length());
|
*
|
||||||
if (cos > 0 && p.length() <= u.length()) {
|
* @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 <b>true</b> to find a crossing point along the whole
|
||||||
|
* straight that contains the current edge
|
||||||
|
* @param extendSecondEdge
|
||||||
|
* set to <b>true</b> 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) {
|
||||||
|
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
|
// p1 is inside the first edge, lets check the other edge now
|
||||||
p = p2.subtract(P2);
|
p = p2.subtract(P2);
|
||||||
cos = p.dot(v) / (p.length() * v.length());
|
cos = p.dot(v) / p.length();
|
||||||
return cos > 0 && p.length() <= u.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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -276,30 +276,45 @@ public class Face implements Comparator<Integer> {
|
|||||||
List<Face> facesToTriangulate = new ArrayList<Face>(Arrays.asList(this.clone()));
|
List<Face> facesToTriangulate = new ArrayList<Face>(Arrays.asList(this.clone()));
|
||||||
while (facesToTriangulate.size() > 0) {
|
while (facesToTriangulate.size() > 0) {
|
||||||
Face face = facesToTriangulate.remove(0);
|
Face face = facesToTriangulate.remove(0);
|
||||||
int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
|
// two special cases will improve the computations speed
|
||||||
while (face.vertexCount() > 0) {
|
if(face.getIndexes().size() == 3) {
|
||||||
indexes[0] = face.getIndex(0);
|
triangulatedFaces.add(face.getIndexes().clone());
|
||||||
indexes[1] = face.findClosestVertex(indexes[0], -1);
|
} else if(face.getIndexes().size() == 4) {
|
||||||
indexes[2] = face.findClosestVertex(indexes[0], indexes[1]);
|
// 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));
|
||||||
|
|
||||||
LOGGER.finer("Veryfying improper triangulation of the temporal mesh.");
|
indexes[1] = face.getIndex(2);
|
||||||
if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) {
|
indexes[2] = face.getIndex(3);
|
||||||
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!");
|
triangulatedFaces.add(new IndexesLoop(indexes));
|
||||||
}
|
} else {
|
||||||
if (previousIndex1 == indexes[0] && previousIndex2 == indexes[1] && previousIndex3 == indexes[2]) {
|
int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
|
||||||
throw new BlenderFileException("Infinite loop detected during triangulation of mesh: " + temporalMesh + "Please apply triangulation modifier in blender as a workaround and load again!");
|
while (face.vertexCount() > 0) {
|
||||||
}
|
indexes[0] = face.getIndex(0);
|
||||||
previousIndex1 = indexes[0];
|
indexes[1] = face.findClosestVertex(indexes[0], -1);
|
||||||
previousIndex2 = indexes[1];
|
indexes[2] = face.findClosestVertex(indexes[0], indexes[1]);
|
||||||
previousIndex3 = indexes[2];
|
|
||||||
|
|
||||||
Arrays.sort(indexes, this);
|
LOGGER.finer("Veryfying improper triangulation of the temporal mesh.");
|
||||||
facesToTriangulate.addAll(face.detachTriangle(indexes));
|
if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) {
|
||||||
triangulatedFaces.add(new IndexesLoop(indexes));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (BlenderFileException e) {
|
} 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);
|
indexes[0] = this.getIndex(0);
|
||||||
for (int i = 1; i < this.vertexCount() - 1; ++i) {
|
for (int i = 1; i < this.vertexCount() - 1; ++i) {
|
||||||
indexes[1] = this.getIndex(i);
|
indexes[1] = this.getIndex(i);
|
||||||
@ -382,11 +397,9 @@ public class Face implements Comparator<Integer> {
|
|||||||
int index2 = edge.getSecondIndex();
|
int index2 = edge.getSecondIndex();
|
||||||
// check if the line between the vertices is not a border edge of the face
|
// check if the line between the vertices is not a border edge of the face
|
||||||
if (!indexes.areNeighbours(index1, index2)) {
|
if (!indexes.areNeighbours(index1, index2)) {
|
||||||
List<Vector3f> vertices = temporalMesh.getVertices();
|
|
||||||
|
|
||||||
for (int i = 0; i < indexes.size(); ++i) {
|
for (int i = 0; i < indexes.size(); ++i) {
|
||||||
int i1 = this.getIndex(i);
|
int i1 = this.getIndex(i - 1);
|
||||||
int i2 = this.getIndex(i + 1);
|
int i2 = this.getIndex(i);
|
||||||
// check if the edges have no common verts (because if they do, they cannot cross)
|
// 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 (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) {
|
||||||
if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) {
|
if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) {
|
||||||
@ -395,31 +408,49 @@ public class Face implements Comparator<Integer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the edge does NOT cross any of other edges, so now we need to verify if it is inside the face or outside
|
// computing the edge's middle point
|
||||||
// we check it by comparing the angle that is created by vertices: [index1 - 1, index1, index1 + 1]
|
Vector3f edgeMiddlePoint = edge.computeCentroid();
|
||||||
// with the one creaded by vertices: [index1 - 1, index1, index2]
|
// computing the edge that is perpendicular to the given edge and has a length of 1 (length actually does not matter)
|
||||||
// if the latter is greater than it means that the edge is outside the face
|
Vector3f edgeVector = edge.getSecondVertex().subtract(edge.getFirstVertex());
|
||||||
// IMPORTANT: we assume that all vertices are in one plane (this should be ensured before creating the Face)
|
Vector3f edgeNormal = temporalMesh.getNormals().get(index1).cross(edgeVector).normalizeLocal();
|
||||||
int indexOfIndex1 = indexes.indexOf(index1);
|
Edge e = new Edge(edgeMiddlePoint, edgeNormal.add(edgeMiddlePoint));
|
||||||
int indexMinus1 = this.getIndex(indexOfIndex1 - 1);// indexOfIndex1 == 0 ? indexes.get(indexes.size() - 1) : indexes.get(indexOfIndex1 - 1);
|
// compute the vectors from the middle point to the crossing between the extended edge 'e' and other edges of the face
|
||||||
int indexPlus1 = this.getIndex(indexOfIndex1 + 1);// indexOfIndex1 == indexes.size() - 1 ? 0 : indexes.get(indexOfIndex1 + 1);
|
List<Vector3f> crossingVectors = new ArrayList<Vector3f>();
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
Vector3f edge1 = vertices.get(indexMinus1).subtract(vertices.get(index1)).normalizeLocal();
|
// use only distinct vertices (doubles may appear if the crossing point is a vertex)
|
||||||
Vector3f edge2 = vertices.get(indexPlus1).subtract(vertices.get(index1)).normalizeLocal();
|
List<Vector3f> distinctCrossingVectors = new ArrayList<Vector3f>();
|
||||||
Vector3f newEdge = vertices.get(index2).subtract(vertices.get(index1)).normalizeLocal();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// verify f the later computed angle is inside or outside the face
|
if(distinctCrossingVectors.size() == 0) {
|
||||||
Vector3f direction1 = edge1.cross(edge2).normalizeLocal();
|
throw new IllegalStateException("There MUST be at least 2 crossing vertices!");
|
||||||
Vector3f direction2 = edge1.cross(newEdge).normalizeLocal();
|
}
|
||||||
Vector3f normal = temporalMesh.getNormals().get(index1);
|
// 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
|
||||||
boolean isAngle1Interior = normal.dot(direction1) < 0;
|
for(int i=1;i<distinctCrossingVectors.size();++i) {
|
||||||
boolean isAngle2Interior = normal.dot(direction2) < 0;
|
if(direction != Math.signum(distinctCrossingVectors.get(i).dot(edgeNormal))) {
|
||||||
|
return true;
|
||||||
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 false;
|
||||||
return angle1 >= angle2;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user