Issue 466: Blender loader duplicates vertices

Done. Hope it does not break anything ... too much :)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9509 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Kae..pl 13 years ago
parent 4e987ae63e
commit d63683d240
  1. 57
      engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java
  2. 7
      engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java
  3. 90
      engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  4. 2
      engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
  5. 244
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java
  6. 73
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java
  7. 235
      engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  8. 169
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  9. 3
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java
  10. 14
      engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  11. 4
      engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java
  12. 9
      engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java

@ -1,31 +1,40 @@
package com.jme3.scene.plugins.blender.curves; package com.jme3.scene.plugins.blender.curves;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.material.RenderState.FaceCullMode; import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.*; import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Spline;
import com.jme3.math.Spline.SplineType; import com.jme3.math.Spline.SplineType;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.*; import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
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.MaterialContext;
import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
import com.jme3.scene.plugins.blender.objects.Properties; import com.jme3.scene.plugins.blender.objects.Properties;
import com.jme3.scene.shape.Curve; import com.jme3.scene.shape.Curve;
import com.jme3.scene.shape.Surface; import com.jme3.scene.shape.Surface;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
/** /**
* A class that is used in mesh calculations. * A class that is used in mesh calculations.
@ -396,7 +405,6 @@ public class CurvesHelper extends AbstractBlenderHelper {
protected List<Geometry> applyBevelAndTaper(Curve curve, List<Geometry> bevelObject, Curve taperObject, protected List<Geometry> applyBevelAndTaper(Curve curve, List<Geometry> bevelObject, Curve taperObject,
boolean smooth, BlenderContext blenderContext) { boolean smooth, BlenderContext blenderContext) {
float[] curvePoints = BufferUtils.getFloatArray(curve.getFloatBuffer(Type.Position)); float[] curvePoints = BufferUtils.getFloatArray(curve.getFloatBuffer(Type.Position));
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
float curveLength = curve.getLength(); float curveLength = curve.getLength();
//TODO: use the smooth var //TODO: use the smooth var
@ -512,7 +520,7 @@ public class CurvesHelper extends AbstractBlenderHelper {
int[] allIndices = BufferUtils.getIntArray(indexBuffers[geomIndex]); int[] allIndices = BufferUtils.getIntArray(indexBuffers[geomIndex]);
for (int i = 0; i < allIndices.length - 3; i += 3) { for (int i = 0; i < allIndices.length - 3; i += 3) {
Vector3f n = FastMath.computeNormal(allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]); Vector3f n = FastMath.computeNormal(allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]);
meshHelper.addNormal(n, normalMap, smooth, allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]); this.addNormal(n, normalMap, smooth, allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]);
} }
if (normalBuffers[geomIndex] == null) { if (normalBuffers[geomIndex] == null) {
normalBuffers[geomIndex] = BufferUtils.createFloatBuffer(allVerts.length * 3); normalBuffers[geomIndex] = BufferUtils.createFloatBuffer(allVerts.length * 3);
@ -541,6 +549,29 @@ public class CurvesHelper extends AbstractBlenderHelper {
return result; return result;
} }
/**
* This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.
*
* @param normalToAdd
* a normal to be added
* @param normalMap
* merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector
* @param smooth
* the variable that indicates wheather to merge normals (creating the smooth mesh) or not
* @param vertices
* a list of vertices read from the blender file
*/
private void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {
for (Vector3f v : vertices) {
Vector3f n = normalMap.get(v);
if (!smooth || n == null) {
normalMap.put(v, normalToAdd.clone());
} else {
n.addLocal(normalToAdd).normalizeLocal();
}
}
}
/** /**
* This method loads the taper object. * This method loads the taper object.
* @param taperStructure * @param taperStructure

@ -103,6 +103,13 @@ public class Pointer {
} else { } else {
structures.addAll(p.fetchData(inputStream)); structures.addAll(p.fetchData(inputStream));
} }
} else {
//it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index
//of the material is important, that is why we need null's to indicate that some materials' slots are empty
if(structures == null) {
structures = new ArrayList<Structure>();
}
structures.add(null);
} }
} }
} else { } else {

@ -195,43 +195,45 @@ public final class MaterialContext {
} }
//applying textures //applying textures
for(Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) { if(!noTextures) {
CombinedTexture combinedTexture = entry.getValue(); for(Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) {
combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); CombinedTexture combinedTexture = entry.getValue();
VertexBuffer.Type uvCoordinatesType = null; combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
VertexBuffer.Type uvCoordinatesType = null;
switch(entry.getKey().intValue()) {
case MTEX_COL: switch(entry.getKey().intValue()) {
uvCoordinatesType = VertexBuffer.Type.TexCoord; case MTEX_COL:
material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, uvCoordinatesType = VertexBuffer.Type.TexCoord;
combinedTexture.getResultTexture()); material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE,
break; combinedTexture.getResultTexture());
case MTEX_NOR: break;
uvCoordinatesType = VertexBuffer.Type.TexCoord2; case MTEX_NOR:
material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, combinedTexture.getResultTexture()); uvCoordinatesType = VertexBuffer.Type.TexCoord2;
break; material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, combinedTexture.getResultTexture());
case MTEX_SPEC: break;
uvCoordinatesType = VertexBuffer.Type.TexCoord3; case MTEX_SPEC:
material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, combinedTexture.getResultTexture()); uvCoordinatesType = VertexBuffer.Type.TexCoord3;
break; material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, combinedTexture.getResultTexture());
case MTEX_EMIT: break;
uvCoordinatesType = VertexBuffer.Type.TexCoord4; case MTEX_EMIT:
material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, combinedTexture.getResultTexture()); uvCoordinatesType = VertexBuffer.Type.TexCoord4;
break; material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, combinedTexture.getResultTexture());
case MTEX_ALPHA: break;
uvCoordinatesType = VertexBuffer.Type.TexCoord5; case MTEX_ALPHA:
material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, combinedTexture.getResultTexture()); uvCoordinatesType = VertexBuffer.Type.TexCoord5;
break; material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, combinedTexture.getResultTexture());
default: break;
LOGGER.severe("Unknown mapping type: " + entry.getKey().intValue()); default:
} LOGGER.severe("Unknown mapping type: " + entry.getKey().intValue());
}
//applying texture coordinates
if(uvCoordinatesType != null) { //applying texture coordinates
VertexBuffer uvCoordsBuffer = new VertexBuffer(uvCoordinatesType); if(uvCoordinatesType != null) {
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, VertexBuffer uvCoordsBuffer = new VertexBuffer(uvCoordinatesType);
BufferUtils.createFloatBuffer(combinedTexture.getResultUVS().toArray(new Vector2f[combinedTexture.getResultUVS().size()]))); uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float,
geometry.getMesh().setBuffer(uvCoordsBuffer); BufferUtils.createFloatBuffer(combinedTexture.getResultUVS().toArray(new Vector2f[combinedTexture.getResultUVS().size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
}
} }
} }
@ -254,6 +256,20 @@ public final class MaterialContext {
geometry.setMaterial(material); geometry.setMaterial(material);
} }
/**
* @return <b>true</b> if the material has at least one generated texture and <b>false</b> otherwise
*/
public boolean hasGeneratedTextures() {
if(loadedTextures != null) {
for(Entry<Number, CombinedTexture> entry : loadedTextures.entrySet()) {
if(entry.getValue().hasGeneratedTextures()) {
return true;
}
}
}
return false;
}
/** /**
* This method sorts the textures by their mapping type. * This method sorts the textures by their mapping type.
* In each group only textures of one type are put (either two- or three-dimensional). * In each group only textures of one type are put (either two- or three-dimensional).

@ -337,7 +337,7 @@ public class MaterialHelper extends AbstractBlenderHelper {
materials = new MaterialContext[materialStructures.size()]; materials = new MaterialContext[materialStructures.size()];
int i = 0; int i = 0;
for (Structure s : materialStructures) { for (Structure s : materialStructures) {
materials[i++] = materialHelper.toMaterialContext(s, blenderContext); materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext);
} }
} }
} }

@ -0,0 +1,244 @@
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
/*package*/ class MeshBuilder {
/** An array of reference vertices. */
private Vector3f[] vertices;
/** 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;
/** A map between vertex and its normal vector. */
private Map<Vector3f, Vector3f> globalNormalMap = new HashMap<Vector3f, Vector3f>();
/** A map between vertex index and its UV coordinates. */
private Map<Integer, Vector2f> uvsMap = new HashMap<Integer, Vector2f>();
/** 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 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 map between material number and UV coordinates of mesh that has this material applied. */
private Map<Integer, List<Vector2f>> uvCoordinates = new HashMap<Integer, List<Vector2f>>();//<material_number; list of uv coordinates for mesh's vertices>
/**
* 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 vertices the reference vertices array
* @param usesGeneratedTextures a variable that indicates if the model uses generated textures or not
*/
public MeshBuilder(Vector3f[] vertices, boolean usesGeneratedTextures) {
if(vertices == null || vertices.length == 0) {
throw new IllegalArgumentException("No vertices loaded to build mesh.");
}
this.vertices = vertices;
this.usesGeneratedTextures = usesGeneratedTextures;
globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(vertices.length);
}
/**
* 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 uvs a 3-element array of vertices UV coordinates
*/
public void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Vector2f[] uvs) {
if(uvs != null && uvs.length != 3) {
throw new IllegalArgumentException("UV coordinates must be a 3-element array!");
}
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<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);
}
List<Vector2f> uvCoordinatesList = null;
if(uvs != null) {
uvCoordinatesList = uvCoordinates.get(Integer.valueOf(materialNumber));
if(uvCoordinatesList == null) {
uvCoordinatesList = new ArrayList<Vector2f>();
uvCoordinates.put(Integer.valueOf(materialNumber), uvCoordinatesList);
}
}
Integer[] index = new Integer[] {v1, v2, v3};
Vector3f n = FastMath.computeNormal(vertices[v1], vertices[v2], vertices[v3]);
this.addNormal(n, globalNormalMap, smooth, vertices[v1], vertices[v2], vertices[v3]);
if(smooth && !usesGeneratedTextures) {
for (int i = 0; i < 3; ++i) {
if(!vertexReferenceMap.containsKey(index[i])) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
vertexList.add(vertices[index[i]]);
normalList.add(globalNormalMap.get(vertices[index[i]]));
if(uvCoordinatesList != null) {
uvsMap.put(vertexList.size(), uvs[i]);
uvCoordinatesList.add(uvs[i]);
}
index[i] = vertexList.size() - 1;
} else if(uvCoordinatesList != null) {
boolean vertexAlreadyUsed = false;
for(Integer vertexIndex : vertexReferenceMap.get(index[i])) {
if(uvs[i].equals(uvsMap.get(vertexIndex))) {
vertexAlreadyUsed = true;
index[i] = vertexIndex;
break;
}
}
if(!vertexAlreadyUsed) {
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
uvsMap.put(vertexList.size(), uvs[i]);
vertexList.add(vertices[index[i]]);
normalList.add(globalNormalMap.get(vertices[index[i]]));
uvCoordinatesList.add(uvs[i]);
index[i] = vertexList.size() - 1;
}
} else {
index[i] = vertexList.indexOf(vertices[index[i]]);
}
indexList.add(index[i]);
}
} else {
for (int i = 0; i < 3; ++i) {
indexList.add(vertexList.size());
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
if(uvCoordinatesList != null) {
uvCoordinatesList.add(uvs[i]);
uvsMap.put(vertexList.size(), uvs[i]);
}
vertexList.add(vertices[index[i]]);
normalList.add(globalNormalMap.get(vertices[index[i]]));
}
}
}
/**
* @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;
}
/**
* @return result vertices array
*/
public Vector3f[] getVertices(int materialNumber) {
return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]);
}
/**
* @return the amount of result vertices
*/
public int getVerticesAmount(int materialNumber) {
return vertexMap.get(materialNumber).size();
}
/**
* @return normals result array
*/
public Vector3f[] getNormals(int materialNumber) {
return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]);
}
/**
* @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 List<Vector2f> getUVCoordinates(int materialNumber) {
return uvCoordinates.get(materialNumber);
}
/**
* @return indicates if the mesh has UV coordinates
*/
public boolean hasUVCoordinates() {
return uvCoordinates.size() > 0;
}
/**
* This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.
*
* @param normalToAdd
* a normal to be added
* @param normalMap
* merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector
* @param smooth
* the variable that indicates wheather to merge normals (creating the smooth mesh) or not
* @param vertices
* a list of vertices read from the blender file
*/
private void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {
for (Vector3f v : vertices) {
Vector3f n = normalMap.get(v);
if (!smooth || n == null) {
normalMap.put(v, normalToAdd.clone());
} else {
n.addLocal(normalToAdd).normalizeLocal();
}
}
}
/**
* 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,11 +1,12 @@
package com.jme3.scene.plugins.blender.meshes; package com.jme3.scene.plugins.blender.meshes;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.VertexBuffer;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import com.jme3.scene.Geometry;
import com.jme3.scene.VertexBuffer;
/** /**
* Class that holds information about the mesh. * Class that holds information about the mesh.
@ -13,12 +14,10 @@ import java.util.Map;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class MeshContext { public class MeshContext {
/** The mesh stored here as a list of geometries. */ /** A map between material index and the geometry. */
private List<Geometry> mesh; private Map<Integer, Geometry> geometries = new HashMap<Integer, Geometry>();
/** Vertex list that is referenced by all the geometries. */
private List<Vector3f> vertexList;
/** The vertex reference map. */ /** The vertex reference map. */
private Map<Integer, List<Integer>> vertexReferenceMap; private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
/** The UV-coordinates for each of the geometries. */ /** The UV-coordinates for each of the geometries. */
private Map<Geometry, VertexBuffer> uvCoordinates = new HashMap<Geometry, VertexBuffer>(); private Map<Geometry, VertexBuffer> uvCoordinates = new HashMap<Geometry, VertexBuffer>();
/** Bind buffer for vertices is stored here and applied when required. */ /** Bind buffer for vertices is stored here and applied when required. */
@ -27,50 +26,44 @@ public class MeshContext {
private VertexBuffer bindNormalBuffer; private VertexBuffer bindNormalBuffer;
/** /**
* This method returns the referenced mesh. * Adds a geometry for the specified material index.
* * @param materialIndex the material index
* @return the referenced mesh * @param geometry the geometry
*/
public List<Geometry> getMesh() {
return mesh;
}
/**
* This method sets the referenced mesh.
*
* @param mesh
* the referenced mesh
*/ */
public void setMesh(List<Geometry> mesh) { public void putGeometry(Integer materialIndex, Geometry geometry) {
this.mesh = mesh; geometries.put(materialIndex, geometry);
} }
/** /**
* This method returns the vertex list. * @param materialIndex the material index
* * @return vertices amount that is used by mesh with the specified material
* @return the vertex list
*/ */
public List<Vector3f> getVertexList() { public int getVertexCount(int materialIndex) {
return vertexList; return geometries.get(materialIndex).getVertexCount();
} }
/** /**
* This method sets the vertex list. * Returns material index for the geometry.
* * @param geometry the geometry
* @param vertexList * @return material index
* the vertex list * @throws IllegalStateException this exception is thrown when no material is found for the specified geometry
*/ */
public void setVertexList(List<Vector3f> vertexList) { public int getMaterialIndex(Geometry geometry) {
this.vertexList = vertexList; for(Entry<Integer, Geometry> entry : geometries.entrySet()) {
if(entry.getValue().equals(geometry)) {
return entry.getKey();
}
}
throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry);
} }
/** /**
* This method returns the vertex reference map. * This method returns the vertex reference map.
* *
* @return the vertex reference map * @return the vertex reference map
*/ */
public Map<Integer, List<Integer>> getVertexReferenceMap() { public Map<Integer, List<Integer>> getVertexReferenceMap(int materialIndex) {
return vertexReferenceMap; return vertexReferenceMap.get(materialIndex);
} }
/** /**
@ -79,7 +72,7 @@ public class MeshContext {
* @param vertexReferenceMap * @param vertexReferenceMap
* the vertex reference map * the vertex reference map
*/ */
public void setVertexReferenceMap(Map<Integer, List<Integer>> vertexReferenceMap) { public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
this.vertexReferenceMap = vertexReferenceMap; this.vertexReferenceMap = vertexReferenceMap;
} }

@ -31,8 +31,15 @@
*/ */
package com.jme3.scene.plugins.blender.meshes; package com.jme3.scene.plugins.blender.meshes;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry; import com.jme3.scene.Geometry;
@ -54,13 +61,6 @@ import com.jme3.scene.plugins.blender.objects.Properties;
import com.jme3.scene.plugins.blender.textures.TextureHelper; import com.jme3.scene.plugins.blender.textures.TextureHelper;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/** /**
* A class that is used in mesh calculations. * A class that is used in mesh calculations.
@ -109,16 +109,20 @@ public class MeshHelper extends AbstractBlenderHelper {
String name = structure.getName(); String name = structure.getName();
MeshContext meshContext = new MeshContext(); MeshContext meshContext = new MeshContext();
// reading materials
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
MaterialContext[] materials = null;
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
materials = materialHelper.getMaterials(structure, blenderContext);
}
// reading vertices // reading vertices
Vector3f[] vertices = this.getVertices(structure, blenderContext); Vector3f[] vertices = this.getVertices(structure, blenderContext);
int verticesAmount = vertices.length; MeshBuilder meshBuilder = new MeshBuilder(vertices, this.areGeneratedTexturesPresent(materials));
// vertices Colors // vertices Colors
List<byte[]> verticesColors = this.getVerticesColors(structure, blenderContext); List<byte[]> verticesColors = this.getVerticesColors(structure, blenderContext);
// reading faces
// the following map sorts faces by material number (because in jme Mesh can have only one material)
Map<Integer, List<Integer>> meshesMap = new HashMap<Integer, List<Integer>>();
Pointer pMFace = (Pointer) structure.getFieldValue("mface"); Pointer pMFace = (Pointer) structure.getFieldValue("mface");
List<Structure> mFaces = null; List<Structure> mFaces = null;
if (pMFace.isNotNull()) { if (pMFace.isNotNull()) {
@ -131,7 +135,6 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
Pointer pMTFace = (Pointer) structure.getFieldValue("mtface"); Pointer pMTFace = (Pointer) structure.getFieldValue("mtface");
Map<Integer, List<Vector2f>> uvCoordinates = new HashMap<Integer, List<Vector2f>>();//<material_number; list of uv coordinates for mesh's vertices>
List<Structure> mtFaces = null; List<Structure> mtFaces = null;
if (pMTFace.isNotNull()) { if (pMTFace.isNotNull()) {
@ -142,17 +145,10 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
} }
// normalMap merges normals of faces that will be rendered smooth
Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(verticesAmount);
List<Vector3f> normalList = new ArrayList<Vector3f>();
List<Vector3f> vertexList = new ArrayList<Vector3f>();
// indicates if the material with the specified number should have a texture attached // indicates if the material with the specified number should have a texture attached
Map<Integer, Texture> materialNumberToTexture = new HashMap<Integer, Texture>(); Map<Integer, Texture> materialNumberToTexture = new HashMap<Integer, Texture>();
// 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)
Map<Integer, List<Integer>> vertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAmount);
int vertexColorIndex = 0; int vertexColorIndex = 0;
Vector2f[] uvCoordinatesForFace = new Vector2f[3];
for (int i = 0; i < mFaces.size(); ++i) { for (int i = 0; i < mFaces.size(); ++i) {
Structure mFace = mFaces.get(i); Structure mFace = mFaces.get(i);
int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue(); int matNr = ((Number) mFace.getFieldValue("mat_nr")).intValue();
@ -161,28 +157,17 @@ public class MeshHelper extends AbstractBlenderHelper {
boolean materialWithoutTextures = false; boolean materialWithoutTextures = false;
Pointer pImage = null; Pointer pImage = null;
List<Vector2f> uvCoordinatesList = uvCoordinates.get(Integer.valueOf(matNr));
if(uvCoordinatesList == null) {
uvCoordinatesList = new ArrayList<Vector2f>();
uvCoordinates.put(Integer.valueOf(matNr), uvCoordinatesList);
}
if (mtFaces != null) { if (mtFaces != null) {
Structure mtFace = mtFaces.get(i); Structure mtFace = mtFaces.get(i);
pImage = (Pointer) mtFace.getFieldValue("tpage"); pImage = (Pointer) mtFace.getFieldValue("tpage");
materialWithoutTextures = pImage.isNull(); materialWithoutTextures = pImage.isNull();
// uvs always must be added wheater we have texture or not // uvs always must be added wheater we have texture or not
uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv"); uvs = (DynamicArray<Number>) mtFace.getFieldValue("uv");
uvCoordinatesList.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue())); uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue());
uvCoordinatesList.add(new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue())); uvCoordinatesForFace[1] = new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue());
uvCoordinatesList.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue())); uvCoordinatesForFace[2] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue());
} }
Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr); Integer materialNumber = Integer.valueOf(materialWithoutTextures ? -1 * matNr - 1 : matNr);
List<Integer> indexList = meshesMap.get(materialNumber);
if (indexList == null) {
indexList = new ArrayList<Integer>();
meshesMap.put(materialNumber, indexList);
}
// attaching image to texture (face can have UV's and image whlie its material may have no texture attached) // attaching image to texture (face can have UV's and image whlie its material may have no texture attached)
if (pImage != null && pImage.isNotNull() && !materialNumberToTexture.containsKey(materialNumber)) { if (pImage != null && pImage.isNotNull() && !materialNumberToTexture.containsKey(materialNumber)) {
@ -198,46 +183,14 @@ public class MeshHelper extends AbstractBlenderHelper {
int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
Vector3f n = FastMath.computeNormal(vertices[v1], vertices[v2], vertices[v3]); meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace);
this.addNormal(n, normalMap, smooth, vertices[v1], vertices[v2], vertices[v3]);
normalList.add(normalMap.get(vertices[v1]));
normalList.add(normalMap.get(vertices[v2]));
normalList.add(normalMap.get(vertices[v3]));
this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap);
indexList.add(vertexList.size());
vertexList.add(vertices[v1]);
this.appendVertexReference(v2, vertexList.size(), vertexReferenceMap);
indexList.add(vertexList.size());
vertexList.add(vertices[v2]);
this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);
indexList.add(vertexList.size());
vertexList.add(vertices[v3]);
if (v4 > 0) { if (v4 > 0) {
if (uvs != null) { if (uvs != null) {
uvCoordinatesList.add(new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue())); uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue());
uvCoordinatesList.add(new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue())); uvCoordinatesForFace[1] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue());
uvCoordinatesList.add(new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue())); uvCoordinatesForFace[2] = new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue());
} }
this.appendVertexReference(v1, vertexList.size(), vertexReferenceMap); meshBuilder.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace);
indexList.add(vertexList.size());
vertexList.add(vertices[v1]);
this.appendVertexReference(v3, vertexList.size(), vertexReferenceMap);
indexList.add(vertexList.size());
vertexList.add(vertices[v3]);
this.appendVertexReference(v4, vertexList.size(), vertexReferenceMap);
indexList.add(vertexList.size());
vertexList.add(vertices[v4]);
this.addNormal(n, normalMap, smooth, vertices[v4]);
normalList.add(normalMap.get(vertices[v1]));
normalList.add(normalMap.get(vertices[v3]));
normalList.add(normalMap.get(vertices[v4]));
if (verticesColors != null) { if (verticesColors != null) {
verticesColors.add(vertexColorIndex + 3, verticesColors.get(vertexColorIndex)); verticesColors.add(vertexColorIndex + 3, verticesColors.get(vertexColorIndex));
@ -251,10 +204,7 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
} }
} }
meshContext.setVertexList(vertexList); meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap());
meshContext.setVertexReferenceMap(vertexReferenceMap);
Vector3f[] normals = normalList.toArray(new Vector3f[normalList.size()]);
// reading vertices groups (from the parent) // reading vertices groups (from the parent)
Structure parent = blenderContext.peekParent(); Structure parent = blenderContext.peekParent();
@ -266,48 +216,23 @@ public class MeshHelper extends AbstractBlenderHelper {
verticesGroups[defIndex++] = def.getFieldValue("name").toString(); verticesGroups[defIndex++] = def.getFieldValue("name").toString();
} }
// reading materials
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
MaterialContext[] materials = null;
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
materials = materialHelper.getMaterials(structure, blenderContext);
}
// creating the result meshes // creating the result meshes
geometries = new ArrayList<Geometry>(meshesMap.size()); geometries = new ArrayList<Geometry>(meshBuilder.getMeshesPartAmount());
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
verticesBuffer.setupData(Usage.Static, 3, Format.Float,
BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[vertexList.size()])));
// initial vertex position (used with animation)
VertexBuffer verticesBind = new VertexBuffer(Type.BindPosePosition);
verticesBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(verticesBuffer.getData()));
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals));
// initial normals position (used with animation)
VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);
normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData()));
//reading custom properties //reading custom properties
Properties properties = this.loadProperties(structure, blenderContext); Properties properties = this.loadProperties(structure, blenderContext);
// generating meshes // generating meshes
//FloatBuffer verticesColorsBuffer = this.createFloatBuffer(verticesColors);
ByteBuffer verticesColorsBuffer = this.createByteBuffer(verticesColors); ByteBuffer verticesColorsBuffer = this.createByteBuffer(verticesColors);
verticesAmount = vertexList.size(); for (Entry<Integer, List<Integer>> meshEntry : meshBuilder.getMeshesMap().entrySet()) {
Map<Mesh, Integer> meshToMAterialMap = new HashMap<Mesh, Integer>(meshesMap.size()); int materialIndex = meshEntry.getKey();
for (Entry<Integer, List<Integer>> meshEntry : meshesMap.entrySet()) {
//key is the material index (or -1 if the material has no texture) //key is the material index (or -1 if the material has no texture)
//value is a list of vertex indices //value is a list of vertex indices
Mesh mesh = new Mesh(); Mesh mesh = new Mesh();
meshToMAterialMap.put(mesh, meshEntry.getKey());
// creating vertices indices for this mesh // creating vertices indices for this mesh
List<Integer> indexList = meshEntry.getValue(); List<Integer> indexList = meshEntry.getValue();
if(verticesAmount <= Short.MAX_VALUE) { if(meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) {
short[] indices = new short[indexList.size()]; short[] indices = new short[indexList.size()];
for (int i = 0; i < indexList.size(); ++i) { for (int i = 0; i < indexList.size(); ++i) {
indices[i] = indexList.get(i).shortValue(); indices[i] = indexList.get(i).shortValue();
@ -321,6 +246,20 @@ public class MeshHelper extends AbstractBlenderHelper {
mesh.setBuffer(Type.Index, 1, indices); mesh.setBuffer(Type.Index, 1, indices);
} }
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getVertices(materialIndex)));
// initial vertex position (used with animation)
VertexBuffer verticesBind = new VertexBuffer(Type.BindPosePosition);
verticesBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(verticesBuffer.getData()));
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getNormals(materialIndex)));
// initial normals position (used with animation)
VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal);
normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.clone(normalsBuffer.getData()));
mesh.setBuffer(verticesBuffer); mesh.setBuffer(verticesBuffer);
meshContext.setBindPoseBuffer(verticesBind);//this is stored in the context and applied when needed (when animation is applied to the mesh) meshContext.setBindPoseBuffer(verticesBind);//this is stored in the context and applied when needed (when animation is applied to the mesh)
@ -340,6 +279,7 @@ public class MeshHelper extends AbstractBlenderHelper {
geometry.setUserData("properties", properties); geometry.setUserData("properties", properties);
} }
geometries.add(geometry); geometries.add(geometry);
meshContext.putGeometry(materialIndex, geometry);
} }
//store the data in blender context before applying the material //store the data in blender context before applying the material
@ -349,20 +289,30 @@ public class MeshHelper extends AbstractBlenderHelper {
//apply materials only when all geometries are in place //apply materials only when all geometries are in place
if(materials != null) { if(materials != null) {
for(Geometry geometry : geometries) { for(Geometry geometry : geometries) {
int materialNumber = meshToMAterialMap.get(geometry.getMesh()).intValue(); int materialNumber = meshContext.getMaterialIndex(geometry);
List<Vector2f> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
boolean noTextures = false; boolean noTextures = false;
if(materialNumber < 0) { if(materialNumber < 0) {
materialNumber = -1 * (materialNumber + 1); materialNumber = -1 * (materialNumber + 1);
noTextures = true; noTextures = true;
} }
MaterialContext materialContext = materials[materialNumber]; if(materials[materialNumber] != null) {
materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), noTextures, uvCoordinates.get(Integer.valueOf(materialNumber)), blenderContext); MaterialContext materialContext = materials[materialNumber];
materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), noTextures, uvCoordinates, blenderContext);
} else {
geometry.setMaterial(blenderContext.getDefaultMaterial());
if(uvCoordinates != null) {
VertexBuffer uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvCoordinates.toArray(new Vector2f[uvCoordinates.size()])));
geometry.getMesh().setBuffer(uvCoordsBuffer);
}
}
} }
} else { } else {
//add UV coordinates if they are defined even if the material is not applied to the model //add UV coordinates if they are defined even if the material is not applied to the model
VertexBuffer uvCoordsBuffer = null; VertexBuffer uvCoordsBuffer = null;
if(uvCoordinates.size() > 0) { if(meshBuilder.hasUVCoordinates()) {
List<Vector2f> uvs = uvCoordinates.get(0); List<Vector2f> uvs = meshBuilder.getUVCoordinates(-1);
uvCoordsBuffer = new VertexBuffer(Type.TexCoord); uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
} }
@ -375,62 +325,23 @@ public class MeshHelper extends AbstractBlenderHelper {
} }
} }
// if there are multiple materials used, extract the shared
// vertex data
if (geometries.size() > 1){
// extract from itself
for (Geometry geom : geometries){
geom.getMesh().extractVertexData(geom.getMesh());
}
}
return geometries; return geometries;
} }
/** /**
* This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth. * @return <b>true</b> if the material has at least one generated component and <b>false</b> otherwise
* */
* @param normalToAdd private boolean areGeneratedTexturesPresent(MaterialContext[] materials) {
* a normal to be added if(materials != null) {
* @param normalMap for(MaterialContext material : materials) {
* merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector if(material != null && material.hasGeneratedTextures()) {
* @param smooth return true;
* the variable that indicates wheather to merge normals (creating the smooth mesh) or not }
* @param vertices }
* a list of vertices read from the blender file }
*/ return false;
public void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {
for (Vector3f v : vertices) {
Vector3f n = normalMap.get(v);
if (!smooth || n == null) {
normalMap.put(v, normalToAdd.clone());
} else {
n.addLocal(normalToAdd).normalizeLocal();
}
}
}
/**
* 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
*/
protected 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));
} }
/** /**
* This method returns the vertices colors. Each vertex is stored in byte[4] array. * This method returns the vertices colors. Each vertex is stored in byte[4] array.
* *

@ -53,19 +53,14 @@ import java.util.logging.Logger;
// If you decide to remove this limitation, remove this code. // If you decide to remove this limitation, remove this code.
// Rémy // Rémy
private Skeleton skeleton;
private Structure objectStructure;
private Structure meshStructure;
/** Loaded animation data. */ /** Loaded animation data. */
private AnimData animData; private AnimData animData;
/** Old memory address of the mesh that will have the skeleton applied. */ /** Old memory address of the mesh that will have the skeleton applied. */
private Long meshOMA; private Long meshOMA;
/**
* The maxiumum amount of bone groups applied to a single vertex (max =
* MAXIMUM_WEIGHTS_PER_VERTEX).
*/
private int boneGroups;
/** The weights of vertices. */
private VertexBuffer verticesWeights;
/** The indexes of bones applied to vertices. */
private VertexBuffer verticesWeightsIndices;
/** /**
* This constructor reads animation data from the object structore. The * This constructor reads animation data from the object structore. The
@ -83,9 +78,7 @@ import java.util.logging.Logger;
*/ */
public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
// =
// DeformVERTices
// if pDvert==null then there are not vertex groups and no need to load // if pDvert==null then there are not vertex groups and no need to load
// skeleton (untill bone envelopes are supported) // skeleton (untill bone envelopes are supported)
@ -120,17 +113,17 @@ import java.util.logging.Logger;
} }
bonesList.add(0, new Bone("")); bonesList.add(0, new Bone(""));
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
Skeleton skeleton = new Skeleton(bones); skeleton = new Skeleton(bones);
this.objectStructure = objectStructure;
this.meshStructure = meshStructure;
// read mesh indexes // read mesh indexes
this.meshOMA = meshStructure.getOldMemoryAddress(); this.meshOMA = meshStructure.getOldMemoryAddress();
this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, blenderContext);
// read animations // read animations
ArrayList<Animation> animations = new ArrayList<Animation>(); ArrayList<Animation> animations = new ArrayList<Animation>();
List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
if (actionHeaders != null) {// it may happen that the model has if (actionHeaders != null) {// it may happen that the model has armature with no actions
// armature with no actions
for (FileBlockHeader header : actionHeaders) { for (FileBlockHeader header : actionHeaders) {
Structure actionStructure = header.getStructure(blenderContext); Structure actionStructure = header.getStructure(blenderContext);
String actionName = actionStructure.getName(); String actionName = actionStructure.getName();
@ -178,22 +171,32 @@ import java.util.logging.Logger;
// setting weights for bones // setting weights for bones
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE); List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
MeshContext meshContext = blenderContext.getMeshContext(meshOMA); MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
int[] bonesGroups = new int[] { 0 };
for (Geometry geom : geomList) { for (Geometry geom : geomList) {
int materialIndex = meshContext.getMaterialIndex(geom);
Mesh mesh = geom.getMesh(); Mesh mesh = geom.getMesh();
if(meshContext.getBindNormalBuffer() != null) {
mesh.setBuffer(meshContext.getBindNormalBuffer());
}
if(meshContext.getBindPoseBuffer() != null) {
mesh.setBuffer(meshContext.getBindPoseBuffer());
}
//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);
if (this.verticesWeights != null) { try {
mesh.setMaxNumWeights(this.boneGroups); VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext);
mesh.setBuffer(this.verticesWeights); if (buffers != null) {
mesh.setBuffer(this.verticesWeightsIndices); mesh.setMaxNumWeights(bonesGroups[0]);
mesh.setBuffer(buffers[0]);
mesh.setBuffer(buffers[1]);
if(meshContext.getBindNormalBuffer() != null) {
mesh.setBuffer(meshContext.getBindNormalBuffer());
}
if(meshContext.getBindPoseBuffer() != null) {
mesh.setBuffer(meshContext.getBindPoseBuffer());
}
//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);
}
} catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
this.invalid = true;
return node;
} }
} }
@ -239,18 +242,16 @@ import java.util.logging.Logger;
* this exception is thrown when the blend file structure is * this exception is thrown when the blend file structure is
* somehow invalid or corrupted * somehow invalid or corrupted
*/ */
private void readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex,
int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException {
ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);
Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); Structure defBase = (Structure) objectStructure.getFieldValue("defbase");
Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext); Map<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext);
int[] bonesGroups = new int[] { 0 };
MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress()); MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress());
VertexBuffer[] boneWeightsAndIndex = this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexList().size(), bonesGroups, meshContext.getVertexReferenceMap(), groupToBoneIndexMap, blenderContext); return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex),
this.verticesWeights = boneWeightsAndIndex[0]; bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap, blenderContext);
this.verticesWeightsIndices = boneWeightsAndIndex[1];
this.boneGroups = bonesGroups[0];
} }
/** /**
@ -284,49 +285,52 @@ import java.util.logging.Logger;
*/ */
private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext) private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext)
throws BlenderFileException { throws BlenderFileException {
bonesGroups[0] = 0;
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
if (pDvert.isNotNull()) {// assigning weights and bone indices if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender)
// vertex in blender)
int vertexIndex = 0; int vertexIndex = 0;
for (Structure dvert : dverts) { for (Structure dvert : dverts) {
int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex
// (max. 4 in JME)
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
if (totweight > 0 && pDW.isNotNull() && groupToBoneIndexMap!=null) {// pDW should never be null here, but I check it just in case :) if(vertexIndices != null) {
int weightIndex = 0; int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex (max. 4 in JME)
List<Structure> dw = pDW.fetchData(blenderContext.getInputStream()); Pointer pDW = (Pointer) dvert.getFieldValue("dw");
for (Structure deformWeight : dw) { if (totweight > 0 && pDW.isNotNull() && groupToBoneIndexMap!=null) {// pDW should never be null here, but I check it just in case :)
Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); int weightIndex = 0;
List<Structure> dw = pDW.fetchData(blenderContext.getInputStream());
// Remove this code if 4 weights limitation is removed for (Structure deformWeight : dw) {
if (weightIndex == 4) { Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
LOGGER.log(Level.WARNING, "{0} has more than 4 weight on bone index {1}", new Object[] { meshStructure.getName(), boneIndex });
break; // Remove this code if 4 weights limitation is removed
} if (weightIndex == 4) {
LOGGER.log(Level.WARNING, "{0} has more than 4 weight on bone index {1}", new Object[] { meshStructure.getName(), boneIndex });
// null here means that we came accross group that has no bone attached to break;
if (boneIndex != null) {
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
if (weight == 0.0f) {
weight = 1;
boneIndex = Integer.valueOf(0);
} }
// we apply the weight to all referenced vertices
for (Integer index : vertexIndices) { // null here means that we came accross group that has no bone attached to
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); if (boneIndex != null) {
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
if (weight == 0.0f) {
weight = 1;
boneIndex = Integer.valueOf(0);
}
// we apply the weight to all referenced vertices
for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
}
} }
++weightIndex;
}
bonesGroups[0] = Math.max(bonesGroups[0], weightIndex);
} else {
for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
} }
++weightIndex;
}
} else {
for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
} }
} }
++vertexIndex; ++vertexIndex;
@ -334,10 +338,8 @@ import java.util.logging.Logger;
} else { } else {
// always bind all vertices to 0-indexed bone // always bind all vertices to 0-indexed bone
// this bone makes the model look normally if vertices have no bone // this bone makes the model look normally if vertices have no bone
// assigned // assigned and it is used in object animation, so if we come accross object
// and it is used in object animation, so if we come accross object // animation we can use the 0-indexed bone for this
// animation
// we can use the 0-indexed bone for this
for (List<Integer> vertexIndexList : vertexReferenceMap.values()) { for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
// we apply the weight to all referenced vertices // we apply the weight to all referenced vertices
for (Integer index : vertexIndexList) { for (Integer index : vertexIndexList) {
@ -347,7 +349,9 @@ import java.util.logging.Logger;
} }
} }
bonesGroups[0] = this.endBoneAssigns(vertexListSize, weightsFloatData); bonesGroups[0] = Math.max(bonesGroups[0], 1);
this.endBoneAssigns(vertexListSize, weightsFloatData);
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData);
@ -365,22 +369,10 @@ import java.util.logging.Logger;
* @param weightsFloatData * @param weightsFloatData
* weights for vertices * weights for vertices
*/ */
private int endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) {
int maxWeightsPerVert = 0;
weightsFloatData.rewind(); weightsFloatData.rewind();
for (int v = 0; v < vertCount; ++v) { for (int v = 0; v < vertCount; ++v) {
float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get(); float w0 = weightsFloatData.get(), w1 = weightsFloatData.get(), w2 = weightsFloatData.get(), w3 = weightsFloatData.get();
if (w3 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
} else if (w2 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
} else if (w1 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
} else if (w0 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
}
float sum = w0 + w1 + w2 + w3; float sum = w0 + w1 + w2 + w3;
if (sum != 1f && sum != 0.0f) { if (sum != 1f && sum != 0.0f) {
weightsFloatData.position(weightsFloatData.position() - 4); weightsFloatData.position(weightsFloatData.position() - 4);
@ -393,7 +385,6 @@ import java.util.logging.Logger;
} }
} }
weightsFloatData.rewind(); weightsFloatData.rewind();
return maxWeightsPerVert;
} }
@Override @Override

@ -140,8 +140,7 @@ public class ModifierHelper extends AbstractBlenderHelper {
if (pAction.isNotNull()) { if (pAction.isNotNull()) {
Structure action = pAction.fetchData(blenderContext.getInputStream()).get(0); Structure action = pAction.fetchData(blenderContext.getInputStream()).get(0);
List<Structure> actionChannels = ((Structure) action.getFieldValue("chanbase")).evaluateListBase(blenderContext); List<Structure> actionChannels = ((Structure) action.getFieldValue("chanbase")).evaluateListBase(blenderContext);
if (actionChannels.size() == 1) {// object's animtion action has if (actionChannels.size() == 1) {// object's animtion action has only one channel
// only one channel
Pointer pChannelIpo = (Pointer) actionChannels.get(0).getFieldValue("ipo"); Pointer pChannelIpo = (Pointer) actionChannels.get(0).getFieldValue("ipo");
Structure ipoStructure = pChannelIpo.fetchData(blenderContext.getInputStream()).get(0); Structure ipoStructure = pChannelIpo.fetchData(blenderContext.getInputStream()).get(0);
Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext);

@ -215,6 +215,20 @@ public class CombinedTexture {
return textureDatas.size(); return textureDatas.size();
} }
/**
* @return <b>true</b> if the texture has at least one generated texture component and <b>false</b> otherwise
*/
public boolean hasGeneratedTextures() {
if(textureDatas != null) {
for(TextureData textureData : textureDatas) {
if(textureData.texture instanceof GeneratedTexture) {
return true;
}
}
}
return false;
}
/** /**
* This method merges two given textures. The result is stored in the * This method merges two given textures. The result is stored in the
* 'target' texture. * 'target' texture.

@ -107,8 +107,10 @@ import java.util.TreeSet;
return o1.faceIndex - o2.faceIndex; return o1.faceIndex - o2.faceIndex;
} }
}); });
int[] indices = new int[3];
for (int i = 0; i < mesh.getTriangleCount(); ++i) { for (int i = 0; i < mesh.getTriangleCount(); ++i) {
triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, blenderContext)); mesh.getTriangle(i, indices);
triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, indices, blenderContext));
} }
return new TriangulatedTexture(triangleTextureElements, blenderContext); return new TriangulatedTexture(triangleTextureElements, blenderContext);
} }

@ -504,7 +504,7 @@ import jme3tools.converters.ImageToAwt;
* @param blenderContext * @param blenderContext
* the blender context * the blender context
*/ */
public TriangleTextureElement(int faceIndex, BoundingBox boundingBox, GeneratedTexture texture, Vector3f[] uv, BlenderContext blenderContext) { public TriangleTextureElement(int faceIndex, BoundingBox boundingBox, GeneratedTexture texture, Vector3f[] uv, int[] uvIndices, BlenderContext blenderContext) {
this.faceIndex = faceIndex; this.faceIndex = faceIndex;
// compute the face vertices from the UV coordinates // compute the face vertices from the UV coordinates
@ -512,11 +512,10 @@ import jme3tools.converters.ImageToAwt;
float height = boundingBox.getYExtent() * 2; float height = boundingBox.getYExtent() * 2;
float depth = boundingBox.getZExtent() * 2; float depth = boundingBox.getZExtent() * 2;
int uvIndex = faceIndex * 3;
Vector3f min = boundingBox.getMin(null); Vector3f min = boundingBox.getMin(null);
Vector3f v1 = min.add(uv[uvIndex].x * width, uv[uvIndex].y * height, uv[uvIndex].z * depth); Vector3f v1 = min.add(uv[uvIndices[0]].x * width, uv[uvIndices[0]].y * height, uv[uvIndices[0]].z * depth);
Vector3f v2 = min.add(uv[uvIndex + 1].x * width, uv[uvIndex + 1].y * height, uv[uvIndex + 1].z * depth); Vector3f v2 = min.add(uv[uvIndices[1]].x * width, uv[uvIndices[1]].y * height, uv[uvIndices[1]].z * depth);
Vector3f v3 = min.add(uv[uvIndex + 2].x * width, uv[uvIndex + 2].y * height, uv[uvIndex + 2].z * depth); Vector3f v3 = min.add(uv[uvIndices[2]].x * width, uv[uvIndices[2]].y * height, uv[uvIndices[2]].z * depth);
// get the rectangle envelope for the triangle // get the rectangle envelope for the triangle
RectangleEnvelope envelope = this.getTriangleEnvelope(v1, v2, v3); RectangleEnvelope envelope = this.getTriangleEnvelope(v1, v2, v3);

Loading…
Cancel
Save