|
@@ -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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|