Feature: added support for subdivision surface modifier.

experimental
jmekaelthas 10 years ago
parent 73b7061b0b
commit f364d66640
  1. 25
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  2. 36
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  3. 26
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java
  4. 120
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java
  5. 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java
  6. 530
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java

@ -94,6 +94,22 @@ public class Edge extends Line {
return index2; return index2;
} }
/**
* Returns the index other than the given.
* @param index
* index of the edge
* @return the remaining index number
*/
public int getOtherIndex(int index) {
if (index == index1) {
return index2;
}
if (index == index2) {
return index1;
}
throw new IllegalArgumentException("Cannot give the other index for [" + index + "] because this index does not exist in edge: " + this);
}
/** /**
* @return the crease value of the edge (its weight) * @return the crease value of the edge (its weight)
*/ */
@ -108,6 +124,15 @@ public class Edge extends Line {
return inFace; return inFace;
} }
/**
* @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);
}
/** /**
* Shifts indexes by a given amount. * Shifts indexes by a given amount.
* @param shift * @param shift

@ -131,6 +131,18 @@ public class Face implements Comparator<Integer> {
return indexes; return indexes;
} }
/**
* @return the centroid of the face
*/
public Vector3f computeCentroid() {
Vector3f result = new Vector3f();
List<Vector3f> vertices = temporalMesh.getVertices();
for (Integer index : indexes) {
result.addLocal(vertices.get(index));
}
return result.divideLocal(indexes.size());
}
/** /**
* @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list) * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list)
*/ */
@ -408,6 +420,30 @@ public class Face implements Comparator<Integer> {
return true; return true;
} }
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + indexes.hashCode();
result = prime * result + temporalMesh.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Face)) {
return false;
}
Face other = (Face) obj;
if (!indexes.equals(other.indexes)) {
return false;
}
return temporalMesh.equals(other.temporalMesh);
}
/** /**
* Loads all faces of a given mesh. * Loads all faces of a given mesh.
* @param meshStructure * @param meshStructure

@ -121,12 +121,34 @@ public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
* @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise * @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise
*/ */
public boolean areNeighbours(Integer index1, Integer index2) { public boolean areNeighbours(Integer index1, Integer index2) {
if (index1.equals(index2)) { if (index1.equals(index2) || !edges.containsKey(index1) || !edges.containsKey(index2)) {
return false; return false;
} }
return edges.get(index1).contains(index2) || edges.get(index2).contains(index1); return edges.get(index1).contains(index2) || edges.get(index2).contains(index1);
} }
/**
* Returns the value of the index located after the given one. Pointint the last index will return the first one.
* @param index
* the index value
* @return the value of 'next' index
*/
public Integer getNextIndex(Integer index) {
int i = nodes.indexOf(index);
return i == nodes.size() - 1 ? nodes.get(0) : nodes.get(i + 1);
}
/**
* Returns the value of the index located before the given one. Pointint the first index will return the last one.
* @param index
* the index value
* @return the value of 'previous' index
*/
public Integer getPreviousIndex(Integer index) {
int i = nodes.indexOf(index);
return i == 0 ? nodes.get(nodes.size() - 1) : nodes.get(i - 1);
}
/** /**
* The method shifts all indexes by a given value. * The method shifts all indexes by a given value.
* @param shift * @param shift
@ -171,7 +193,7 @@ public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
* the index whose neighbour count will be checked * the index whose neighbour count will be checked
* @return the count of neighbours of the given index * @return the count of neighbours of the given index
*/ */
public int getNeighbourCount(Integer index) { private int getNeighbourCount(Integer index) {
int result = 0; int result = 0;
if (edges.containsKey(index)) { if (edges.containsKey(index)) {
result = edges.get(index).size(); result = edges.get(index).size();

@ -72,6 +72,9 @@ public class TemporalMesh extends Geometry {
/** The points of the mesh. */ /** The points of the mesh. */
protected List<Point> points = new ArrayList<Point>(); protected List<Point> points = new ArrayList<Point>();
protected Map<Integer, List<Face>> indexToFaceMapping = new HashMap<Integer, List<Face>>();
protected Map<Integer, List<Edge>> indexToEdgeMapping = new HashMap<Integer, List<Edge>>();
/** The bounding box of the temporal mesh. */ /** The bounding box of the temporal mesh. */
protected BoundingBox boundingBox; protected BoundingBox boundingBox;
@ -116,6 +119,8 @@ public class TemporalMesh extends Geometry {
faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext); faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext);
edges = Edge.loadAll(meshStructure); edges = Edge.loadAll(meshStructure);
points = Point.loadAll(meshStructure); points = Point.loadAll(meshStructure);
this.rebuildIndexesMappings();
} }
} }
@ -175,6 +180,61 @@ public class TemporalMesh extends Geometry {
return vertexGroups; return vertexGroups;
} }
/**
* @return the faces that contain the given index or null if none contain it
*/
public List<Face> getAdjacentFaces(Integer index) {
return indexToFaceMapping.get(index);
}
/**
* @param the
* edge of the mesh
* @return a list of faces that contain the given edge or an empty list
*/
public List<Face> getAdjacentFaces(Edge edge) {
List<Face> result = new ArrayList<Face>(indexToFaceMapping.get(edge.getFirstIndex()));
result.retainAll(indexToFaceMapping.get(edge.getSecondIndex()));
return result;
}
/**
* @param the
* index of the mesh
* @return a list of edges that contain the index
*/
public List<Edge> getAdjacentEdges(Integer index) {
return indexToEdgeMapping.get(index);
}
/**
* Tells if the given edge is a boundary edge. The boundary edge means that it belongs to a single
* face or to none.
* @param the
* edge of the mesh
* @return <b>true</b> if the edge is a boundary one and <b>false</b> otherwise
*/
public boolean isBoundary(Edge edge) {
return this.getAdjacentFaces(edge).size() <= 1;
}
/**
* The method tells if the given index is a boundary index. A boundary index belongs to at least
* one boundary edge.
* @param index
* the index of the mesh
* @return <b>true</b> if the index is a boundary one and <b>false</b> otherwise
*/
public boolean isBoundary(Integer index) {
List<Edge> adjacentEdges = indexToEdgeMapping.get(index);
for (Edge edge : adjacentEdges) {
if (this.isBoundary(edge)) {
return true;
}
}
return false;
}
@Override @Override
public TemporalMesh clone() { public TemporalMesh clone() {
try { try {
@ -205,6 +265,7 @@ public class TemporalMesh extends Geometry {
for (Point point : points) { for (Point point : points) {
result.points.add(point.clone()); result.points.add(point.clone());
} }
result.rebuildIndexesMappings();
return result; return result;
} catch (BlenderFileException e) { } catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage()); LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage());
@ -212,6 +273,41 @@ public class TemporalMesh extends Geometry {
return null; return null;
} }
/**
* The method rebuilds the mappings between faces and edges. Should be called after
* every major change of the temporal mesh done outside it.
* @note I will remove this method soon and make the mappings to be done automatically
* when the mesh is modified.
*/
public void rebuildIndexesMappings() {
indexToEdgeMapping.clear();
indexToFaceMapping.clear();
for (Face face : faces) {
for (Integer index : face.getIndexes()) {
List<Face> faces = indexToFaceMapping.get(index);
if (faces == null) {
faces = new ArrayList<Face>();
indexToFaceMapping.put(index, faces);
}
faces.add(face);
}
}
for (Edge edge : edges) {
List<Edge> edges = indexToEdgeMapping.get(edge.getFirstIndex());
if (edges == null) {
edges = new ArrayList<Edge>();
indexToEdgeMapping.put(edge.getFirstIndex(), edges);
}
edges.add(edge);
edges = indexToEdgeMapping.get(edge.getSecondIndex());
if (edges == null) {
edges = new ArrayList<Edge>();
indexToEdgeMapping.put(edge.getSecondIndex(), edges);
}
edges.add(edge);
}
}
@Override @Override
public void updateModelBound() { public void updateModelBound() {
if (boundingBox == null) { if (boundingBox == null) {
@ -285,6 +381,8 @@ public class TemporalMesh extends Geometry {
vertexGroups.addAll(mesh.vertexGroups); vertexGroups.addAll(mesh.vertexGroups);
verticesColors.addAll(mesh.verticesColors); verticesColors.addAll(mesh.verticesColors);
boneIndexes.putAll(mesh.boneIndexes); boneIndexes.putAll(mesh.boneIndexes);
this.rebuildIndexesMappings();
} }
/** /**
@ -341,6 +439,8 @@ public class TemporalMesh extends Geometry {
faces.clear(); faces.clear();
edges.clear(); edges.clear();
points.clear(); points.clear();
indexToEdgeMapping.clear();
indexToFaceMapping.clear();
} }
/** /**
@ -600,4 +700,24 @@ public class TemporalMesh extends Geometry {
public String toString() { public String toString() {
return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]"; return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]";
} }
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (meshStructure == null ? 0 : meshStructure.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TemporalMesh)) {
return false;
}
TemporalMesh other = (TemporalMesh) obj;
return meshStructure.getOldMemoryAddress().equals(other.meshStructure.getOldMemoryAddress());
}
} }

@ -96,6 +96,8 @@ public class ModifierHelper extends AbstractBlenderHelper {
modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext); modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext);
} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) { } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new ParticlesModifier(modifierStructure, blenderContext); modifier = new ParticlesModifier(modifierStructure, blenderContext);
} else if(Modifier.SUBSURF_MODIFIER_DATA.equals(modifierStructure.getType())) {
modifier = new SubdivisionSurfaceModifier(modifierStructure, blenderContext);
} }
if (modifier != null) { if (modifier != null) {

@ -0,0 +1,530 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.Edge;
import com.jme3.scene.plugins.blender.meshes.Face;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
/**
* A modifier that subdivides the mesh using either simple or catmull-clark subdivision.
*
* @author Marcin Roguski (Kaelthas)
*/
public class SubdivisionSurfaceModifier extends Modifier {
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 FLAG_SUBDIVIDE_UVS = 0x8;
/** The subdivision type. */
private int subdivType;
/** The amount of subdivision levels. */
private int levels;
/** Indicates if the UV's should also be subdivided. */
private boolean subdivideUVS;
/**
* Constructor loads all neccessary modifier data.
* @param modifierStructure
* the modifier structure
* @param blenderContext
* the blender context
*/
public SubdivisionSurfaceModifier(Structure modifierStructure, BlenderContext blenderContext) {
if (this.validate(modifierStructure, blenderContext)) {
subdivType = ((Number) modifierStructure.getFieldValue("subdivType")).intValue();
levels = ((Number) modifierStructure.getFieldValue("levels")).intValue();
int flag = ((Number) modifierStructure.getFieldValue("flags")).intValue();
subdivideUVS = (flag & FLAG_SUBDIVIDE_UVS) != 0 && subdivType == TYPE_CATMULLCLARK;
if (subdivType != TYPE_CATMULLCLARK && subdivType != TYPE_SIMPLE) {
LOGGER.log(Level.SEVERE, "Unknown subdivision type: {0}.", subdivType);
invalid = true;
}
if (levels < 0) {
LOGGER.severe("The amount of subdivision levels cannot be negative.");
invalid = true;
}
}
}
@Override
public void apply(Node node, BlenderContext blenderContext) {
if (invalid) {
LOGGER.log(Level.WARNING, "Subdivision surface modifier is invalid! Cannot be applied to: {0}", node.getName());
} else if (levels > 0) {// no need to do anything if the levels is set to zero
TemporalMesh temporalMesh = this.getTemporalMesh(node);
if (temporalMesh != null) {
LOGGER.log(Level.FINE, "Applying subdivision surface modifier to: {0}", temporalMesh);
if (subdivType == TYPE_CATMULLCLARK) {
for (int i = 0; i < levels; ++i) {
this.subdivideSimple(temporalMesh);// first do simple subdivision ...
this.subdivideCatmullClark(temporalMesh);// ... and then apply Catmull-Clark algorithm
if (subdivideUVS) {// UV's can be subdivided only for Catmull-Clark subdivision algorithm
this.subdivideUVs(temporalMesh);
}
}
} else {
for (int i = 0; i < levels; ++i) {
this.subdivideSimple(temporalMesh);
}
}
} else {
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
}
}
}
/**
* Catmull-Clark subdivision. It assumes that the mesh was already simple-subdivided.
* @param temporalMesh
* the mesh whose vertices will be transformed to form Catmull-Clark subdivision
*/
private void subdivideCatmullClark(TemporalMesh temporalMesh) {
Set<Integer> boundaryVertices = new HashSet<Integer>();
for (Face face : temporalMesh.getFaces()) {
for (Integer index : face.getIndexes()) {
if (temporalMesh.isBoundary(index)) {
boundaryVertices.add(index);
}
}
}
Vector3f[] averageVert = new Vector3f[temporalMesh.getVertices().size()];
int[] averageCount = new int[temporalMesh.getVertices().size()];
for (Face face : temporalMesh.getFaces()) {
Vector3f centroid = face.computeCentroid();
for (Integer index : face.getIndexes()) {
if (boundaryVertices.contains(index)) {
Edge edge = this.findEdge(temporalMesh, index, face.getIndexes().getNextIndex(index));
if (temporalMesh.isBoundary(edge)) {
averageVert[index] = averageVert[index] == null ? edge.computeCentroid() : averageVert[index].addLocal(edge.computeCentroid());
averageCount[index] += 1;
}
edge = this.findEdge(temporalMesh, face.getIndexes().getPreviousIndex(index), index);
if (temporalMesh.isBoundary(edge)) {
averageVert[index] = averageVert[index] == null ? edge.computeCentroid() : averageVert[index].addLocal(edge.computeCentroid());
averageCount[index] += 1;
}
} else {
averageVert[index] = averageVert[index] == null ? centroid.clone() : averageVert[index].addLocal(centroid);
averageCount[index] += 1;
}
}
}
for (Edge edge : temporalMesh.getEdges()) {
if (!edge.isInFace()) {
Vector3f centroid = temporalMesh.getVertices().get(edge.getFirstIndex()).add(temporalMesh.getVertices().get(edge.getSecondIndex())).divideLocal(2);
averageVert[edge.getFirstIndex()] = averageVert[edge.getFirstIndex()] == null ? centroid.clone() : averageVert[edge.getFirstIndex()].addLocal(centroid);
averageVert[edge.getSecondIndex()] = averageVert[edge.getSecondIndex()] == null ? centroid.clone() : averageVert[edge.getSecondIndex()].addLocal(centroid);
averageCount[edge.getSecondIndex()] += 2;
}
}
for (int i = 0; i < averageVert.length; ++i) {
averageVert[i].divideLocal(averageCount[i]);
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]);
}
}
}
/**
* The method performs a simple subdivision of the mesh.
*
* @param temporalMesh
* the mesh to be subdivided
*/
@SuppressWarnings("unchecked")
private void subdivideSimple(TemporalMesh temporalMesh) {
Map<Edge, Integer> edgePoints = new HashMap<Edge, Integer>();
Map<Face, Integer> facePoints = new HashMap<Face, Integer>();
List<Face> newFaces = new ArrayList<Face>();
int originalFacesCount = temporalMesh.getFaces().size();
List<Map<String, Float>> vertexGroups = temporalMesh.getVertexGroups();
// the result vertex array will have verts in the following order [[original_verts], [face_verts], [edge_verts]]
List<Vector3f> vertices = temporalMesh.getVertices();
List<Vector3f> edgeVertices = new ArrayList<Vector3f>();
List<Vector3f> faceVertices = new ArrayList<Vector3f>();
// the same goes for normals
List<Vector3f> normals = temporalMesh.getNormals();
List<Vector3f> edgeNormals = new ArrayList<Vector3f>();
List<Vector3f> faceNormals = new ArrayList<Vector3f>();
List<Face> faces = temporalMesh.getFaces();
for (Face face : faces) {
Map<String, List<Vector2f>> uvSets = face.getUvSets();
Vector3f facePoint = face.computeCentroid();
Integer facePointIndex = vertices.size() + faceVertices.size();
facePoints.put(face, facePointIndex);
faceVertices.add(facePoint);
faceNormals.add(this.computeFaceNormal(face));
Map<String, Vector2f> faceUV = this.computeFaceUVs(face);
byte[] faceVertexColor = this.computeFaceVertexColor(face);
Map<String, Float> faceVertexGroups = this.computeFaceVertexGroups(face);
if (vertexGroups.size() > 0) {
vertexGroups.add(faceVertexGroups);
}
for (int i = 0; i < face.getIndexes().size(); ++i) {
int vIndex = face.getIndexes().get(i);
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());
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();
edgeVertices.add(vertices.get(vPrevIndex).add(v).divideLocal(2));
edgeNormals.add(normals.get(vPrevIndex).add(normals.get(vIndex)).normalizeLocal());
edgePoints.put(prevEdge, vPrevEdgeVertIndex);
if (vertexGroups.size() > 0) {
vertexGroups.add(this.interpolateVertexGroups(Arrays.asList(vertexGroups.get(vPrevIndex), vertexGroups.get(vIndex))));
}
}
if (vNextEdgeVertIndex < 0) {
vNextEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size();
edgeVertices.add(vertices.get(vNextIndex).add(v).divideLocal(2));
edgeNormals.add(normals.get(vNextIndex).add(normals.get(vIndex)).normalizeLocal());
edgePoints.put(nextEdge, vNextEdgeVertIndex);
if (vertexGroups.size() > 0) {
vertexGroups.add(this.interpolateVertexGroups(Arrays.asList(vertexGroups.get(vNextIndex), vertexGroups.get(vIndex))));
}
}
Integer[] indexes = new Integer[] { vIndex, vNextEdgeVertIndex, facePointIndex, vPrevEdgeVertIndex };
Map<String, List<Vector2f>> newUVSets = null;
if (uvSets != null) {
newUVSets = new HashMap<String, List<Vector2f>>(uvSets.size());
for (Entry<String, List<Vector2f>> uvset : uvSets.entrySet()) {
int indexOfvIndex = i;
int indexOfvPrevIndex = face.getIndexes().indexOf(vPrevIndex);
int indexOfvNextIndex = face.getIndexes().indexOf(vNextIndex);
Vector2f uv1 = uvset.getValue().get(indexOfvIndex);
Vector2f uv2 = uvset.getValue().get(indexOfvNextIndex).add(uv1).divideLocal(2);
Vector2f uv3 = faceUV.get(uvset.getKey());
Vector2f uv4 = uvset.getValue().get(indexOfvPrevIndex).add(uv1).divideLocal(2);
List<Vector2f> uvList = Arrays.asList(uv1, uv2, uv3, uv4);
newUVSets.put(uvset.getKey(), new ArrayList<Vector2f>(uvList));
}
}
List<byte[]> vertexColors = null;
if (face.getVertexColors() != null) {
int indexOfvIndex = i;
int indexOfvPrevIndex = face.getIndexes().indexOf(vPrevIndex);
int indexOfvNextIndex = face.getIndexes().indexOf(vNextIndex);
byte[] vCol1 = face.getVertexColors().get(indexOfvIndex);
byte[] vCol2 = this.interpolateVertexColors(face.getVertexColors().get(indexOfvNextIndex), vCol1);
byte[] vCol3 = faceVertexColor;
byte[] vCol4 = this.interpolateVertexColors(face.getVertexColors().get(indexOfvPrevIndex), vCol1);
vertexColors = new ArrayList<byte[]>(Arrays.asList(vCol1, vCol2, vCol3, vCol4));
}
newFaces.add(new Face(indexes, face.isSmooth(), face.getMaterialNumber(), newUVSets, vertexColors, temporalMesh));
}
}
vertices.addAll(faceVertices);
vertices.addAll(edgeVertices);
normals.addAll(faceNormals);
normals.addAll(edgeNormals);
List<Edge> newEdges = new ArrayList<Edge>(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<Face> facesContainingTheEdge = temporalMesh.getAdjacentFaces(edge);
for (Face f : facesContainingTheEdge) {
newEdges.add(new Edge(facePoints.get(f), edgePoint, 0, true, vertices));
}
}
}
temporalMesh.getFaces().clear();
temporalMesh.getFaces().addAll(newFaces);
temporalMesh.getEdges().clear();
temporalMesh.getEdges().addAll(newEdges);
temporalMesh.rebuildIndexesMappings();
}
/**
* The method subdivides mesh's UV coordinates. It actually performs only Catmull-Clark modifications because if any UV's are present then they are
* automatically subdivided by the simple algorithm.
* @param temporalMesh
* the mesh whose UV coordinates will be applied Catmull-Clark algorithm
*/
private void subdivideUVs(TemporalMesh temporalMesh) {
List<Face> faces = temporalMesh.getFaces();
Map<String, UvCoordsSubdivideTemporalMesh> subdividedUVS = new HashMap<String, UvCoordsSubdivideTemporalMesh>();
for (Face face : faces) {
if (face.getUvSets() != null) {
for (Entry<String, List<Vector2f>> uvset : face.getUvSets().entrySet()) {
UvCoordsSubdivideTemporalMesh uvCoordsSubdivideTemporalMesh = subdividedUVS.get(uvset.getKey());
if (uvCoordsSubdivideTemporalMesh == null) {
try {
uvCoordsSubdivideTemporalMesh = new UvCoordsSubdivideTemporalMesh(temporalMesh.getBlenderContext());
} catch (BlenderFileException e) {
assert false : "Something went really wrong! The UvCoordsSubdivideTemporalMesh class should NOT throw exceptions here!";
}
subdividedUVS.put(uvset.getKey(), uvCoordsSubdivideTemporalMesh);
}
uvCoordsSubdivideTemporalMesh.addFace(uvset.getValue());
}
}
}
for (Entry<String, UvCoordsSubdivideTemporalMesh> entry : subdividedUVS.entrySet()) {
entry.getValue().rebuildIndexesMappings();
this.subdivideCatmullClark(entry.getValue());
for (int i = 0; i < faces.size(); ++i) {
List<Vector2f> uvs = faces.get(i).getUvSets().get(entry.getKey());
if (uvs != null) {
uvs.clear();
uvs.addAll(entry.getValue().faceToUVs(i));
}
}
}
}
/**
* The method computes the face's normal vector.
* @param face
* the face of the mesh
* @return face's normal vector
*/
private Vector3f computeFaceNormal(Face face) {
Vector3f result = new Vector3f();
for (Integer index : face.getIndexes()) {
result.addLocal(face.getTemporalMesh().getNormals().get(index));
}
result.divideLocal(face.getIndexes().size());
return result;
}
/**
* The method computes the UV coordinates of the face middle point.
* @param face
* the face of the mesh
* @return a map whose key is the name of the UV set and value is the UV coordinate of the face's middle point
*/
private Map<String, Vector2f> computeFaceUVs(Face face) {
Map<String, Vector2f> result = null;
Map<String, List<Vector2f>> uvSets = face.getUvSets();
if (uvSets != null && uvSets.size() > 0) {
result = new HashMap<String, Vector2f>(uvSets.size());
for (Entry<String, List<Vector2f>> entry : uvSets.entrySet()) {
Vector2f faceUV = new Vector2f();
for (Vector2f uv : entry.getValue()) {
faceUV.addLocal(uv);
}
faceUV.divideLocal(entry.getValue().size());
result.put(entry.getKey(), faceUV);
}
}
return result;
}
/**
* The mesh interpolates the values of vertex groups weights for new vertices.
* @param vertexGroups
* the vertex groups
* @return interpolated weights of given vertex groups' weights
*/
private Map<String, Float> interpolateVertexGroups(List<Map<String, Float>> vertexGroups) {
Map<String, Float> weightSums = new HashMap<String, Float>();
if (vertexGroups.size() > 0) {
for (Map<String, Float> vGroup : vertexGroups) {
for (Entry<String, Float> entry : vGroup.entrySet()) {
if (weightSums.containsKey(entry.getKey())) {
weightSums.put(entry.getKey(), weightSums.get(entry.getKey()) + entry.getValue());
} else {
weightSums.put(entry.getKey(), entry.getValue());
}
}
}
}
Map<String, Float> result = new HashMap<String, Float>(weightSums.size());
for (Entry<String, Float> entry : weightSums.entrySet()) {
result.put(entry.getKey(), entry.getValue() / vertexGroups.size());
}
return result;
}
/**
* The method computes the vertex groups values for face's middle point.
* @param face
* the face of the mesh
* @return face's middle point interpolated vertex groups' weights
*/
private Map<String, Float> computeFaceVertexGroups(Face face) {
if (face.getTemporalMesh().getVertexGroups().size() > 0) {
List<Map<String, Float>> vertexGroups = new ArrayList<Map<String, Float>>(face.getIndexes().size());
for (Integer index : face.getIndexes()) {
vertexGroups.add(face.getTemporalMesh().getVertexGroups().get(index));
}
return this.interpolateVertexGroups(vertexGroups);
}
return new HashMap<String, Float>();
}
/**
* The method computes face's middle point vertex color.
* @param face
* the face of the mesh
* @return face's middle point vertex color
*/
private byte[] computeFaceVertexColor(Face face) {
if (face.getVertexColors() != null) {
return this.interpolateVertexColors(face.getVertexColors().toArray(new byte[face.getVertexColors().size()][]));
}
return null;
}
/**
* The method computes the average value for the given vertex colors.
* @param colors
* the vertex colors
* @return vertex colors' average value
*/
private byte[] interpolateVertexColors(byte[]... colors) {
TexturePixel pixel = new TexturePixel();
TexturePixel temp = new TexturePixel();
for (int i = 0; i < colors.length; ++i) {
temp.fromARGB8(colors[i][3], colors[i][0], colors[i][1], colors[i][2]);
pixel.add(temp);
}
pixel.divide(colors.length);
byte[] result = new byte[4];
pixel.toRGBA8(result);
return result;
}
/**
* The method finds an edge between the given vertices in the mesh.
* @param temporalMesh
* the mesh
* @param index1
* first index of the edge
* @param index2
* second index of the edge
* @return found edge or null
*/
private Edge findEdge(TemporalMesh temporalMesh, int index1, int index2) {
for (Edge edge : temporalMesh.getEdges()) {
if (edge.getFirstIndex() == index1 && edge.getSecondIndex() == index2 || edge.getFirstIndex() == index2 && edge.getSecondIndex() == index1) {
return edge;
}
}
return null;
}
/**
* This is a helper class for UV coordinates subdivision. UV's form a mesh that is being applied the same algorithms as a regular mesh.
* This way one code handles two issues. After applying Catmull-Clark algorithm the UV-mesh is transformed back into UV coordinates.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class UvCoordsSubdivideTemporalMesh extends TemporalMesh {
private static final Vector3f NORMAL = new Vector3f(0, 0, 1);
public UvCoordsSubdivideTemporalMesh(BlenderContext blenderContext) throws BlenderFileException {
super(null, blenderContext, false);
}
/**
* Adds a UV-face to the mesh.
* @param uvs
* the UV coordinates
*/
public void addFace(List<Vector2f> uvs) {
Integer[] indexes = new Integer[uvs.size()];
int i = 0;
for (Vector2f uv : uvs) {
Vector3f v = new Vector3f(uv.x, uv.y, 0);
int index = vertices.indexOf(v);
if (index >= 0) {
indexes[i++] = index;
} else {
indexes[i++] = vertices.size();
vertices.add(v);
normals.add(NORMAL);
}
}
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[indexes.length - 1], indexes[0], 0, true, vertices));
}
/**
* Converts the mesh back into UV coordinates for the given face.
* @param faceIndex
* the index of the face
* @return UV coordinates
*/
public List<Vector2f> faceToUVs(int faceIndex) {
Face face = faces.get(faceIndex);
List<Vector2f> result = new ArrayList<Vector2f>(face.getIndexes().size());
for (Integer index : face.getIndexes()) {
Vector3f v = vertices.get(index);
result.add(new Vector2f(v.x, v.y));
}
return result;
}
}
}
Loading…
Cancel
Save