Bugfix: fixed an issue with wrong vertrex colors applying (along with classes prepared to implement line and point mesh loading).

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10913 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
experimental
Kae..pl 11 years ago
parent e20d0c0045
commit 69e4392ba9
  1. 342
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java
  2. 365
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  3. 591
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java
  4. 5
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java
  5. 129
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java
  6. 5
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java

@ -1,342 +0,0 @@
package com.jme3.scene.plugins.blender.meshes;
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.plugins.blender.textures.UserUVCollection;
import com.jme3.util.BufferUtils;
/**
* A builder class for meshes.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class MeshBuilder {
private static final Logger LOGGER = Logger.getLogger(MeshBuilder.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 MeshBuilder(Vector3f[][] verticesAndNormals, List<byte[]> verticesColors, boolean usesGeneratedTextures) {
this.verticesAndNormals = verticesAndNormals;
this.verticesColors = verticesColors;
this.usesGeneratedTextures = usesGeneratedTextures;
globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length);
}
/**
* This method adds a point to the mesh.
* @param coordinates
* the coordinates of the point
* @param normal
* the point's normal vector
* @param materialNumber
* the material number for this point
*/
public void appendPoint(Vector3f coordinates, Vector3f normal, int materialNumber) {
LOGGER.warning("Appending single point not yet supported!");// TODO
}
/**
* This method adds a line 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 smooth
* indicates if this face should have smooth shading or flat shading
*/
public void appendEdge(int v1, int v2, boolean smooth) {
LOGGER.warning("Appending single line not yet supported!");// TODO
}
/**
* 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 quad
* indicates if the appended face is a part of a quad face (used for creating vertex colors buffer)
* @param faceIndex
* the face index (used for creating vertex colors buffer)
*/
public void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Map<String, Vector2f[]> uvsForFace, boolean quad, int faceIndex) {
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;
int[] vertexColorIndex = new int[] { 0, 1, 2 };
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);
}
faceIndex *= 3;
if (quad) {
vertexColorIndex[1] = 2;
vertexColorIndex[2] = 3;
}
// 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(faceIndex + 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(faceIndex + 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(faceIndex + vertexColorIndex[i]));
}
normalList.add(smooth ? verticesAndNormals[index[i]][1] : n);
}
}
}
/**
* @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
*/
public 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
*/
public int getVerticesAmount(int materialNumber) {
return vertexMap.get(materialNumber).size();
}
/**
* @param materialNumber
* the material index
* @return normals result array
*/
public 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
*/
public 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;
}
/**
* @return a map between material number and the mesh part vertices indices
*/
public Map<Integer, List<Integer>> getMeshesMap() {
return indexMap;
}
/**
* @return the amount of meshes the source mesh was split into (depends on the applied materials count)
*/
public int getMeshesPartAmount() {
return indexMap.size();
}
/**
* @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));
}
}

@ -32,7 +32,6 @@
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;
@ -42,22 +41,20 @@ import java.util.logging.Logger;
import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
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.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
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;
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.builders.MeshBuilder;
import com.jme3.scene.plugins.blender.objects.Properties;
import com.jme3.scene.plugins.blender.textures.TextureHelper;
import com.jme3.util.BufferUtils;
@ -71,10 +68,10 @@ public class MeshHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName());
/** A type of UV data layer in traditional faced mesh (triangles or quads). */
private static final int UV_DATA_LAYER_TYPE_FMESH = 5;
public static final int UV_DATA_LAYER_TYPE_FMESH = 5;
/** A type of UV data layer in bmesh type. */
private static final int UV_DATA_LAYER_TYPE_BMESH = 16;
public static final int UV_DATA_LAYER_TYPE_BMESH = 16;
/**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* versions.
@ -119,17 +116,7 @@ public class MeshHelper extends AbstractBlenderHelper {
}
LOGGER.fine("Reading vertices and their colors.");
Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(structure, blenderContext);
List<byte[]> verticesColors = this.getVerticesColors(structure, blenderContext);
MeshBuilder meshBuilder = new MeshBuilder(verticesAndNormals, verticesColors, this.areGeneratedTexturesPresent(materials));
if (this.isBMeshCompatible(structure)) {
this.readBMesh(meshBuilder, structure, blenderContext);
} else {
this.readTraditionalFaces(meshBuilder, structure, blenderContext);
}
MeshBuilder meshBuilder = new MeshBuilder(structure, materials, blenderContext);
if (meshBuilder.isEmpty()) {
LOGGER.fine("The geometry is empty.");
geometries = new ArrayList<Geometry>(0);
@ -154,52 +141,19 @@ public class MeshHelper extends AbstractBlenderHelper {
Properties properties = this.loadProperties(structure, blenderContext);
LOGGER.fine("Generating meshes.");
geometries = new ArrayList<Geometry>(meshBuilder.getMeshesPartAmount());
for (Entry<Integer, List<Integer>> meshEntry : meshBuilder.getMeshesMap().entrySet()) {
Map<Integer, List<Mesh>> meshes = meshBuilder.buildMeshes();
geometries = new ArrayList<Geometry>(meshes.size());
for(Entry<Integer, List<Mesh>> meshEntry : meshes.entrySet()) {
int materialIndex = meshEntry.getKey();
// key is the material index (or -1 if the material has no texture)
// value is a list of vertex indices
Mesh mesh = new Mesh();
// creating vertices indices for this mesh
List<Integer> indexList = meshEntry.getValue();
if (meshBuilder.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();
for(Mesh mesh : meshEntry.getValue()) {
LOGGER.fine("Preparing the result part.");
Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);
if (properties != null && properties.getValue() != null) {
this.applyProperties(geometry, properties);
}
mesh.setBuffer(Type.Index, 1, indices);
geometries.add(geometry);
meshContext.putGeometry(materialIndex, geometry);
}
LOGGER.fine("Creating vertices buffer.");
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.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(meshBuilder.getNormals(materialIndex)));
mesh.setBuffer(normalsBuffer);
if (verticesColors != null) {
LOGGER.fine("Setting vertices colors.");
mesh.setBuffer(Type.Color, 4, meshBuilder.getVertexColorsBuffer(materialIndex));
mesh.getBuffer(Type.Color).setNormalized(true);
}
LOGGER.fine("Preparing the result part.");
Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);
if (properties != null && properties.getValue() != null) {
this.applyProperties(geometry, properties);
}
geometries.add(geometry);
meshContext.putGeometry(materialIndex, geometry);
}
// store the data in blender context before applying the material
@ -247,7 +201,7 @@ public class MeshHelper extends AbstractBlenderHelper {
return geometries;
}
/**
* Tells if the given mesh structure supports BMesh.
*
@ -255,294 +209,9 @@ public class MeshHelper extends AbstractBlenderHelper {
* the mesh structure
* @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
*/
private boolean isBMeshCompatible(Structure meshStructure) {
public boolean isBMeshCompatible(Structure meshStructure) {
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull();
}
/**
* 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
* @param blenderContext
* the blender context
* @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, BlenderContext blenderContext) 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(blenderContext.getInputStream());
for (Structure structure : loopDataLayers) {
Pointer p = (Pointer) structure.getFieldValue("data");
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == UV_DATA_LAYER_TYPE_BMESH) {
String uvSetName = structure.getFieldValue("name").toString();
List<Structure> uvsStructures = p.fetchData(blenderContext.getInputStream());
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(blenderContext.getInputStream());
for (Structure structure : facesDataLayers) {
Pointer p = (Pointer) structure.getFieldValue("data");
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == UV_DATA_LAYER_TYPE_FMESH) {
String uvSetName = structure.getFieldValue("name").toString();
List<Structure> uvsStructures = p.fetchData(blenderContext.getInputStream());
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 reads the mesh from the new BMesh system.
*
* @param meshBuilder
* the mesh builder
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private void readBMesh(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Reading BMesh.");
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) {
Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, true, blenderContext);
int faceIndex = 0;
List<Structure> polys = pMPoly.fetchData(blenderContext.getInputStream());
List<Structure> loops = pMLoop.fetchData(blenderContext.getInputStream());
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 (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);
}
}
meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, faceIndex);
uvCoordinatesForFace.clear();
++i;
}
++faceIndex;
}
}
}
/**
* This method reads the mesh from traditional triangle/quad storing
* structures.
*
* @param meshBuilder
* the mesh builder
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private void readTraditionalFaces(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Reading traditional faces.");
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : 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, blenderContext);
Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
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();
meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, i);
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);
}
}
meshBuilder.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, true, i);
uvCoordinatesForFace.clear();
}
}
} else {
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
List<Structure> mEdges = pMEdge.isNotNull() ? pMEdge.fetchData(blenderContext.getInputStream()) : null;
if (mEdges != null && mEdges.size() > 0) {
for (int i = 0; i < mEdges.size(); ++i) {
Structure mEdge = mEdges.get(i);
boolean smooth = (((Number) mEdge.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
int v1 = ((Number) mEdge.getFieldValue("v1")).intValue();
int v2 = ((Number) mEdge.getFieldValue("v2")).intValue();
meshBuilder.appendEdge(v1, v2, smooth);
}
}
}
}
/**
* @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;
}
/**
* 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 {
Pointer pMCol = (Pointer) meshStructure.getFieldValue(this.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(blenderContext.getInputStream());
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;
}
/**
* This method returns the vertices.
*
* @param meshStructure
* the structure containing the mesh data
* @param blenderContext
* the blender context
* @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, BlenderContext blenderContext) 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(blenderContext.getInputStream());
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;
}
}

@ -0,0 +1,591 @@
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, blenderContext);
} else {
this.readTraditionalFaces(structure, blenderContext);
}
}
/**
* 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 (or -1 if the material has no texture)
// 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
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private void readBMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Reading BMesh.");
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) {
Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, true, blenderContext);
List<Structure> polys = pMPoly.fetchData(blenderContext.getInputStream());
List<Structure> loops = pMLoop.fetchData(blenderContext.getInputStream());
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
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private void readTraditionalFaces(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Reading traditional faces.");
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : 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, blenderContext);
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
*/
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
* @param blenderContext
* the blender context
* @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, BlenderContext blenderContext) 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(blenderContext.getInputStream());
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(blenderContext.getInputStream());
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(blenderContext.getInputStream());
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(blenderContext.getInputStream());
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(blenderContext.getInputStream());
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));
}
}

