parent
124b5e51da
commit
6e21b0527c
@ -0,0 +1,222 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.meshes; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
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; |
||||||
|
import com.jme3.scene.plugins.blender.file.Structure; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that represents a single edge between two vertices. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
/* package */class Edge extends Line { |
||||||
|
private static final long serialVersionUID = 7172714692126675311L; |
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(Edge.class.getName()); |
||||||
|
|
||||||
|
/** The vertices indexes. */ |
||||||
|
private int index1, index2; |
||||||
|
|
||||||
|
public Edge() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor only stores the indexes of the vertices. The position vertices should be stored |
||||||
|
* outside this class. |
||||||
|
* @param index1 |
||||||
|
* the first index of the edge |
||||||
|
* @param index2 |
||||||
|
* the second index of the edge |
||||||
|
*/ |
||||||
|
private Edge(int index1, int index2) { |
||||||
|
this.index1 = index1; |
||||||
|
this.index2 = index2; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 vertices |
||||||
|
* the vertices of the mesh |
||||||
|
*/ |
||||||
|
public Edge(int index1, int index2, List<Vector3f> vertices) { |
||||||
|
this(index1, index2); |
||||||
|
this.set(vertices.get(index1), vertices.get(index2)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Edge clone() { |
||||||
|
Edge result = new Edge(index1, index2); |
||||||
|
result.setOrigin(this.getOrigin()); |
||||||
|
result.setDirection(this.getDirection()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the first index of the edge |
||||||
|
*/ |
||||||
|
public int getFirstIndex() { |
||||||
|
return index1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the second index of the edge |
||||||
|
*/ |
||||||
|
public int getSecondIndex() { |
||||||
|
return index2; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Shifts indexes by a given amount. |
||||||
|
* @param shift |
||||||
|
* how much the indexes should be shifted |
||||||
|
*/ |
||||||
|
public void shiftIndexes(int shift) { |
||||||
|
index1 += shift; |
||||||
|
index2 += shift; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Flips the order of the indexes. |
||||||
|
*/ |
||||||
|
public void flipIndexes() { |
||||||
|
int temp = index1; |
||||||
|
index1 = index2; |
||||||
|
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 |
||||||
|
* the we consider them to be the same point (the lines cross). |
||||||
|
* The second step is to check if both points are contained within the edges. |
||||||
|
* |
||||||
|
* The method of computing the crossing point is as follows: |
||||||
|
* Let's assume that: |
||||||
|
* (P0, P1) are the points of the first edge |
||||||
|
* (Q0, Q1) are the points of the second edge |
||||||
|
* |
||||||
|
* u = P1 - P0 |
||||||
|
* v = Q1 - Q0 |
||||||
|
* |
||||||
|
* This gives us the equations of two lines: |
||||||
|
* L1: (x = P1x + ux*t1; y = P1y + uy*t1; z = P1z + uz*t1) |
||||||
|
* L2: (x = P2x + vx*t2; y = P2y + vy*t2; z = P2z + vz*t2) |
||||||
|
* |
||||||
|
* Comparing the x and y of the first two equations for each line will allow us to compute t1 and t2 |
||||||
|
* (which is implemented below). |
||||||
|
* Using t1 and t2 we can compute (x, y, z) of each line and that will give us two points that we need to compare. |
||||||
|
* |
||||||
|
* @param edge |
||||||
|
* the edge we check against crossing |
||||||
|
* @return <b>true</b> 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(); |
||||||
|
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
|
||||||
|
Vector3f p = p1.subtract(P1); |
||||||
|
float cos = p.dot(u) / (p.length() * u.length()); |
||||||
|
if (cos > 0 && p.length() <= u.length()) { |
||||||
|
// 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(); |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
String result = "Edge [" + index1 + ", " + index2 + "]"; |
||||||
|
if (this.getOrigin() != null && this.getDirection() != null) { |
||||||
|
result += " -> {" + this.getOrigin() + ", " + this.getOrigin().add(this.getDirection()) + "}"; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
// The hash code must be identical for the same two indexes, no matter their order.
|
||||||
|
final int prime = 31; |
||||||
|
int result = 1; |
||||||
|
int lowerIndex = Math.min(index1, index2); |
||||||
|
int higherIndex = Math.max(index1, index2); |
||||||
|
result = prime * result + lowerIndex; |
||||||
|
result = prime * result + higherIndex; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if (!(obj instanceof Edge)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (this == obj) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
Edge other = (Edge) obj; |
||||||
|
return Math.min(index1, index2) == Math.min(other.index1, other.index2) && Math.max(index1, index2) == Math.max(other.index1, other.index2); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method loads all edges from the given mesh structure that does not belong to any face. |
||||||
|
* @param meshStructure |
||||||
|
* the mesh structure |
||||||
|
* @return all edges without faces |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when problems with file reading occur |
||||||
|
*/ |
||||||
|
public static List<Edge> loadAll(Structure meshStructure) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName()); |
||||||
|
List<Edge> result = new ArrayList<Edge>(); |
||||||
|
|
||||||
|
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); |
||||||
|
|
||||||
|
if (pMEdge.isNotNull()) { |
||||||
|
List<Structure> edges = pMEdge.fetchData(); |
||||||
|
for (Structure edge : edges) { |
||||||
|
int flag = ((Number) edge.getFieldValue("flag")).intValue(); |
||||||
|
if ((flag & MeshHelper.EDGE_NOT_IN_FACE_FLAG) != 0) { |
||||||
|
int v1 = ((Number) edge.getFieldValue("v1")).intValue(); |
||||||
|
int v2 = ((Number) edge.getFieldValue("v2")).intValue(); |
||||||
|
result.add(new Edge(v1, v2)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,533 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.meshes; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.blender.BlenderContext; |
||||||
|
import com.jme3.scene.plugins.blender.file.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.file.Pointer; |
||||||
|
import com.jme3.scene.plugins.blender.file.Structure; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that represents a single face in the mesh. The face is a polygon. Its minimum count of |
||||||
|
* vertices is = 3. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
/* package */class Face implements Comparator<Integer> { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(Face.class.getName()); |
||||||
|
|
||||||
|
/** The indexes loop of the face. */ |
||||||
|
private IndexesLoop indexes; |
||||||
|
/** Indicates if the face is smooth or solid. */ |
||||||
|
private boolean smooth; |
||||||
|
/** The material index of the face. */ |
||||||
|
private int materialNumber; |
||||||
|
/** UV coordinate sets attached to the face. The key is the set name and value are the UV coords. */ |
||||||
|
private Map<String, List<Vector2f>> faceUVCoords; |
||||||
|
/** The vertex colors of the face. */ |
||||||
|
private List<byte[]> vertexColors; |
||||||
|
/** The temporal mesh the face belongs to. */ |
||||||
|
private TemporalMesh temporalMesh; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a complete face with all available data. |
||||||
|
* @param indexes |
||||||
|
* the indexes of the face (required) |
||||||
|
* @param smooth |
||||||
|
* indicates if the face is smooth or solid |
||||||
|
* @param materialNumber |
||||||
|
* the material index of the face |
||||||
|
* @param faceUVCoords |
||||||
|
* UV coordinate sets of the face (optional) |
||||||
|
* @param vertexColors |
||||||
|
* the vertex colors of the face (optional) |
||||||
|
* @param temporalMesh |
||||||
|
* the temporal mesh the face belongs to (required) |
||||||
|
*/ |
||||||
|
public Face(Integer[] indexes, boolean smooth, int materialNumber, Map<String, List<Vector2f>> faceUVCoords, List<byte[]> vertexColors, TemporalMesh temporalMesh) { |
||||||
|
this.setTemporalMesh(temporalMesh); |
||||||
|
this.indexes = new IndexesLoop(indexes); |
||||||
|
this.smooth = smooth; |
||||||
|
this.materialNumber = materialNumber; |
||||||
|
this.faceUVCoords = faceUVCoords; |
||||||
|
this.temporalMesh = temporalMesh; |
||||||
|
this.vertexColors = vertexColors; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor. Used by the clone method. |
||||||
|
*/ |
||||||
|
private Face() { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Face clone() { |
||||||
|
Face result = new Face(); |
||||||
|
result.indexes = indexes.clone(); |
||||||
|
result.smooth = smooth; |
||||||
|
result.materialNumber = materialNumber; |
||||||
|
if (faceUVCoords != null) { |
||||||
|
result.faceUVCoords = new HashMap<String, List<Vector2f>>(faceUVCoords.size()); |
||||||
|
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) { |
||||||
|
List<Vector2f> uvs = new ArrayList<Vector2f>(entry.getValue().size()); |
||||||
|
for (Vector2f v : entry.getValue()) { |
||||||
|
uvs.add(v.clone()); |
||||||
|
} |
||||||
|
result.faceUVCoords.put(entry.getKey(), uvs); |
||||||
|
} |
||||||
|
} |
||||||
|
if (vertexColors != null) { |
||||||
|
result.vertexColors = new ArrayList<byte[]>(vertexColors.size()); |
||||||
|
for (byte[] colors : vertexColors) { |
||||||
|
result.vertexColors.add(colors.clone()); |
||||||
|
} |
||||||
|
} |
||||||
|
result.temporalMesh = temporalMesh; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the index at the given position in the index loop. If the given position is negative or exceeds |
||||||
|
* the amount of vertices - it is being looped properly so that it always hits an index. |
||||||
|
* For example getIndex(-1) will return the index before the 0 - in this case it will be the last one. |
||||||
|
* @param indexPosition |
||||||
|
* the index position |
||||||
|
* @return index value at the given position |
||||||
|
*/ |
||||||
|
private Integer getIndex(int indexPosition) { |
||||||
|
if (indexPosition >= indexes.size()) { |
||||||
|
indexPosition = indexPosition % indexes.size(); |
||||||
|
} else if (indexPosition < 0) { |
||||||
|
indexPosition = indexes.size() - -indexPosition % indexes.size(); |
||||||
|
} |
||||||
|
return indexes.get(indexPosition); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return all indexes |
||||||
|
*/ |
||||||
|
public List<Integer> getIndexes() { |
||||||
|
return indexes.getAll(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method detaches the triangle from the face. This method keeps the indexes loop normalized - every index |
||||||
|
* has only two neighbours. So if detaching the triangle causes a vertex to have more than two neighbours - it is |
||||||
|
* also detached and returned as a result. |
||||||
|
* The result is an empty list if no such situation happens. |
||||||
|
* @param triangleIndexes |
||||||
|
* the indexes of a triangle to be detached |
||||||
|
* @return a list of faces that need to be detached as well in order to keep them normalized |
||||||
|
*/ |
||||||
|
private List<Face> detachTriangle(Integer[] triangleIndexes) { |
||||||
|
LOGGER.fine("Detaching triangle."); |
||||||
|
if (triangleIndexes.length != 3) { |
||||||
|
throw new IllegalArgumentException("Cannot detach triangle with that does not have 3 indexes!"); |
||||||
|
} |
||||||
|
List<Face> detachedFaces = new ArrayList<Face>(); |
||||||
|
|
||||||
|
boolean[] edgeRemoved = new boolean[] { indexes.removeEdge(triangleIndexes[0], triangleIndexes[1]), indexes.removeEdge(triangleIndexes[0], triangleIndexes[2]), indexes.removeEdge(triangleIndexes[1], triangleIndexes[2]) }; |
||||||
|
Integer[][] indexesPairs = new Integer[][] { new Integer[] { triangleIndexes[0], triangleIndexes[1] }, new Integer[] { triangleIndexes[0], triangleIndexes[2] }, new Integer[] { triangleIndexes[1], triangleIndexes[2] } }; |
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) { |
||||||
|
if (!edgeRemoved[i]) { |
||||||
|
List<Integer> path = indexes.findPath(indexesPairs[i][0], indexesPairs[i][1]); |
||||||
|
if (path == null) { |
||||||
|
path = indexes.findPath(indexesPairs[i][1], indexesPairs[i][0]); |
||||||
|
} |
||||||
|
if (path == null) { |
||||||
|
throw new IllegalStateException("Triangulation failed. Cannot find path between two indexes. Please apply triangulation in Blender as a workaround."); |
||||||
|
} |
||||||
|
if (detachedFaces.size() == 0 && path.size() < indexes.size()) { |
||||||
|
detachedFaces.add(new Face(path.toArray(new Integer[path.size()]), smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh)); |
||||||
|
for (int j = 0; j < path.size() - 1; ++j) { |
||||||
|
indexes.removeEdge(path.get(j), path.get(j + 1)); |
||||||
|
} |
||||||
|
indexes.removeEdge(path.get(path.size() - 1), path.get(0)); |
||||||
|
} else { |
||||||
|
indexes.addEdge(path.get(path.size() - 1), path.get(0)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return detachedFaces; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method returns the position of the given index in the indexes loop. |
||||||
|
* @param index |
||||||
|
* the index whose position will be queried |
||||||
|
* @return position of the given index or -1 if such index is not in the index loop |
||||||
|
*/ |
||||||
|
private int indexOf(Integer index) { |
||||||
|
return indexes.indexOf(index); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method shifts all indexes by a given value. |
||||||
|
* @param shift |
||||||
|
* the value to shift all indexes |
||||||
|
*/ |
||||||
|
public void shiftIndexes(int shift) { |
||||||
|
indexes.shiftIndexes(shift); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the temporal mesh for the face. The given mesh cannot be null. |
||||||
|
* @param temporalMesh |
||||||
|
* the temporal mesh of the face |
||||||
|
* @throws IllegalArgumentException |
||||||
|
* thrown if given temporal mesh is null |
||||||
|
*/ |
||||||
|
public void setTemporalMesh(TemporalMesh temporalMesh) { |
||||||
|
if (temporalMesh == null) { |
||||||
|
throw new IllegalArgumentException("No temporal mesh for the face given!"); |
||||||
|
} |
||||||
|
this.temporalMesh = temporalMesh; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Flips the order of the indexes. |
||||||
|
*/ |
||||||
|
public void flipIndexes() { |
||||||
|
indexes.reverse(); |
||||||
|
if (faceUVCoords != null) { |
||||||
|
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) { |
||||||
|
Collections.reverse(entry.getValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Flips UV coordinates. |
||||||
|
* @param u |
||||||
|
* indicates if U coords should be flipped |
||||||
|
* @param v |
||||||
|
* indicates if V coords should be flipped |
||||||
|
*/ |
||||||
|
public void flipUV(boolean u, boolean v) { |
||||||
|
if (faceUVCoords != null) { |
||||||
|
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) { |
||||||
|
for (Vector2f uv : entry.getValue()) { |
||||||
|
uv.set(u ? 1 - uv.x : uv.x, v ? 1 - uv.y : uv.y); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the UV sets of the face |
||||||
|
*/ |
||||||
|
public Map<String, List<Vector2f>> getUvSets() { |
||||||
|
return faceUVCoords; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return current vertex count of the face |
||||||
|
*/ |
||||||
|
public int vertexCount() { |
||||||
|
return indexes.size(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method triangulates the face. |
||||||
|
* @param vertices |
||||||
|
* the vertices of the mesh (all verts and not only those belonging to the face) |
||||||
|
* @param normals |
||||||
|
* the normals of the mesh (all normals and not only those belonging to the face) |
||||||
|
* @return a list of faces that are triangles |
||||||
|
*/ |
||||||
|
public List<Face> triangulate(List<Vector3f> vertices, List<Vector3f> normals) { |
||||||
|
LOGGER.fine("Triangulating face."); |
||||||
|
assert indexes.size() >= 3 : "Invalid indexes amount for face. 3 is the required minimum!"; |
||||||
|
List<Face> result = new ArrayList<Face>(); |
||||||
|
|
||||||
|
List<Face> facesToTriangulate = new ArrayList<Face>(Arrays.asList(this.clone())); |
||||||
|
while (facesToTriangulate.size() > 0) { |
||||||
|
Face face = facesToTriangulate.remove(0); |
||||||
|
int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1; |
||||||
|
while (face.vertexCount() > 0) { |
||||||
|
int index1 = face.getIndex(0); |
||||||
|
int index2 = face.findClosestVertex(index1, -1); |
||||||
|
int index3 = face.findClosestVertex(index1, index2); |
||||||
|
|
||||||
|
LOGGER.finer("Veryfying improper triangulation of the temporal mesh."); |
||||||
|
if(index1 < 0 || index2 < 0 || index3 < 0) { |
||||||
|
throw new IllegalStateException("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 == index1 && previousIndex2 == index2 && previousIndex3 == index3) { |
||||||
|
throw new IllegalStateException("Infinite loop detected during triangulation of mesh: " + temporalMesh + |
||||||
|
"Please apply triangulation modifier in blender as a workaround and load again!"); |
||||||
|
} |
||||||
|
previousIndex1 = index1; |
||||||
|
previousIndex2 = index2; |
||||||
|
previousIndex3 = index3; |
||||||
|
|
||||||
|
Integer[] indexes = new Integer[] { index1, index2, index3 }; |
||||||
|
Arrays.sort(indexes, this); |
||||||
|
|
||||||
|
List<Face> detachedFaces = face.detachTriangle(indexes); |
||||||
|
facesToTriangulate.addAll(detachedFaces); |
||||||
|
|
||||||
|
int indexOf0 = this.indexOf(indexes[0]); |
||||||
|
int indexOf1 = this.indexOf(indexes[1]); |
||||||
|
int indexOf2 = this.indexOf(indexes[2]); |
||||||
|
|
||||||
|
Map<String, List<Vector2f>> faceUVS = new HashMap<String, List<Vector2f>>(); |
||||||
|
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) { |
||||||
|
List<Vector2f> uvs = new ArrayList<Vector2f>(3); |
||||||
|
uvs.add(entry.getValue().get(indexOf0)); |
||||||
|
uvs.add(entry.getValue().get(indexOf1)); |
||||||
|
uvs.add(entry.getValue().get(indexOf2)); |
||||||
|
faceUVS.put(entry.getKey(), uvs); |
||||||
|
} |
||||||
|
|
||||||
|
List<byte[]> vertexColors = null; |
||||||
|
if (this.vertexColors != null) { |
||||||
|
vertexColors = new ArrayList<byte[]>(3); |
||||||
|
vertexColors.add(this.vertexColors.get(indexOf0)); |
||||||
|
vertexColors.add(this.vertexColors.get(indexOf1)); |
||||||
|
vertexColors.add(this.vertexColors.get(indexOf2)); |
||||||
|
} |
||||||
|
|
||||||
|
result.add(new Face(indexes, smooth, materialNumber, faceUVS, vertexColors, temporalMesh)); |
||||||
|
} |
||||||
|
} |
||||||
|
LOGGER.log(Level.FINE, "Face triangulated on {0} faces.", result.size()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return <b>true</b> if the face is smooth and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean isSmooth() { |
||||||
|
return smooth; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the material index of the face |
||||||
|
*/ |
||||||
|
public int getMaterialNumber() { |
||||||
|
return materialNumber; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the vertices colord of the face |
||||||
|
*/ |
||||||
|
public List<byte[]> getVertexColors() { |
||||||
|
return vertexColors; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "Face " + indexes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method finds the closest vertex to the one specified by <b>index</b>. |
||||||
|
* If the vertexToIgnore is positive than it will be ignored in the result. |
||||||
|
* The closes vertex must be able to create an edge that is fully contained within the face and does not cross |
||||||
|
* any other edges. |
||||||
|
* @param index |
||||||
|
* the index of the vertex that needs to have found the nearest neighbour |
||||||
|
* @param indexToIgnore |
||||||
|
* the index to ignore in the result (pass -1 if none is to be ignored) |
||||||
|
* @return the index of the closest vertex to the given one |
||||||
|
*/ |
||||||
|
private int findClosestVertex(int index, int indexToIgnore) { |
||||||
|
int result = -1; |
||||||
|
List<Vector3f> vertices = temporalMesh.getVertices(); |
||||||
|
Vector3f v1 = vertices.get(index); |
||||||
|
float distance = Float.MAX_VALUE; |
||||||
|
for (int i : indexes) { |
||||||
|
if (i != index && i != indexToIgnore) { |
||||||
|
Vector3f v2 = vertices.get(i); |
||||||
|
float d = v2.distance(v1); |
||||||
|
if (d < distance && this.contains(new Edge(index, i, vertices))) { |
||||||
|
result = i; |
||||||
|
distance = d; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method verifies if the edge is contained within the face. |
||||||
|
* It means it cannot cross any other edge and it must be inside the face and not outside of it. |
||||||
|
* @param edge |
||||||
|
* the edge to be checked |
||||||
|
* @return <b>true</b> if the given edge is contained within the face and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
private boolean contains(Edge edge) { |
||||||
|
int index1 = edge.getFirstIndex(); |
||||||
|
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<Vector3f> 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)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 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 = this.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; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Loads all faces of a given mesh. |
||||||
|
* @param meshStructure |
||||||
|
* the mesh structure we read the faces from |
||||||
|
* @param userUVGroups |
||||||
|
* UV groups defined by the user |
||||||
|
* @param verticesColors |
||||||
|
* the vertices colors of the mesh |
||||||
|
* @param temporalMesh |
||||||
|
* the temporal mesh the faces will belong to |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @return list of faces read from the given mesh structure |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when problems with file reading occur |
||||||
|
*/ |
||||||
|
public static List<Face> loadAll(Structure meshStructure, Map<String, List<Vector2f>> userUVGroups, List<byte[]> verticesColors, TemporalMesh temporalMesh, BlenderContext blenderContext) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.FINE, "Loading all faces from mesh: {0}", meshStructure.getName()); |
||||||
|
List<Face> result = new ArrayList<Face>(); |
||||||
|
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); |
||||||
|
if (meshHelper.isBMeshCompatible(meshStructure)) { |
||||||
|
LOGGER.fine("Reading BMesh."); |
||||||
|
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); |
||||||
|
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); |
||||||
|
|
||||||
|
if (pMPoly.isNotNull() && pMLoop.isNotNull()) { |
||||||
|
List<Structure> polys = pMPoly.fetchData(); |
||||||
|
List<Structure> loops = pMLoop.fetchData(); |
||||||
|
for (Structure poly : polys) { |
||||||
|
int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); |
||||||
|
int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); |
||||||
|
int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); |
||||||
|
boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; |
||||||
|
Integer[] vertexIndexes = new Integer[totLoop]; |
||||||
|
|
||||||
|
for (int i = loopStart; i < loopStart + totLoop; ++i) { |
||||||
|
vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); |
||||||
|
} |
||||||
|
|
||||||
|
// uvs always must be added wheater we have texture or not
|
||||||
|
Map<String, List<Vector2f>> uvCoords = new HashMap<String, List<Vector2f>>(); |
||||||
|
for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) { |
||||||
|
List<Vector2f> uvs = entry.getValue().subList(loopStart, loopStart + totLoop); |
||||||
|
uvCoords.put(entry.getKey(), new ArrayList<Vector2f>(uvs)); |
||||||
|
} |
||||||
|
|
||||||
|
List<byte[]> vertexColors = null; |
||||||
|
if (verticesColors != null && verticesColors.size() > 0) { |
||||||
|
vertexColors = new ArrayList<byte[]>(totLoop); |
||||||
|
for (int i = loopStart; i < loopStart + totLoop; ++i) { |
||||||
|
vertexColors.add(verticesColors.get(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
result.add(new Face(vertexIndexes, smooth, materialNumber, uvCoords, vertexColors, temporalMesh)); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
LOGGER.fine("Reading traditional faces."); |
||||||
|
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); |
||||||
|
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null; |
||||||
|
if (mFaces != null && mFaces.size() > 0) { |
||||||
|
// indicates if the material with the specified number should have a texture attached
|
||||||
|
for (int i = 0; i < mFaces.size(); ++i) { |
||||||
|
Structure mFace = mFaces.get(i); |
||||||
|
int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); |
||||||
|
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; |
||||||
|
|
||||||
|
int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); |
||||||
|
int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); |
||||||
|
int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); |
||||||
|
int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); |
||||||
|
|
||||||
|
int vertCount = v4 == 0 ? 3 : 4; |
||||||
|
|
||||||
|
// uvs always must be added wheater we have texture or not
|
||||||
|
Map<String, List<Vector2f>> faceUVCoords = new HashMap<String, List<Vector2f>>(); |
||||||
|
for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) { |
||||||
|
List<Vector2f> uvCoordsForASingleFace = new ArrayList<Vector2f>(vertCount); |
||||||
|
for (int j = 0; j < vertCount; ++j) { |
||||||
|
uvCoordsForASingleFace.add(entry.getValue().get(i * 4 + j)); |
||||||
|
} |
||||||
|
faceUVCoords.put(entry.getKey(), uvCoordsForASingleFace); |
||||||
|
} |
||||||
|
|
||||||
|
List<byte[]> vertexColors = null; |
||||||
|
if (verticesColors != null && verticesColors.size() > 0) { |
||||||
|
vertexColors = new ArrayList<byte[]>(vertCount); |
||||||
|
|
||||||
|
vertexColors.add(verticesColors.get(v1)); |
||||||
|
vertexColors.add(verticesColors.get(v2)); |
||||||
|
vertexColors.add(verticesColors.get(v3)); |
||||||
|
if (vertCount == 4) { |
||||||
|
vertexColors.add(verticesColors.get(v4)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
result.add(new Face(vertCount == 4 ? new Integer[] { v1, v2, v3, v4 } : new Integer[] { v1, v2, v3 }, smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
LOGGER.log(Level.FINE, "Loaded {0} faces.", result.size()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int compare(Integer index1, Integer index2) { |
||||||
|
return this.indexOf(index1) - this.indexOf(index2); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,258 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.meshes; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
|
||||||
|
/** |
||||||
|
* This class represents the Face's indexes loop. It is a simplified implementation of directed graph. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> { |
||||||
|
/** The indexes. */ |
||||||
|
private List<Integer> nodes; |
||||||
|
/** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */ |
||||||
|
private Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* The constructor uses the given nodes in their give order. Each neighbour indexes will form an edge. |
||||||
|
* @param nodes |
||||||
|
* the nodes for the loop |
||||||
|
*/ |
||||||
|
public IndexesLoop(Integer[] nodes) { |
||||||
|
this.nodes = new ArrayList<Integer>(Arrays.asList(nodes)); |
||||||
|
this.prepareEdges(this.nodes); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public IndexesLoop clone() { |
||||||
|
return new IndexesLoop(nodes.toArray(new Integer[nodes.size()])); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method prepares edges for the given indexes. |
||||||
|
* @param nodes |
||||||
|
* the indexes |
||||||
|
*/ |
||||||
|
private void prepareEdges(List<Integer> nodes) { |
||||||
|
for (int i = 0; i < nodes.size() - 1; ++i) { |
||||||
|
if (edges.containsKey(nodes.get(i))) { |
||||||
|
edges.get(nodes.get(i)).add(nodes.get(i + 1)); |
||||||
|
} else { |
||||||
|
edges.put(nodes.get(i), new ArrayList<Integer>(Arrays.asList(nodes.get(i + 1)))); |
||||||
|
} |
||||||
|
} |
||||||
|
edges.put(nodes.get(nodes.size() - 1), new ArrayList<Integer>(Arrays.asList(nodes.get(0)))); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the count of indexes |
||||||
|
*/ |
||||||
|
public int size() { |
||||||
|
return nodes.size(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds edge to the loop. |
||||||
|
* @param from |
||||||
|
* the start index |
||||||
|
* @param to |
||||||
|
* the end index |
||||||
|
*/ |
||||||
|
public void addEdge(Integer from, Integer to) { |
||||||
|
if (nodes.contains(from) && nodes.contains(to)) { |
||||||
|
if (edges.containsKey(from) && !edges.get(from).contains(to)) { |
||||||
|
edges.get(from).add(to); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes edge from the face. The edge is removed if it already exists in the face. |
||||||
|
* @param node1 |
||||||
|
* the first index of the edge to be removed |
||||||
|
* @param node2 |
||||||
|
* the second index of the edge to be removed |
||||||
|
* @return <b>true</b> if the edge was removed and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean removeEdge(Integer node1, Integer node2) { |
||||||
|
boolean edgeRemoved = false; |
||||||
|
if (nodes.contains(node1) && nodes.contains(node2)) { |
||||||
|
if (edges.containsKey(node1)) { |
||||||
|
edgeRemoved |= edges.get(node1).remove(node2); |
||||||
|
} |
||||||
|
if (edges.containsKey(node2)) { |
||||||
|
edgeRemoved |= edges.get(node2).remove(node1); |
||||||
|
} |
||||||
|
if (edgeRemoved) { |
||||||
|
if (this.getNeighbourCount(node1) == 0) { |
||||||
|
this.removeIndexes(node1); |
||||||
|
} |
||||||
|
if (this.getNeighbourCount(node2) == 0) { |
||||||
|
this.removeIndexes(node2); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return edgeRemoved; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tells if the given indexes are neighbours. |
||||||
|
* @param index1 |
||||||
|
* the first index |
||||||
|
* @param index2 |
||||||
|
* the second index |
||||||
|
* @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean areNeighbours(Integer index1, Integer index2) { |
||||||
|
if (index1.equals(index2)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return edges.get(index1).contains(index2) || edges.get(index2).contains(index1); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method shifts all indexes by a given value. |
||||||
|
* @param shift |
||||||
|
* the value to shift all indexes |
||||||
|
*/ |
||||||
|
public void shiftIndexes(int shift) { |
||||||
|
List<Integer> nodes = new ArrayList<Integer>(this.nodes.size()); |
||||||
|
for (Integer node : this.nodes) { |
||||||
|
nodes.add(node + shift); |
||||||
|
} |
||||||
|
|
||||||
|
Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>(); |
||||||
|
for (Entry<Integer, List<Integer>> entry : this.edges.entrySet()) { |
||||||
|
List<Integer> neighbours = new ArrayList<Integer>(entry.getValue().size()); |
||||||
|
for (Integer neighbour : entry.getValue()) { |
||||||
|
neighbours.add(neighbour + shift); |
||||||
|
} |
||||||
|
edges.put(entry.getKey() + shift, neighbours); |
||||||
|
} |
||||||
|
|
||||||
|
this.nodes = nodes; |
||||||
|
this.edges = edges; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Reverses the order of the indexes. |
||||||
|
*/ |
||||||
|
public void reverse() { |
||||||
|
Collections.reverse(nodes); |
||||||
|
edges.clear(); |
||||||
|
this.prepareEdges(nodes); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the neighbour count of the given index. |
||||||
|
* @param index |
||||||
|
* the index whose neighbour count will be checked |
||||||
|
* @return the count of neighbours of the given index |
||||||
|
*/ |
||||||
|
public int getNeighbourCount(Integer index) { |
||||||
|
int result = 0; |
||||||
|
if (edges.containsKey(index)) { |
||||||
|
result = edges.get(index).size(); |
||||||
|
for (List<Integer> neighbours : edges.values()) { |
||||||
|
if (neighbours.contains(index)) { |
||||||
|
++result; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the position of the given index in the loop. |
||||||
|
* @param index |
||||||
|
* the index of the face |
||||||
|
* @return the indexe's position in the loop |
||||||
|
*/ |
||||||
|
public int indexOf(Integer index) { |
||||||
|
return nodes.indexOf(index); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the index at the given position. |
||||||
|
* @param indexPosition |
||||||
|
* the position of the index |
||||||
|
* @return the index at a given position |
||||||
|
*/ |
||||||
|
public Integer get(int indexPosition) { |
||||||
|
return nodes.get(indexPosition); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return all indexes of the face |
||||||
|
*/ |
||||||
|
public List<Integer> getAll() { |
||||||
|
return new ArrayList<Integer>(nodes); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method removes all given indexes. |
||||||
|
* @param indexes |
||||||
|
* the indexes to be removed |
||||||
|
*/ |
||||||
|
public void removeIndexes(Integer... indexes) { |
||||||
|
for (Integer index : indexes) { |
||||||
|
nodes.remove(index); |
||||||
|
edges.remove(index); |
||||||
|
for (List<Integer> neighbours : edges.values()) { |
||||||
|
neighbours.remove(index); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method finds the path between the given indexes. |
||||||
|
* @param start |
||||||
|
* the start index |
||||||
|
* @param end |
||||||
|
* the end index |
||||||
|
* @return a list containing indexes on the path from start to end (inclusive) |
||||||
|
* @throws IllegalStateException |
||||||
|
* an exception is thrown when the loop is not normalized (at least one |
||||||
|
* index has more than 2 neighbours) |
||||||
|
*/ |
||||||
|
public List<Integer> findPath(Integer start, Integer end) { |
||||||
|
List<Integer> result = new ArrayList<Integer>(); |
||||||
|
Integer node = start; |
||||||
|
while (!node.equals(end)) { |
||||||
|
result.add(node); |
||||||
|
List<Integer> nextSteps = edges.get(node); |
||||||
|
if (nextSteps.size() == 0) { |
||||||
|
return null; |
||||||
|
} else if (nextSteps.size() == 1) { |
||||||
|
node = nextSteps.get(0); |
||||||
|
} else { |
||||||
|
throw new IllegalStateException("Triangulation failed. Face has ambiguous indexes loop. Please triangulate your model in Blender as a workaround."); |
||||||
|
} |
||||||
|
} |
||||||
|
result.add(end); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "IndexesLoop " + nodes.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int compare(Integer i1, Integer i2) { |
||||||
|
return nodes.indexOf(i1) - nodes.indexOf(i2); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterator<Integer> iterator() { |
||||||
|
return nodes.iterator(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,364 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.meshes; |
||||||
|
|
||||||
|
import java.nio.Buffer; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.NavigableMap; |
||||||
|
import java.util.TreeMap; |
||||||
|
|
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.VertexBuffer; |
||||||
|
import com.jme3.scene.VertexBuffer.Format; |
||||||
|
import com.jme3.scene.VertexBuffer.Type; |
||||||
|
import com.jme3.scene.VertexBuffer.Usage; |
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that aggregates the mesh data to prepare proper buffers. The buffers refer only to ONE material. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
/* package */class MeshBuffers { |
||||||
|
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; |
||||||
|
|
||||||
|
/** The material index. */ |
||||||
|
private final int materialIndex; |
||||||
|
/** The vertices. */ |
||||||
|
private List<Vector3f> verts = new ArrayList<Vector3f>(); |
||||||
|
/** The normals. */ |
||||||
|
private List<Vector3f> normals = new ArrayList<Vector3f>(); |
||||||
|
/** The UV coordinate sets. */ |
||||||
|
private Map<String, List<Vector2f>> uvCoords = new HashMap<String, List<Vector2f>>(); |
||||||
|
/** The vertex colors. */ |
||||||
|
private List<byte[]> vertColors = new ArrayList<byte[]>(); |
||||||
|
/** The indexes. */ |
||||||
|
private List<Integer> indexes = new ArrayList<Integer>(); |
||||||
|
/** The maximum weights count assigned to a single vertex. Used during weights normalization. */ |
||||||
|
private int maximumWeightsPerVertex; |
||||||
|
/** A list of mapping between weights and indexes. Each entry for the proper vertex. */ |
||||||
|
private List<TreeMap<Float, Integer>> boneWeightAndIndexes = new ArrayList<TreeMap<Float, Integer>>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor stores only the material index value. |
||||||
|
* @param materialIndex |
||||||
|
* the material index |
||||||
|
*/ |
||||||
|
public MeshBuffers(int materialIndex) { |
||||||
|
this.materialIndex = materialIndex; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the material index |
||||||
|
*/ |
||||||
|
public int getMaterialIndex() { |
||||||
|
return materialIndex; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return indexes buffer |
||||||
|
*/ |
||||||
|
public Buffer getIndexBuffer() { |
||||||
|
if (indexes.size() <= Short.MAX_VALUE) { |
||||||
|
short[] indices = new short[indexes.size()]; |
||||||
|
for (int i = 0; i < indexes.size(); ++i) { |
||||||
|
indices[i] = indexes.get(i).shortValue(); |
||||||
|
} |
||||||
|
return BufferUtils.createShortBuffer(indices); |
||||||
|
} else { |
||||||
|
int[] indices = new int[indexes.size()]; |
||||||
|
for (int i = 0; i < indexes.size(); ++i) { |
||||||
|
indices[i] = indexes.get(i).intValue(); |
||||||
|
} |
||||||
|
return BufferUtils.createIntBuffer(indices); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return positions buffer |
||||||
|
*/ |
||||||
|
public VertexBuffer getPositionsBuffer() { |
||||||
|
VertexBuffer positionBuffer = new VertexBuffer(Type.Position); |
||||||
|
Vector3f[] data = verts.toArray(new Vector3f[verts.size()]); |
||||||
|
positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data)); |
||||||
|
return positionBuffer; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return normals buffer |
||||||
|
*/ |
||||||
|
public VertexBuffer getNormalsBuffer() { |
||||||
|
VertexBuffer positionBuffer = new VertexBuffer(Type.Normal); |
||||||
|
Vector3f[] data = normals.toArray(new Vector3f[normals.size()]); |
||||||
|
positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data)); |
||||||
|
return positionBuffer; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return bone buffers |
||||||
|
*/ |
||||||
|
public BoneBuffersData getBoneBuffers() { |
||||||
|
BoneBuffersData result = null; |
||||||
|
if (maximumWeightsPerVertex > 0) { |
||||||
|
this.normalizeBoneBuffers(MAXIMUM_WEIGHTS_PER_VERTEX); |
||||||
|
maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX; |
||||||
|
|
||||||
|
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX); |
||||||
|
ByteBuffer indicesData = BufferUtils.createByteBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX); |
||||||
|
int index = 0; |
||||||
|
for (Map<Float, Integer> boneBuffersData : boneWeightAndIndexes) { |
||||||
|
if (boneBuffersData.size() > 0) { |
||||||
|
int count = 0; |
||||||
|
for (Entry<Float, Integer> entry : boneBuffersData.entrySet()) { |
||||||
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey()); |
||||||
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue().byteValue()); |
||||||
|
++count; |
||||||
|
} |
||||||
|
} else { |
||||||
|
// if no bone is assigned to this vertex then attach it to the 0-indexed root bone
|
||||||
|
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); |
||||||
|
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); |
||||||
|
} |
||||||
|
++index; |
||||||
|
} |
||||||
|
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); |
||||||
|
verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData); |
||||||
|
|
||||||
|
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); |
||||||
|
verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData); |
||||||
|
|
||||||
|
result = new BoneBuffersData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return UV coordinates sets |
||||||
|
*/ |
||||||
|
public Map<String, List<Vector2f>> getUvCoords() { |
||||||
|
return uvCoords; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return <b>true</b> if vertex colors are used and <b>false</b> otherwise |
||||||
|
*/ |
||||||
|
public boolean areVertexColorsUsed() { |
||||||
|
return vertColors.size() > 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return vertex colors buffer |
||||||
|
*/ |
||||||
|
public ByteBuffer getVertexColorsBuffer() { |
||||||
|
ByteBuffer result = null; |
||||||
|
if (vertColors.size() > 0) { |
||||||
|
result = BufferUtils.createByteBuffer(4 * vertColors.size()); |
||||||
|
for (byte[] v : vertColors) { |
||||||
|
if (v != null) { |
||||||
|
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); |
||||||
|
} else { |
||||||
|
result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0); |
||||||
|
} |
||||||
|
} |
||||||
|
result.flip(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return <b>true</b> if indexes can be shorts' and <b>false</b> if they need to be ints' |
||||||
|
*/ |
||||||
|
public boolean isShortIndexBuffer() { |
||||||
|
return indexes.size() <= Short.MAX_VALUE; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Appends a vertex and normal to the buffers. |
||||||
|
* @param vert |
||||||
|
* vertex |
||||||
|
* @param normal |
||||||
|
* normal vector |
||||||
|
*/ |
||||||
|
public void append(Vector3f vert, Vector3f normal) { |
||||||
|
int index = this.indexOf(vert, normal, null); |
||||||
|
if (index >= 0) { |
||||||
|
indexes.add(index); |
||||||
|
} else { |
||||||
|
indexes.add(verts.size()); |
||||||
|
verts.add(vert); |
||||||
|
normals.add(normal); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Appends the face data to the buffers. |
||||||
|
* @param smooth |
||||||
|
* tells if the face is smooth or flat |
||||||
|
* @param verts |
||||||
|
* the vertices |
||||||
|
* @param normals |
||||||
|
* the normals |
||||||
|
* @param uvCoords |
||||||
|
* the UV coordinates |
||||||
|
* @param vertColors |
||||||
|
* the vertex colors |
||||||
|
* @param vertexGroups |
||||||
|
* the vertex groups |
||||||
|
*/ |
||||||
|
public void append(boolean smooth, Vector3f[] verts, Vector3f[] normals, Map<String, List<Vector2f>> uvCoords, byte[][] vertColors, List<Map<Float, Integer>> vertexGroups) { |
||||||
|
if (verts.length != normals.length) { |
||||||
|
throw new IllegalArgumentException("The amount of verts and normals MUST be equal!"); |
||||||
|
} |
||||||
|
if (vertColors != null && vertColors.length != verts.length) { |
||||||
|
throw new IllegalArgumentException("The amount of vertex colors and vertices MUST be equal!"); |
||||||
|
} |
||||||
|
if (vertexGroups.size() != 0 && vertexGroups.size() != verts.length) { |
||||||
|
throw new IllegalArgumentException("The amount of (if given) vertex groups and vertices MUST be equal!"); |
||||||
|
} |
||||||
|
|
||||||
|
if (!smooth) { |
||||||
|
// make the normals perpendicular to the face
|
||||||
|
normals[0] = normals[1] = normals[2] = FastMath.computeNormal(verts[0], verts[1], verts[2]); |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < verts.length; ++i) { |
||||||
|
int index = -1; |
||||||
|
Map<String, Vector2f> uvCoordsForVertex = this.getUVsForVertex(i, uvCoords); |
||||||
|
if (smooth && (index = this.indexOf(verts[i], normals[i], uvCoordsForVertex)) >= 0) { |
||||||
|
indexes.add(index); |
||||||
|
} else { |
||||||
|
indexes.add(this.verts.size()); |
||||||
|
this.verts.add(verts[i]); |
||||||
|
this.normals.add(normals[i]); |
||||||
|
this.vertColors.add(vertColors[i]); |
||||||
|
|
||||||
|
if (uvCoords != null && uvCoords.size() > 0) { |
||||||
|
for (Entry<String, List<Vector2f>> entry : uvCoords.entrySet()) { |
||||||
|
if (this.uvCoords.containsKey(entry.getKey())) { |
||||||
|
this.uvCoords.get(entry.getKey()).add(entry.getValue().get(i)); |
||||||
|
} else { |
||||||
|
List<Vector2f> uvs = new ArrayList<Vector2f>(); |
||||||
|
uvs.add(entry.getValue().get(i)); |
||||||
|
this.uvCoords.put(entry.getKey(), uvs); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (vertexGroups != null && vertexGroups.size() > 0) { |
||||||
|
Map<Float, Integer> group = vertexGroups.get(i); |
||||||
|
maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, group.size()); |
||||||
|
boneWeightAndIndexes.add(new TreeMap<Float, Integer>(group)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns UV coordinates assigned for the vertex with the proper index. |
||||||
|
* @param vertexIndex |
||||||
|
* the index of the vertex |
||||||
|
* @param uvs |
||||||
|
* all UV coordinates we search in |
||||||
|
* @return a set of UV coordinates assigned to the given vertex |
||||||
|
*/ |
||||||
|
private Map<String, Vector2f> getUVsForVertex(int vertexIndex, Map<String, List<Vector2f>> uvs) { |
||||||
|
if (uvs == null || uvs.size() == 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
Map<String, Vector2f> result = new HashMap<String, Vector2f>(uvs.size()); |
||||||
|
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) { |
||||||
|
result.put(entry.getKey(), entry.getValue().get(vertexIndex)); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method returns an index of a vertex described by the given data. |
||||||
|
* The method tries to find a vertex that mathes the given data. If it does it means |
||||||
|
* that such vertex is already used. |
||||||
|
* @param vert |
||||||
|
* the vertex position coordinates |
||||||
|
* @param normal |
||||||
|
* the vertex's normal vector |
||||||
|
* @param uvCoords |
||||||
|
* the UV coords of the vertex |
||||||
|
* @return index of the found vertex of -1 |
||||||
|
*/ |
||||||
|
private int indexOf(Vector3f vert, Vector3f normal, Map<String, Vector2f> uvCoords) { |
||||||
|
for (int i = 0; i < verts.size(); ++i) { |
||||||
|
if (verts.get(i).equals(vert) && normals.get(i).equals(normal)) { |
||||||
|
if (uvCoords != null && uvCoords.size() > 0) { |
||||||
|
for (Entry<String, Vector2f> entry : uvCoords.entrySet()) { |
||||||
|
List<Vector2f> uvs = this.uvCoords.get(entry.getKey()); |
||||||
|
if (uvs == null) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
if (!uvs.get(i).equals(entry.getValue())) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method normalizes the weights and bone indexes data. |
||||||
|
* First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle. |
||||||
|
* Next it normalizes the weights so that the sum of all verts is 1. |
||||||
|
* @param maximumSize |
||||||
|
* the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX) |
||||||
|
*/ |
||||||
|
private void normalizeBoneBuffers(int maximumSize) { |
||||||
|
for (TreeMap<Float, Integer> group : boneWeightAndIndexes) { |
||||||
|
if (group.size() > maximumSize) { |
||||||
|
NavigableMap<Float, Integer> descendingWeights = group.descendingMap(); |
||||||
|
while (descendingWeights.size() > maximumSize) { |
||||||
|
descendingWeights.pollLastEntry(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// normalizing the weights so that the sum of the values is equal to '1'
|
||||||
|
TreeMap<Float, Integer> normalizedGroup = new TreeMap<Float, Integer>(); |
||||||
|
float sum = 0; |
||||||
|
for (Entry<Float, Integer> entry : group.entrySet()) { |
||||||
|
sum += entry.getKey(); |
||||||
|
} |
||||||
|
|
||||||
|
if (sum != 0 && sum != 1) { |
||||||
|
for (Entry<Float, Integer> entry : group.entrySet()) { |
||||||
|
normalizedGroup.put(entry.getKey() / sum, entry.getValue()); |
||||||
|
} |
||||||
|
group.clear(); |
||||||
|
group.putAll(normalizedGroup); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that gathers the data for mesh bone buffers. |
||||||
|
* Added to increase code readability. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
public static class BoneBuffersData { |
||||||
|
public final int maximumWeightsPerVertex; |
||||||
|
public final VertexBuffer verticesWeights; |
||||||
|
public final VertexBuffer verticesWeightsIndices; |
||||||
|
|
||||||
|
public BoneBuffersData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) { |
||||||
|
this.maximumWeightsPerVertex = maximumWeightsPerVertex; |
||||||
|
this.verticesWeights = verticesWeights; |
||||||
|
this.verticesWeightsIndices = verticesWeightsIndices; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,185 +0,0 @@ |
|||||||
package com.jme3.scene.plugins.blender.meshes; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.LinkedHashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Map.Entry; |
|
||||||
import java.util.logging.Level; |
|
||||||
import java.util.logging.Logger; |
|
||||||
|
|
||||||
import com.jme3.scene.Geometry; |
|
||||||
|
|
||||||
/** |
|
||||||
* Class that holds information about the mesh. |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
public class MeshContext { |
|
||||||
private static final Logger LOGGER = Logger.getLogger(MeshContext.class.getName()); |
|
||||||
|
|
||||||
/** A map between material index and the geometry. */ |
|
||||||
private Map<Integer, List<Geometry>> geometries = new HashMap<Integer, List<Geometry>>(); |
|
||||||
/** The vertex reference map. */ |
|
||||||
private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap; |
|
||||||
/** |
|
||||||
* A vertex group map. The key is the vertex group name and the value is the set of vertex groups. |
|
||||||
* Linked hash map is used because the insertion order is important. |
|
||||||
*/ |
|
||||||
private LinkedHashMap<String, VertexGroup> vertexGroups = new LinkedHashMap<String, VertexGroup>(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a geometry for the specified material index. |
|
||||||
* @param materialIndex |
|
||||||
* the material index |
|
||||||
* @param geometry |
|
||||||
* the geometry |
|
||||||
*/ |
|
||||||
public void putGeometry(Integer materialIndex, Geometry geometry) { |
|
||||||
List<Geometry> geomList = geometries.get(materialIndex); |
|
||||||
if (geomList == null) { |
|
||||||
geomList = new ArrayList<Geometry>(); |
|
||||||
geometries.put(materialIndex, geomList); |
|
||||||
} |
|
||||||
geomList.add(geometry); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param materialIndex |
|
||||||
* the material index |
|
||||||
* @return vertices amount that is used by mesh with the specified material |
|
||||||
*/ |
|
||||||
public int getVertexCount(int materialIndex) { |
|
||||||
int result = 0; |
|
||||||
for (Geometry geometry : geometries.get(materialIndex)) { |
|
||||||
result += geometry.getVertexCount(); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns material index for the geometry. |
|
||||||
* @param geometry |
|
||||||
* the geometry |
|
||||||
* @return material index |
|
||||||
* @throws IllegalStateException |
|
||||||
* this exception is thrown when no material is found for the specified geometry |
|
||||||
*/ |
|
||||||
public int getMaterialIndex(Geometry geometry) { |
|
||||||
for (Entry<Integer, List<Geometry>> entry : geometries.entrySet()) { |
|
||||||
for (Geometry g : entry.getValue()) { |
|
||||||
if (g.equals(geometry)) { |
|
||||||
return entry.getKey(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method returns the vertex reference map. |
|
||||||
* |
|
||||||
* @return the vertex reference map |
|
||||||
*/ |
|
||||||
public Map<Integer, List<Integer>> getVertexReferenceMap(int materialIndex) { |
|
||||||
return vertexReferenceMap.get(materialIndex); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method sets the vertex reference map. |
|
||||||
* |
|
||||||
* @param vertexReferenceMap |
|
||||||
* the vertex reference map |
|
||||||
*/ |
|
||||||
public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) { |
|
||||||
this.vertexReferenceMap = vertexReferenceMap; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a new empty vertex group to the mesh context. |
|
||||||
* @param name |
|
||||||
* the name of the vertex group |
|
||||||
*/ |
|
||||||
public void addVertexGroup(String name) { |
|
||||||
if (!vertexGroups.containsKey(name)) { |
|
||||||
vertexGroups.put(name, new VertexGroup()); |
|
||||||
} else { |
|
||||||
LOGGER.log(Level.WARNING, "Vertex group already added: {0}", name); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a vertex to the vertex group with specified index (the index is the order of adding a group). |
|
||||||
* @param vertexIndex |
|
||||||
* the vertex index |
|
||||||
* @param weight |
|
||||||
* the vertex weight |
|
||||||
* @param vertexGroupIndex |
|
||||||
* the index of a vertex group |
|
||||||
*/ |
|
||||||
public void addVertexToGroup(int vertexIndex, float weight, int vertexGroupIndex) { |
|
||||||
if (vertexGroupIndex < 0 || vertexGroupIndex >= vertexGroups.size()) { |
|
||||||
throw new IllegalArgumentException("Invalid group index: " + vertexGroupIndex); |
|
||||||
} |
|
||||||
int counter = 0; |
|
||||||
for (Entry<String, VertexGroup> vg : vertexGroups.entrySet()) { |
|
||||||
if (vertexGroupIndex == counter) { |
|
||||||
vg.getValue().addVertex(vertexIndex, weight); |
|
||||||
return; |
|
||||||
} |
|
||||||
++counter; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a group with given name of null if such group does not exist. |
|
||||||
* @param groupName |
|
||||||
* the name of a vertex group |
|
||||||
* @return vertex group with the given name or null |
|
||||||
*/ |
|
||||||
public VertexGroup getGroup(String groupName) { |
|
||||||
return vertexGroups.get(groupName); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* A vertex group class that maps vertex index to its weight in a single group. |
|
||||||
* The group will need to be set a bone index in order to prepare proper buffers for the jme mesh. |
|
||||||
* But that information is available after the skeleton is loaded. |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
public static class VertexGroup extends HashMap<Integer, Float> { |
|
||||||
private static final long serialVersionUID = 5601646768279643957L; |
|
||||||
|
|
||||||
/** The index of the bone f the vertex group is to be used for attaching vertices to bones. */ |
|
||||||
private int boneIndex; |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a mapping between vertex index and its weight. |
|
||||||
* @param index |
|
||||||
* the index of the vertex (in JME mesh) |
|
||||||
* @param weight |
|
||||||
* the weight of the vertex |
|
||||||
*/ |
|
||||||
public void addVertex(int index, float weight) { |
|
||||||
this.put(index, weight); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The method sets the bone index for the current vertex group. |
|
||||||
* @param boneIndex |
|
||||||
* the index of the bone |
|
||||||
*/ |
|
||||||
public void setBoneIndex(int boneIndex) { |
|
||||||
this.boneIndex = boneIndex; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return the index of the bone |
|
||||||
*/ |
|
||||||
public int getBoneIndex() { |
|
||||||
return boneIndex; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,88 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.meshes; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.blender.file.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.file.Pointer; |
||||||
|
import com.jme3.scene.plugins.blender.file.Structure; |
||||||
|
|
||||||
|
/** |
||||||
|
* A class that represents a single point on the scene that is not a part of an edge. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
/* package */class Point { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(Point.class.getName()); |
||||||
|
|
||||||
|
/** The point's index. */ |
||||||
|
private int index; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a point for a given index. |
||||||
|
* @param index |
||||||
|
* the index of the point |
||||||
|
*/ |
||||||
|
public Point(int index) { |
||||||
|
this.index = index; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Point clone() { |
||||||
|
return new Point(index); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the index of the point |
||||||
|
*/ |
||||||
|
public int getIndex() { |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method shifts the index by a given value. |
||||||
|
* @param shift |
||||||
|
* the value to shift the index |
||||||
|
*/ |
||||||
|
public void shiftIndexes(int shift) { |
||||||
|
index += shift; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Loads all points of the mesh that do not belong to any edge. |
||||||
|
* @param meshStructure |
||||||
|
* the mesh structure |
||||||
|
* @return a list of points |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when problems with file reading occur |
||||||
|
*/ |
||||||
|
public static List<Point> loadAll(Structure meshStructure) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.FINE, "Loading all points that do not belong to any edge from mesh: {0}", meshStructure.getName()); |
||||||
|
List<Point> result = new ArrayList<Point>(); |
||||||
|
|
||||||
|
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); |
||||||
|
if (pMEdge.isNotNull()) { |
||||||
|
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); |
||||||
|
Set<Integer> unusedVertices = new HashSet<Integer>(count); |
||||||
|
for (int i = 0; i < count; ++i) { |
||||||
|
unusedVertices.add(i); |
||||||
|
} |
||||||
|
|
||||||
|
List<Structure> edges = pMEdge.fetchData(); |
||||||
|
for (Structure edge : edges) { |
||||||
|
unusedVertices.remove(((Number) edge.getFieldValue("v1")).intValue()); |
||||||
|
unusedVertices.remove(((Number) edge.getFieldValue("v2")).intValue()); |
||||||
|
} |
||||||
|
|
||||||
|
for (Integer unusedIndex : unusedVertices) { |
||||||
|
result.add(new Point(unusedIndex)); |
||||||
|
} |
||||||
|
} |
||||||
|
LOGGER.log(Level.FINE, "Loaded {0} points.", result.size()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,599 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.meshes; |
||||||
|
|
||||||
|
import java.nio.IntBuffer; |
||||||
|
import java.nio.ShortBuffer; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.bounding.BoundingBox; |
||||||
|
import com.jme3.bounding.BoundingVolume; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.Mesh.Mode; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.VertexBuffer; |
||||||
|
import com.jme3.scene.VertexBuffer.Type; |
||||||
|
import com.jme3.scene.VertexBuffer.Usage; |
||||||
|
import com.jme3.scene.plugins.blender.BlenderContext; |
||||||
|
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; |
||||||
|
import com.jme3.scene.plugins.blender.file.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.file.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.materials.MaterialContext; |
||||||
|
import com.jme3.scene.plugins.blender.meshes.MeshBuffers.BoneBuffersData; |
||||||
|
import com.jme3.scene.plugins.blender.modifiers.Modifier; |
||||||
|
import com.jme3.scene.plugins.blender.objects.Properties; |
||||||
|
|
||||||
|
/** |
||||||
|
* The class extends Geometry so that it can be temporalily added to the object's node. |
||||||
|
* Later each such node's child will be transformed into a list of geometries. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
public class TemporalMesh extends Geometry { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(TemporalMesh.class.getName()); |
||||||
|
|
||||||
|
/** The blender context. */ |
||||||
|
protected final BlenderContext blenderContext; |
||||||
|
|
||||||
|
/** The mesh's structure. */ |
||||||
|
protected final Structure meshStructure; |
||||||
|
|
||||||
|
/** Loaded vertices. */ |
||||||
|
protected List<Vector3f> vertices = new ArrayList<Vector3f>(); |
||||||
|
/** Loaded normals. */ |
||||||
|
protected List<Vector3f> normals = new ArrayList<Vector3f>(); |
||||||
|
/** Loaded vertex groups. */ |
||||||
|
protected List<Map<String, Float>> vertexGroups = new ArrayList<Map<String, Float>>(); |
||||||
|
/** Loaded vertex colors. */ |
||||||
|
protected List<byte[]> verticesColors = new ArrayList<byte[]>(); |
||||||
|
|
||||||
|
/** Materials used by the mesh. */ |
||||||
|
protected MaterialContext[] materials; |
||||||
|
/** The properties of the mesh. */ |
||||||
|
protected Properties properties; |
||||||
|
/** The bone indexes. */ |
||||||
|
protected Map<String, Integer> boneIndexes = new HashMap<String, Integer>(); |
||||||
|
/** The modifiers that should be applied after the mesh has been created. */ |
||||||
|
protected List<Modifier> postMeshCreationModifiers = new ArrayList<Modifier>(); |
||||||
|
|
||||||
|
/** The faces of the mesh. */ |
||||||
|
protected List<Face> faces = new ArrayList<Face>(); |
||||||
|
/** The edges of the mesh. */ |
||||||
|
protected List<Edge> edges = new ArrayList<Edge>(); |
||||||
|
/** The points of the mesh. */ |
||||||
|
protected List<Point> points = new ArrayList<Point>(); |
||||||
|
|
||||||
|
/** The bounding box of the temporal mesh. */ |
||||||
|
protected BoundingBox boundingBox; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a temporal mesh based on the given mesh structure. |
||||||
|
* @param meshStructure |
||||||
|
* the mesh structure |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when problems with file reading occur |
||||||
|
*/ |
||||||
|
public TemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { |
||||||
|
this(meshStructure, blenderContext, true); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a temporal mesh based on the given mesh structure. |
||||||
|
* @param meshStructure |
||||||
|
* the mesh structure |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @param loadData |
||||||
|
* tells if the data should be loaded from the mesh structure |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when problems with file reading occur |
||||||
|
*/ |
||||||
|
protected TemporalMesh(Structure meshStructure, BlenderContext blenderContext, boolean loadData) throws BlenderFileException { |
||||||
|
this.blenderContext = blenderContext; |
||||||
|
name = meshStructure.getName(); |
||||||
|
this.meshStructure = meshStructure; |
||||||
|
|
||||||
|
if (loadData) { |
||||||
|
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); |
||||||
|
|
||||||
|
meshHelper.loadVerticesAndNormals(meshStructure, vertices, normals); |
||||||
|
verticesColors = meshHelper.loadVerticesColors(meshStructure, blenderContext); |
||||||
|
LinkedHashMap<String, List<Vector2f>> userUVGroups = meshHelper.loadUVCoordinates(meshStructure); |
||||||
|
vertexGroups = meshHelper.loadVerticesGroups(meshStructure); |
||||||
|
|
||||||
|
faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext); |
||||||
|
edges = Edge.loadAll(meshStructure); |
||||||
|
points = Point.loadAll(meshStructure); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public TemporalMesh clone() { |
||||||
|
try { |
||||||
|
TemporalMesh result = new TemporalMesh(meshStructure, blenderContext, false); |
||||||
|
for (Vector3f v : vertices) { |
||||||
|
result.vertices.add(v.clone()); |
||||||
|
} |
||||||
|
for (Vector3f n : normals) { |
||||||
|
result.normals.add(n.clone()); |
||||||
|
} |
||||||
|
for (Map<String, Float> group : vertexGroups) { |
||||||
|
result.vertexGroups.add(new HashMap<String, Float>(group)); |
||||||
|
} |
||||||
|
for (byte[] vertColor : verticesColors) { |
||||||
|
result.verticesColors.add(vertColor.clone()); |
||||||
|
} |
||||||
|
result.materials = materials; |
||||||
|
result.properties = properties; |
||||||
|
result.boneIndexes.putAll(boneIndexes); |
||||||
|
result.postMeshCreationModifiers.addAll(postMeshCreationModifiers); |
||||||
|
for (Face face : faces) { |
||||||
|
result.faces.add(face.clone()); |
||||||
|
} |
||||||
|
for (Edge edge : edges) { |
||||||
|
result.edges.add(edge.clone()); |
||||||
|
} |
||||||
|
for (Point point : points) { |
||||||
|
result.points.add(point.clone()); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} catch (BlenderFileException e) { |
||||||
|
LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage()); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the vertices of the mesh |
||||||
|
*/ |
||||||
|
protected List<Vector3f> getVertices() { |
||||||
|
return vertices; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the normals of the mesh |
||||||
|
*/ |
||||||
|
protected List<Vector3f> getNormals() { |
||||||
|
return normals; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void updateModelBound() { |
||||||
|
if (boundingBox == null) { |
||||||
|
boundingBox = new BoundingBox(); |
||||||
|
} |
||||||
|
Vector3f min = new Vector3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); |
||||||
|
Vector3f max = new Vector3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE); |
||||||
|
for (Vector3f v : vertices) { |
||||||
|
min.set(Math.min(min.x, v.x), Math.min(min.y, v.y), Math.min(min.z, v.z)); |
||||||
|
max.set(Math.max(max.x, v.x), Math.max(max.y, v.y), Math.max(max.z, v.z)); |
||||||
|
} |
||||||
|
boundingBox.setMinMax(min, max); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoundingVolume getModelBound() { |
||||||
|
this.updateModelBound(); |
||||||
|
return boundingBox; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoundingVolume getWorldBound() { |
||||||
|
this.updateModelBound(); |
||||||
|
Node parent = this.getParent(); |
||||||
|
if (parent != null) { |
||||||
|
BoundingVolume bv = boundingBox.clone(); |
||||||
|
bv.setCenter(parent.getWorldTranslation()); |
||||||
|
return bv; |
||||||
|
} else { |
||||||
|
return boundingBox; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Triangulates the mesh. |
||||||
|
*/ |
||||||
|
public void triangulate() { |
||||||
|
LOGGER.fine("Triangulating temporal mesh."); |
||||||
|
List<Face> triangulatedFaces = new ArrayList<Face>(); |
||||||
|
for (Face face : faces) { |
||||||
|
triangulatedFaces.addAll(face.triangulate(vertices, normals)); |
||||||
|
} |
||||||
|
faces = triangulatedFaces; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method appends the given mesh to the current one. New faces and vertices and indexes are added. |
||||||
|
* @param mesh |
||||||
|
* the mesh to be appended |
||||||
|
*/ |
||||||
|
public void append(TemporalMesh mesh) { |
||||||
|
// we need to shift the indexes in faces, lines and points
|
||||||
|
int shift = vertices.size(); |
||||||
|
if (shift > 0) { |
||||||
|
for (Face face : mesh.faces) { |
||||||
|
face.shiftIndexes(shift); |
||||||
|
face.setTemporalMesh(this); |
||||||
|
} |
||||||
|
for (Edge edge : mesh.edges) { |
||||||
|
edge.shiftIndexes(shift); |
||||||
|
} |
||||||
|
for (Point point : mesh.points) { |
||||||
|
point.shiftIndexes(shift); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
faces.addAll(mesh.faces); |
||||||
|
edges.addAll(mesh.edges); |
||||||
|
points.addAll(mesh.points); |
||||||
|
|
||||||
|
vertices.addAll(mesh.vertices); |
||||||
|
normals.addAll(mesh.normals); |
||||||
|
vertexGroups.addAll(mesh.vertexGroups); |
||||||
|
verticesColors.addAll(mesh.verticesColors); |
||||||
|
boneIndexes.putAll(mesh.boneIndexes); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Translate all vertices by the given vector. |
||||||
|
* @param translation |
||||||
|
* the translation vector |
||||||
|
* @return this mesh after translation (NO new instance is created) |
||||||
|
*/ |
||||||
|
public TemporalMesh translate(Vector3f translation) { |
||||||
|
for (Vector3f v : vertices) { |
||||||
|
v.addLocal(translation); |
||||||
|
} |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the properties of the mesh. |
||||||
|
* @param properties |
||||||
|
* the properties of the mesh |
||||||
|
*/ |
||||||
|
public void setProperties(Properties properties) { |
||||||
|
this.properties = properties; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the materials of the mesh. |
||||||
|
* @param materials |
||||||
|
* the materials of the mesh |
||||||
|
*/ |
||||||
|
public void setMaterials(MaterialContext[] materials) { |
||||||
|
this.materials = materials; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds bone index to the mesh. |
||||||
|
* @param boneName |
||||||
|
* the name of the bone |
||||||
|
* @param boneIndex |
||||||
|
* the index of the bone |
||||||
|
*/ |
||||||
|
public void addBoneIndex(String boneName, Integer boneIndex) { |
||||||
|
boneIndexes.put(boneName, boneIndex); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The modifier to be applied after the geometries are created. |
||||||
|
* @param modifier |
||||||
|
* the modifier to be applied |
||||||
|
*/ |
||||||
|
public void applyAfterMeshCreate(Modifier modifier) { |
||||||
|
postMeshCreationModifiers.add(modifier); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getVertexCount() { |
||||||
|
return vertices.size(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the vertex at the given position. |
||||||
|
* @param i |
||||||
|
* the vertex position |
||||||
|
* @return the vertex at the given position |
||||||
|
*/ |
||||||
|
public Vector3f getVertex(int i) { |
||||||
|
return vertices.get(i); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the normal at the given position. |
||||||
|
* @param i |
||||||
|
* the normal position |
||||||
|
* @return the normal at the given position |
||||||
|
*/ |
||||||
|
public Vector3f getNormal(int i) { |
||||||
|
return normals.get(i); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Flips the order of the mesh's indexes. |
||||||
|
*/ |
||||||
|
public void flipIndexes() { |
||||||
|
for (Face face : faces) { |
||||||
|
face.flipIndexes(); |
||||||
|
} |
||||||
|
for (Edge edge : edges) { |
||||||
|
edge.flipIndexes(); |
||||||
|
} |
||||||
|
Collections.reverse(points); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Flips UV coordinates. |
||||||
|
* @param u |
||||||
|
* indicates if U coords should be flipped |
||||||
|
* @param v |
||||||
|
* indicates if V coords should be flipped |
||||||
|
*/ |
||||||
|
public void flipUV(boolean u, boolean v) { |
||||||
|
for (Face face : faces) { |
||||||
|
face.flipUV(u, v); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The mesh builds geometries from the mesh. The result is stored in the blender context |
||||||
|
* under the mesh's OMA. |
||||||
|
*/ |
||||||
|
public void toGeometries() { |
||||||
|
LOGGER.log(Level.FINE, "Converting temporal mesh {0} to jme geometries.", name); |
||||||
|
List<Geometry> result = new ArrayList<Geometry>(); |
||||||
|
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); |
||||||
|
Node parent = this.getParent(); |
||||||
|
parent.detachChild(this); |
||||||
|
|
||||||
|
this.prepareFacesGeometry(result, meshHelper); |
||||||
|
this.prepareLinesGeometry(result, meshHelper); |
||||||
|
this.preparePointsGeometry(result, meshHelper); |
||||||
|
|
||||||
|
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result); |
||||||
|
|
||||||
|
for (Geometry geometry : result) { |
||||||
|
parent.attachChild(geometry); |
||||||
|
} |
||||||
|
|
||||||
|
for (Modifier modifier : postMeshCreationModifiers) { |
||||||
|
modifier.postMeshCreationApply(parent, blenderContext); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method creates geometries from faces. |
||||||
|
* @param result |
||||||
|
* the list where new geometries will be appended |
||||||
|
* @param meshHelper |
||||||
|
* the mesh helper |
||||||
|
*/ |
||||||
|
protected void prepareFacesGeometry(List<Geometry> result, MeshHelper meshHelper) { |
||||||
|
LOGGER.fine("Preparing faces geometries."); |
||||||
|
this.triangulate(); |
||||||
|
|
||||||
|
Vector3f[] tempVerts = new Vector3f[3]; |
||||||
|
Vector3f[] tempNormals = new Vector3f[3]; |
||||||
|
byte[][] tempVertColors = new byte[3][]; |
||||||
|
List<Map<Float, Integer>> boneBuffers = new ArrayList<Map<Float, Integer>>(3); |
||||||
|
|
||||||
|
LOGGER.log(Level.FINE, "Appending {0} faces to mesh buffers.", faces.size()); |
||||||
|
Map<Integer, MeshBuffers> faceMeshes = new HashMap<Integer, MeshBuffers>(); |
||||||
|
for (Face face : faces) { |
||||||
|
MeshBuffers meshBuffers = faceMeshes.get(face.getMaterialNumber()); |
||||||
|
if (meshBuffers == null) { |
||||||
|
meshBuffers = new MeshBuffers(face.getMaterialNumber()); |
||||||
|
faceMeshes.put(face.getMaterialNumber(), meshBuffers); |
||||||
|
} |
||||||
|
|
||||||
|
List<Integer> indexes = face.getIndexes(); |
||||||
|
List<byte[]> vertexColors = face.getVertexColors(); |
||||||
|
boneBuffers.clear(); |
||||||
|
assert indexes.size() == 3 : "The mesh has not been properly triangulated!"; |
||||||
|
for (int i = 0; i < 3; ++i) { |
||||||
|
int vertIndex = indexes.get(i); |
||||||
|
tempVerts[i] = vertices.get(vertIndex); |
||||||
|
tempNormals[i] = normals.get(vertIndex); |
||||||
|
tempVertColors[i] = vertexColors != null ? vertexColors.get(i) : null; |
||||||
|
|
||||||
|
if (boneIndexes.size() > 0) { |
||||||
|
Map<Float, Integer> boneBuffersForVertex = new HashMap<Float, Integer>(); |
||||||
|
Map<String, Float> vertexGroupsForVertex = vertexGroups.get(vertIndex); |
||||||
|
for (Entry<String, Integer> entry : boneIndexes.entrySet()) { |
||||||
|
if (vertexGroupsForVertex.containsKey(entry.getKey())) { |
||||||
|
boneBuffersForVertex.put(vertexGroupsForVertex.get(entry.getKey()), entry.getValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
boneBuffers.add(boneBuffersForVertex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
meshBuffers.append(face.isSmooth(), tempVerts, tempNormals, face.getUvSets(), tempVertColors, boneBuffers); |
||||||
|
} |
||||||
|
|
||||||
|
LOGGER.fine("Converting mesh buffers to geometries."); |
||||||
|
Map<Geometry, MeshBuffers> geometryToBuffersMap = new HashMap<Geometry, MeshBuffers>(); |
||||||
|
for (Entry<Integer, MeshBuffers> entry : faceMeshes.entrySet()) { |
||||||
|
MeshBuffers meshBuffers = entry.getValue(); |
||||||
|
|
||||||
|
Mesh mesh = new Mesh(); |
||||||
|
|
||||||
|
if (meshBuffers.isShortIndexBuffer()) { |
||||||
|
mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer()); |
||||||
|
} else { |
||||||
|
mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer()); |
||||||
|
} |
||||||
|
mesh.setBuffer(meshBuffers.getPositionsBuffer()); |
||||||
|
mesh.setBuffer(meshBuffers.getNormalsBuffer()); |
||||||
|
if (meshBuffers.areVertexColorsUsed()) { |
||||||
|
mesh.setBuffer(Type.Color, 4, meshBuffers.getVertexColorsBuffer()); |
||||||
|
mesh.getBuffer(Type.Color).setNormalized(true); |
||||||
|
} |
||||||
|
|
||||||
|
BoneBuffersData boneBuffersData = meshBuffers.getBoneBuffers(); |
||||||
|
if (boneBuffersData != null) { |
||||||
|
mesh.setMaxNumWeights(boneBuffersData.maximumWeightsPerVertex); |
||||||
|
mesh.setBuffer(boneBuffersData.verticesWeights); |
||||||
|
mesh.setBuffer(boneBuffersData.verticesWeightsIndices); |
||||||
|
|
||||||
|
LOGGER.fine("Generating bind pose and normal buffers."); |
||||||
|
mesh.generateBindPose(true); |
||||||
|
|
||||||
|
// change the usage type of vertex and normal buffers from Static to Stream
|
||||||
|
mesh.getBuffer(Type.Position).setUsage(Usage.Stream); |
||||||
|
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); |
||||||
|
|
||||||
|
// creating empty buffers for HW skinning; the buffers will be setup if ever used
|
||||||
|
VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight); |
||||||
|
VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex); |
||||||
|
mesh.setBuffer(verticesWeightsHW); |
||||||
|
mesh.setBuffer(verticesWeightsIndicesHW); |
||||||
|
} |
||||||
|
|
||||||
|
Geometry geometry = new Geometry(name + (result.size() + 1), mesh); |
||||||
|
if (properties != null && properties.getValue() != null) { |
||||||
|
meshHelper.applyProperties(geometry, properties); |
||||||
|
} |
||||||
|
result.add(geometry); |
||||||
|
|
||||||
|
geometryToBuffersMap.put(geometry, meshBuffers); |
||||||
|
} |
||||||
|
|
||||||
|
LOGGER.fine("Applying materials to geometries."); |
||||||
|
for (Entry<Geometry, MeshBuffers> entry : geometryToBuffersMap.entrySet()) { |
||||||
|
int materialIndex = entry.getValue().getMaterialIndex(); |
||||||
|
Geometry geometry = entry.getKey(); |
||||||
|
if (materialIndex >= 0 && materials != null && materials.length > materialIndex && materials[materialIndex] != null) { |
||||||
|
materials[materialIndex].applyMaterial(geometry, meshStructure.getOldMemoryAddress(), entry.getValue().getUvCoords(), blenderContext); |
||||||
|
} else { |
||||||
|
geometry.setMaterial(blenderContext.getDefaultMaterial()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method creates geometries from lines. |
||||||
|
* @param result |
||||||
|
* the list where new geometries will be appended |
||||||
|
* @param meshHelper |
||||||
|
* the mesh helper |
||||||
|
*/ |
||||||
|
protected void prepareLinesGeometry(List<Geometry> result, MeshHelper meshHelper) { |
||||||
|
if (edges.size() > 0) { |
||||||
|
LOGGER.fine("Preparing lines geometries."); |
||||||
|
|
||||||
|
List<List<Integer>> separateEdges = new ArrayList<List<Integer>>(); |
||||||
|
List<Edge> edges = new ArrayList<Edge>(this.edges); |
||||||
|
while (edges.size() > 0) { |
||||||
|
boolean edgeAppended = false; |
||||||
|
int edgeIndex = 0; |
||||||
|
for (List<Integer> list : separateEdges) { |
||||||
|
for (edgeIndex = 0; edgeIndex < edges.size() && !edgeAppended; ++edgeIndex) { |
||||||
|
Edge edge = edges.get(edgeIndex); |
||||||
|
if (list.get(0).equals(edge.getFirstIndex())) { |
||||||
|
list.add(0, edge.getSecondIndex()); |
||||||
|
--edgeIndex; |
||||||
|
edgeAppended = true; |
||||||
|
} else if (list.get(0).equals(edge.getSecondIndex())) { |
||||||
|
list.add(0, edge.getFirstIndex()); |
||||||
|
--edgeIndex; |
||||||
|
edgeAppended = true; |
||||||
|
} else if (list.get(list.size() - 1).equals(edge.getFirstIndex())) { |
||||||
|
list.add(edge.getSecondIndex()); |
||||||
|
--edgeIndex; |
||||||
|
edgeAppended = true; |
||||||
|
} else if (list.get(list.size() - 1).equals(edge.getSecondIndex())) { |
||||||
|
list.add(edge.getFirstIndex()); |
||||||
|
--edgeIndex; |
||||||
|
edgeAppended = true; |
||||||
|
} |
||||||
|
} |
||||||
|
if (edgeAppended) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
Edge edge = edges.remove(edgeAppended ? edgeIndex : 0); |
||||||
|
if (!edgeAppended) { |
||||||
|
separateEdges.add(new ArrayList<Integer>(Arrays.asList(edge.getFirstIndex(), edge.getSecondIndex()))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (List<Integer> list : separateEdges) { |
||||||
|
MeshBuffers meshBuffers = new MeshBuffers(0); |
||||||
|
for (int index : list) { |
||||||
|
meshBuffers.append(vertices.get(index), normals.get(index)); |
||||||
|
} |
||||||
|
Mesh mesh = new Mesh(); |
||||||
|
mesh.setPointSize(2); |
||||||
|
mesh.setMode(Mode.LineStrip); |
||||||
|
if (meshBuffers.isShortIndexBuffer()) { |
||||||
|
mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer()); |
||||||
|
} else { |
||||||
|
mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer()); |
||||||
|
} |
||||||
|
mesh.setBuffer(meshBuffers.getPositionsBuffer()); |
||||||
|
mesh.setBuffer(meshBuffers.getNormalsBuffer()); |
||||||
|
|
||||||
|
Geometry geometry = new Geometry(meshStructure.getName() + (result.size() + 1), mesh); |
||||||
|
geometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext)); |
||||||
|
if (properties != null && properties.getValue() != null) { |
||||||
|
meshHelper.applyProperties(geometry, properties); |
||||||
|
} |
||||||
|
result.add(geometry); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method creates geometries from points. |
||||||
|
* @param result |
||||||
|
* the list where new geometries will be appended |
||||||
|
* @param meshHelper |
||||||
|
* the mesh helper |
||||||
|
*/ |
||||||
|
protected void preparePointsGeometry(List<Geometry> result, MeshHelper meshHelper) { |
||||||
|
if (points.size() > 0) { |
||||||
|
LOGGER.fine("Preparing point geometries."); |
||||||
|
|
||||||
|
MeshBuffers pointBuffers = new MeshBuffers(0); |
||||||
|
for (Point point : points) { |
||||||
|
pointBuffers.append(vertices.get(point.getIndex()), normals.get(point.getIndex())); |
||||||
|
} |
||||||
|
Mesh pointsMesh = new Mesh(); |
||||||
|
pointsMesh.setMode(Mode.Points); |
||||||
|
pointsMesh.setPointSize(blenderContext.getBlenderKey().getPointsSize()); |
||||||
|
if (pointBuffers.isShortIndexBuffer()) { |
||||||
|
pointsMesh.setBuffer(Type.Index, 1, (ShortBuffer) pointBuffers.getIndexBuffer()); |
||||||
|
} else { |
||||||
|
pointsMesh.setBuffer(Type.Index, 1, (IntBuffer) pointBuffers.getIndexBuffer()); |
||||||
|
} |
||||||
|
pointsMesh.setBuffer(pointBuffers.getPositionsBuffer()); |
||||||
|
pointsMesh.setBuffer(pointBuffers.getNormalsBuffer()); |
||||||
|
|
||||||
|
Geometry pointsGeometry = new Geometry(meshStructure.getName() + (result.size() + 1), pointsMesh); |
||||||
|
pointsGeometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext)); |
||||||
|
if (properties != null && properties.getValue() != null) { |
||||||
|
meshHelper.applyProperties(pointsGeometry, properties); |
||||||
|
} |
||||||
|
result.add(pointsGeometry); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]"; |
||||||
|
} |
||||||
|
} |
@ -1,586 +0,0 @@ |
|||||||
package com.jme3.scene.plugins.blender.meshes.builders; |
|
||||||
|
|
||||||
import java.nio.ByteBuffer; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.LinkedHashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Map.Entry; |
|
||||||
import java.util.logging.Logger; |
|
||||||
|
|
||||||
import com.jme3.math.FastMath; |
|
||||||
import com.jme3.math.Vector2f; |
|
||||||
import com.jme3.math.Vector3f; |
|
||||||
import com.jme3.scene.Mesh; |
|
||||||
import com.jme3.scene.VertexBuffer; |
|
||||||
import com.jme3.scene.VertexBuffer.Format; |
|
||||||
import com.jme3.scene.VertexBuffer.Type; |
|
||||||
import com.jme3.scene.VertexBuffer.Usage; |
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext; |
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException; |
|
||||||
import com.jme3.scene.plugins.blender.file.DynamicArray; |
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer; |
|
||||||
import com.jme3.scene.plugins.blender.file.Structure; |
|
||||||
import com.jme3.scene.plugins.blender.meshes.MeshHelper; |
|
||||||
import com.jme3.scene.plugins.blender.textures.UserUVCollection; |
|
||||||
import com.jme3.util.BufferUtils; |
|
||||||
|
|
||||||
/** |
|
||||||
* A builder class for meshes made of triangles (faces). |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
/* package */class FaceMeshBuilder { |
|
||||||
private static final Logger LOGGER = Logger.getLogger(FaceMeshBuilder.class.getName()); |
|
||||||
|
|
||||||
/** An array of reference vertices. */ |
|
||||||
private Vector3f[][] verticesAndNormals; |
|
||||||
/** An list of vertices colors. */ |
|
||||||
private List<byte[]> verticesColors; |
|
||||||
/** A variable that indicates if the model uses generated textures. */ |
|
||||||
private boolean usesGeneratedTextures; |
|
||||||
/** |
|
||||||
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' |
|
||||||
* positions (it simply tells which vertex is referenced where in the result list). |
|
||||||
*/ |
|
||||||
private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap; |
|
||||||
/** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ |
|
||||||
private Map<Integer, List<Vector3f>> normalMap = new HashMap<Integer, List<Vector3f>>(); |
|
||||||
/** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ |
|
||||||
private Map<Integer, List<Vector3f>> vertexMap = new HashMap<Integer, List<Vector3f>>(); |
|
||||||
/** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */ |
|
||||||
private Map<Integer, List<byte[]>> vertexColorsMap = new HashMap<Integer, List<byte[]>>(); |
|
||||||
/** The following map sorts indexes by material number (because in jme Mesh can have only one material). */ |
|
||||||
private Map<Integer, List<Integer>> indexMap = new HashMap<Integer, List<Integer>>(); |
|
||||||
/** A collection of user defined UV coordinates (one mesh can have more than one such mappings). */ |
|
||||||
private UserUVCollection userUVCollection = new UserUVCollection(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor. Stores the given array (not copying it). |
|
||||||
* The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. |
|
||||||
* The amount of vertices is always faceCount * 3. |
|
||||||
* @param verticesAndNormals |
|
||||||
* the reference vertices and normals array |
|
||||||
* @param usesGeneratedTextures |
|
||||||
* a variable that indicates if the model uses generated textures or not |
|
||||||
*/ |
|
||||||
public FaceMeshBuilder(Vector3f[][] verticesAndNormals, boolean usesGeneratedTextures) { |
|
||||||
this.verticesAndNormals = verticesAndNormals; |
|
||||||
this.usesGeneratedTextures = usesGeneratedTextures; |
|
||||||
globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length); |
|
||||||
} |
|
||||||
|
|
||||||
public void readMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException { |
|
||||||
verticesColors = this.getVerticesColors(structure, blenderContext); |
|
||||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); |
|
||||||
|
|
||||||
if (meshHelper.isBMeshCompatible(structure)) { |
|
||||||
this.readBMesh(structure); |
|
||||||
} else { |
|
||||||
this.readTraditionalFaces(structure); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Builds the meshes. |
|
||||||
* @return a map between material index and the mesh |
|
||||||
*/ |
|
||||||
public Map<Integer, Mesh> buildMeshes() { |
|
||||||
Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(indexMap.size()); |
|
||||||
|
|
||||||
for (Entry<Integer, List<Integer>> meshEntry : indexMap.entrySet()) { |
|
||||||
int materialIndex = meshEntry.getKey(); |
|
||||||
// key is the material index
|
|
||||||
// value is a list of vertex indices
|
|
||||||
Mesh mesh = new Mesh(); |
|
||||||
|
|
||||||
// creating vertices indices for this mesh
|
|
||||||
List<Integer> indexList = meshEntry.getValue(); |
|
||||||
if (this.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) { |
|
||||||
short[] indices = new short[indexList.size()]; |
|
||||||
for (int i = 0; i < indexList.size(); ++i) { |
|
||||||
indices[i] = indexList.get(i).shortValue(); |
|
||||||
} |
|
||||||
mesh.setBuffer(Type.Index, 1, indices); |
|
||||||
} else { |
|
||||||
int[] indices = new int[indexList.size()]; |
|
||||||
for (int i = 0; i < indexList.size(); ++i) { |
|
||||||
indices[i] = indexList.get(i).intValue(); |
|
||||||
} |
|
||||||
mesh.setBuffer(Type.Index, 1, indices); |
|
||||||
} |
|
||||||
|
|
||||||
LOGGER.fine("Creating vertices buffer."); |
|
||||||
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); |
|
||||||
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getVertices(materialIndex))); |
|
||||||
mesh.setBuffer(verticesBuffer); |
|
||||||
|
|
||||||
LOGGER.fine("Creating normals buffer."); |
|
||||||
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); |
|
||||||
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getNormals(materialIndex))); |
|
||||||
mesh.setBuffer(normalsBuffer); |
|
||||||
|
|
||||||
if (verticesColors != null) { |
|
||||||
LOGGER.fine("Setting vertices colors."); |
|
||||||
mesh.setBuffer(Type.Color, 4, this.getVertexColorsBuffer(materialIndex)); |
|
||||||
mesh.getBuffer(Type.Color).setNormalized(true); |
|
||||||
} |
|
||||||
|
|
||||||
result.put(materialIndex, mesh); |
|
||||||
} |
|
||||||
|
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method reads the mesh from the new BMesh system. |
|
||||||
* |
|
||||||
* @param meshStructure |
|
||||||
* the mesh structure |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception is thrown when there are problems with the |
|
||||||
* blender file |
|
||||||
*/ |
|
||||||
private void readBMesh(Structure meshStructure) throws BlenderFileException { |
|
||||||
LOGGER.fine("Reading BMesh."); |
|
||||||
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); |
|
||||||
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); |
|
||||||
Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>(); |
|
||||||
|
|
||||||
if (pMPoly.isNotNull() && pMLoop.isNotNull()) { |
|
||||||
Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, true); |
|
||||||
List<Structure> polys = pMPoly.fetchData(); |
|
||||||
List<Structure> loops = pMLoop.fetchData(); |
|
||||||
int[] vertexColorIndex = verticesColors == null ? null : new int[3]; |
|
||||||
for (Structure poly : polys) { |
|
||||||
int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); |
|
||||||
int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); |
|
||||||
int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); |
|
||||||
boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; |
|
||||||
int[] vertexIndexes = new int[totLoop]; |
|
||||||
|
|
||||||
for (int i = loopStart; i < loopStart + totLoop; ++i) { |
|
||||||
vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); |
|
||||||
} |
|
||||||
|
|
||||||
int i = 0; |
|
||||||
while (i < totLoop - 2) { |
|
||||||
int v1 = vertexIndexes[0]; |
|
||||||
int v2 = vertexIndexes[i + 1]; |
|
||||||
int v3 = vertexIndexes[i + 2]; |
|
||||||
if (vertexColorIndex != null) { |
|
||||||
vertexColorIndex[0] = loopStart; |
|
||||||
vertexColorIndex[1] = loopStart + i + 1; |
|
||||||
vertexColorIndex[2] = loopStart + i + 2; |
|
||||||
} |
|
||||||
|
|
||||||
if (uvs != null) { |
|
||||||
// uvs always must be added wheater we have texture or not
|
|
||||||
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) { |
|
||||||
Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; |
|
||||||
uvCoordsForASingleFace[0] = entry.getValue().get(loopStart); |
|
||||||
uvCoordsForASingleFace[1] = entry.getValue().get(loopStart + i + 1); |
|
||||||
uvCoordsForASingleFace[2] = entry.getValue().get(loopStart + i + 2); |
|
||||||
uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); |
|
||||||
uvCoordinatesForFace.clear(); |
|
||||||
++i; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method reads the mesh from traditional triangle/quad storing |
|
||||||
* structures. |
|
||||||
* |
|
||||||
* @param meshStructure |
|
||||||
* the mesh structure |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception is thrown when there are problems with the |
|
||||||
* blender file |
|
||||||
*/ |
|
||||||
private void readTraditionalFaces(Structure meshStructure) throws BlenderFileException { |
|
||||||
LOGGER.fine("Reading traditional faces."); |
|
||||||
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); |
|
||||||
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null; |
|
||||||
if (mFaces != null && mFaces.size() > 0) { |
|
||||||
// indicates if the material with the specified number should have a texture attached
|
|
||||||
Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, false); |
|
||||||
Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>(); |
|
||||||
int[] vertexColorIndex = verticesColors == null ? null : new int[3]; |
|
||||||
for (int i = 0; i < mFaces.size(); ++i) { |
|
||||||
Structure mFace = mFaces.get(i); |
|
||||||
int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); |
|
||||||
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; |
|
||||||
if (uvs != null) { |
|
||||||
// uvs always must be added wheater we have texture or not
|
|
||||||
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) { |
|
||||||
Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; |
|
||||||
uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); |
|
||||||
uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 1); |
|
||||||
uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 2); |
|
||||||
uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); |
|
||||||
int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); |
|
||||||
int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); |
|
||||||
int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); |
|
||||||
if (vertexColorIndex != null) { |
|
||||||
vertexColorIndex[0] = i * 4; |
|
||||||
vertexColorIndex[1] = i * 4 + 1; |
|
||||||
vertexColorIndex[2] = i * 4 + 2; |
|
||||||
} |
|
||||||
|
|
||||||
this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); |
|
||||||
uvCoordinatesForFace.clear(); |
|
||||||
if (v4 > 0) { |
|
||||||
if (uvs != null) { |
|
||||||
// uvs always must be added wheater we have texture or not
|
|
||||||
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) { |
|
||||||
Vector2f[] uvCoordsForASingleFace = new Vector2f[3]; |
|
||||||
uvCoordsForASingleFace[0] = entry.getValue().get(i * 4); |
|
||||||
uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 2); |
|
||||||
uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 3); |
|
||||||
uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace); |
|
||||||
} |
|
||||||
} |
|
||||||
if (vertexColorIndex != null) { |
|
||||||
vertexColorIndex[0] = i * 4; |
|
||||||
vertexColorIndex[1] = i * 4 + 2; |
|
||||||
vertexColorIndex[2] = i * 4 + 3; |
|
||||||
} |
|
||||||
this.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex); |
|
||||||
uvCoordinatesForFace.clear(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method adds a face to the mesh. |
|
||||||
* @param v1 |
|
||||||
* index of the 1'st vertex from the reference vertex table |
|
||||||
* @param v2 |
|
||||||
* index of the 2'nd vertex from the reference vertex table |
|
||||||
* @param v3 |
|
||||||
* index of the 3'rd vertex from the reference vertex table |
|
||||||
* @param smooth |
|
||||||
* indicates if this face should have smooth shading or flat shading |
|
||||||
* @param materialNumber |
|
||||||
* the material number for this face |
|
||||||
* @param uvsForFace |
|
||||||
* a 3-element array of vertices UV coordinates mapped to the UV's set name |
|
||||||
* @param vertexColorIndex |
|
||||||
* a table of 3 elements that indicates the verts' colors indexes |
|
||||||
*/ |
|
||||||
private void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Map<String, Vector2f[]> uvsForFace, int[] vertexColorIndex) { |
|
||||||
if (uvsForFace != null && uvsForFace.size() > 0) { |
|
||||||
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) { |
|
||||||
if (entry.getValue().length != 3) { |
|
||||||
throw new IllegalArgumentException("UV coordinates must be a 3-element array!" + (entry.getKey() != null ? " (UV set name: " + entry.getKey() + ')' : "")); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// getting the required lists
|
|
||||||
List<Integer> indexList = indexMap.get(materialNumber); |
|
||||||
if (indexList == null) { |
|
||||||
indexList = new ArrayList<Integer>(); |
|
||||||
indexMap.put(materialNumber, indexList); |
|
||||||
} |
|
||||||
List<Vector3f> vertexList = vertexMap.get(materialNumber); |
|
||||||
if (vertexList == null) { |
|
||||||
vertexList = new ArrayList<Vector3f>(); |
|
||||||
vertexMap.put(materialNumber, vertexList); |
|
||||||
} |
|
||||||
List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null; |
|
||||||
if (vertexColorsList == null && vertexColorsMap != null) { |
|
||||||
vertexColorsList = new ArrayList<byte[]>(); |
|
||||||
vertexColorsMap.put(materialNumber, vertexColorsList); |
|
||||||
} |
|
||||||
List<Vector3f> normalList = normalMap.get(materialNumber); |
|
||||||
if (normalList == null) { |
|
||||||
normalList = new ArrayList<Vector3f>(); |
|
||||||
normalMap.put(materialNumber, normalList); |
|
||||||
} |
|
||||||
Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber); |
|
||||||
if (vertexReferenceMap == null) { |
|
||||||
vertexReferenceMap = new HashMap<Integer, List<Integer>>(); |
|
||||||
globalVertexReferenceMap.put(materialNumber, vertexReferenceMap); |
|
||||||
} |
|
||||||
|
|
||||||
// creating faces
|
|
||||||
Integer[] index = new Integer[] { v1, v2, v3 }; |
|
||||||
if (smooth && !usesGeneratedTextures) { |
|
||||||
for (int i = 0; i < 3; ++i) { |
|
||||||
if (!vertexReferenceMap.containsKey(index[i])) { |
|
||||||
// if this index is not yet used then create another face
|
|
||||||
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); |
|
||||||
if (uvsForFace != null) { |
|
||||||
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) { |
|
||||||
userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
vertexList.add(verticesAndNormals[index[i]][0]); |
|
||||||
if (verticesColors != null) { |
|
||||||
vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); |
|
||||||
} |
|
||||||
normalList.add(verticesAndNormals[index[i]][1]); |
|
||||||
|
|
||||||
index[i] = vertexList.size() - 1; |
|
||||||
} else if (uvsForFace != null) { |
|
||||||
// if the index is used then check if the vertexe's UV coordinates match, if yes then the vertex doesn't have separate UV's
|
|
||||||
// in different faces so we can use it here as well, if UV's are different in separate faces the we need to add this vert
|
|
||||||
// because in jme one vertex can have only on UV coordinate
|
|
||||||
boolean vertexAlreadyUsed = false; |
|
||||||
for (Integer vertexIndex : vertexReferenceMap.get(index[i])) { |
|
||||||
int vertexUseCounter = 0; |
|
||||||
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) { |
|
||||||
if (entry.getValue()[i].equals(userUVCollection.getUVForVertex(entry.getKey(), vertexIndex))) { |
|
||||||
++vertexUseCounter; |
|
||||||
} |
|
||||||
} |
|
||||||
if (vertexUseCounter == uvsForFace.size()) { |
|
||||||
vertexAlreadyUsed = true; |
|
||||||
index[i] = vertexIndex; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (!vertexAlreadyUsed) { |
|
||||||
// treat this face as a new one because its vertices have separate UV's
|
|
||||||
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); |
|
||||||
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) { |
|
||||||
userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); |
|
||||||
} |
|
||||||
vertexList.add(verticesAndNormals[index[i]][0]); |
|
||||||
if (verticesColors != null) { |
|
||||||
vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); |
|
||||||
} |
|
||||||
normalList.add(verticesAndNormals[index[i]][1]); |
|
||||||
index[i] = vertexList.size() - 1; |
|
||||||
} |
|
||||||
} else { |
|
||||||
// use this index again
|
|
||||||
index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]); |
|
||||||
} |
|
||||||
indexList.add(index[i]); |
|
||||||
} |
|
||||||
} else { |
|
||||||
Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]); |
|
||||||
for (int i = 0; i < 3; ++i) { |
|
||||||
indexList.add(vertexList.size()); |
|
||||||
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); |
|
||||||
if (uvsForFace != null) { |
|
||||||
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) { |
|
||||||
userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size()); |
|
||||||
} |
|
||||||
} |
|
||||||
vertexList.add(verticesAndNormals[index[i]][0]); |
|
||||||
if (verticesColors != null) { |
|
||||||
vertexColorsList.add(verticesColors.get(vertexColorIndex[i])); |
|
||||||
} |
|
||||||
normalList.add(smooth ? verticesAndNormals[index[i]][1] : n); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates. |
|
||||||
* But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning. |
|
||||||
* For bmesh they are enlisted just like they are stored in the blend file (in loops). |
|
||||||
* For traditional faces every 4 UV's should be assigned for a single face. |
|
||||||
* @param meshStructure |
|
||||||
* the mesh structure |
|
||||||
* @param useBMesh |
|
||||||
* tells if we should load the coordinates from loops of from faces |
|
||||||
* @return a map that sorts UV coordinates between different UV sets |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception is thrown when problems with blend file occur |
|
||||||
*/ |
|
||||||
@SuppressWarnings("unchecked") |
|
||||||
private Map<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure, boolean useBMesh) throws BlenderFileException { |
|
||||||
Map<String, List<Vector2f>> result = new HashMap<String, List<Vector2f>>(); |
|
||||||
if (useBMesh) { |
|
||||||
// in this case the UV's are assigned to vertices (an array is the same length as the vertex array)
|
|
||||||
Structure loopData = (Structure) meshStructure.getFieldValue("ldata"); |
|
||||||
Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers"); |
|
||||||
List<Structure> loopDataLayers = pLoopDataLayers.fetchData(); |
|
||||||
for (Structure structure : loopDataLayers) { |
|
||||||
Pointer p = (Pointer) structure.getFieldValue("data"); |
|
||||||
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) { |
|
||||||
String uvSetName = structure.getFieldValue("name").toString(); |
|
||||||
List<Structure> uvsStructures = p.fetchData(); |
|
||||||
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size()); |
|
||||||
for (Structure uvStructure : uvsStructures) { |
|
||||||
DynamicArray<Number> loopUVS = (DynamicArray<Number>) uvStructure.getFieldValue("uv"); |
|
||||||
uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue())); |
|
||||||
} |
|
||||||
result.put(uvSetName, uvs); |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
// in this case UV's are assigned to faces (the array has the same legnth as the faces count)
|
|
||||||
Structure facesData = (Structure) meshStructure.getFieldValue("fdata"); |
|
||||||
Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers"); |
|
||||||
List<Structure> facesDataLayers = pFacesDataLayers.fetchData(); |
|
||||||
for (Structure structure : facesDataLayers) { |
|
||||||
Pointer p = (Pointer) structure.getFieldValue("data"); |
|
||||||
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) { |
|
||||||
String uvSetName = structure.getFieldValue("name").toString(); |
|
||||||
List<Structure> uvsStructures = p.fetchData(); |
|
||||||
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size()); |
|
||||||
for (Structure uvStructure : uvsStructures) { |
|
||||||
DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) uvStructure.getFieldValue("uv"); |
|
||||||
uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue())); |
|
||||||
uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue())); |
|
||||||
uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue())); |
|
||||||
uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue())); |
|
||||||
} |
|
||||||
result.put(uvSetName, uvs); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method returns the vertices colors. Each vertex is stored in byte[4] array. |
|
||||||
* |
|
||||||
* @param meshStructure |
|
||||||
* the structure containing the mesh data |
|
||||||
* @param blenderContext |
|
||||||
* the blender context |
|
||||||
* @return a list of vertices colors, each color belongs to a single vertex |
|
||||||
* @throws BlenderFileException |
|
||||||
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
|
||||||
*/ |
|
||||||
private List<byte[]> getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { |
|
||||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); |
|
||||||
Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol"); |
|
||||||
List<byte[]> verticesColors = null; |
|
||||||
// it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure)
|
|
||||||
// so we need to put them right
|
|
||||||
boolean useBGRA = blenderContext.getBlenderVersion() < 263; |
|
||||||
if (pMCol.isNotNull()) { |
|
||||||
List<Structure> mCol = pMCol.fetchData(); |
|
||||||
verticesColors = new ArrayList<byte[]>(mCol.size()); |
|
||||||
for (Structure color : mCol) { |
|
||||||
byte r = ((Number) color.getFieldValue("r")).byteValue(); |
|
||||||
byte g = ((Number) color.getFieldValue("g")).byteValue(); |
|
||||||
byte b = ((Number) color.getFieldValue("b")).byteValue(); |
|
||||||
byte a = ((Number) color.getFieldValue("a")).byteValue(); |
|
||||||
verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a }); |
|
||||||
} |
|
||||||
} |
|
||||||
return verticesColors; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return a map that maps vertex index from reference array to its indices in the result list |
|
||||||
*/ |
|
||||||
public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() { |
|
||||||
return globalVertexReferenceMap; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param materialNumber |
|
||||||
* the material index |
|
||||||
* @return result vertices array |
|
||||||
*/ |
|
||||||
private Vector3f[] getVertices(int materialNumber) { |
|
||||||
return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param materialNumber |
|
||||||
* the material index |
|
||||||
* @return the amount of result vertices |
|
||||||
*/ |
|
||||||
private int getVerticesAmount(int materialNumber) { |
|
||||||
return vertexMap.get(materialNumber).size(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param materialNumber |
|
||||||
* the material index |
|
||||||
* @return normals result array |
|
||||||
*/ |
|
||||||
private Vector3f[] getNormals(int materialNumber) { |
|
||||||
return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param materialNumber |
|
||||||
* the material index |
|
||||||
* @return the vertices colors buffer or null if no vertex colors is set |
|
||||||
*/ |
|
||||||
private ByteBuffer getVertexColorsBuffer(int materialNumber) { |
|
||||||
ByteBuffer result = null; |
|
||||||
if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) { |
|
||||||
List<byte[]> data = vertexColorsMap.get(materialNumber); |
|
||||||
result = BufferUtils.createByteBuffer(4 * data.size()); |
|
||||||
for (byte[] v : data) { |
|
||||||
if (v != null) { |
|
||||||
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); |
|
||||||
} else { |
|
||||||
result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0); |
|
||||||
} |
|
||||||
} |
|
||||||
result.flip(); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param materialNumber |
|
||||||
* the material number that is appied to the mesh |
|
||||||
* @return UV coordinates of vertices that belong to the required mesh part |
|
||||||
*/ |
|
||||||
public LinkedHashMap<String, List<Vector2f>> getUVCoordinates(int materialNumber) { |
|
||||||
return userUVCollection.getUVCoordinates(materialNumber); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return indicates if the mesh has UV coordinates |
|
||||||
*/ |
|
||||||
public boolean hasUVCoordinates() { |
|
||||||
return userUVCollection.hasUVCoordinates(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise |
|
||||||
*/ |
|
||||||
public boolean isEmpty() { |
|
||||||
return vertexMap.size() == 0; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created |
|
||||||
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key |
|
||||||
* - the reference indices list. |
|
||||||
* |
|
||||||
* @param basicVertexIndex |
|
||||||
* the index of the vertex from its basic table |
|
||||||
* @param resultIndex |
|
||||||
* the index of the vertex in its result vertex list |
|
||||||
* @param vertexReferenceMap |
|
||||||
* the reference map |
|
||||||
*/ |
|
||||||
private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) { |
|
||||||
List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); |
|
||||||
if (referenceList == null) { |
|
||||||
referenceList = new ArrayList<Integer>(); |
|
||||||
vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); |
|
||||||
} |
|
||||||
referenceList.add(Integer.valueOf(resultIndex)); |
|
||||||
} |
|
||||||
} |
|
@ -1,160 +0,0 @@ |
|||||||
package com.jme3.scene.plugins.blender.meshes.builders; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.logging.Logger; |
|
||||||
|
|
||||||
import com.jme3.math.Vector3f; |
|
||||||
import com.jme3.scene.Mesh; |
|
||||||
import com.jme3.scene.Mesh.Mode; |
|
||||||
import com.jme3.scene.VertexBuffer; |
|
||||||
import com.jme3.scene.VertexBuffer.Format; |
|
||||||
import com.jme3.scene.VertexBuffer.Type; |
|
||||||
import com.jme3.scene.VertexBuffer.Usage; |
|
||||||
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.util.BufferUtils; |
|
||||||
|
|
||||||
/** |
|
||||||
* A builder that creates a lines mesh. The result is made of lines that do not belong to any face. |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
/* package */class LineMeshBuilder { |
|
||||||
private static final Logger LOGGER = Logger.getLogger(LineMeshBuilder.class.getName()); |
|
||||||
|
|
||||||
private static final int EDGE_NOT_IN_FACE_FLAG = 0x80; |
|
||||||
|
|
||||||
/** An array of reference vertices. */ |
|
||||||
private Vector3f[][] verticesAndNormals; |
|
||||||
/** The vertices of the mesh. */ |
|
||||||
private List<Vector3f> vertices = new ArrayList<Vector3f>(); |
|
||||||
/** The normals of the mesh. */ |
|
||||||
private List<Vector3f> normals = new ArrayList<Vector3f>(); |
|
||||||
|
|
||||||
/** |
|
||||||
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' |
|
||||||
* positions (it simply tells which vertex is referenced where in the result list). |
|
||||||
*/ |
|
||||||
private Map<Integer, List<Integer>> globalVertexReferenceMap; |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor. Stores the given array (not copying it). |
|
||||||
* The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. |
|
||||||
* The amount of vertices is always faceCount * 3. |
|
||||||
* @param verticesAndNormals |
|
||||||
* the reference vertices and normals array |
|
||||||
*/ |
|
||||||
public LineMeshBuilder(Vector3f[][] verticesAndNormals) { |
|
||||||
this.verticesAndNormals = verticesAndNormals; |
|
||||||
globalVertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAndNormals.length); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The method reads the mesh. It loads only edges that are marked as not belonging to any face in their flag field. |
|
||||||
* @param meshStructure |
|
||||||
* the mesh structure |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception thrown when reading from the blend file fails |
|
||||||
*/ |
|
||||||
public void readMesh(Structure meshStructure) throws BlenderFileException { |
|
||||||
LOGGER.fine("Reading line mesh."); |
|
||||||
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); |
|
||||||
|
|
||||||
if (pMEdge.isNotNull()) { |
|
||||||
List<Structure> edges = pMEdge.fetchData(); |
|
||||||
int vertexIndex = 0;//vertex index in the result mesh
|
|
||||||
for (Structure edge : edges) { |
|
||||||
int flag = ((Number) edge.getFieldValue("flag")).intValue(); |
|
||||||
if ((flag & EDGE_NOT_IN_FACE_FLAG) != 0) { |
|
||||||
int v1 = ((Number) edge.getFieldValue("v1")).intValue(); |
|
||||||
int v2 = ((Number) edge.getFieldValue("v2")).intValue(); |
|
||||||
|
|
||||||
vertices.add(verticesAndNormals[v1][0]); |
|
||||||
normals.add(verticesAndNormals[v1][1]); |
|
||||||
this.appendVertexReference(v1, vertexIndex++, globalVertexReferenceMap); |
|
||||||
|
|
||||||
vertices.add(verticesAndNormals[v2][0]); |
|
||||||
normals.add(verticesAndNormals[v2][1]); |
|
||||||
this.appendVertexReference(v2, vertexIndex++, globalVertexReferenceMap); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Builds the meshes. |
|
||||||
* @return a map between material index and the mesh |
|
||||||
*/ |
|
||||||
public Map<Integer, Mesh> buildMeshes() { |
|
||||||
LOGGER.fine("Building line mesh."); |
|
||||||
Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(1); |
|
||||||
if (vertices.size() > 0) { |
|
||||||
Mesh mesh = new Mesh(); |
|
||||||
mesh.setMode(Mode.Lines); |
|
||||||
|
|
||||||
LOGGER.fine("Creating indices buffer."); |
|
||||||
if (vertices.size() <= Short.MAX_VALUE) { |
|
||||||
short[] indices = new short[vertices.size()]; |
|
||||||
for (int i = 0; i < vertices.size(); ++i) { |
|
||||||
indices[i] = (short) i; |
|
||||||
} |
|
||||||
mesh.setBuffer(Type.Index, 1, indices); |
|
||||||
} else { |
|
||||||
int[] indices = new int[vertices.size()]; |
|
||||||
for (int i = 0; i < vertices.size(); ++i) { |
|
||||||
indices[i] = i; |
|
||||||
} |
|
||||||
mesh.setBuffer(Type.Index, 1, indices); |
|
||||||
} |
|
||||||
|
|
||||||
LOGGER.fine("Creating vertices buffer."); |
|
||||||
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); |
|
||||||
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()]))); |
|
||||||
mesh.setBuffer(verticesBuffer); |
|
||||||
|
|
||||||
LOGGER.fine("Creating normals buffer (in case of lines it is required if skeleton is applied)."); |
|
||||||
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); |
|
||||||
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()]))); |
|
||||||
mesh.setBuffer(normalsBuffer); |
|
||||||
|
|
||||||
result.put(-1, mesh); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise |
|
||||||
*/ |
|
||||||
public boolean isEmpty() { |
|
||||||
return vertices == null; |
|
||||||
} |
|
||||||
|
|
||||||
public Map<Integer, List<Integer>> getGlobalVertexReferenceMap() { |
|
||||||
return globalVertexReferenceMap; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created |
|
||||||
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key |
|
||||||
* - the reference indices list. |
|
||||||
* |
|
||||||
* @param basicVertexIndex |
|
||||||
* the index of the vertex from its basic table |
|
||||||
* @param resultIndex |
|
||||||
* the index of the vertex in its result vertex list |
|
||||||
* @param vertexReferenceMap |
|
||||||
* the reference map |
|
||||||
*/ |
|
||||||
private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) { |
|
||||||
List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); |
|
||||||
if (referenceList == null) { |
|
||||||
referenceList = new ArrayList<Integer>(); |
|
||||||
vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); |
|
||||||
} |
|
||||||
referenceList.add(Integer.valueOf(resultIndex)); |
|
||||||
} |
|
||||||
} |
|
@ -1,161 +0,0 @@ |
|||||||
package com.jme3.scene.plugins.blender.meshes.builders; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.LinkedHashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Map.Entry; |
|
||||||
|
|
||||||
import com.jme3.math.Vector2f; |
|
||||||
import com.jme3.math.Vector3f; |
|
||||||
import com.jme3.scene.Mesh; |
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext; |
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException; |
|
||||||
import com.jme3.scene.plugins.blender.file.DynamicArray; |
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer; |
|
||||||
import com.jme3.scene.plugins.blender.file.Structure; |
|
||||||
import com.jme3.scene.plugins.blender.materials.MaterialContext; |
|
||||||
|
|
||||||
public class MeshBuilder { |
|
||||||
private boolean fixUpAxis; |
|
||||||
private PointMeshBuilder pointMeshBuilder; |
|
||||||
private LineMeshBuilder lineMeshBuilder; |
|
||||||
private FaceMeshBuilder faceMeshBuilder; |
|
||||||
/** |
|
||||||
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' |
|
||||||
* positions (it simply tells which vertex is referenced where in the result list). |
|
||||||
*/ |
|
||||||
private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap = new HashMap<Integer, Map<Integer,List<Integer>>>(); |
|
||||||
|
|
||||||
public MeshBuilder(Structure meshStructure, MaterialContext[] materials, BlenderContext blenderContext) throws BlenderFileException { |
|
||||||
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); |
|
||||||
Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(meshStructure); |
|
||||||
|
|
||||||
faceMeshBuilder = new FaceMeshBuilder(verticesAndNormals, this.areGeneratedTexturesPresent(materials)); |
|
||||||
faceMeshBuilder.readMesh(meshStructure, blenderContext); |
|
||||||
lineMeshBuilder = new LineMeshBuilder(verticesAndNormals); |
|
||||||
lineMeshBuilder.readMesh(meshStructure); |
|
||||||
pointMeshBuilder = new PointMeshBuilder(verticesAndNormals); |
|
||||||
pointMeshBuilder.readMesh(meshStructure); |
|
||||||
} |
|
||||||
|
|
||||||
public Map<Integer, List<Mesh>> buildMeshes() { |
|
||||||
Map<Integer, List<Mesh>> result = new HashMap<Integer, List<Mesh>>(); |
|
||||||
|
|
||||||
Map<Integer, Mesh> meshes = faceMeshBuilder.buildMeshes(); |
|
||||||
for (Entry<Integer, Mesh> entry : meshes.entrySet()) { |
|
||||||
List<Mesh> meshList = new ArrayList<Mesh>(); |
|
||||||
meshList.add(entry.getValue()); |
|
||||||
result.put(entry.getKey(), meshList); |
|
||||||
} |
|
||||||
|
|
||||||
meshes = lineMeshBuilder.buildMeshes(); |
|
||||||
for (Entry<Integer, Mesh> entry : meshes.entrySet()) { |
|
||||||
List<Mesh> meshList = result.get(entry.getKey()); |
|
||||||
if (meshList == null) { |
|
||||||
meshList = new ArrayList<Mesh>(); |
|
||||||
result.put(entry.getKey(), meshList); |
|
||||||
} |
|
||||||
meshList.add(entry.getValue()); |
|
||||||
} |
|
||||||
|
|
||||||
meshes = pointMeshBuilder.buildMeshes(); |
|
||||||
for (Entry<Integer, Mesh> entry : meshes.entrySet()) { |
|
||||||
List<Mesh> meshList = result.get(entry.getKey()); |
|
||||||
if (meshList == null) { |
|
||||||
meshList = new ArrayList<Mesh>(); |
|
||||||
result.put(entry.getKey(), meshList); |
|
||||||
} |
|
||||||
meshList.add(entry.getValue()); |
|
||||||
} |
|
||||||
|
|
||||||
globalVertexReferenceMap.putAll(faceMeshBuilder.getVertexReferenceMap()); |
|
||||||
globalVertexReferenceMap.put(-1, lineMeshBuilder.getGlobalVertexReferenceMap()); |
|
||||||
globalVertexReferenceMap.get(-1).putAll(pointMeshBuilder.getGlobalVertexReferenceMap()); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise |
|
||||||
*/ |
|
||||||
public boolean isEmpty() { |
|
||||||
return faceMeshBuilder.isEmpty() && lineMeshBuilder.isEmpty() && pointMeshBuilder.isEmpty(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return a map that maps vertex index from reference array to its indices in the result list |
|
||||||
*/ |
|
||||||
public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() { |
|
||||||
return globalVertexReferenceMap; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @param materialNumber |
|
||||||
* the material number that is appied to the mesh |
|
||||||
* @return UV coordinates of vertices that belong to the required mesh part |
|
||||||
*/ |
|
||||||
public LinkedHashMap<String, List<Vector2f>> getUVCoordinates(int materialNumber) { |
|
||||||
return faceMeshBuilder.getUVCoordinates(materialNumber); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return indicates if the mesh has UV coordinates |
|
||||||
*/ |
|
||||||
public boolean hasUVCoordinates() { |
|
||||||
return faceMeshBuilder.hasUVCoordinates(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method returns the vertices. |
|
||||||
* |
|
||||||
* @param meshStructure |
|
||||||
* the structure containing the mesh data |
|
||||||
* @return a list of two - element arrays, the first element is the vertex and the second - its normal |
|
||||||
* @throws BlenderFileException |
|
||||||
* this exception is thrown when the blend file structure is somehow invalid or corrupted |
|
||||||
*/ |
|
||||||
@SuppressWarnings("unchecked") |
|
||||||
private Vector3f[][] getVerticesAndNormals(Structure meshStructure) throws BlenderFileException { |
|
||||||
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); |
|
||||||
Vector3f[][] result = new Vector3f[count][2]; |
|
||||||
if (count == 0) { |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); |
|
||||||
List<Structure> mVerts = pMVert.fetchData(); |
|
||||||
if (fixUpAxis) { |
|
||||||
for (int i = 0; i < count; ++i) { |
|
||||||
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co"); |
|
||||||
result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); |
|
||||||
|
|
||||||
DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no"); |
|
||||||
result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f); |
|
||||||
} |
|
||||||
} else { |
|
||||||
for (int i = 0; i < count; ++i) { |
|
||||||
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co"); |
|
||||||
result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); |
|
||||||
|
|
||||||
DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no"); |
|
||||||
result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f); |
|
||||||
} |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return <b>true</b> if the material has at least one generated component and <b>false</b> otherwise |
|
||||||
*/ |
|
||||||
private boolean areGeneratedTexturesPresent(MaterialContext[] materials) { |
|
||||||
if (materials != null) { |
|
||||||
for (MaterialContext material : materials) { |
|
||||||
if (material != null && material.hasGeneratedTextures()) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
} |
|
@ -1,171 +0,0 @@ |
|||||||
package com.jme3.scene.plugins.blender.meshes.builders; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.HashSet; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
import java.util.logging.Logger; |
|
||||||
|
|
||||||
import com.jme3.math.Vector3f; |
|
||||||
import com.jme3.scene.Mesh; |
|
||||||
import com.jme3.scene.Mesh.Mode; |
|
||||||
import com.jme3.scene.VertexBuffer; |
|
||||||
import com.jme3.scene.VertexBuffer.Format; |
|
||||||
import com.jme3.scene.VertexBuffer.Type; |
|
||||||
import com.jme3.scene.VertexBuffer.Usage; |
|
||||||
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.util.BufferUtils; |
|
||||||
|
|
||||||
/** |
|
||||||
* A builder that creates a points mesh. The result is made of points that do not belong to any edge and face. |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
/* package */class PointMeshBuilder { |
|
||||||
private static final Logger LOGGER = Logger.getLogger(PointMeshBuilder.class.getName()); |
|
||||||
|
|
||||||
/** An array of reference vertices. */ |
|
||||||
private Vector3f[][] verticesAndNormals; |
|
||||||
/** The vertices of the mesh. */ |
|
||||||
private List<Vector3f> vertices = new ArrayList<Vector3f>(); |
|
||||||
/** The normals of the mesh. */ |
|
||||||
private List<Vector3f> normals = new ArrayList<Vector3f>(); |
|
||||||
|
|
||||||
/** |
|
||||||
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' |
|
||||||
* positions (it simply tells which vertex is referenced where in the result list). |
|
||||||
*/ |
|
||||||
private Map<Integer, List<Integer>> globalVertexReferenceMap; |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor. Stores the given array (not copying it). |
|
||||||
* The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. |
|
||||||
* The amount of vertices is always faceCount * 3. |
|
||||||
* @param verticesAndNormals |
|
||||||
* the reference vertices and normals array |
|
||||||
*/ |
|
||||||
public PointMeshBuilder(Vector3f[][] verticesAndNormals) { |
|
||||||
this.verticesAndNormals = verticesAndNormals; |
|
||||||
globalVertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAndNormals.length); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The method reads the mesh. Since blender does not store the information in the vertex itself whether it belongs |
|
||||||
* anywhere or not, we need to check all vertices and use here only those that are not used. |
|
||||||
* @param meshStructure |
|
||||||
* the mesh structure |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception thrown when reading from the blend file fails |
|
||||||
*/ |
|
||||||
public void readMesh(Structure meshStructure) throws BlenderFileException { |
|
||||||
LOGGER.fine("Reading points mesh."); |
|
||||||
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); |
|
||||||
|
|
||||||
if (pMEdge.isNotNull()) { |
|
||||||
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); |
|
||||||
Set<Vector3f> usedVertices = new HashSet<Vector3f>(count); |
|
||||||
List<Structure> edges = pMEdge.fetchData(); |
|
||||||
|
|
||||||
for (Structure edge : edges) { |
|
||||||
int v1 = ((Number) edge.getFieldValue("v1")).intValue(); |
|
||||||
int v2 = ((Number) edge.getFieldValue("v2")).intValue(); |
|
||||||
usedVertices.add(verticesAndNormals[v1][0]); |
|
||||||
usedVertices.add(verticesAndNormals[v2][0]); |
|
||||||
} |
|
||||||
|
|
||||||
if (usedVertices.size() < count) { |
|
||||||
vertices = new ArrayList<Vector3f>(count - usedVertices.size()); |
|
||||||
int vertexIndex = 0, blenderVertexIndex = 0; |
|
||||||
for (Vector3f[] vertAndNormal : verticesAndNormals) { |
|
||||||
if (!usedVertices.contains(vertAndNormal[0])) { |
|
||||||
vertices.add(vertAndNormal[0]); |
|
||||||
normals.add(vertAndNormal[1]); |
|
||||||
this.appendVertexReference(blenderVertexIndex, vertexIndex++, globalVertexReferenceMap); |
|
||||||
} |
|
||||||
++blenderVertexIndex; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Builds the meshes. |
|
||||||
* @return a map between material index and the mesh |
|
||||||
*/ |
|
||||||
public Map<Integer, Mesh> buildMeshes() { |
|
||||||
LOGGER.fine("Building point mesh."); |
|
||||||
Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(1); |
|
||||||
|
|
||||||
if (vertices.size() > 0) { |
|
||||||
Mesh mesh = new Mesh(); |
|
||||||
mesh.setMode(Mode.Points); |
|
||||||
mesh.setPointSize(3); |
|
||||||
|
|
||||||
// the point mesh does not need index buffer, but some modifiers applied by importer need it
|
|
||||||
// the 'alone point' situation should be quite rare so not too many resources are wasted here
|
|
||||||
LOGGER.fine("Creating indices buffer."); |
|
||||||
if (vertices.size() <= Short.MAX_VALUE) { |
|
||||||
short[] indices = new short[vertices.size()]; |
|
||||||
for (int i = 0; i < vertices.size(); ++i) { |
|
||||||
indices[i] = (short) i; |
|
||||||
} |
|
||||||
mesh.setBuffer(Type.Index, 1, indices); |
|
||||||
} else { |
|
||||||
int[] indices = new int[vertices.size()]; |
|
||||||
for (int i = 0; i < vertices.size(); ++i) { |
|
||||||
indices[i] = i; |
|
||||||
} |
|
||||||
mesh.setBuffer(Type.Index, 1, indices); |
|
||||||
} |
|
||||||
|
|
||||||
LOGGER.fine("Creating vertices buffer."); |
|
||||||
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); |
|
||||||
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()]))); |
|
||||||
mesh.setBuffer(verticesBuffer); |
|
||||||
|
|
||||||
LOGGER.fine("Creating normals buffer (in case of points it is required if skeleton is applied)."); |
|
||||||
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal); |
|
||||||
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()]))); |
|
||||||
mesh.setBuffer(normalsBuffer); |
|
||||||
|
|
||||||
result.put(-1, mesh); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise |
|
||||||
*/ |
|
||||||
public boolean isEmpty() { |
|
||||||
return vertices == null; |
|
||||||
} |
|
||||||
|
|
||||||
public Map<Integer, List<Integer>> getGlobalVertexReferenceMap() { |
|
||||||
return globalVertexReferenceMap; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created |
|
||||||
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key |
|
||||||
* - the reference indices list. |
|
||||||
* |
|
||||||
* @param basicVertexIndex |
|
||||||
* the index of the vertex from its basic table |
|
||||||
* @param resultIndex |
|
||||||
* the index of the vertex in its result vertex list |
|
||||||
* @param vertexReferenceMap |
|
||||||
* the reference map |
|
||||||
*/ |
|
||||||
private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) { |
|
||||||
List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); |
|
||||||
if (referenceList == null) { |
|
||||||
referenceList = new ArrayList<Integer>(); |
|
||||||
vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); |
|
||||||
} |
|
||||||
referenceList.add(Integer.valueOf(resultIndex)); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,54 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.modifiers; |
||||||
|
|
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
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.TemporalMesh; |
||||||
|
|
||||||
|
/** |
||||||
|
* The triangulation modifier. It does not take any settings into account so if the result is different than |
||||||
|
* in blender then please apply the modifier before importing. |
||||||
|
* |
||||||
|
* @author Marcin Roguski |
||||||
|
*/ |
||||||
|
public class TriangulateModifier extends Modifier { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(TriangulateModifier.class.getName()); |
||||||
|
|
||||||
|
/** |
||||||
|
* This constructor reads animation data from the object structore. The |
||||||
|
* stored data is the AnimData and additional data is armature's OMA. |
||||||
|
* |
||||||
|
* @param objectStructure |
||||||
|
* the structure of the object |
||||||
|
* @param modifierStructure |
||||||
|
* the structure of the modifier |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow |
||||||
|
* corrupted |
||||||
|
*/ |
||||||
|
public TriangulateModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { |
||||||
|
if (this.validate(modifierStructure, blenderContext)) { |
||||||
|
LOGGER.warning("Triangulation modifier does not take modifier options into account. If triangulation result is different" + " than the model in blender please apply the modifier before importing!"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void apply(Node node, BlenderContext blenderContext) { |
||||||
|
if (invalid) { |
||||||
|
LOGGER.log(Level.WARNING, "Triangulate modifier is invalid! Cannot be applied to: {0}", node.getName()); |
||||||
|
} |
||||||
|
TemporalMesh temporalMesh = this.getTemporalMesh(node); |
||||||
|
if (temporalMesh != null) { |
||||||
|
LOGGER.log(Level.FINE, "Applying triangulation modifier to: {0}", temporalMesh); |
||||||
|
temporalMesh.triangulate(); |
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue