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 b12826001..4ee552d20 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 @@ -6,7 +6,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.math.FastMath; -import com.jme3.math.Line; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; @@ -18,8 +17,7 @@ import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate; * * @author Marcin Roguski (Kaelthas) */ -public class Edge extends Line { - private static final long serialVersionUID = 7172714692126675311L; +public class Edge { private static final Logger LOGGER = Logger.getLogger(Edge.class.getName()); private static final int FLAG_EDGE_NOT_IN_FACE = 0x80; @@ -30,9 +28,8 @@ public class Edge extends Line { private float crease; /** A variable that indicates if this edge belongs to any face or not. */ private boolean inFace; - - public Edge() { - } + /** The mesh that owns the edge. */ + private TemporalMesh temporalMesh; /** * This constructor only stores the indexes of the vertices. The position vertices should be stored @@ -46,38 +43,17 @@ public class Edge extends Line { * @param inFace * a variable that indicates if this edge belongs to any face or not */ - private Edge(int index1, int index2, float crease, boolean inFace) { + public Edge(int index1, int index2, float crease, boolean inFace, TemporalMesh temporalMesh) { this.index1 = index1; this.index2 = index2; this.crease = crease; this.inFace = inFace; - } - - /** - * This constructor stores both indexes and vertices list. The list should contain ALL verts and not - * only those belonging to the edge. - * @param index1 - * the first index of the edge - * @param index2 - * the second index of the edge - * @param crease - * the weight of the face - * @param inFace - * a variable that indicates if this edge belongs to any face or not - * @param vertices - * the vertices of the mesh - */ - public Edge(int index1, int index2, float crease, boolean inFace, List vertices) { - this(index1, index2, crease, inFace); - this.set(vertices.get(index1), vertices.get(index2)); + this.temporalMesh = temporalMesh; } @Override public Edge clone() { - Edge result = new Edge(index1, index2, crease, inFace); - result.setOrigin(this.getOrigin()); - result.setDirection(this.getDirection()); - return result; + return new Edge(index1, index2, crease, inFace, temporalMesh); } /** @@ -128,9 +104,8 @@ public class Edge extends Line { * @return the centroid of the edge */ public Vector3f computeCentroid() { - Vector3f v1 = this.getOrigin(); - Vector3f v2 = v1.add(this.getDirection()); - return v2.addLocal(v1).divideLocal(2); + List vertices = temporalMesh.getVertices(); + return vertices.get(index1).add(vertices.get(index2)).divideLocal(2); } /** @@ -159,18 +134,6 @@ public class Edge extends Line { index2 = temp; } - /** - * The method sets the vertices for the first and second index. - * @param v1 - * the first vertex - * @param v2 - * the second vertex - */ - public void set(Vector3f v1, Vector3f v2) { - this.setOrigin(v1); - this.setDirection(v2.subtract(v1)); - } - /** * The crossing method first computes the points on both lines (that contain the edges) * who are closest in distance. If the distance between points is smaller than FastMath.FLT_EPSILON @@ -198,9 +161,10 @@ public class Edge extends Line { * @return true if the edges cross and false otherwise */ public boolean cross(Edge edge) { - Vector3f P1 = this.getOrigin(), P2 = edge.getOrigin(); - Vector3f u = this.getDirection(); - Vector3f v = edge.getDirection(); + Vector3f P1 = temporalMesh.getVertices().get(index1); + Vector3f P2 = edge.temporalMesh.getVertices().get(edge.index1); + Vector3f u = temporalMesh.getVertices().get(index2).subtract(P1); + Vector3f v = edge.temporalMesh.getVertices().get(edge.index2).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)); @@ -223,9 +187,7 @@ public class Edge extends Line { @Override public String toString() { String result = "Edge [" + index1 + ", " + index2 + "] {" + crease + "}"; - if (this.getOrigin() != null && this.getDirection() != null) { - result += " -> {" + this.getOrigin() + ", " + this.getOrigin().add(this.getDirection()) + "}"; - } + result += " (" + temporalMesh.getVertices().get(index1) + " -> " + temporalMesh.getVertices().get(index2) + ")"; if (inFace) { result += "[F]"; } @@ -260,11 +222,13 @@ public class Edge extends Line { * The method loads all edges from the given mesh structure that does not belong to any face. * @param meshStructure * the mesh structure + * @param temporalMesh + * the owner of the edges * @return all edges without faces * @throws BlenderFileException * an exception is thrown when problems with file reading occur */ - public static List loadAll(Structure meshStructure) throws BlenderFileException { + public static List loadAll(Structure meshStructure, TemporalMesh temporalMesh) throws BlenderFileException { LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName()); List result = new ArrayList(); @@ -277,9 +241,10 @@ public class Edge extends Line { int v1 = ((Number) edge.getFieldValue("v1")).intValue(); int v2 = ((Number) edge.getFieldValue("v2")).intValue(); - float crease = ((Number) edge.getFieldValue("crease")).floatValue(); + // I do not know why, but blender stores (possibly only sometimes) crease as negative values and shows positive in the editor + float crease = Math.abs(((Number) edge.getFieldValue("crease")).floatValue()); boolean edgeInFace = (flag & Edge.FLAG_EDGE_NOT_IN_FACE) == 0; - result.add(new Edge(v1, v2, crease, edgeInFace)); + result.add(new Edge(v1, v2, crease, edgeInFace, temporalMesh)); } } LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size()); 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 be9d65e1b..9dc851a58 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 @@ -355,7 +355,7 @@ public class Face implements Comparator { if (i != index && i != indexToIgnore) { Vector3f v2 = vertices.get(i); float d = v2.distance(v1); - if (d < distance && this.contains(new Edge(index, i, 0, true, vertices))) { + if (d < distance && this.contains(new Edge(index, i, 0, true, temporalMesh))) { result = i; distance = d; } @@ -378,14 +378,12 @@ public class Face implements Comparator { if (!indexes.areNeighbours(index1, index2)) { List vertices = temporalMesh.getVertices(); - Edge e2 = new Edge(); for (int i = 0; i < indexes.size(); ++i) { int i1 = this.getIndex(i); int i2 = this.getIndex(i + 1); // check if the edges have no common verts (because if they do, they cannot cross) if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) { - e2.set(vertices.get(i1), vertices.get(i2)); - if (edge.cross(e2)) { + if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) { return false; } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java index 05d3dcb11..ee899d268 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java @@ -117,7 +117,7 @@ public class TemporalMesh extends Geometry { vertexGroups = meshHelper.loadVerticesGroups(meshStructure); faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext); - edges = Edge.loadAll(meshStructure); + edges = Edge.loadAll(meshStructure, this); points = Point.loadAll(meshStructure); this.rebuildIndexesMappings(); @@ -229,7 +229,7 @@ public class TemporalMesh extends Geometry { * @return true if the index is a boundary one and false otherwise */ public boolean isBoundary(Integer index) { - List adjacentEdges = indexToEdgeMapping.get(index); + List adjacentEdges = this.getAdjacentEdges(index); for (Edge edge : adjacentEdges) { if (this.isBoundary(edge)) { return true; diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java index c34665e6a..cbd003ed5 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java @@ -28,12 +28,12 @@ import com.jme3.scene.plugins.blender.textures.TexturePixel; * @author Marcin Roguski (Kaelthas) */ public class SubdivisionSurfaceModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(SubdivisionSurfaceModifier.class.getName()); + private static final Logger LOGGER = Logger.getLogger(SubdivisionSurfaceModifier.class.getName()); - private static final int TYPE_CATMULLCLARK = 0; - private static final int TYPE_SIMPLE = 1; + private static final int TYPE_CATMULLCLARK = 0; + private static final int TYPE_SIMPLE = 1; - private static final int FLAG_SUBDIVIDE_UVS = 0x8; + private static final int FLAG_SUBDIVIDE_UVS = 0x8; /** The subdivision type. */ private int subdivType; @@ -42,6 +42,8 @@ public class SubdivisionSurfaceModifier extends Modifier { /** Indicates if the UV's should also be subdivided. */ private boolean subdivideUVS; + private List verticesOnOriginalEdges = new ArrayList(); + /** * Constructor loads all neccessary modifier data. * @param modifierStructure @@ -75,6 +77,12 @@ public class SubdivisionSurfaceModifier extends Modifier { TemporalMesh temporalMesh = this.getTemporalMesh(node); if (temporalMesh != null) { LOGGER.log(Level.FINE, "Applying subdivision surface modifier to: {0}", temporalMesh); + + for (Edge edge : temporalMesh.getEdges()) { + verticesOnOriginalEdges.add(edge.getFirstIndex()); + verticesOnOriginalEdges.add(edge.getSecondIndex()); + } + if (subdivType == TYPE_CATMULLCLARK) { for (int i = 0; i < levels; ++i) { this.subdivideSimple(temporalMesh);// first do simple subdivision ... @@ -109,8 +117,21 @@ public class SubdivisionSurfaceModifier extends Modifier { } } - Vector3f[] averageVert = new Vector3f[temporalMesh.getVertices().size()]; - int[] averageCount = new int[temporalMesh.getVertices().size()]; + List creasePoints = new ArrayList(temporalMesh.getVertexCount()); + for (int i = 0; i < temporalMesh.getVertexCount(); ++i) { + // finding adjacent edges that were created by dividing original edges + List adjacentOriginalEdges = new ArrayList(); + for (Edge edge : temporalMesh.getAdjacentEdges(i)) { + if (verticesOnOriginalEdges.contains(edge.getFirstIndex()) || verticesOnOriginalEdges.contains(edge.getSecondIndex())) { + adjacentOriginalEdges.add(edge); + } + } + + creasePoints.add(new CreasePoint(i, boundaryVertices.contains(i), adjacentOriginalEdges, temporalMesh)); + } + + Vector3f[] averageVert = new Vector3f[temporalMesh.getVertexCount()]; + int[] averageCount = new int[temporalMesh.getVertexCount()]; for (Face face : temporalMesh.getFaces()) { Vector3f centroid = face.computeCentroid(); @@ -144,11 +165,23 @@ public class SubdivisionSurfaceModifier extends Modifier { } for (int i = 0; i < averageVert.length; ++i) { + Vector3f v = temporalMesh.getVertices().get(i); averageVert[i].divideLocal(averageCount[i]); + + // computing translation vector + Vector3f t = averageVert[i].subtract(v); if (!boundaryVertices.contains(i)) { - temporalMesh.getVertices().get(i).addLocal(averageVert[i].subtract(temporalMesh.getVertices().get(i)).multLocal(4 / (float) averageCount[i])); - } else { - temporalMesh.getVertices().get(i).set(averageVert[i]); + t.multLocal(4 / (float) averageCount[i]); + } + + // moving the vertex + v.addLocal(t); + + // applying crease weight if neccessary + CreasePoint creasePoint = creasePoints.get(i); + if (creasePoint.getTarget() != null && creasePoint.getWeight() != 0) { + t = creasePoint.getTarget().subtractLocal(v).multLocal(creasePoint.getWeight()); + v.addLocal(t); } } } @@ -164,6 +197,7 @@ public class SubdivisionSurfaceModifier extends Modifier { Map edgePoints = new HashMap(); Map facePoints = new HashMap(); List newFaces = new ArrayList(); + List newEdges = new ArrayList(temporalMesh.getEdges().size() * 2); int originalFacesCount = temporalMesh.getFaces().size(); @@ -198,14 +232,15 @@ public class SubdivisionSurfaceModifier extends Modifier { int vPrevIndex = i == 0 ? face.getIndexes().get(face.getIndexes().size() - 1) : face.getIndexes().get(i - 1); int vNextIndex = i == face.getIndexes().size() - 1 ? face.getIndexes().get(0) : face.getIndexes().get(i + 1); - Edge prevEdge = this.findEdge(temporalMesh, vPrevIndex, vIndex);// new Edge(vPrevIndex, vIndex, 0, true, temporalMesh.getVertices()); - Edge nextEdge = this.findEdge(temporalMesh, vIndex, vNextIndex);// new Edge(vIndex, vNextIndex, 0, true, temporalMesh.getVertices()); + Edge prevEdge = this.findEdge(temporalMesh, vPrevIndex, vIndex); + Edge nextEdge = this.findEdge(temporalMesh, vIndex, vNextIndex); int vPrevEdgeVertIndex = edgePoints.containsKey(prevEdge) ? edgePoints.get(prevEdge) : -1; int vNextEdgeVertIndex = edgePoints.containsKey(nextEdge) ? edgePoints.get(nextEdge) : -1; Vector3f v = temporalMesh.getVertices().get(vIndex); if (vPrevEdgeVertIndex < 0) { vPrevEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size(); + verticesOnOriginalEdges.add(vNextEdgeVertIndex); edgeVertices.add(vertices.get(vPrevIndex).add(v).divideLocal(2)); edgeNormals.add(normals.get(vPrevIndex).add(normals.get(vIndex)).normalizeLocal()); edgePoints.put(prevEdge, vPrevEdgeVertIndex); @@ -215,6 +250,7 @@ public class SubdivisionSurfaceModifier extends Modifier { } if (vNextEdgeVertIndex < 0) { vNextEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size(); + verticesOnOriginalEdges.add(vPrevEdgeVertIndex); edgeVertices.add(vertices.get(vNextIndex).add(v).divideLocal(2)); edgeNormals.add(normals.get(vNextIndex).add(normals.get(vIndex)).normalizeLocal()); edgePoints.put(nextEdge, vNextEdgeVertIndex); @@ -257,6 +293,11 @@ public class SubdivisionSurfaceModifier extends Modifier { } newFaces.add(new Face(indexes, face.isSmooth(), face.getMaterialNumber(), newUVSets, vertexColors, temporalMesh)); + + newEdges.add(new Edge(vIndex, vNextEdgeVertIndex, nextEdge.getCrease(), true, temporalMesh)); + newEdges.add(new Edge(vNextEdgeVertIndex, facePointIndex, 0, true, temporalMesh)); + newEdges.add(new Edge(facePointIndex, vPrevEdgeVertIndex, 0, true, temporalMesh)); + newEdges.add(new Edge(vPrevEdgeVertIndex, vIndex, prevEdge.getCrease(), true, temporalMesh)); } } @@ -265,24 +306,14 @@ public class SubdivisionSurfaceModifier extends Modifier { normals.addAll(faceNormals); normals.addAll(edgeNormals); - List newEdges = new ArrayList(temporalMesh.getEdges().size() * 2); for (Edge edge : temporalMesh.getEdges()) { if (!edge.isInFace()) { int newVertexIndex = vertices.size(); vertices.add(vertices.get(edge.getFirstIndex()).add(vertices.get(edge.getSecondIndex())).divideLocal(2)); normals.add(normals.get(edge.getFirstIndex()).add(normals.get(edge.getSecondIndex())).normalizeLocal()); - newEdges.add(new Edge(edge.getFirstIndex(), newVertexIndex, 0, false, vertices)); - newEdges.add(new Edge(newVertexIndex, edge.getSecondIndex(), 0, false, vertices)); - } else { - Integer edgePoint = edgePoints.get(edge); - newEdges.add(new Edge(edge.getFirstIndex(), edgePoint, edge.getCrease(), true, vertices)); - newEdges.add(new Edge(edgePoint, edge.getSecondIndex(), edge.getCrease(), true, vertices)); - // adding edges between face points and edge points - List facesContainingTheEdge = temporalMesh.getAdjacentFaces(edge); - for (Face f : facesContainingTheEdge) { - newEdges.add(new Edge(facePoints.get(f), edgePoint, 0, true, vertices)); - } + newEdges.add(new Edge(edge.getFirstIndex(), newVertexIndex, 0, false, temporalMesh)); + newEdges.add(new Edge(newVertexIndex, edge.getSecondIndex(), 0, false, temporalMesh)); } } @@ -506,9 +537,9 @@ public class SubdivisionSurfaceModifier extends Modifier { } faces.add(new Face(indexes, false, 0, null, null, this)); for (i = 1; i < indexes.length; ++i) { - edges.add(new Edge(indexes[i - 1], indexes[i], 0, true, vertices)); + edges.add(new Edge(indexes[i - 1], indexes[i], 0, true, this)); } - edges.add(new Edge(indexes[indexes.length - 1], indexes[0], 0, true, vertices)); + edges.add(new Edge(indexes[indexes.length - 1], indexes[0], 0, true, this)); } /** @@ -527,4 +558,61 @@ public class SubdivisionSurfaceModifier extends Modifier { return result; } } + + /** + * A point computed for each vertex before applying CC subdivision and after simple subdivision. + * This class has a target where the vertices will be drawn to with a proper strength (value from 0 to 1). + * + * The algorithm of computing the target point was made by observing how blender behaves. + * If a vertex has one or less creased edges (means edges that have non zero crease value) the target will not exist. + * If a vertex is a border vertex and has two creased edges - the target will be the original simple subdivided vertex. + * If a vertex is not a border vertex and have two creased edges - then it will be drawned to the plane defined by those + * two edges. + * If a vertex has 3 or more creased edges it will be drawn to its original vertex before CC subdivision with average strength + * computed from edges' crease values. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class CreasePoint { + private Vector3f target = new Vector3f(); + private float weight; + + public CreasePoint(int index, boolean borderIndex, List creaseEdges, TemporalMesh temporalMesh) { + if (creaseEdges == null || creaseEdges.size() <= 1) { + target = null;// crease is used when vertex belongs to at least 2 creased edges + } else { + int creasedEdgesCount = 0; + for (Edge edge : creaseEdges) { + if (edge.getCrease() > 0) { + ++creasedEdgesCount; + weight += edge.getCrease(); + target.addLocal(temporalMesh.getVertices().get(edge.getOtherIndex(index))); + } + } + + if (creasedEdgesCount <= 1) { + target = null;// crease is used when vertex belongs to at least 2 creased edges + } else if (creasedEdgesCount == 2) { + if (borderIndex) { + target.set(temporalMesh.getVertices().get(index)); + } else { + target.divideLocal(creasedEdgesCount); + } + } else { + target.set(temporalMesh.getVertices().get(index)); + } + if (creasedEdgesCount > 0) { + weight /= creasedEdgesCount; + } + } + } + + public Vector3f getTarget() { + return target; + } + + public float getWeight() { + return weight; + } + } }