@ -0,0 +1,5 @@
package com.jme3.scene.plugins.blender.meshes.builders;
/*package*/ class LineMeshBuilder {
//TODO: this will be implemented soon
}

@ -0,0 +1,129 @@
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;
//TODO: these will be added soon
// private PointMeshBuilder pointMeshBuilder;
// private LineMeshBuilder lineMeshBuilder;
private FaceMeshBuilder faceMeshBuilder;
public MeshBuilder(Structure meshStructure, MaterialContext[] materials, BlenderContext blenderContext) throws BlenderFileException {
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(meshStructure, blenderContext);
boolean generatedTexturesPresent = this.areGeneratedTexturesPresent(materials);
faceMeshBuilder = new FaceMeshBuilder(verticesAndNormals, generatedTexturesPresent);
faceMeshBuilder.readMesh(meshStructure, blenderContext);
}
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);
}
return result;
}
public boolean isEmpty() {
return faceMeshBuilder.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 faceMeshBuilder.getVertexReferenceMap();
}
/**
* @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
* @param blenderContext
* the blender context
* @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, BlenderContext blenderContext) 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(blenderContext.getInputStream());
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;
}
}

@ -0,0 +1,5 @@
package com.jme3.scene.plugins.blender.meshes.builders;
/*package*/ class PointMeshBuilder {
//TODO: this will be implemented soon
}
Loading…
Cancel
Save