Feature: new triangulation modifier.

experimental
jmekaelthas 11 years ago
parent 124b5e51da
commit 6e21b0527c
  1. 24
      jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java
  2. 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
  3. 70
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
  4. 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java
  5. 7
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java
  6. 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java
  7. 10
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
  8. 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java
  9. 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java
  10. 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java
  11. 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java
  12. 6
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java
  13. 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  14. 8
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
  15. 222
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  16. 533
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  17. 258
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java
  18. 364
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java
  19. 185
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java
  20. 328
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  21. 88
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java
  22. 599
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java
  23. 586
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java
  24. 160
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java
  25. 161
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java
  26. 171
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java
  27. 336
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  28. 229
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java
  29. 266
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java
  30. 22
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java
  31. 81
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java
  32. 54
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java
  33. 119
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
  34. 17
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  35. 7
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java
  36. 12
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  37. 132
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java

@ -124,6 +124,8 @@ public class BlenderKey extends ModelKey {
protected boolean optimiseTextures;
/** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */
protected AnimationMatchMethod animationMatchMethod = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH;
/** The size of points that are loaded and do not belong to any edge of the mesh. */
protected float pointsSize = 1;
/**
* Constructor used by serialization mechanisms.
@ -457,6 +459,22 @@ public class BlenderKey extends ModelKey {
return animationMatchMethod;
}
/**
* @return the size of points that are loaded and do not belong to any edge of the mesh
*/
public float getPointsSize() {
return pointsSize;
}
/**
* Sets the size of points that are loaded and do not belong to any edge of the mesh.
* @param pointsSize
* The size of points that are loaded and do not belong to any edge of the mesh
*/
public void setPointsSize(float pointsSize) {
this.pointsSize = pointsSize;
}
/**
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
@ -513,6 +531,7 @@ public class BlenderKey extends ModelKey {
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
oc.write(optimiseTextures, "optimise-textures", false);
oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
oc.write(pointsSize, "points-size", 1);
}
@Override
@ -535,6 +554,7 @@ public class BlenderKey extends ModelKey {
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
optimiseTextures = ic.readBoolean("optimise-textures", false);
animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
pointsSize = ic.readFloat("points-size", 1);
}
@Override
@ -560,6 +580,7 @@ public class BlenderKey extends ModelKey {
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
result = prime * result + skyGeneratedTextureSize;
result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
result = prime * result + (int)pointsSize;
return result;
}
@ -638,6 +659,9 @@ public class BlenderKey extends ModelKey {
} else if (!usedWorld.equals(other.usedWorld)) {
return false;
}
if (pointsSize != other.pointsSize) {
return false;
}
return true;
}

@ -110,7 +110,7 @@ public abstract class AbstractBlenderHelper {
* @param properties
* the properties to be applied
*/
protected void applyProperties(Spatial spatial, Properties properties) {
public void applyProperties(Spatial spatial, Properties properties) {
List<String> propertyNames = properties.getSubPropertiesNames();
if (propertyNames != null && propertyNames.size() > 0) {
for (String propertyName : propertyNames) {

@ -54,7 +54,6 @@ import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.DnaBlockData;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.MeshContext;
/**
* The class that stores temporary data and manages it during loading the belnd
@ -89,14 +88,7 @@ public class BlenderContext {
* first object in the value table is the loaded structure and the second -
* the structure already converted into proper data.
*/
private Map<Long, Object[]> loadedFeatures = new HashMap<Long, Object[]>();
/**
* This map stores the loaded features by their name. Only features with ID
* structure can be stored here. The first object in the value table is the
* loaded structure and the second - the structure already converted into
* proper data.
*/
private Map<String, Object[]> loadedFeaturesByName = new HashMap<String, Object[]>();
private Map<Long, Map<LoadedDataType, Object>> loadedFeatures = new HashMap<Long, Map<LoadedDataType, Object>>();
/** A stack that hold the parent structure of currently loaded feature. */
private Stack<Structure> parentStack = new Stack<Structure>();
/** A list of constraints for the specified object. */
@ -107,8 +99,6 @@ public class BlenderContext {
private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>();
/** A map between skeleton and node it modifies. */
private Map<Skeleton, Node> nodesWithSkeletons = new HashMap<Skeleton, Node>();
/** A map of mesh contexts. */
protected Map<Long, MeshContext> meshContexts = new HashMap<Long, MeshContext>();
/** A map of bone contexts. */
protected Map<Long, BoneContext> boneContexts = new HashMap<Long, BoneContext>();
/** A map og helpers that perform loading. */
@ -304,15 +294,16 @@ public class BlenderContext {
* @param feature
* the feature we want to store
*/
public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) {
if (oldMemoryAddress == null || structure == null || feature == null) {
public void addLoadedFeatures(Long oldMemoryAddress, LoadedDataType featureDataType, Object feature) {
if (oldMemoryAddress == null || featureDataType == null || feature == null) {
throw new IllegalArgumentException("One of the given arguments is null!");
}
Object[] storedData = new Object[] { structure, feature };
loadedFeatures.put(oldMemoryAddress, storedData);
if (featureName != null) {
loadedFeaturesByName.put(featureName, storedData);
Map<LoadedDataType, Object> map = loadedFeatures.get(oldMemoryAddress);
if(map == null) {
map = new HashMap<BlenderContext.LoadedDataType, Object>();
loadedFeatures.put(oldMemoryAddress, map);
}
map.put(featureDataType, feature);
}
/**
@ -326,10 +317,10 @@ public class BlenderContext {
* structure or already converted feature
* @return loaded feature or null if it was not yet loaded
*/
public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) {
Object[] result = loadedFeatures.get(oldMemoryAddress);
public Object getLoadedFeature(Long oldMemoryAddress, LoadedDataType loadedFeatureDataType) {
Map<LoadedDataType, Object> result = loadedFeatures.get(oldMemoryAddress);
if (result != null) {
return result[loadedFeatureDataType.getIndex()];
return result.get(loadedFeatureDataType);
}
return null;
}
@ -484,31 +475,6 @@ public class BlenderContext {
return skeletons.get(skeletonOMA);
}
/**
* This method sets the mesh context for the given mesh old memory address.
* If the context is already set it will be replaced.
*
* @param meshOMA
* the mesh's old memory address
* @param meshContext
* the mesh's context
*/
public void setMeshContext(Long meshOMA, MeshContext meshContext) {
meshContexts.put(meshOMA, meshContext);
}
/**
* This method returns the mesh context for the given mesh old memory
* address. If no context exists then <b>null</b> is returned.
*
* @param meshOMA
* the mesh's old memory address
* @return mesh's context
*/
public MeshContext getMeshContext(Long meshOMA) {
return meshContexts.get(meshOMA);
}
/**
* This method sets the bone context for the given bone old memory address.
* If the context is already set it will be replaced.
@ -645,17 +611,7 @@ public class BlenderContext {
*
* @author Marcin Roguski (Kaelthas)
*/
public static enum LoadedFeatureDataType {
LOADED_STRUCTURE(0), LOADED_FEATURE(1);
private int index;
private LoadedFeatureDataType(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
public static enum LoadedDataType {
STRUCTURE, FEATURE, TEMPORAL_MESH;
}
}

@ -19,6 +19,7 @@ import com.jme3.asset.BlenderKey.AnimationMatchMethod;
import com.jme3.scene.Node;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo;
import com.jme3.scene.plugins.blender.curves.BezierCurve;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
@ -166,7 +167,9 @@ public class AnimationHelper extends AbstractBlenderHelper {
}
curves.clear();
result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result);
Long ipoOma = ipoStructure.getOldMemoryAddress();
blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.STRUCTURE, ipoStructure);
blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.FEATURE, result);
}
return result;
}

@ -10,7 +10,7 @@ import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
@ -144,10 +144,11 @@ public class BoneContext {
Long boneOMA = boneStructure.getOldMemoryAddress();
bone = new Bone(boneName);
bones.add(bone);
blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.STRUCTURE, boneStructure);
blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.FEATURE, bone);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedFeatureDataType.LOADED_STRUCTURE);
Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedDataType.STRUCTURE);
// I could load 'imat' here, but apparently in some older blenders there were bugs or unfinished functionalities that stored ZERO matrix in imat field
// loading 'obmat' and inverting it makes us avoid errors in such cases
Matrix4f invertedObjectOwnerGlobalMatrix = objectHelper.getMatrix(skeletonOwnerObjectStructure, "obmat", blenderContext.getBlenderKey().isFixUpAxis()).invertLocal();

@ -5,7 +5,7 @@ import java.util.logging.Logger;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
@ -41,7 +41,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
@Override
public boolean validate() {
if (targetOMA != null) {
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
if (nodeTarget == null) {
LOGGER.log(Level.WARNING, "Cannot find target for constraint: {0}.", name);
return false;

@ -15,7 +15,7 @@ import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
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.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
@ -185,7 +185,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
simulationRootNodes.add(new SimulationNode(boneContext.getArmatureObjectOMA(), blenderContext));
} else if (constraint instanceof SpatialConstraint) {
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedDataType.FEATURE);
while (spatial.getParent() != null) {
spatial = spatial.getParent();
}
@ -213,7 +213,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
* @return thensform of a feature in a given space
*/
public Transform getTransform(Long oma, String subtargetName, Space space) {
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
if (isArmature) {
blenderContext.getSkeleton(oma).updateWorldVectors();
@ -228,7 +228,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
Transform result;
switch (space) {
case CONSTRAINT_SPACE_WORLD:
Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedFeatureDataType.LOADED_FEATURE);
Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedDataType.FEATURE);
Matrix4f boneModelMatrix = this.toMatrix(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale(), tempVars.tempMat4);
Matrix4f modelWorldMatrix = this.toMatrix(model.getWorldTransform(), tempVars.tempMat42);
Matrix4f boneMatrixInWorldSpace = modelWorldMatrix.multLocal(boneModelMatrix);
@ -295,7 +295,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
* the transform we apply
*/
public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
if (isArmature) {
Skeleton skeleton = blenderContext.getSkeleton(oma);

@ -24,7 +24,7 @@ import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.util.TempVars;
@ -93,7 +93,7 @@ public class SimulationNode {
*/
private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
this.blenderContext = blenderContext;
Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE);
Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedDataType.FEATURE);
if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, spatial) != null) {
skeleton = blenderContext.getSkeleton(featureOMA);

@ -1,7 +1,7 @@
package com.jme3.scene.plugins.blender.constraints;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
@ -20,7 +20,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
@Override
public boolean validate() {
if (targetOMA != null) {
return blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) != null;
return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null;
}
return true;
}

@ -5,7 +5,7 @@ import java.util.Set;
import com.jme3.animation.Bone;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
@ -61,7 +61,7 @@ public abstract class ConstraintDefinition {
*/
protected Object getOwner() {
if (ownerOMA != null && owner == null) {
owner = blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
owner = blenderContext.getLoadedFeature(ownerOMA, LoadedDataType.FEATURE);
if (owner == null) {
throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName());
}

@ -5,7 +5,7 @@ import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
@ -58,7 +58,7 @@ public class ConstraintDefinitionTransLike extends ConstraintDefinition {
* @return the target feature; it is either Node or Bone (vertex group subtarger is not yet supported)
*/
private Object getTarget() {
Object target = blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
Object target = blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
if (subtargetName != null && blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, target) != null) {
Skeleton skeleton = blenderContext.getSkeleton(targetOMA);
target = skeleton.getBone(subtargetName);

@ -43,7 +43,7 @@ import com.jme3.math.FastMath;
import com.jme3.scene.LightNode;
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.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
@ -68,7 +68,7 @@ public class LightHelper extends AbstractBlenderHelper {
}
public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
if (result != null) {
return result;
}
@ -111,7 +111,7 @@ public class LightHelper extends AbstractBlenderHelper {
float g = ((Number) structure.getFieldValue("g")).floatValue();
float b = ((Number) structure.getFieldValue("b")).floatValue();
light.setColor(new ColorRGBA(r, g, b, 1.0f));
result = new LightNode(null, light);
result = new LightNode(structure.getName(), light);
}
return result;
}

@ -1,7 +1,7 @@
package com.jme3.scene.plugins.blender.materials;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -119,7 +119,7 @@ public final class MaterialContext {
* @param blenderContext
* the blender context
*/
public void applyMaterial(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
public void applyMaterial(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
Material material = null;
if (shadeless) {
material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");

@ -44,7 +44,7 @@ import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
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.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
@ -162,14 +162,16 @@ public class MaterialHelper extends AbstractBlenderHelper {
*/
public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading material.");
MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
if (result != null) {
return result;
}
result = new MaterialContext(structure, blenderContext);
LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);
Long oma = structure.getOldMemoryAddress();
blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, structure);
blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result);
return result;
}

@ -0,0 +1,222 @@
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.FastMath;
import com.jme3.math.Line;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* A class that represents a single edge between two vertices.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class Edge extends Line {
private static final long serialVersionUID = 7172714692126675311L;
private static final Logger LOGGER = Logger.getLogger(Edge.class.getName());
/** The vertices indexes. */
private int index1, index2;
public Edge() {
}
/**
* This constructor only stores the indexes of the vertices. The position vertices should be stored
* outside this class.
* @param index1
* the first index of the edge
* @param index2
* the second index of the edge
*/
private Edge(int index1, int index2) {
this.index1 = index1;
this.index2 = index2;
}
/**
* This constructor stores both indexes and vertices list. The list should contain ALL verts and not
* only those belonging to the edge.
* @param index1
* the first index of the edge
* @param index2
* the second index of the edge
* @param vertices
* the vertices of the mesh
*/
public Edge(int index1, int index2, List<Vector3f> vertices) {
this(index1, index2);
this.set(vertices.get(index1), vertices.get(index2));
}
@Override
public Edge clone() {
Edge result = new Edge(index1, index2);
result.setOrigin(this.getOrigin());
result.setDirection(this.getDirection());
return result;
}
/**
* @return the first index of the edge
*/
public int getFirstIndex() {
return index1;
}
/**
* @return the second index of the edge
*/
public int getSecondIndex() {
return index2;
}
/**
* Shifts indexes by a given amount.
* @param shift
* how much the indexes should be shifted
*/
public void shiftIndexes(int shift) {
index1 += shift;
index2 += shift;
}
/**
* Flips the order of the indexes.
*/
public void flipIndexes() {
int temp = index1;
index1 = index2;
index2 = temp;
}
/**
* The method sets the vertices for the first and second index.
* @param v1
* the first vertex
* @param v2
* the second vertex
*/
public void set(Vector3f v1, Vector3f v2) {
this.setOrigin(v1);
this.setDirection(v2.subtract(v1));
}
/**
* The crossing method first computes the points on both lines (that contain the edges)
* who are closest in distance. If the distance between points is smaller than FastMath.FLT_EPSILON
* the we consider them to be the same point (the lines cross).
* The second step is to check if both points are contained within the edges.
*
* The method of computing the crossing point is as follows:
* Let's assume that:
* (P0, P1) are the points of the first edge
* (Q0, Q1) are the points of the second edge
*
* u = P1 - P0
* v = Q1 - Q0
*
* This gives us the equations of two lines:
* L1: (x = P1x + ux*t1; y = P1y + uy*t1; z = P1z + uz*t1)
* L2: (x = P2x + vx*t2; y = P2y + vy*t2; z = P2z + vz*t2)
*
* Comparing the x and y of the first two equations for each line will allow us to compute t1 and t2
* (which is implemented below).
* Using t1 and t2 we can compute (x, y, z) of each line and that will give us two points that we need to compare.
*
* @param edge
* the edge we check against crossing
* @return <b>true</b> if the edges cross and false otherwise
*/
public boolean cross(Edge edge) {
Vector3f P1 = this.getOrigin(), P2 = edge.getOrigin();
Vector3f u = this.getDirection();
Vector3f v = edge.getDirection();
float t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y);
float t1 = (P2.x - P1.x + v.x * t2) / u.x;
Vector3f p1 = P1.add(u.mult(t1));
Vector3f p2 = P2.add(v.mult(t2));
if (p1.distance(p2) <= FastMath.FLT_EPSILON) {
// the lines cross, check if p1 and p2 are within the edges
Vector3f p = p1.subtract(P1);
float cos = p.dot(u) / (p.length() * u.length());
if (cos > 0 && p.length() <= u.length()) {
// p1 is inside the first edge, lets check the other edge now
p = p2.subtract(P2);
cos = p.dot(v) / (p.length() * v.length());
return cos > 0 && p.length() <= u.length();
}
}
return false;
}
@Override
public String toString() {
String result = "Edge [" + index1 + ", " + index2 + "]";
if (this.getOrigin() != null && this.getDirection() != null) {
result += " -> {" + this.getOrigin() + ", " + this.getOrigin().add(this.getDirection()) + "}";
}
return result;
}
@Override
public int hashCode() {
// The hash code must be identical for the same two indexes, no matter their order.
final int prime = 31;
int result = 1;
int lowerIndex = Math.min(index1, index2);
int higherIndex = Math.max(index1, index2);
result = prime * result + lowerIndex;
result = prime * result + higherIndex;
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Edge)) {
return false;
}
if (this == obj) {
return true;
}
Edge other = (Edge) obj;
return Math.min(index1, index2) == Math.min(other.index1, other.index2) && Math.max(index1, index2) == Math.max(other.index1, other.index2);
}
/**
* The method loads all edges from the given mesh structure that does not belong to any face.
* @param meshStructure
* the mesh structure
* @return all edges without faces
* @throws BlenderFileException
* an exception is thrown when problems with file reading occur
*/
public static List<Edge> loadAll(Structure meshStructure) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName());
List<Edge> result = new ArrayList<Edge>();
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
if (pMEdge.isNotNull()) {
List<Structure> edges = pMEdge.fetchData();
for (Structure edge : edges) {
int flag = ((Number) edge.getFieldValue("flag")).intValue();
if ((flag & MeshHelper.EDGE_NOT_IN_FACE_FLAG) != 0) {
int v1 = ((Number) edge.getFieldValue("v1")).intValue();
int v2 = ((Number) edge.getFieldValue("v2")).intValue();
result.add(new Edge(v1, v2));
}
}
}
LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size());
return result;
}
}

@ -0,0 +1,533 @@
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* A class that represents a single face in the mesh. The face is a polygon. Its minimum count of
* vertices is = 3.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class Face implements Comparator<Integer> {
private static final Logger LOGGER = Logger.getLogger(Face.class.getName());
/** The indexes loop of the face. */
private IndexesLoop indexes;
/** Indicates if the face is smooth or solid. */
private boolean smooth;
/** The material index of the face. */
private int materialNumber;
/** UV coordinate sets attached to the face. The key is the set name and value are the UV coords. */
private Map<String, List<Vector2f>> faceUVCoords;
/** The vertex colors of the face. */
private List<byte[]> vertexColors;
/** The temporal mesh the face belongs to. */
private TemporalMesh temporalMesh;
/**
* Creates a complete face with all available data.
* @param indexes
* the indexes of the face (required)
* @param smooth
* indicates if the face is smooth or solid
* @param materialNumber
* the material index of the face
* @param faceUVCoords
* UV coordinate sets of the face (optional)
* @param vertexColors
* the vertex colors of the face (optional)
* @param temporalMesh
* the temporal mesh the face belongs to (required)
*/
public Face(Integer[] indexes, boolean smooth, int materialNumber, Map<String, List<Vector2f>> faceUVCoords, List<byte[]> vertexColors, TemporalMesh temporalMesh) {
this.setTemporalMesh(temporalMesh);
this.indexes = new IndexesLoop(indexes);
this.smooth = smooth;
this.materialNumber = materialNumber;
this.faceUVCoords = faceUVCoords;
this.temporalMesh = temporalMesh;
this.vertexColors = vertexColors;
}
/**
* Default constructor. Used by the clone method.
*/
private Face() {
}
@Override
public Face clone() {
Face result = new Face();
result.indexes = indexes.clone();
result.smooth = smooth;
result.materialNumber = materialNumber;
if (faceUVCoords != null) {
result.faceUVCoords = new HashMap<String, List<Vector2f>>(faceUVCoords.size());
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
List<Vector2f> uvs = new ArrayList<Vector2f>(entry.getValue().size());
for (Vector2f v : entry.getValue()) {
uvs.add(v.clone());
}
result.faceUVCoords.put(entry.getKey(), uvs);
}
}
if (vertexColors != null) {
result.vertexColors = new ArrayList<byte[]>(vertexColors.size());
for (byte[] colors : vertexColors) {
result.vertexColors.add(colors.clone());
}
}
result.temporalMesh = temporalMesh;
return result;
}
/**
* Returns the index at the given position in the index loop. If the given position is negative or exceeds
* the amount of vertices - it is being looped properly so that it always hits an index.
* For example getIndex(-1) will return the index before the 0 - in this case it will be the last one.
* @param indexPosition
* the index position
* @return index value at the given position
*/
private Integer getIndex(int indexPosition) {
if (indexPosition >= indexes.size()) {
indexPosition = indexPosition % indexes.size();
} else if (indexPosition < 0) {
indexPosition = indexes.size() - -indexPosition % indexes.size();
}
return indexes.get(indexPosition);
}
/**
* @return all indexes
*/
public List<Integer> getIndexes() {
return indexes.getAll();
}
/**
* The method detaches the triangle from the face. This method keeps the indexes loop normalized - every index
* has only two neighbours. So if detaching the triangle causes a vertex to have more than two neighbours - it is
* also detached and returned as a result.
* The result is an empty list if no such situation happens.
* @param triangleIndexes
* the indexes of a triangle to be detached
* @return a list of faces that need to be detached as well in order to keep them normalized
*/
private List<Face> detachTriangle(Integer[] triangleIndexes) {
LOGGER.fine("Detaching triangle.");
if (triangleIndexes.length != 3) {
throw new IllegalArgumentException("Cannot detach triangle with that does not have 3 indexes!");
}
List<Face> detachedFaces = new ArrayList<Face>();
boolean[] edgeRemoved = new boolean[] { indexes.removeEdge(triangleIndexes[0], triangleIndexes[1]), indexes.removeEdge(triangleIndexes[0], triangleIndexes[2]), indexes.removeEdge(triangleIndexes[1], triangleIndexes[2]) };
Integer[][] indexesPairs = new Integer[][] { new Integer[] { triangleIndexes[0], triangleIndexes[1] }, new Integer[] { triangleIndexes[0], triangleIndexes[2] }, new Integer[] { triangleIndexes[1], triangleIndexes[2] } };
for (int i = 0; i < 3; ++i) {
if (!edgeRemoved[i]) {
List<Integer> path = indexes.findPath(indexesPairs[i][0], indexesPairs[i][1]);
if (path == null) {
path = indexes.findPath(indexesPairs[i][1], indexesPairs[i][0]);
}
if (path == null) {
throw new IllegalStateException("Triangulation failed. Cannot find path between two indexes. Please apply triangulation in Blender as a workaround.");
}
if (detachedFaces.size() == 0 && path.size() < indexes.size()) {
detachedFaces.add(new Face(path.toArray(new Integer[path.size()]), smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh));
for (int j = 0; j < path.size() - 1; ++j) {
indexes.removeEdge(path.get(j), path.get(j + 1));
}
indexes.removeEdge(path.get(path.size() - 1), path.get(0));
} else {
indexes.addEdge(path.get(path.size() - 1), path.get(0));
}
}
}
return detachedFaces;
}
/**
* The method returns the position of the given index in the indexes loop.
* @param index
* the index whose position will be queried
* @return position of the given index or -1 if such index is not in the index loop
*/
private int indexOf(Integer index) {
return indexes.indexOf(index);
}
/**
* The method shifts all indexes by a given value.
* @param shift
* the value to shift all indexes
*/
public void shiftIndexes(int shift) {
indexes.shiftIndexes(shift);
}
/**
* Sets the temporal mesh for the face. The given mesh cannot be null.
* @param temporalMesh
* the temporal mesh of the face
* @throws IllegalArgumentException
* thrown if given temporal mesh is null
*/
public void setTemporalMesh(TemporalMesh temporalMesh) {
if (temporalMesh == null) {
throw new IllegalArgumentException("No temporal mesh for the face given!");
}
this.temporalMesh = temporalMesh;
}
/**
* Flips the order of the indexes.
*/
public void flipIndexes() {
indexes.reverse();
if (faceUVCoords != null) {
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
Collections.reverse(entry.getValue());
}
}
}
/**
* Flips UV coordinates.
* @param u
* indicates if U coords should be flipped
* @param v
* indicates if V coords should be flipped
*/
public void flipUV(boolean u, boolean v) {
if (faceUVCoords != null) {
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
for (Vector2f uv : entry.getValue()) {
uv.set(u ? 1 - uv.x : uv.x, v ? 1 - uv.y : uv.y);
}
}
}
}
/**
* @return the UV sets of the face
*/
public Map<String, List<Vector2f>> getUvSets() {
return faceUVCoords;
}
/**
* @return current vertex count of the face
*/
public int vertexCount() {
return indexes.size();
}
/**
* The method triangulates the face.
* @param vertices
* the vertices of the mesh (all verts and not only those belonging to the face)
* @param normals
* the normals of the mesh (all normals and not only those belonging to the face)
* @return a list of faces that are triangles
*/
public List<Face> triangulate(List<Vector3f> vertices, List<Vector3f> normals) {
LOGGER.fine("Triangulating face.");
assert indexes.size() >= 3 : "Invalid indexes amount for face. 3 is the required minimum!";
List<Face> result = new ArrayList<Face>();
List<Face> facesToTriangulate = new ArrayList<Face>(Arrays.asList(this.clone()));
while (facesToTriangulate.size() > 0) {
Face face = facesToTriangulate.remove(0);
int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
while (face.vertexCount() > 0) {
int index1 = face.getIndex(0);
int index2 = face.findClosestVertex(index1, -1);
int index3 = face.findClosestVertex(index1, index2);
LOGGER.finer("Veryfying improper triangulation of the temporal mesh.");
if(index1 < 0 || index2 < 0 || index3 < 0) {
throw new IllegalStateException("Unable to find two closest vertices while triangulating face in mesh: " + temporalMesh +
"Please apply triangulation modifier in blender as a workaround and load again!");
}
if(previousIndex1 == index1 && previousIndex2 == index2 && previousIndex3 == index3) {
throw new IllegalStateException("Infinite loop detected during triangulation of mesh: " + temporalMesh +
"Please apply triangulation modifier in blender as a workaround and load again!");
}
previousIndex1 = index1;
previousIndex2 = index2;
previousIndex3 = index3;
Integer[] indexes = new Integer[] { index1, index2, index3 };
Arrays.sort(indexes, this);
List<Face> detachedFaces = face.detachTriangle(indexes);
facesToTriangulate.addAll(detachedFaces);
int indexOf0 = this.indexOf(indexes[0]);
int indexOf1 = this.indexOf(indexes[1]);
int indexOf2 = this.indexOf(indexes[2]);
Map<String, List<Vector2f>> faceUVS = new HashMap<String, List<Vector2f>>();
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
List<Vector2f> uvs = new ArrayList<Vector2f>(3);
uvs.add(entry.getValue().get(indexOf0));
uvs.add(entry.getValue().get(indexOf1));
uvs.add(entry.getValue().get(indexOf2));
faceUVS.put(entry.getKey(), uvs);
}
List<byte[]> vertexColors = null;
if (this.vertexColors != null) {
vertexColors = new ArrayList<byte[]>(3);
vertexColors.add(this.vertexColors.get(indexOf0));
vertexColors.add(this.vertexColors.get(indexOf1));
vertexColors.add(this.vertexColors.get(indexOf2));
}
result.add(new Face(indexes, smooth, materialNumber, faceUVS, vertexColors, temporalMesh));
}
}
LOGGER.log(Level.FINE, "Face triangulated on {0} faces.", result.size());
return result;
}
/**
* @return <b>true</b> if the face is smooth and <b>false</b> otherwise
*/
public boolean isSmooth() {
return smooth;
}
/**
* @return the material index of the face
*/
public int getMaterialNumber() {
return materialNumber;
}
/**
* @return the vertices colord of the face
*/
public List<byte[]> getVertexColors() {
return vertexColors;
}
@Override
public String toString() {
return "Face " + indexes;
}
/**
* The method finds the closest vertex to the one specified by <b>index</b>.
* If the vertexToIgnore is positive than it will be ignored in the result.
* The closes vertex must be able to create an edge that is fully contained within the face and does not cross
* any other edges.
* @param index
* the index of the vertex that needs to have found the nearest neighbour
* @param indexToIgnore
* the index to ignore in the result (pass -1 if none is to be ignored)
* @return the index of the closest vertex to the given one
*/
private int findClosestVertex(int index, int indexToIgnore) {
int result = -1;
List<Vector3f> vertices = temporalMesh.getVertices();
Vector3f v1 = vertices.get(index);
float distance = Float.MAX_VALUE;
for (int i : indexes) {
if (i != index && i != indexToIgnore) {
Vector3f v2 = vertices.get(i);
float d = v2.distance(v1);
if (d < distance && this.contains(new Edge(index, i, vertices))) {
result = i;
distance = d;
}
}
}
return result;
}
/**
* The method verifies if the edge is contained within the face.
* It means it cannot cross any other edge and it must be inside the face and not outside of it.
* @param edge
* the edge to be checked
* @return <b>true</b> if the given edge is contained within the face and <b>false</b> otherwise
*/
private boolean contains(Edge edge) {
int index1 = edge.getFirstIndex();
int index2 = edge.getSecondIndex();
// check if the line between the vertices is not a border edge of the face
if (!indexes.areNeighbours(index1, index2)) {
List<Vector3f> vertices = temporalMesh.getVertices();
Edge e2 = new Edge();
for (int i = 0; i < indexes.size(); ++i) {
int i1 = this.getIndex(i);
int i2 = this.getIndex(i + 1);
// check if the edges have no common verts (because if they do, they cannot cross)
if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) {
e2.set(vertices.get(i1), vertices.get(i2));
if (edge.cross(e2)) {
return false;
}
}
}
// the edge does NOT cross any of other edges, so now we need to verify if it is inside the face or outside
// we check it by comparing the angle that is created by vertices: [index1 - 1, index1, index1 + 1]
// with the one creaded by vertices: [index1 - 1, index1, index2]
// if the latter is greater than it means that the edge is outside the face
// IMPORTANT: we assume that all vertices are in one plane (this should be ensured before creating the Face)
int indexOfIndex1 = this.indexOf(index1);
int indexMinus1 = this.getIndex(indexOfIndex1 - 1);// indexOfIndex1 == 0 ? indexes.get(indexes.size() - 1) : indexes.get(indexOfIndex1 - 1);
int indexPlus1 = this.getIndex(indexOfIndex1 + 1);// indexOfIndex1 == indexes.size() - 1 ? 0 : indexes.get(indexOfIndex1 + 1);
Vector3f edge1 = vertices.get(indexMinus1).subtract(vertices.get(index1)).normalizeLocal();
Vector3f edge2 = vertices.get(indexPlus1).subtract(vertices.get(index1)).normalizeLocal();
Vector3f newEdge = vertices.get(index2).subtract(vertices.get(index1)).normalizeLocal();
// verify f the later computed angle is inside or outside the face
Vector3f direction1 = edge1.cross(edge2).normalizeLocal();
Vector3f direction2 = edge1.cross(newEdge).normalizeLocal();
Vector3f normal = temporalMesh.getNormals().get(index1);
boolean isAngle1Interior = normal.dot(direction1) < 0;
boolean isAngle2Interior = normal.dot(direction2) < 0;
float angle1 = isAngle1Interior ? edge1.angleBetween(edge2) : FastMath.TWO_PI - edge1.angleBetween(edge2);
float angle2 = isAngle2Interior ? edge1.angleBetween(newEdge) : FastMath.TWO_PI - edge1.angleBetween(newEdge);
return angle1 >= angle2;
}
return true;
}
/**
* Loads all faces of a given mesh.
* @param meshStructure
* the mesh structure we read the faces from
* @param userUVGroups
* UV groups defined by the user
* @param verticesColors
* the vertices colors of the mesh
* @param temporalMesh
* the temporal mesh the faces will belong to
* @param blenderContext
* the blender context
* @return list of faces read from the given mesh structure
* @throws BlenderFileException
* an exception is thrown when problems with file reading occur
*/
public static List<Face> loadAll(Structure meshStructure, Map<String, List<Vector2f>> userUVGroups, List<byte[]> verticesColors, TemporalMesh temporalMesh, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading all faces from mesh: {0}", meshStructure.getName());
List<Face> result = new ArrayList<Face>();
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
if (meshHelper.isBMeshCompatible(meshStructure)) {
LOGGER.fine("Reading BMesh.");
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
if (pMPoly.isNotNull() && pMLoop.isNotNull()) {
List<Structure> polys = pMPoly.fetchData();
List<Structure> loops = pMLoop.fetchData();
for (Structure poly : polys) {
int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue();
int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue();
int totLoop = ((Number) poly.getFieldValue("totloop")).intValue();
boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
Integer[] vertexIndexes = new Integer[totLoop];
for (int i = loopStart; i < loopStart + totLoop; ++i) {
vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue();
}
// uvs always must be added wheater we have texture or not
Map<String, List<Vector2f>> uvCoords = new HashMap<String, List<Vector2f>>();
for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) {
List<Vector2f> uvs = entry.getValue().subList(loopStart, loopStart + totLoop);
uvCoords.put(entry.getKey(), new ArrayList<Vector2f>(uvs));
}
List<byte[]> vertexColors = null;
if (verticesColors != null && verticesColors.size() > 0) {
vertexColors = new ArrayList<byte[]>(totLoop);
for (int i = loopStart; i < loopStart + totLoop; ++i) {
vertexColors.add(verticesColors.get(i));
}
}
result.add(new Face(vertexIndexes, smooth, materialNumber, uvCoords, vertexColors, temporalMesh));
}
}
} else {
LOGGER.fine("Reading traditional faces.");
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null;
if (mFaces != null && mFaces.size() > 0) {
// indicates if the material with the specified number should have a texture attached
for (int i = 0; i < mFaces.size(); ++i) {
Structure mFace = mFaces.get(i);
int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue();
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
int v1 = ((Number) mFace.getFieldValue("v1")).intValue();
int v2 = ((Number) mFace.getFieldValue("v2")).intValue();
int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
int vertCount = v4 == 0 ? 3 : 4;
// uvs always must be added wheater we have texture or not
Map<String, List<Vector2f>> faceUVCoords = new HashMap<String, List<Vector2f>>();
for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) {
List<Vector2f> uvCoordsForASingleFace = new ArrayList<Vector2f>(vertCount);
for (int j = 0; j < vertCount; ++j) {
uvCoordsForASingleFace.add(entry.getValue().get(i * 4 + j));
}
faceUVCoords.put(entry.getKey(), uvCoordsForASingleFace);
}
List<byte[]> vertexColors = null;
if (verticesColors != null && verticesColors.size() > 0) {
vertexColors = new ArrayList<byte[]>(vertCount);
vertexColors.add(verticesColors.get(v1));
vertexColors.add(verticesColors.get(v2));
vertexColors.add(verticesColors.get(v3));
if (vertCount == 4) {
vertexColors.add(verticesColors.get(v4));
}
}
result.add(new Face(vertCount == 4 ? new Integer[] { v1, v2, v3, v4 } : new Integer[] { v1, v2, v3 }, smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh));
}
}
}
LOGGER.log(Level.FINE, "Loaded {0} faces.", result.size());
return result;
}
@Override
public int compare(Integer index1, Integer index2) {
return this.indexOf(index1) - this.indexOf(index2);
}
}

@ -0,0 +1,258 @@
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* This class represents the Face's indexes loop. It is a simplified implementation of directed graph.
*
* @author Marcin Roguski (Kaelthas)
*/
public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
/** The indexes. */
private List<Integer> nodes;
/** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */
private Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
/**
* The constructor uses the given nodes in their give order. Each neighbour indexes will form an edge.
* @param nodes
* the nodes for the loop
*/
public IndexesLoop(Integer[] nodes) {
this.nodes = new ArrayList<Integer>(Arrays.asList(nodes));
this.prepareEdges(this.nodes);
}
@Override
public IndexesLoop clone() {
return new IndexesLoop(nodes.toArray(new Integer[nodes.size()]));
}
/**
* The method prepares edges for the given indexes.
* @param nodes
* the indexes
*/
private void prepareEdges(List<Integer> nodes) {
for (int i = 0; i < nodes.size() - 1; ++i) {
if (edges.containsKey(nodes.get(i))) {
edges.get(nodes.get(i)).add(nodes.get(i + 1));
} else {
edges.put(nodes.get(i), new ArrayList<Integer>(Arrays.asList(nodes.get(i + 1))));
}
}
edges.put(nodes.get(nodes.size() - 1), new ArrayList<Integer>(Arrays.asList(nodes.get(0))));
}
/**
* @return the count of indexes
*/
public int size() {
return nodes.size();
}
/**
* Adds edge to the loop.
* @param from
* the start index
* @param to
* the end index
*/
public void addEdge(Integer from, Integer to) {
if (nodes.contains(from) && nodes.contains(to)) {
if (edges.containsKey(from) && !edges.get(from).contains(to)) {
edges.get(from).add(to);
}
}
}
/**
* Removes edge from the face. The edge is removed if it already exists in the face.
* @param node1
* the first index of the edge to be removed
* @param node2
* the second index of the edge to be removed
* @return <b>true</b> if the edge was removed and <b>false</b> otherwise
*/
public boolean removeEdge(Integer node1, Integer node2) {
boolean edgeRemoved = false;
if (nodes.contains(node1) && nodes.contains(node2)) {
if (edges.containsKey(node1)) {
edgeRemoved |= edges.get(node1).remove(node2);
}
if (edges.containsKey(node2)) {
edgeRemoved |= edges.get(node2).remove(node1);
}
if (edgeRemoved) {
if (this.getNeighbourCount(node1) == 0) {
this.removeIndexes(node1);
}
if (this.getNeighbourCount(node2) == 0) {
this.removeIndexes(node2);
}
}
}
return edgeRemoved;
}
/**
* Tells if the given indexes are neighbours.
* @param index1
* the first index
* @param index2
* the second index
* @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise
*/
public boolean areNeighbours(Integer index1, Integer index2) {
if (index1.equals(index2)) {
return false;
}
return edges.get(index1).contains(index2) || edges.get(index2).contains(index1);
}
/**
* The method shifts all indexes by a given value.
* @param shift
* the value to shift all indexes
*/
public void shiftIndexes(int shift) {
List<Integer> nodes = new ArrayList<Integer>(this.nodes.size());
for (Integer node : this.nodes) {
nodes.add(node + shift);
}
Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
for (Entry<Integer, List<Integer>> entry : this.edges.entrySet()) {
List<Integer> neighbours = new ArrayList<Integer>(entry.getValue().size());
for (Integer neighbour : entry.getValue()) {
neighbours.add(neighbour + shift);
}
edges.put(entry.getKey() + shift, neighbours);
}
this.nodes = nodes;
this.edges = edges;
}
/**
* Reverses the order of the indexes.
*/
public void reverse() {
Collections.reverse(nodes);
edges.clear();
this.prepareEdges(nodes);
}
/**
* Returns the neighbour count of the given index.
* @param index
* the index whose neighbour count will be checked
* @return the count of neighbours of the given index
*/
public int getNeighbourCount(Integer index) {
int result = 0;
if (edges.containsKey(index)) {
result = edges.get(index).size();
for (List<Integer> neighbours : edges.values()) {
if (neighbours.contains(index)) {
++result;
}
}
}
return result;
}
/**
* Returns the position of the given index in the loop.
* @param index
* the index of the face
* @return the indexe's position in the loop
*/
public int indexOf(Integer index) {
return nodes.indexOf(index);
}
/**
* Returns the index at the given position.
* @param indexPosition
* the position of the index
* @return the index at a given position
*/
public Integer get(int indexPosition) {
return nodes.get(indexPosition);
}
/**
* @return all indexes of the face
*/
public List<Integer> getAll() {
return new ArrayList<Integer>(nodes);
}
/**
* The method removes all given indexes.
* @param indexes
* the indexes to be removed
*/
public void removeIndexes(Integer... indexes) {
for (Integer index : indexes) {
nodes.remove(index);
edges.remove(index);
for (List<Integer> neighbours : edges.values()) {
neighbours.remove(index);
}
}
}
/**
* The method finds the path between the given indexes.
* @param start
* the start index
* @param end
* the end index
* @return a list containing indexes on the path from start to end (inclusive)
* @throws IllegalStateException
* an exception is thrown when the loop is not normalized (at least one
* index has more than 2 neighbours)
*/
public List<Integer> findPath(Integer start, Integer end) {
List<Integer> result = new ArrayList<Integer>();
Integer node = start;
while (!node.equals(end)) {
result.add(node);
List<Integer> nextSteps = edges.get(node);
if (nextSteps.size() == 0) {
return null;
} else if (nextSteps.size() == 1) {
node = nextSteps.get(0);
} else {
throw new IllegalStateException("Triangulation failed. Face has ambiguous indexes loop. Please triangulate your model in Blender as a workaround.");
}
}
result.add(end);
return result;
}
@Override
public String toString() {
return "IndexesLoop " + nodes.toString();
}
@Override
public int compare(Integer i1, Integer i2) {
return nodes.indexOf(i1) - nodes.indexOf(i2);
}
@Override
public Iterator<Integer> iterator() {
return nodes.iterator();
}
}

@ -0,0 +1,364 @@
package com.jme3.scene.plugins.blender.meshes;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
/**
* A class that aggregates the mesh data to prepare proper buffers. The buffers refer only to ONE material.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class MeshBuffers {
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4;
/** The material index. */
private final int materialIndex;
/** The vertices. */
private List<Vector3f> verts = new ArrayList<Vector3f>();
/** The normals. */
private List<Vector3f> normals = new ArrayList<Vector3f>();
/** The UV coordinate sets. */
private Map<String, List<Vector2f>> uvCoords = new HashMap<String, List<Vector2f>>();
/** The vertex colors. */
private List<byte[]> vertColors = new ArrayList<byte[]>();
/** The indexes. */
private List<Integer> indexes = new ArrayList<Integer>();
/** The maximum weights count assigned to a single vertex. Used during weights normalization. */
private int maximumWeightsPerVertex;
/** A list of mapping between weights and indexes. Each entry for the proper vertex. */
private List<TreeMap<Float, Integer>> boneWeightAndIndexes = new ArrayList<TreeMap<Float, Integer>>();
/**
* Constructor stores only the material index value.
* @param materialIndex
* the material index
*/
public MeshBuffers(int materialIndex) {
this.materialIndex = materialIndex;
}
/**
* @return the material index
*/
public int getMaterialIndex() {
return materialIndex;
}
/**
* @return indexes buffer
*/
public Buffer getIndexBuffer() {
if (indexes.size() <= Short.MAX_VALUE) {
short[] indices = new short[indexes.size()];
for (int i = 0; i < indexes.size(); ++i) {
indices[i] = indexes.get(i).shortValue();
}
return BufferUtils.createShortBuffer(indices);
} else {
int[] indices = new int[indexes.size()];
for (int i = 0; i < indexes.size(); ++i) {
indices[i] = indexes.get(i).intValue();
}
return BufferUtils.createIntBuffer(indices);
}
}
/**
* @return positions buffer
*/
public VertexBuffer getPositionsBuffer() {
VertexBuffer positionBuffer = new VertexBuffer(Type.Position);
Vector3f[] data = verts.toArray(new Vector3f[verts.size()]);
positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data));
return positionBuffer;
}
/**
* @return normals buffer
*/
public VertexBuffer getNormalsBuffer() {
VertexBuffer positionBuffer = new VertexBuffer(Type.Normal);
Vector3f[] data = normals.toArray(new Vector3f[normals.size()]);
positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data));
return positionBuffer;
}
/**
* @return bone buffers
*/
public BoneBuffersData getBoneBuffers() {
BoneBuffersData result = null;
if (maximumWeightsPerVertex > 0) {
this.normalizeBoneBuffers(MAXIMUM_WEIGHTS_PER_VERTEX);
maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX);
ByteBuffer indicesData = BufferUtils.createByteBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX);
int index = 0;
for (Map<Float, Integer> boneBuffersData : boneWeightAndIndexes) {
if (boneBuffersData.size() > 0) {
int count = 0;
for (Entry<Float, Integer> entry : boneBuffersData.entrySet()) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey());
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue().byteValue());
++count;
}
} else {
// if no bone is assigned to this vertex then attach it to the 0-indexed root bone
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
}
++index;
}
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
result = new BoneBuffersData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
}
return result;
}
/**
* @return UV coordinates sets
*/
public Map<String, List<Vector2f>> getUvCoords() {
return uvCoords;
}
/**
* @return <b>true</b> if vertex colors are used and <b>false</b> otherwise
*/
public boolean areVertexColorsUsed() {
return vertColors.size() > 0;
}
/**
* @return vertex colors buffer
*/
public ByteBuffer getVertexColorsBuffer() {
ByteBuffer result = null;
if (vertColors.size() > 0) {
result = BufferUtils.createByteBuffer(4 * vertColors.size());
for (byte[] v : vertColors) {
if (v != null) {
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
} else {
result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0);
}
}
result.flip();
}
return result;
}
/**
* @return <b>true</b> if indexes can be shorts' and <b>false</b> if they need to be ints'
*/
public boolean isShortIndexBuffer() {
return indexes.size() <= Short.MAX_VALUE;
}
/**
* Appends a vertex and normal to the buffers.
* @param vert
* vertex
* @param normal
* normal vector
*/
public void append(Vector3f vert, Vector3f normal) {
int index = this.indexOf(vert, normal, null);
if (index >= 0) {
indexes.add(index);
} else {
indexes.add(verts.size());
verts.add(vert);
normals.add(normal);
}
}
/**
* Appends the face data to the buffers.
* @param smooth
* tells if the face is smooth or flat
* @param verts
* the vertices
* @param normals
* the normals
* @param uvCoords
* the UV coordinates
* @param vertColors
* the vertex colors
* @param vertexGroups
* the vertex groups
*/
public void append(boolean smooth, Vector3f[] verts, Vector3f[] normals, Map<String, List<Vector2f>> uvCoords, byte[][] vertColors, List<Map<Float, Integer>> vertexGroups) {
if (verts.length != normals.length) {
throw new IllegalArgumentException("The amount of verts and normals MUST be equal!");
}
if (vertColors != null && vertColors.length != verts.length) {
throw new IllegalArgumentException("The amount of vertex colors and vertices MUST be equal!");
}
if (vertexGroups.size() != 0 && vertexGroups.size() != verts.length) {
throw new IllegalArgumentException("The amount of (if given) vertex groups and vertices MUST be equal!");
}
if (!smooth) {
// make the normals perpendicular to the face
normals[0] = normals[1] = normals[2] = FastMath.computeNormal(verts[0], verts[1], verts[2]);
}
for (int i = 0; i < verts.length; ++i) {
int index = -1;
Map<String, Vector2f> uvCoordsForVertex = this.getUVsForVertex(i, uvCoords);
if (smooth && (index = this.indexOf(verts[i], normals[i], uvCoordsForVertex)) >= 0) {
indexes.add(index);
} else {
indexes.add(this.verts.size());
this.verts.add(verts[i]);
this.normals.add(normals[i]);
this.vertColors.add(vertColors[i]);
if (uvCoords != null && uvCoords.size() > 0) {
for (Entry<String, List<Vector2f>> entry : uvCoords.entrySet()) {
if (this.uvCoords.containsKey(entry.getKey())) {
this.uvCoords.get(entry.getKey()).add(entry.getValue().get(i));
} else {
List<Vector2f> uvs = new ArrayList<Vector2f>();
uvs.add(entry.getValue().get(i));
this.uvCoords.put(entry.getKey(), uvs);
}
}
}
if (vertexGroups != null && vertexGroups.size() > 0) {
Map<Float, Integer> group = vertexGroups.get(i);
maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, group.size());
boneWeightAndIndexes.add(new TreeMap<Float, Integer>(group));
}
}
}
}
/**
* Returns UV coordinates assigned for the vertex with the proper index.
* @param vertexIndex
* the index of the vertex
* @param uvs
* all UV coordinates we search in
* @return a set of UV coordinates assigned to the given vertex
*/
private Map<String, Vector2f> getUVsForVertex(int vertexIndex, Map<String, List<Vector2f>> uvs) {
if (uvs == null || uvs.size() == 0) {
return null;
}
Map<String, Vector2f> result = new HashMap<String, Vector2f>(uvs.size());
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
result.put(entry.getKey(), entry.getValue().get(vertexIndex));
}
return result;
}
/**
* The method returns an index of a vertex described by the given data.
* The method tries to find a vertex that mathes the given data. If it does it means
* that such vertex is already used.
* @param vert
* the vertex position coordinates
* @param normal
* the vertex's normal vector
* @param uvCoords
* the UV coords of the vertex
* @return index of the found vertex of -1
*/
private int indexOf(Vector3f vert, Vector3f normal, Map<String, Vector2f> uvCoords) {
for (int i = 0; i < verts.size(); ++i) {
if (verts.get(i).equals(vert) && normals.get(i).equals(normal)) {
if (uvCoords != null && uvCoords.size() > 0) {
for (Entry<String, Vector2f> entry : uvCoords.entrySet()) {
List<Vector2f> uvs = this.uvCoords.get(entry.getKey());
if (uvs == null) {
return -1;
}
if (!uvs.get(i).equals(entry.getValue())) {
return -1;
}
}
}
return i;
}
}
return -1;
}
/**
* The method normalizes the weights and bone indexes data.
* First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
* Next it normalizes the weights so that the sum of all verts is 1.
* @param maximumSize
* the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
*/
private void normalizeBoneBuffers(int maximumSize) {
for (TreeMap<Float, Integer> group : boneWeightAndIndexes) {
if (group.size() > maximumSize) {
NavigableMap<Float, Integer> descendingWeights = group.descendingMap();
while (descendingWeights.size() > maximumSize) {
descendingWeights.pollLastEntry();
}
}
// normalizing the weights so that the sum of the values is equal to '1'
TreeMap<Float, Integer> normalizedGroup = new TreeMap<Float, Integer>();
float sum = 0;
for (Entry<Float, Integer> entry : group.entrySet()) {
sum += entry.getKey();
}
if (sum != 0 && sum != 1) {
for (Entry<Float, Integer> entry : group.entrySet()) {
normalizedGroup.put(entry.getKey() / sum, entry.getValue());
}
group.clear();
group.putAll(normalizedGroup);
}
}
}
/**
* A class that gathers the data for mesh bone buffers.
* Added to increase code readability.
*
* @author Marcin Roguski (Kaelthas)
*/
public static class BoneBuffersData {
public final int maximumWeightsPerVertex;
public final VertexBuffer verticesWeights;
public final VertexBuffer verticesWeightsIndices;
public BoneBuffersData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
this.maximumWeightsPerVertex = maximumWeightsPerVertex;
this.verticesWeights = verticesWeights;
this.verticesWeightsIndices = verticesWeightsIndices;
}
}
}

@ -1,185 +0,0 @@
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.scene.Geometry;
/**
* Class that holds information about the mesh.
*
* @author Marcin Roguski (Kaelthas)
*/
public class MeshContext {
private static final Logger LOGGER = Logger.getLogger(MeshContext.class.getName());
/** A map between material index and the geometry. */
private Map<Integer, List<Geometry>> geometries = new HashMap<Integer, List<Geometry>>();
/** The vertex reference map. */
private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
/**
* A vertex group map. The key is the vertex group name and the value is the set of vertex groups.
* Linked hash map is used because the insertion order is important.
*/
private LinkedHashMap<String, VertexGroup> vertexGroups = new LinkedHashMap<String, VertexGroup>();
/**
* Adds a geometry for the specified material index.
* @param materialIndex
* the material index
* @param geometry
* the geometry
*/
public void putGeometry(Integer materialIndex, Geometry geometry) {
List<Geometry> geomList = geometries.get(materialIndex);
if (geomList == null) {
geomList = new ArrayList<Geometry>();
geometries.put(materialIndex, geomList);
}
geomList.add(geometry);
}
/**
* @param materialIndex
* the material index
* @return vertices amount that is used by mesh with the specified material
*/
public int getVertexCount(int materialIndex) {
int result = 0;
for (Geometry geometry : geometries.get(materialIndex)) {
result += geometry.getVertexCount();
}
return result;
}
/**
* Returns material index for the geometry.
* @param geometry
* the geometry
* @return material index
* @throws IllegalStateException
* this exception is thrown when no material is found for the specified geometry
*/
public int getMaterialIndex(Geometry geometry) {
for (Entry<Integer, List<Geometry>> entry : geometries.entrySet()) {
for (Geometry g : entry.getValue()) {
if (g.equals(geometry)) {
return entry.getKey();
}
}
}
throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry);
}
/**
* This method returns the vertex reference map.
*
* @return the vertex reference map
*/
public Map<Integer, List<Integer>> getVertexReferenceMap(int materialIndex) {
return vertexReferenceMap.get(materialIndex);
}
/**
* This method sets the vertex reference map.
*
* @param vertexReferenceMap
* the vertex reference map
*/
public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
this.vertexReferenceMap = vertexReferenceMap;
}
/**
* Adds a new empty vertex group to the mesh context.
* @param name
* the name of the vertex group
*/
public void addVertexGroup(String name) {
if (!vertexGroups.containsKey(name)) {
vertexGroups.put(name, new VertexGroup());
} else {
LOGGER.log(Level.WARNING, "Vertex group already added: {0}", name);
}
}
/**
* Adds a vertex to the vertex group with specified index (the index is the order of adding a group).
* @param vertexIndex
* the vertex index
* @param weight
* the vertex weight
* @param vertexGroupIndex
* the index of a vertex group
*/
public void addVertexToGroup(int vertexIndex, float weight, int vertexGroupIndex) {
if (vertexGroupIndex < 0 || vertexGroupIndex >= vertexGroups.size()) {
throw new IllegalArgumentException("Invalid group index: " + vertexGroupIndex);
}
int counter = 0;
for (Entry<String, VertexGroup> vg : vertexGroups.entrySet()) {
if (vertexGroupIndex == counter) {
vg.getValue().addVertex(vertexIndex, weight);
return;
}
++counter;
}
}
/**
* Returns a group with given name of null if such group does not exist.
* @param groupName
* the name of a vertex group
* @return vertex group with the given name or null
*/
public VertexGroup getGroup(String groupName) {
return vertexGroups.get(groupName);
}
/**
* A vertex group class that maps vertex index to its weight in a single group.
* The group will need to be set a bone index in order to prepare proper buffers for the jme mesh.
* But that information is available after the skeleton is loaded.
*
* @author Marcin Roguski (Kaelthas)
*/
public static class VertexGroup extends HashMap<Integer, Float> {
private static final long serialVersionUID = 5601646768279643957L;
/** The index of the bone f the vertex group is to be used for attaching vertices to bones. */
private int boneIndex;
/**
* Adds a mapping between vertex index and its weight.
* @param index
* the index of the vertex (in JME mesh)
* @param weight
* the weight of the vertex
*/
public void addVertex(int index, float weight) {
this.put(index, weight);
}
/**
* The method sets the bone index for the current vertex group.
* @param boneIndex
* the index of the bone
*/
public void setBoneIndex(int boneIndex) {
this.boneIndex = boneIndex;
}
/**
* @return the index of the bone
*/
public int getBoneIndex() {
return boneIndex;
}
}
}

@ -32,10 +32,10 @@
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -43,25 +43,17 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.math.Vector3f;
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.BlenderContext.LoadedDataType;
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;
/**
* A class that is used in mesh calculations.
@ -75,6 +67,9 @@ public class MeshHelper extends AbstractBlenderHelper {
public static final int UV_DATA_LAYER_TYPE_FMESH = 5;
/** A type of UV data layer in bmesh type. */
public static final int UV_DATA_LAYER_TYPE_BMESH = 16;
/** The flag mask indicating if the edge belongs to a face or not. */
public static final int EDGE_NOT_IN_FACE_FLAG = 0x80;
/** A material used for single lines and points. */
private Material blackUnshadedMaterial;
@ -92,27 +87,29 @@ public class MeshHelper extends AbstractBlenderHelper {
}
/**
* This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.
* Converts the mesh structure into temporal mesh.
* The temporal mesh is stored in blender context and here always a clone is being returned because the mesh might
* be modified by modifiers.
*
* @param meshStructure
* the structure we read the mesh from
* @return the mesh feature
* the mesh structure
* @param blenderContext
* the blender context
* @return temporal mesh read from the given structure
* @throws BlenderFileException
* an exception is thrown when problems with reading blend file occur
*/
@SuppressWarnings("unchecked")
public List<Geometry> toMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (geometries != null) {
List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size());
for (Geometry geometry : geometries) {
copiedGeometries.add(geometry.clone());
}
return copiedGeometries;
public TemporalMesh toTemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading temporal mesh named: {0}.", meshStructure.getName());
TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH);
if (temporalMesh != null) {
LOGGER.fine("The mesh is already loaded. Returning its clone.");
return temporalMesh.clone();
}
String name = meshStructure.getName();
MeshContext meshContext = new MeshContext();
LOGGER.log(Level.FINE, "Reading mesh: {0}.", name);
temporalMesh = new TemporalMesh(meshStructure, blenderContext);
LOGGER.fine("Loading materials.");
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
@ -120,145 +117,214 @@ public class MeshHelper extends AbstractBlenderHelper {
if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
materials = materialHelper.getMaterials(meshStructure, blenderContext);
}
LOGGER.fine("Reading vertices.");
MeshBuilder meshBuilder = new MeshBuilder(meshStructure, materials, blenderContext);
if (meshBuilder.isEmpty()) {
LOGGER.fine("The geometry is empty.");
geometries = new ArrayList<Geometry>(0);
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries);
blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext);
return geometries;
}
meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap());
temporalMesh.setMaterials(materials);
LOGGER.fine("Reading custom properties.");
Properties properties = this.loadProperties(meshStructure, blenderContext);
temporalMesh.setProperties(properties);
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, meshStructure);
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH, temporalMesh);
return temporalMesh.clone();
}
LOGGER.fine("Generating meshes.");
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();
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);
/**
* Tells if the given mesh structure supports BMesh.
*
* @param meshStructure
* the mesh structure
* @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
*/
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();
}
/**
* This method returns the vertices.
*
* @param meshStructure
* the structure containing the mesh data
* @return a list of two - element arrays, the first element is the vertex and the second - its normal
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/
@SuppressWarnings("unchecked")
public void loadVerticesAndNormals(Structure meshStructure, List<Vector3f> vertices, List<Vector3f> normals) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading vertices and normals from mesh: {0}.", meshStructure.getName());
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
if (count > 0) {
Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
List<Structure> mVerts = pMVert.fetchData();
Vector3f co = null, no = null;
if (fixUpAxis) {
for (int i = 0; i < count; ++i) {
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
vertices.add(co);
DynamicArray<Number> norm = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f, -norm.get(1).shortValue() / 32767.0f);
normals.add(no);
}
} else {
for (int i = 0; i < count; ++i) {
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
vertices.add(co);
DynamicArray<Number> norm = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(1).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f);
normals.add(no);
}
geometries.add(geometry);
meshContext.putGeometry(materialIndex, geometry);
}
}
LOGGER.log(Level.FINE, "Loaded {0} vertices and normals.", vertices.size());
}
/**
* 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 or empty list of colors are not specified
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/
public List<byte[]> loadVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading vertices colors from mesh: {0}.", meshStructure.getName());
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol");
List<byte[]> verticesColors = new ArrayList<byte[]>();
// 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();
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;
}
/**
* 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
* @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")
public LinkedHashMap<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading UV coordinates from mesh: {0}.", meshStructure.getName());
LinkedHashMap<String, List<Vector2f>> result = new LinkedHashMap<String, List<Vector2f>>();
if (this.isBMeshCompatible(meshStructure)) {
// in this case the UV's are assigned to vertices (an array is the same length as the vertex array)
Structure loopData = (Structure) meshStructure.getFieldValue("ldata");
Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers");
List<Structure> loopDataLayers = pLoopDataLayers.fetchData();
for (Structure structure : loopDataLayers) {
Pointer p = (Pointer) structure.getFieldValue("data");
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) {
String uvSetName = structure.getFieldValue("name").toString();
List<Structure> uvsStructures = p.fetchData();
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
for (Structure uvStructure : uvsStructures) {
DynamicArray<Number> loopUVS = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue()));
}
result.put(uvSetName, uvs);
}
}
} else {
// in this case UV's are assigned to faces (the array has the same legnth as the faces count)
Structure facesData = (Structure) meshStructure.getFieldValue("fdata");
Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers");
if (pFacesDataLayers.isNotNull()) {
List<Structure> facesDataLayers = pFacesDataLayers.fetchData();
for (Structure structure : facesDataLayers) {
Pointer p = (Pointer) structure.getFieldValue("data");
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) {
String uvSetName = structure.getFieldValue("name").toString();
List<Structure> uvsStructures = p.fetchData();
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
for (Structure uvStructure : uvsStructures) {
DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue()));
uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue()));
uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue()));
uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue()));
}
result.put(uvSetName, uvs);
}
}
}
}
return result;
}
/**
* Loads all vertices groups.
* @param meshStructure
* the mesh structure
* @return a list of vertex groups for every vertex in the mesh
* @throws BlenderFileException
* an exception is thrown when problems with blend file occur
*/
public List<Map<String, Float>> loadVerticesGroups(Structure meshStructure) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading vertices groups from mesh: {0}.", meshStructure.getName());
List<Map<String, Float>> result = new ArrayList<Map<String, Float>>();
LOGGER.fine("Reading vertices groups.");// this MUST be done AFTER meshes are built, because otherwise we have no vertex references maps
Structure parent = blenderContext.peekParent();
Structure defbase = (Structure) parent.getFieldValue("defbase");
List<String> groupNames = new ArrayList<String>();
List<Structure> defs = defbase.evaluateListBase();
for (Structure def : defs) {
meshContext.addVertexGroup(def.getFieldValue("name").toString());
groupNames.add(def.getFieldValue("name").toString());
}
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
if (pDvert.isNotNull()) {// assigning weights and bone indices
List<Structure> dverts = pDvert.fetchData();
int blenderVertexIndex = 0;
for (Structure dvert : dverts) {
Map<String, Float> weightsForVertex = new HashMap<String, Float>();
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
if (pDW.isNotNull()) {
List<Structure> dw = pDW.fetchData();
for (Structure deformWeight : dw) {
int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
String groupName = groupNames.get(groupIndex);
// we need to use JME vertex index here and NOT blender vertex index
for (Entry<Integer, Map<Integer, List<Integer>>> vertexReferenceMap : meshBuilder.getVertexReferenceMap().entrySet()) {// iterate through the meshes [key is the material index]
for (Entry<Integer, List<Integer>> vertexEntry : vertexReferenceMap.getValue().entrySet()) {// iterate through the vertex references for the specified material
if (vertexEntry.getKey().intValue() == blenderVertexIndex) {// if the indexes match then ...
for (Integer jmeVertexIndex : vertexEntry.getValue()) {// ... add all jme vertices to the specified group
meshContext.addVertexToGroup(jmeVertexIndex, weight, groupIndex);
}
}
}
}
}
}
++blenderVertexIndex;
}
}
// store the data in blender context before applying the material
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries);
blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext);
// apply materials only when all geometries are in place
if (materials != null) {
for (Geometry geometry : geometries) {
int materialNumber = meshContext.getMaterialIndex(geometry);
if (materialNumber < 0) {
geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext));
} else if (materials[materialNumber] != null) {
LinkedHashMap<String, List<Vector2f>> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
MaterialContext materialContext = materials[materialNumber];
materialContext.applyMaterial(geometry, meshStructure.getOldMemoryAddress(), uvCoordinates, blenderContext);
} else {
geometry.setMaterial(blenderContext.getDefaultMaterial());
LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + "Enter the edit mode and assign materials once more to your faces.");
}
}
} else {
// add UV coordinates if they are defined even if the material is not applied to the model
List<VertexBuffer> uvCoordsBuffer = null;
if (meshBuilder.hasUVCoordinates()) {
Map<String, List<Vector2f>> uvs = meshBuilder.getUVCoordinates(0);
if (uvs != null && uvs.size() > 0) {
uvCoordsBuffer = new ArrayList<VertexBuffer>(uvs.size());
int uvIndex = 0;
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
VertexBuffer buffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[uvIndex++]);
buffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(entry.getValue().toArray(new Vector2f[uvs.size()])));
uvCoordsBuffer.add(buffer);
}
}
}
for (Geometry geometry : geometries) {
Mode mode = geometry.getMesh().getMode();
if (mode != Mode.Triangles && mode != Mode.TriangleFan && mode != Mode.TriangleStrip) {
geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext));
} else {
Material defaultMaterial = blenderContext.getDefaultMaterial();
if (geometry.getMesh().getBuffer(Type.Color) != null) {
defaultMaterial = defaultMaterial.clone();
defaultMaterial.setBoolean("VertexColor", true);
}
geometry.setMaterial(defaultMaterial);
}
if (uvCoordsBuffer != null) {
for (VertexBuffer buffer : uvCoordsBuffer) {
geometry.getMesh().setBuffer(buffer);
weightsForVertex.put(groupName, weight);
}
}
result.add(weightsForVertex);
}
}
return geometries;
return result;
}
/**
* Tells if the given mesh structure supports BMesh.
*
* @param meshStructure
* the mesh structure
* @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
* Returns the black unshaded material. It is used for lines and points because that is how blender
* renders it.
* @param blenderContext
* the blender context
* @return black unshaded material
*/
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();
}
private synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) {
public synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) {
if (blackUnshadedMaterial == null) {
blackUnshadedMaterial = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
blackUnshadedMaterial.setColor("Color", ColorRGBA.Black);

@ -0,0 +1,88 @@
package com.jme3.scene.plugins.blender.meshes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* A class that represents a single point on the scene that is not a part of an edge.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class Point {
private static final Logger LOGGER = Logger.getLogger(Point.class.getName());
/** The point's index. */
private int index;
/**
* Constructs a point for a given index.
* @param index
* the index of the point
*/
public Point(int index) {
this.index = index;
}
@Override
public Point clone() {
return new Point(index);
}
/**
* @return the index of the point
*/
public int getIndex() {
return index;
}
/**
* The method shifts the index by a given value.
* @param shift
* the value to shift the index
*/
public void shiftIndexes(int shift) {
index += shift;
}
/**
* Loads all points of the mesh that do not belong to any edge.
* @param meshStructure
* the mesh structure
* @return a list of points
* @throws BlenderFileException
* an exception is thrown when problems with file reading occur
*/
public static List<Point> loadAll(Structure meshStructure) throws BlenderFileException {
LOGGER.log(Level.FINE, "Loading all points that do not belong to any edge from mesh: {0}", meshStructure.getName());
List<Point> result = new ArrayList<Point>();
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
if (pMEdge.isNotNull()) {
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
Set<Integer> unusedVertices = new HashSet<Integer>(count);
for (int i = 0; i < count; ++i) {
unusedVertices.add(i);
}
List<Structure> edges = pMEdge.fetchData();
for (Structure edge : edges) {
unusedVertices.remove(((Number) edge.getFieldValue("v1")).intValue());
unusedVertices.remove(((Number) edge.getFieldValue("v2")).intValue());
}
for (Integer unusedIndex : unusedVertices) {
result.add(new Point(unusedIndex));
}
}
LOGGER.log(Level.FINE, "Loaded {0} points.", result.size());
return result;
}
}

@ -0,0 +1,599 @@
package com.jme3.scene.plugins.blender.meshes;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingVolume;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.meshes.MeshBuffers.BoneBuffersData;
import com.jme3.scene.plugins.blender.modifiers.Modifier;
import com.jme3.scene.plugins.blender.objects.Properties;
/**
* The class extends Geometry so that it can be temporalily added to the object's node.
* Later each such node's child will be transformed into a list of geometries.
*
* @author Marcin Roguski (Kaelthas)
*/
public class TemporalMesh extends Geometry {
private static final Logger LOGGER = Logger.getLogger(TemporalMesh.class.getName());
/** The blender context. */
protected final BlenderContext blenderContext;
/** The mesh's structure. */
protected final Structure meshStructure;
/** Loaded vertices. */
protected List<Vector3f> vertices = new ArrayList<Vector3f>();
/** Loaded normals. */
protected List<Vector3f> normals = new ArrayList<Vector3f>();
/** Loaded vertex groups. */
protected List<Map<String, Float>> vertexGroups = new ArrayList<Map<String, Float>>();
/** Loaded vertex colors. */
protected List<byte[]> verticesColors = new ArrayList<byte[]>();
/** Materials used by the mesh. */
protected MaterialContext[] materials;
/** The properties of the mesh. */
protected Properties properties;
/** The bone indexes. */
protected Map<String, Integer> boneIndexes = new HashMap<String, Integer>();
/** The modifiers that should be applied after the mesh has been created. */
protected List<Modifier> postMeshCreationModifiers = new ArrayList<Modifier>();
/** The faces of the mesh. */
protected List<Face> faces = new ArrayList<Face>();
/** The edges of the mesh. */
protected List<Edge> edges = new ArrayList<Edge>();
/** The points of the mesh. */
protected List<Point> points = new ArrayList<Point>();
/** The bounding box of the temporal mesh. */
protected BoundingBox boundingBox;
/**
* Creates a temporal mesh based on the given mesh structure.
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problems with file reading occur
*/
public TemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
this(meshStructure, blenderContext, true);
}
/**
* Creates a temporal mesh based on the given mesh structure.
* @param meshStructure
* the mesh structure
* @param blenderContext
* the blender context
* @param loadData
* tells if the data should be loaded from the mesh structure
* @throws BlenderFileException
* an exception is thrown when problems with file reading occur
*/
protected TemporalMesh(Structure meshStructure, BlenderContext blenderContext, boolean loadData) throws BlenderFileException {
this.blenderContext = blenderContext;
name = meshStructure.getName();
this.meshStructure = meshStructure;
if (loadData) {
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
meshHelper.loadVerticesAndNormals(meshStructure, vertices, normals);
verticesColors = meshHelper.loadVerticesColors(meshStructure, blenderContext);
LinkedHashMap<String, List<Vector2f>> userUVGroups = meshHelper.loadUVCoordinates(meshStructure);
vertexGroups = meshHelper.loadVerticesGroups(meshStructure);
faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext);
edges = Edge.loadAll(meshStructure);
points = Point.loadAll(meshStructure);
}
}
@Override
public TemporalMesh clone() {
try {
TemporalMesh result = new TemporalMesh(meshStructure, blenderContext, false);
for (Vector3f v : vertices) {
result.vertices.add(v.clone());
}
for (Vector3f n : normals) {
result.normals.add(n.clone());
}
for (Map<String, Float> group : vertexGroups) {
result.vertexGroups.add(new HashMap<String, Float>(group));
}
for (byte[] vertColor : verticesColors) {
result.verticesColors.add(vertColor.clone());
}
result.materials = materials;
result.properties = properties;
result.boneIndexes.putAll(boneIndexes);
result.postMeshCreationModifiers.addAll(postMeshCreationModifiers);
for (Face face : faces) {
result.faces.add(face.clone());
}
for (Edge edge : edges) {
result.edges.add(edge.clone());
}
for (Point point : points) {
result.points.add(point.clone());
}
return result;
} catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage());
}
return null;
}
/**
* @return the vertices of the mesh
*/
protected List<Vector3f> getVertices() {
return vertices;
}
/**
* @return the normals of the mesh
*/
protected List<Vector3f> getNormals() {
return normals;
}
@Override
public void updateModelBound() {
if (boundingBox == null) {
boundingBox = new BoundingBox();
}
Vector3f min = new Vector3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
Vector3f max = new Vector3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
for (Vector3f v : vertices) {
min.set(Math.min(min.x, v.x), Math.min(min.y, v.y), Math.min(min.z, v.z));
max.set(Math.max(max.x, v.x), Math.max(max.y, v.y), Math.max(max.z, v.z));
}
boundingBox.setMinMax(min, max);
}
@Override
public BoundingVolume getModelBound() {
this.updateModelBound();
return boundingBox;
}
@Override
public BoundingVolume getWorldBound() {
this.updateModelBound();
Node parent = this.getParent();
if (parent != null) {
BoundingVolume bv = boundingBox.clone();
bv.setCenter(parent.getWorldTranslation());
return bv;
} else {
return boundingBox;
}
}
/**
* Triangulates the mesh.
*/
public void triangulate() {
LOGGER.fine("Triangulating temporal mesh.");
List<Face> triangulatedFaces = new ArrayList<Face>();
for (Face face : faces) {
triangulatedFaces.addAll(face.triangulate(vertices, normals));
}
faces = triangulatedFaces;
}
/**
* The method appends the given mesh to the current one. New faces and vertices and indexes are added.
* @param mesh
* the mesh to be appended
*/
public void append(TemporalMesh mesh) {
// we need to shift the indexes in faces, lines and points
int shift = vertices.size();
if (shift > 0) {
for (Face face : mesh.faces) {
face.shiftIndexes(shift);
face.setTemporalMesh(this);
}
for (Edge edge : mesh.edges) {
edge.shiftIndexes(shift);
}
for (Point point : mesh.points) {
point.shiftIndexes(shift);
}
}
faces.addAll(mesh.faces);
edges.addAll(mesh.edges);
points.addAll(mesh.points);
vertices.addAll(mesh.vertices);
normals.addAll(mesh.normals);
vertexGroups.addAll(mesh.vertexGroups);
verticesColors.addAll(mesh.verticesColors);
boneIndexes.putAll(mesh.boneIndexes);
}
/**
* Translate all vertices by the given vector.
* @param translation
* the translation vector
* @return this mesh after translation (NO new instance is created)
*/
public TemporalMesh translate(Vector3f translation) {
for (Vector3f v : vertices) {
v.addLocal(translation);
}
return this;
}
/**
* Sets the properties of the mesh.
* @param properties
* the properties of the mesh
*/
public void setProperties(Properties properties) {
this.properties = properties;
}
/**
* Sets the materials of the mesh.
* @param materials
* the materials of the mesh
*/
public void setMaterials(MaterialContext[] materials) {
this.materials = materials;
}
/**
* Adds bone index to the mesh.
* @param boneName
* the name of the bone
* @param boneIndex
* the index of the bone
*/
public void addBoneIndex(String boneName, Integer boneIndex) {
boneIndexes.put(boneName, boneIndex);
}
/**
* The modifier to be applied after the geometries are created.
* @param modifier
* the modifier to be applied
*/
public void applyAfterMeshCreate(Modifier modifier) {
postMeshCreationModifiers.add(modifier);
}
@Override
public int getVertexCount() {
return vertices.size();
}
/**
* Returns the vertex at the given position.
* @param i
* the vertex position
* @return the vertex at the given position
*/
public Vector3f getVertex(int i) {
return vertices.get(i);
}
/**
* Returns the normal at the given position.
* @param i
* the normal position
* @return the normal at the given position
*/
public Vector3f getNormal(int i) {
return normals.get(i);
}
/**
* Flips the order of the mesh's indexes.
*/
public void flipIndexes() {
for (Face face : faces) {
face.flipIndexes();
}
for (Edge edge : edges) {
edge.flipIndexes();
}
Collections.reverse(points);
}
/**
* Flips UV coordinates.
* @param u
* indicates if U coords should be flipped
* @param v
* indicates if V coords should be flipped
*/
public void flipUV(boolean u, boolean v) {
for (Face face : faces) {
face.flipUV(u, v);
}
}
/**
* The mesh builds geometries from the mesh. The result is stored in the blender context
* under the mesh's OMA.
*/
public void toGeometries() {
LOGGER.log(Level.FINE, "Converting temporal mesh {0} to jme geometries.", name);
List<Geometry> result = new ArrayList<Geometry>();
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
Node parent = this.getParent();
parent.detachChild(this);
this.prepareFacesGeometry(result, meshHelper);
this.prepareLinesGeometry(result, meshHelper);
this.preparePointsGeometry(result, meshHelper);
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
for (Geometry geometry : result) {
parent.attachChild(geometry);
}
for (Modifier modifier : postMeshCreationModifiers) {
modifier.postMeshCreationApply(parent, blenderContext);
}
}
/**
* The method creates geometries from faces.
* @param result
* the list where new geometries will be appended
* @param meshHelper
* the mesh helper
*/
protected void prepareFacesGeometry(List<Geometry> result, MeshHelper meshHelper) {
LOGGER.fine("Preparing faces geometries.");
this.triangulate();
Vector3f[] tempVerts = new Vector3f[3];
Vector3f[] tempNormals = new Vector3f[3];
byte[][] tempVertColors = new byte[3][];
List<Map<Float, Integer>> boneBuffers = new ArrayList<Map<Float, Integer>>(3);
LOGGER.log(Level.FINE, "Appending {0} faces to mesh buffers.", faces.size());
Map<Integer, MeshBuffers> faceMeshes = new HashMap<Integer, MeshBuffers>();
for (Face face : faces) {
MeshBuffers meshBuffers = faceMeshes.get(face.getMaterialNumber());
if (meshBuffers == null) {
meshBuffers = new MeshBuffers(face.getMaterialNumber());
faceMeshes.put(face.getMaterialNumber(), meshBuffers);
}
List<Integer> indexes = face.getIndexes();
List<byte[]> vertexColors = face.getVertexColors();
boneBuffers.clear();
assert indexes.size() == 3 : "The mesh has not been properly triangulated!";
for (int i = 0; i < 3; ++i) {
int vertIndex = indexes.get(i);
tempVerts[i] = vertices.get(vertIndex);
tempNormals[i] = normals.get(vertIndex);
tempVertColors[i] = vertexColors != null ? vertexColors.get(i) : null;
if (boneIndexes.size() > 0) {
Map<Float, Integer> boneBuffersForVertex = new HashMap<Float, Integer>();
Map<String, Float> vertexGroupsForVertex = vertexGroups.get(vertIndex);
for (Entry<String, Integer> entry : boneIndexes.entrySet()) {
if (vertexGroupsForVertex.containsKey(entry.getKey())) {
boneBuffersForVertex.put(vertexGroupsForVertex.get(entry.getKey()), entry.getValue());
}
}
boneBuffers.add(boneBuffersForVertex);
}
}
meshBuffers.append(face.isSmooth(), tempVerts, tempNormals, face.getUvSets(), tempVertColors, boneBuffers);
}
LOGGER.fine("Converting mesh buffers to geometries.");
Map<Geometry, MeshBuffers> geometryToBuffersMap = new HashMap<Geometry, MeshBuffers>();
for (Entry<Integer, MeshBuffers> entry : faceMeshes.entrySet()) {
MeshBuffers meshBuffers = entry.getValue();
Mesh mesh = new Mesh();
if (meshBuffers.isShortIndexBuffer()) {
mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer());
} else {
mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer());
}
mesh.setBuffer(meshBuffers.getPositionsBuffer());
mesh.setBuffer(meshBuffers.getNormalsBuffer());
if (meshBuffers.areVertexColorsUsed()) {
mesh.setBuffer(Type.Color, 4, meshBuffers.getVertexColorsBuffer());
mesh.getBuffer(Type.Color).setNormalized(true);
}
BoneBuffersData boneBuffersData = meshBuffers.getBoneBuffers();
if (boneBuffersData != null) {
mesh.setMaxNumWeights(boneBuffersData.maximumWeightsPerVertex);
mesh.setBuffer(boneBuffersData.verticesWeights);
mesh.setBuffer(boneBuffersData.verticesWeightsIndices);
LOGGER.fine("Generating bind pose and normal buffers.");
mesh.generateBindPose(true);
// change the usage type of vertex and normal buffers from Static to Stream
mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
// creating empty buffers for HW skinning; the buffers will be setup if ever used
VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
mesh.setBuffer(verticesWeightsHW);
mesh.setBuffer(verticesWeightsIndicesHW);
}
Geometry geometry = new Geometry(name + (result.size() + 1), mesh);
if (properties != null && properties.getValue() != null) {
meshHelper.applyProperties(geometry, properties);
}
result.add(geometry);
geometryToBuffersMap.put(geometry, meshBuffers);
}
LOGGER.fine("Applying materials to geometries.");
for (Entry<Geometry, MeshBuffers> entry : geometryToBuffersMap.entrySet()) {
int materialIndex = entry.getValue().getMaterialIndex();
Geometry geometry = entry.getKey();
if (materialIndex >= 0 && materials != null && materials.length > materialIndex && materials[materialIndex] != null) {
materials[materialIndex].applyMaterial(geometry, meshStructure.getOldMemoryAddress(), entry.getValue().getUvCoords(), blenderContext);
} else {
geometry.setMaterial(blenderContext.getDefaultMaterial());
}
}
}
/**
* The method creates geometries from lines.
* @param result
* the list where new geometries will be appended
* @param meshHelper
* the mesh helper
*/
protected void prepareLinesGeometry(List<Geometry> result, MeshHelper meshHelper) {
if (edges.size() > 0) {
LOGGER.fine("Preparing lines geometries.");
List<List<Integer>> separateEdges = new ArrayList<List<Integer>>();
List<Edge> edges = new ArrayList<Edge>(this.edges);
while (edges.size() > 0) {
boolean edgeAppended = false;
int edgeIndex = 0;
for (List<Integer> list : separateEdges) {
for (edgeIndex = 0; edgeIndex < edges.size() && !edgeAppended; ++edgeIndex) {
Edge edge = edges.get(edgeIndex);
if (list.get(0).equals(edge.getFirstIndex())) {
list.add(0, edge.getSecondIndex());
--edgeIndex;
edgeAppended = true;
} else if (list.get(0).equals(edge.getSecondIndex())) {
list.add(0, edge.getFirstIndex());
--edgeIndex;
edgeAppended = true;
} else if (list.get(list.size() - 1).equals(edge.getFirstIndex())) {
list.add(edge.getSecondIndex());
--edgeIndex;
edgeAppended = true;
} else if (list.get(list.size() - 1).equals(edge.getSecondIndex())) {
list.add(edge.getFirstIndex());
--edgeIndex;
edgeAppended = true;
}
}
if (edgeAppended) {
break;
}
}
Edge edge = edges.remove(edgeAppended ? edgeIndex : 0);
if (!edgeAppended) {
separateEdges.add(new ArrayList<Integer>(Arrays.asList(edge.getFirstIndex(), edge.getSecondIndex())));
}
}
for (List<Integer> list : separateEdges) {
MeshBuffers meshBuffers = new MeshBuffers(0);
for (int index : list) {
meshBuffers.append(vertices.get(index), normals.get(index));
}
Mesh mesh = new Mesh();
mesh.setPointSize(2);
mesh.setMode(Mode.LineStrip);
if (meshBuffers.isShortIndexBuffer()) {
mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer());
} else {
mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer());
}
mesh.setBuffer(meshBuffers.getPositionsBuffer());
mesh.setBuffer(meshBuffers.getNormalsBuffer());
Geometry geometry = new Geometry(meshStructure.getName() + (result.size() + 1), mesh);
geometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext));
if (properties != null && properties.getValue() != null) {
meshHelper.applyProperties(geometry, properties);
}
result.add(geometry);
}
}
}
/**
* The method creates geometries from points.
* @param result
* the list where new geometries will be appended
* @param meshHelper
* the mesh helper
*/
protected void preparePointsGeometry(List<Geometry> result, MeshHelper meshHelper) {
if (points.size() > 0) {
LOGGER.fine("Preparing point geometries.");
MeshBuffers pointBuffers = new MeshBuffers(0);
for (Point point : points) {
pointBuffers.append(vertices.get(point.getIndex()), normals.get(point.getIndex()));
}
Mesh pointsMesh = new Mesh();
pointsMesh.setMode(Mode.Points);
pointsMesh.setPointSize(blenderContext.getBlenderKey().getPointsSize());
if (pointBuffers.isShortIndexBuffer()) {
pointsMesh.setBuffer(Type.Index, 1, (ShortBuffer) pointBuffers.getIndexBuffer());
} else {
pointsMesh.setBuffer(Type.Index, 1, (IntBuffer) pointBuffers.getIndexBuffer());
}
pointsMesh.setBuffer(pointBuffers.getPositionsBuffer());
pointsMesh.setBuffer(pointBuffers.getNormalsBuffer());
Geometry pointsGeometry = new Geometry(meshStructure.getName() + (result.size() + 1), pointsMesh);
pointsGeometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext));
if (properties != null && properties.getValue() != null) {
meshHelper.applyProperties(pointsGeometry, properties);
}
result.add(pointsGeometry);
}
}
@Override
public String toString() {
return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]";
}
}

@ -1,586 +0,0 @@
package com.jme3.scene.plugins.blender.meshes.builders;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
import com.jme3.scene.plugins.blender.textures.UserUVCollection;
import com.jme3.util.BufferUtils;
/**
* A builder class for meshes made of triangles (faces).
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class FaceMeshBuilder {
private static final Logger LOGGER = Logger.getLogger(FaceMeshBuilder.class.getName());
/** An array of reference vertices. */
private Vector3f[][] verticesAndNormals;
/** An list of vertices colors. */
private List<byte[]> verticesColors;
/** A variable that indicates if the model uses generated textures. */
private boolean usesGeneratedTextures;
/**
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
* positions (it simply tells which vertex is referenced where in the result list).
*/
private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap;
/** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<Vector3f>> normalMap = new HashMap<Integer, List<Vector3f>>();
/** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<Vector3f>> vertexMap = new HashMap<Integer, List<Vector3f>>();
/** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<byte[]>> vertexColorsMap = new HashMap<Integer, List<byte[]>>();
/** The following map sorts indexes by material number (because in jme Mesh can have only one material). */
private Map<Integer, List<Integer>> indexMap = new HashMap<Integer, List<Integer>>();
/** A collection of user defined UV coordinates (one mesh can have more than one such mappings). */
private UserUVCollection userUVCollection = new UserUVCollection();
/**
* Constructor. Stores the given array (not copying it).
* The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied.
* The amount of vertices is always faceCount * 3.
* @param verticesAndNormals
* the reference vertices and normals array
* @param usesGeneratedTextures
* a variable that indicates if the model uses generated textures or not
*/
public FaceMeshBuilder(Vector3f[][] verticesAndNormals, boolean usesGeneratedTextures) {
this.verticesAndNormals = verticesAndNormals;
this.usesGeneratedTextures = usesGeneratedTextures;
globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length);
}
public void readMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
verticesColors = this.getVerticesColors(structure, blenderContext);
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
if (meshHelper.isBMeshCompatible(structure)) {
this.readBMesh(structure);
} else {
this.readTraditionalFaces(structure);
}
}
/**
* Builds the meshes.
* @return a map between material index and the mesh
*/
public Map<Integer, Mesh> buildMeshes() {
Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(indexMap.size());
for (Entry<Integer, List<Integer>> meshEntry : indexMap.entrySet()) {
int materialIndex = meshEntry.getKey();
// key is the material index
// value is a list of vertex indices
Mesh mesh = new Mesh();
// creating vertices indices for this mesh
List<Integer> indexList = meshEntry.getValue();
if (this.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) {
short[] indices = new short[indexList.size()];
for (int i = 0; i < indexList.size(); ++i) {
indices[i] = indexList.get(i).shortValue();
}
mesh.setBuffer(Type.Index, 1, indices);
} else {
int[] indices = new int[indexList.size()];
for (int i = 0; i < indexList.size(); ++i) {
indices[i] = indexList.get(i).intValue();
}
mesh.setBuffer(Type.Index, 1, indices);
}
LOGGER.fine("Creating vertices buffer.");
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getVertices(materialIndex)));
mesh.setBuffer(verticesBuffer);
LOGGER.fine("Creating normals buffer.");
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getNormals(materialIndex)));
mesh.setBuffer(normalsBuffer);
if (verticesColors != null) {
LOGGER.fine("Setting vertices colors.");
mesh.setBuffer(Type.Color, 4, this.getVertexColorsBuffer(materialIndex));
mesh.getBuffer(Type.Color).setNormalized(true);
}
result.put(materialIndex, mesh);
}
return result;
}
/**
* This method reads the mesh from the new BMesh system.
*
* @param meshStructure
* the mesh structure
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private void readBMesh(Structure meshStructure) throws BlenderFileException {
LOGGER.fine("Reading BMesh.");
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
if (pMPoly.isNotNull() && pMLoop.isNotNull()) {
Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, true);
List<Structure> polys = pMPoly.fetchData();
List<Structure> loops = pMLoop.fetchData();
int[] vertexColorIndex = verticesColors == null ? null : new int[3];
for (Structure poly : polys) {
int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue();
int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue();
int totLoop = ((Number) poly.getFieldValue("totloop")).intValue();
boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
int[] vertexIndexes = new int[totLoop];
for (int i = loopStart; i < loopStart + totLoop; ++i) {
vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue();
}
int i = 0;
while (i < totLoop - 2) {
int v1 = vertexIndexes[0];
int v2 = vertexIndexes[i + 1];
int v3 = vertexIndexes[i + 2];
if (vertexColorIndex != null) {
vertexColorIndex[0] = loopStart;
vertexColorIndex[1] = loopStart + i + 1;
vertexColorIndex[2] = loopStart + i + 2;
}
if (uvs != null) {
// uvs always must be added wheater we have texture or not
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
Vector2f[] uvCoordsForASingleFace = new Vector2f[3];
uvCoordsForASingleFace[0] = entry.getValue().get(loopStart);
uvCoordsForASingleFace[1] = entry.getValue().get(loopStart + i + 1);
uvCoordsForASingleFace[2] = entry.getValue().get(loopStart + i + 2);
uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace);
}
}
this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex);
uvCoordinatesForFace.clear();
++i;
}
}
}
}
/**
* This method reads the mesh from traditional triangle/quad storing
* structures.
*
* @param meshStructure
* the mesh structure
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private void readTraditionalFaces(Structure meshStructure) throws BlenderFileException {
LOGGER.fine("Reading traditional faces.");
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null;
if (mFaces != null && mFaces.size() > 0) {
// indicates if the material with the specified number should have a texture attached
Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, false);
Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
int[] vertexColorIndex = verticesColors == null ? null : new int[3];
for (int i = 0; i < mFaces.size(); ++i) {
Structure mFace = mFaces.get(i);
int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue();
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
if (uvs != null) {
// uvs always must be added wheater we have texture or not
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
Vector2f[] uvCoordsForASingleFace = new Vector2f[3];
uvCoordsForASingleFace[0] = entry.getValue().get(i * 4);
uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 1);
uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 2);
uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace);
}
}
int v1 = ((Number) mFace.getFieldValue("v1")).intValue();
int v2 = ((Number) mFace.getFieldValue("v2")).intValue();
int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
if (vertexColorIndex != null) {
vertexColorIndex[0] = i * 4;
vertexColorIndex[1] = i * 4 + 1;
vertexColorIndex[2] = i * 4 + 2;
}
this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex);
uvCoordinatesForFace.clear();
if (v4 > 0) {
if (uvs != null) {
// uvs always must be added wheater we have texture or not
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
Vector2f[] uvCoordsForASingleFace = new Vector2f[3];
uvCoordsForASingleFace[0] = entry.getValue().get(i * 4);
uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 2);
uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 3);
uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace);
}
}
if (vertexColorIndex != null) {
vertexColorIndex[0] = i * 4;
vertexColorIndex[1] = i * 4 + 2;
vertexColorIndex[2] = i * 4 + 3;
}
this.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex);
uvCoordinatesForFace.clear();
}
}
}
}
/**
* This method adds a face to the mesh.
* @param v1
* index of the 1'st vertex from the reference vertex table
* @param v2
* index of the 2'nd vertex from the reference vertex table
* @param v3
* index of the 3'rd vertex from the reference vertex table
* @param smooth
* indicates if this face should have smooth shading or flat shading
* @param materialNumber
* the material number for this face
* @param uvsForFace
* a 3-element array of vertices UV coordinates mapped to the UV's set name
* @param vertexColorIndex
* a table of 3 elements that indicates the verts' colors indexes
*/
private void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Map<String, Vector2f[]> uvsForFace, int[] vertexColorIndex) {
if (uvsForFace != null && uvsForFace.size() > 0) {
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
if (entry.getValue().length != 3) {
throw new IllegalArgumentException("UV coordinates must be a 3-element array!" + (entry.getKey() != null ? " (UV set name: " + entry.getKey() + ')' : ""));
}
}
}
// getting the required lists
List<Integer> indexList = indexMap.get(materialNumber);
if (indexList == null) {
indexList = new ArrayList<Integer>();
indexMap.put(materialNumber, indexList);
}
List<Vector3f> vertexList = vertexMap.get(materialNumber);
if (vertexList == null) {
vertexList = new ArrayList<Vector3f>();
vertexMap.put(materialNumber, vertexList);
}
List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null;
if (vertexColorsList == null && vertexColorsMap != null) {
vertexColorsList = new ArrayList<byte[]>();
vertexColorsMap.put(materialNumber, vertexColorsList);
}
List<Vector3f> normalList = normalMap.get(materialNumber);
if (normalList == null) {
normalList = new ArrayList<Vector3f>();
normalMap.put(materialNumber, normalList);
}
Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber);
if (vertexReferenceMap == null) {
vertexReferenceMap = new HashMap<Integer, List<Integer>>();
globalVertexReferenceMap.put(materialNumber, vertexReferenceMap);
}
// creating faces
Integer[] index = new Integer[] { v1, v2, v3 };
if (smooth && !usesGeneratedTextures) {
for (int i = 0; i < 3; ++i) {
if (!vertexReferenceMap.containsKey(index[i])) {
// if this index is not yet used then create another face
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
if (uvsForFace != null) {
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size());
}
}
vertexList.add(verticesAndNormals[index[i]][0]);
if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(vertexColorIndex[i]));
}
normalList.add(verticesAndNormals[index[i]][1]);
index[i] = vertexList.size() - 1;
} else if (uvsForFace != null) {
// if the index is used then check if the vertexe's UV coordinates match, if yes then the vertex doesn't have separate UV's
// in different faces so we can use it here as well, if UV's are different in separate faces the we need to add this vert
// because in jme one vertex can have only on UV coordinate
boolean vertexAlreadyUsed = false;
for (Integer vertexIndex : vertexReferenceMap.get(index[i])) {
int vertexUseCounter = 0;
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
if (entry.getValue()[i].equals(userUVCollection.getUVForVertex(entry.getKey(), vertexIndex))) {
++vertexUseCounter;
}
}
if (vertexUseCounter == uvsForFace.size()) {
vertexAlreadyUsed = true;
index[i] = vertexIndex;
break;
}
}
if (!vertexAlreadyUsed) {
// treat this face as a new one because its vertices have separate UV's
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size());
}
vertexList.add(verticesAndNormals[index[i]][0]);
if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(vertexColorIndex[i]));
}
normalList.add(verticesAndNormals[index[i]][1]);
index[i] = vertexList.size() - 1;
}
} else {
// use this index again
index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]);
}
indexList.add(index[i]);
}
} else {
Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]);
for (int i = 0; i < 3; ++i) {
indexList.add(vertexList.size());
this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
if (uvsForFace != null) {
for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size());
}
}
vertexList.add(verticesAndNormals[index[i]][0]);
if (verticesColors != null) {
vertexColorsList.add(verticesColors.get(vertexColorIndex[i]));
}
normalList.add(smooth ? verticesAndNormals[index[i]][1] : n);
}
}
}
/**
* The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates.
* But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning.
* For bmesh they are enlisted just like they are stored in the blend file (in loops).
* For traditional faces every 4 UV's should be assigned for a single face.
* @param meshStructure
* the mesh structure
* @param useBMesh
* tells if we should load the coordinates from loops of from faces
* @return a map that sorts UV coordinates between different UV sets
* @throws BlenderFileException
* an exception is thrown when problems with blend file occur
*/
@SuppressWarnings("unchecked")
private Map<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure, boolean useBMesh) throws BlenderFileException {
Map<String, List<Vector2f>> result = new HashMap<String, List<Vector2f>>();
if (useBMesh) {
// in this case the UV's are assigned to vertices (an array is the same length as the vertex array)
Structure loopData = (Structure) meshStructure.getFieldValue("ldata");
Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers");
List<Structure> loopDataLayers = pLoopDataLayers.fetchData();
for (Structure structure : loopDataLayers) {
Pointer p = (Pointer) structure.getFieldValue("data");
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) {
String uvSetName = structure.getFieldValue("name").toString();
List<Structure> uvsStructures = p.fetchData();
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
for (Structure uvStructure : uvsStructures) {
DynamicArray<Number> loopUVS = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue()));
}
result.put(uvSetName, uvs);
}
}
} else {
// in this case UV's are assigned to faces (the array has the same legnth as the faces count)
Structure facesData = (Structure) meshStructure.getFieldValue("fdata");
Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers");
List<Structure> facesDataLayers = pFacesDataLayers.fetchData();
for (Structure structure : facesDataLayers) {
Pointer p = (Pointer) structure.getFieldValue("data");
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) {
String uvSetName = structure.getFieldValue("name").toString();
List<Structure> uvsStructures = p.fetchData();
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
for (Structure uvStructure : uvsStructures) {
DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue()));
uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue()));
uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue()));
uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue()));
}
result.put(uvSetName, uvs);
}
}
}
return result;
}
/**
* This method returns the vertices colors. Each vertex is stored in byte[4] array.
*
* @param meshStructure
* the structure containing the mesh data
* @param blenderContext
* the blender context
* @return a list of vertices colors, each color belongs to a single vertex
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/
private List<byte[]> getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol");
List<byte[]> verticesColors = null;
// it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure)
// so we need to put them right
boolean useBGRA = blenderContext.getBlenderVersion() < 263;
if (pMCol.isNotNull()) {
List<Structure> mCol = pMCol.fetchData();
verticesColors = new ArrayList<byte[]>(mCol.size());
for (Structure color : mCol) {
byte r = ((Number) color.getFieldValue("r")).byteValue();
byte g = ((Number) color.getFieldValue("g")).byteValue();
byte b = ((Number) color.getFieldValue("b")).byteValue();
byte a = ((Number) color.getFieldValue("a")).byteValue();
verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a });
}
}
return verticesColors;
}
/**
* @return a map that maps vertex index from reference array to its indices in the result list
*/
public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() {
return globalVertexReferenceMap;
}
/**
* @param materialNumber
* the material index
* @return result vertices array
*/
private Vector3f[] getVertices(int materialNumber) {
return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]);
}
/**
* @param materialNumber
* the material index
* @return the amount of result vertices
*/
private int getVerticesAmount(int materialNumber) {
return vertexMap.get(materialNumber).size();
}
/**
* @param materialNumber
* the material index
* @return normals result array
*/
private Vector3f[] getNormals(int materialNumber) {
return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]);
}
/**
* @param materialNumber
* the material index
* @return the vertices colors buffer or null if no vertex colors is set
*/
private ByteBuffer getVertexColorsBuffer(int materialNumber) {
ByteBuffer result = null;
if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) {
List<byte[]> data = vertexColorsMap.get(materialNumber);
result = BufferUtils.createByteBuffer(4 * data.size());
for (byte[] v : data) {
if (v != null) {
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
} else {
result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0);
}
}
result.flip();
}
return result;
}
/**
* @param materialNumber
* the material number that is appied to the mesh
* @return UV coordinates of vertices that belong to the required mesh part
*/
public LinkedHashMap<String, List<Vector2f>> getUVCoordinates(int materialNumber) {
return userUVCollection.getUVCoordinates(materialNumber);
}
/**
* @return indicates if the mesh has UV coordinates
*/
public boolean hasUVCoordinates() {
return userUVCollection.hasUVCoordinates();
}
/**
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
*/
public boolean isEmpty() {
return vertexMap.size() == 0;
}
/**
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key
* - the reference indices list.
*
* @param basicVertexIndex
* the index of the vertex from its basic table
* @param resultIndex
* the index of the vertex in its result vertex list
* @param vertexReferenceMap
* the reference map
*/
private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {
List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
if (referenceList == null) {
referenceList = new ArrayList<Integer>();
vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
}
referenceList.add(Integer.valueOf(resultIndex));
}
}

@ -1,160 +0,0 @@
package com.jme3.scene.plugins.blender.meshes.builders;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.util.BufferUtils;
/**
* A builder that creates a lines mesh. The result is made of lines that do not belong to any face.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class LineMeshBuilder {
private static final Logger LOGGER = Logger.getLogger(LineMeshBuilder.class.getName());
private static final int EDGE_NOT_IN_FACE_FLAG = 0x80;
/** An array of reference vertices. */
private Vector3f[][] verticesAndNormals;
/** The vertices of the mesh. */
private List<Vector3f> vertices = new ArrayList<Vector3f>();
/** The normals of the mesh. */
private List<Vector3f> normals = new ArrayList<Vector3f>();
/**
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
* positions (it simply tells which vertex is referenced where in the result list).
*/
private Map<Integer, List<Integer>> globalVertexReferenceMap;
/**
* Constructor. Stores the given array (not copying it).
* The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied.
* The amount of vertices is always faceCount * 3.
* @param verticesAndNormals
* the reference vertices and normals array
*/
public LineMeshBuilder(Vector3f[][] verticesAndNormals) {
this.verticesAndNormals = verticesAndNormals;
globalVertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAndNormals.length);
}
/**
* The method reads the mesh. It loads only edges that are marked as not belonging to any face in their flag field.
* @param meshStructure
* the mesh structure
* @throws BlenderFileException
* an exception thrown when reading from the blend file fails
*/
public void readMesh(Structure meshStructure) throws BlenderFileException {
LOGGER.fine("Reading line mesh.");
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
if (pMEdge.isNotNull()) {
List<Structure> edges = pMEdge.fetchData();
int vertexIndex = 0;//vertex index in the result mesh
for (Structure edge : edges) {
int flag = ((Number) edge.getFieldValue("flag")).intValue();
if ((flag & EDGE_NOT_IN_FACE_FLAG) != 0) {
int v1 = ((Number) edge.getFieldValue("v1")).intValue();
int v2 = ((Number) edge.getFieldValue("v2")).intValue();
vertices.add(verticesAndNormals[v1][0]);
normals.add(verticesAndNormals[v1][1]);
this.appendVertexReference(v1, vertexIndex++, globalVertexReferenceMap);
vertices.add(verticesAndNormals[v2][0]);
normals.add(verticesAndNormals[v2][1]);
this.appendVertexReference(v2, vertexIndex++, globalVertexReferenceMap);
}
}
}
}
/**
* Builds the meshes.
* @return a map between material index and the mesh
*/
public Map<Integer, Mesh> buildMeshes() {
LOGGER.fine("Building line mesh.");
Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(1);
if (vertices.size() > 0) {
Mesh mesh = new Mesh();
mesh.setMode(Mode.Lines);
LOGGER.fine("Creating indices buffer.");
if (vertices.size() <= Short.MAX_VALUE) {
short[] indices = new short[vertices.size()];
for (int i = 0; i < vertices.size(); ++i) {
indices[i] = (short) i;
}
mesh.setBuffer(Type.Index, 1, indices);
} else {
int[] indices = new int[vertices.size()];
for (int i = 0; i < vertices.size(); ++i) {
indices[i] = i;
}
mesh.setBuffer(Type.Index, 1, indices);
}
LOGGER.fine("Creating vertices buffer.");
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()])));
mesh.setBuffer(verticesBuffer);
LOGGER.fine("Creating normals buffer (in case of lines it is required if skeleton is applied).");
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()])));
mesh.setBuffer(normalsBuffer);
result.put(-1, mesh);
}
return result;
}
/**
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
*/
public boolean isEmpty() {
return vertices == null;
}
public Map<Integer, List<Integer>> getGlobalVertexReferenceMap() {
return globalVertexReferenceMap;
}
/**
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key
* - the reference indices list.
*
* @param basicVertexIndex
* the index of the vertex from its basic table
* @param resultIndex
* the index of the vertex in its result vertex list
* @param vertexReferenceMap
* the reference map
*/
private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {
List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
if (referenceList == null) {
referenceList = new ArrayList<Integer>();
vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
}
referenceList.add(Integer.valueOf(resultIndex));
}
}

@ -1,161 +0,0 @@
package com.jme3.scene.plugins.blender.meshes.builders;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
public class MeshBuilder {
private boolean fixUpAxis;
private PointMeshBuilder pointMeshBuilder;
private LineMeshBuilder lineMeshBuilder;
private FaceMeshBuilder faceMeshBuilder;
/**
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
* positions (it simply tells which vertex is referenced where in the result list).
*/
private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap = new HashMap<Integer, Map<Integer,List<Integer>>>();
public MeshBuilder(Structure meshStructure, MaterialContext[] materials, BlenderContext blenderContext) throws BlenderFileException {
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(meshStructure);
faceMeshBuilder = new FaceMeshBuilder(verticesAndNormals, this.areGeneratedTexturesPresent(materials));
faceMeshBuilder.readMesh(meshStructure, blenderContext);
lineMeshBuilder = new LineMeshBuilder(verticesAndNormals);
lineMeshBuilder.readMesh(meshStructure);
pointMeshBuilder = new PointMeshBuilder(verticesAndNormals);
pointMeshBuilder.readMesh(meshStructure);
}
public Map<Integer, List<Mesh>> buildMeshes() {
Map<Integer, List<Mesh>> result = new HashMap<Integer, List<Mesh>>();
Map<Integer, Mesh> meshes = faceMeshBuilder.buildMeshes();
for (Entry<Integer, Mesh> entry : meshes.entrySet()) {
List<Mesh> meshList = new ArrayList<Mesh>();
meshList.add(entry.getValue());
result.put(entry.getKey(), meshList);
}
meshes = lineMeshBuilder.buildMeshes();
for (Entry<Integer, Mesh> entry : meshes.entrySet()) {
List<Mesh> meshList = result.get(entry.getKey());
if (meshList == null) {
meshList = new ArrayList<Mesh>();
result.put(entry.getKey(), meshList);
}
meshList.add(entry.getValue());
}
meshes = pointMeshBuilder.buildMeshes();
for (Entry<Integer, Mesh> entry : meshes.entrySet()) {
List<Mesh> meshList = result.get(entry.getKey());
if (meshList == null) {
meshList = new ArrayList<Mesh>();
result.put(entry.getKey(), meshList);
}
meshList.add(entry.getValue());
}
globalVertexReferenceMap.putAll(faceMeshBuilder.getVertexReferenceMap());
globalVertexReferenceMap.put(-1, lineMeshBuilder.getGlobalVertexReferenceMap());
globalVertexReferenceMap.get(-1).putAll(pointMeshBuilder.getGlobalVertexReferenceMap());
return result;
}
/**
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
*/
public boolean isEmpty() {
return faceMeshBuilder.isEmpty() && lineMeshBuilder.isEmpty() && pointMeshBuilder.isEmpty();
}
/**
* @return a map that maps vertex index from reference array to its indices in the result list
*/
public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() {
return globalVertexReferenceMap;
}
/**
* @param materialNumber
* the material number that is appied to the mesh
* @return UV coordinates of vertices that belong to the required mesh part
*/
public LinkedHashMap<String, List<Vector2f>> getUVCoordinates(int materialNumber) {
return faceMeshBuilder.getUVCoordinates(materialNumber);
}
/**
* @return indicates if the mesh has UV coordinates
*/
public boolean hasUVCoordinates() {
return faceMeshBuilder.hasUVCoordinates();
}
/**
* This method returns the vertices.
*
* @param meshStructure
* the structure containing the mesh data
* @return a list of two - element arrays, the first element is the vertex and the second - its normal
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/
@SuppressWarnings("unchecked")
private Vector3f[][] getVerticesAndNormals(Structure meshStructure) throws BlenderFileException {
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
Vector3f[][] result = new Vector3f[count][2];
if (count == 0) {
return result;
}
Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
List<Structure> mVerts = pMVert.fetchData();
if (fixUpAxis) {
for (int i = 0; i < count; ++i) {
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f);
}
} else {
for (int i = 0; i < count; ++i) {
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f);
}
}
return result;
}
/**
* @return <b>true</b> if the material has at least one generated component and <b>false</b> otherwise
*/
private boolean areGeneratedTexturesPresent(MaterialContext[] materials) {
if (materials != null) {
for (MaterialContext material : materials) {
if (material != null && material.hasGeneratedTextures()) {
return true;
}
}
}
return false;
}
}

@ -1,171 +0,0 @@
package com.jme3.scene.plugins.blender.meshes.builders;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.util.BufferUtils;
/**
* A builder that creates a points mesh. The result is made of points that do not belong to any edge and face.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class PointMeshBuilder {
private static final Logger LOGGER = Logger.getLogger(PointMeshBuilder.class.getName());
/** An array of reference vertices. */
private Vector3f[][] verticesAndNormals;
/** The vertices of the mesh. */
private List<Vector3f> vertices = new ArrayList<Vector3f>();
/** The normals of the mesh. */
private List<Vector3f> normals = new ArrayList<Vector3f>();
/**
* This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
* positions (it simply tells which vertex is referenced where in the result list).
*/
private Map<Integer, List<Integer>> globalVertexReferenceMap;
/**
* Constructor. Stores the given array (not copying it).
* The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied.
* The amount of vertices is always faceCount * 3.
* @param verticesAndNormals
* the reference vertices and normals array
*/
public PointMeshBuilder(Vector3f[][] verticesAndNormals) {
this.verticesAndNormals = verticesAndNormals;
globalVertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAndNormals.length);
}
/**
* The method reads the mesh. Since blender does not store the information in the vertex itself whether it belongs
* anywhere or not, we need to check all vertices and use here only those that are not used.
* @param meshStructure
* the mesh structure
* @throws BlenderFileException
* an exception thrown when reading from the blend file fails
*/
public void readMesh(Structure meshStructure) throws BlenderFileException {
LOGGER.fine("Reading points mesh.");
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
if (pMEdge.isNotNull()) {
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
Set<Vector3f> usedVertices = new HashSet<Vector3f>(count);
List<Structure> edges = pMEdge.fetchData();
for (Structure edge : edges) {
int v1 = ((Number) edge.getFieldValue("v1")).intValue();
int v2 = ((Number) edge.getFieldValue("v2")).intValue();
usedVertices.add(verticesAndNormals[v1][0]);
usedVertices.add(verticesAndNormals[v2][0]);
}
if (usedVertices.size() < count) {
vertices = new ArrayList<Vector3f>(count - usedVertices.size());
int vertexIndex = 0, blenderVertexIndex = 0;
for (Vector3f[] vertAndNormal : verticesAndNormals) {
if (!usedVertices.contains(vertAndNormal[0])) {
vertices.add(vertAndNormal[0]);
normals.add(vertAndNormal[1]);
this.appendVertexReference(blenderVertexIndex, vertexIndex++, globalVertexReferenceMap);
}
++blenderVertexIndex;
}
}
}
}
/**
* Builds the meshes.
* @return a map between material index and the mesh
*/
public Map<Integer, Mesh> buildMeshes() {
LOGGER.fine("Building point mesh.");
Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(1);
if (vertices.size() > 0) {
Mesh mesh = new Mesh();
mesh.setMode(Mode.Points);
mesh.setPointSize(3);
// the point mesh does not need index buffer, but some modifiers applied by importer need it
// the 'alone point' situation should be quite rare so not too many resources are wasted here
LOGGER.fine("Creating indices buffer.");
if (vertices.size() <= Short.MAX_VALUE) {
short[] indices = new short[vertices.size()];
for (int i = 0; i < vertices.size(); ++i) {
indices[i] = (short) i;
}
mesh.setBuffer(Type.Index, 1, indices);
} else {
int[] indices = new int[vertices.size()];
for (int i = 0; i < vertices.size(); ++i) {
indices[i] = i;
}
mesh.setBuffer(Type.Index, 1, indices);
}
LOGGER.fine("Creating vertices buffer.");
VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()])));
mesh.setBuffer(verticesBuffer);
LOGGER.fine("Creating normals buffer (in case of points it is required if skeleton is applied).");
VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()])));
mesh.setBuffer(normalsBuffer);
result.put(-1, mesh);
}
return result;
}
/**
* @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
*/
public boolean isEmpty() {
return vertices == null;
}
public Map<Integer, List<Integer>> getGlobalVertexReferenceMap() {
return globalVertexReferenceMap;
}
/**
* This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created
* to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key
* - the reference indices list.
*
* @param basicVertexIndex
* the index of the vertex from its basic table
* @param resultIndex
* the index of the vertex in its result vertex list
* @param vertexReferenceMap
* the reference map
*/
private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {
List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
if (referenceList == null) {
referenceList = new ArrayList<Integer>();
vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
}
referenceList.add(Integer.valueOf(resultIndex));
}
}

@ -1,42 +1,20 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
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.Level;
import java.util.logging.Logger;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
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.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.BoneEnvelope;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.MeshContext;
import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup;
import com.jme3.util.BufferUtils;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
/**
* This modifier allows to add bone animation to the object.
@ -44,23 +22,12 @@ import com.jme3.util.BufferUtils;
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ArmatureModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
private static final int FLAG_VERTEX_GROUPS = 0x01;
private static final int FLAG_BONE_ENVELOPES = 0x02;
private static final int FLAG_VERTEX_GROUPS = 0x01;
private static final int FLAG_BONE_ENVELOPES = 0x02;
private Structure armatureObject;
private Skeleton skeleton;
private Structure meshStructure;
/** The wold transform matrix of the armature object. */
private Matrix4f objectWorldMatrix;
/** Old memory address of the mesh that will have the skeleton applied. */
private Long meshOMA;
/** The variable tells if the vertex groups of the mesh should be used to assign verts to bones. */
private boolean useVertexGroups;
/** The variable tells if the bones' envelopes should be used to assign verts to bones. */
private boolean useBoneEnvelopes;
/**
* This constructor reads animation data from the object structore. The
@ -77,16 +44,15 @@ import com.jme3.util.BufferUtils;
* corrupted
*/
public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData().get(0);
if (this.validate(modifierStructure, blenderContext)) {
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
if (pArmatureObject.isNotNull()) {
int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
boolean useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
boolean useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
modifying = useBoneEnvelopes || useVertexGroups;
if (modifying) {// if neither option is used the modifier will not modify anything anyway
armatureObject = pArmatureObject.fetchData().get(0);
Structure armatureObject = pArmatureObject.fetchData().get(0);
// load skeleton
Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
@ -99,16 +65,6 @@ import com.jme3.util.BufferUtils;
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
skeleton = new Skeleton(bones);
blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
this.meshStructure = meshStructure;
// read mesh indexes
meshOMA = meshStructure.getOldMemoryAddress();
if (useBoneEnvelopes) {
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
Spatial object = (Spatial) blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
objectWorldMatrix = constraintHelper.toMatrix(object.getWorldTransform(), new Matrix4f());
}
}
} else {
modifying = false;
@ -116,280 +72,38 @@ import com.jme3.util.BufferUtils;
}
}
/**
* This method builds the object's bones structure.
*
* @param armatureObjectOMA
* the OMa of the armature node
* @param boneStructure
* the structure containing the bones' data
* @param parent
* the parent bone
* @param result
* the list where the newly created bone will be added
* @param spatialOMA
* the OMA of the spatial that will own the skeleton
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there is problem with the blender
* file
*/
private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
bc.buildBone(result, spatialOMA, blenderContext);
}
@Override
@SuppressWarnings("unchecked")
public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
LOGGER.fine("Applying armature modifier after mesh has been created.");
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
node.updateModelBound();
}
@Override
public void apply(Node node, BlenderContext blenderContext) {
if (invalid) {
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
}// if invalid, animData will be null
if (skeleton != null) {
// setting weights for bones
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
for (Geometry geom : geomList) {
int materialIndex = meshContext.getMaterialIndex(geom);
Mesh mesh = geom.getMesh();
MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext);
if (buffers != null) {
mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex);
mesh.setBuffer(buffers.verticesWeights);
mesh.setBuffer(buffers.verticesWeightsIndices);
LOGGER.fine("Generating bind pose and normal buffers.");
mesh.generateBindPose(true);
// change the usage type of vertex and normal buffers from
// Static to Stream
mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
// creating empty buffers for HW skinning
// the buffers will be setup if ever used.
VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
mesh.setBuffer(verticesWeightsHW);
mesh.setBuffer(verticesWeightsIndicesHW);
}
}
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
node.updateModelBound();
}
}
/**
* Reads the vertices data and prepares appropriate buffers to be added to the mesh. There is a bone index buffer and weitghts buffer.
*
* @param meshContext
* the mesh context
* @param skeleton
* the current skeleton
* @param materialIndex
* the material index
* @param mesh
* the mesh we create the buffers for
* @param blenderContext
* the blender context
* @return an instance that aggregates all needed data for the mesh
*/
private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) {
int vertexListSize = meshContext.getVertexCount(materialIndex);
Map<Integer, List<Integer>> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex);
Map<String, VertexGroup> vertexGroups = new HashMap<String, VertexGroup>();
Buffer indexes = mesh.getBuffer(Type.Index).getData();
FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
if (modifying) {
TemporalMesh temporalMesh = this.getTemporalMesh(node);
if (temporalMesh != null) {
LOGGER.log(Level.FINE, "Applying armature modifier to: {0}", temporalMesh);
int maximumWeightsPerVertex = 0;
if (useVertexGroups) {
LOGGER.fine("Attaching verts to bones using vertex groups.");
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
Bone bone = skeleton.getBone(boneIndex);
VertexGroup vertexGroup = meshContext.getGroup(bone.getName());
if (vertexGroup != null) {
vertexGroup.setBoneIndex(boneIndex);
vertexGroups.put(bone.getName(), vertexGroup);
}
}
}
if (useBoneEnvelopes) {
LOGGER.fine("Attaching verts to bones using bone envelopes.");
Vector3f pos = new Vector3f();
for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
Bone bone = skeleton.getBone(boneIndex);
BoneContext boneContext = blenderContext.getBoneContext(bone);
BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope();
if (boneEnvelope != null) {
VertexGroup vertexGroup = vertexGroups.get(bone.getName());
if (vertexGroup == null) {
vertexGroup = new VertexGroup();
vertexGroups.put(bone.getName(), vertexGroup);
}
vertexGroup.setBoneIndex(boneIndex);
for (Entry<Integer, List<Integer>> entry : vertexReferenceMap.entrySet()) {
List<Integer> vertexIndices = entry.getValue();
for (int j = 0; j < indexes.limit(); ++j) {
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j);
if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh
int ii = index * 3;
pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2));
// move the vertex to the global space position
objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references
if (boneEnvelope.isInEnvelope(pos)) {
vertexGroup.addVertex(index, boneEnvelope.getWeight());
} else if (boneIndex == 5) {
System.out.println("Si nie zaapa: " + pos);
}
}
}
}
}
}
}
Map<Integer, WeightsAndBoneIndexes> weights = new HashMap<Integer, WeightsAndBoneIndexes>();// [vertex_index; [bone_index; weight]]
if (vertexGroups.size() > 0) {
LOGGER.fine("Gathering vertex groups information to prepare the buffers for the mesh.");
for (VertexGroup vertexGroup : vertexGroups.values()) {
for (Entry<Integer, Float> entry : vertexGroup.entrySet()) {
WeightsAndBoneIndexes vertexWeights = weights.get(entry.getKey());
if (vertexWeights == null) {
vertexWeights = new WeightsAndBoneIndexes();
weights.put(entry.getKey(), vertexWeights);
}
vertexWeights.put(vertexGroup.getBoneIndex(), entry.getValue());
}
}
LOGGER.log(Level.FINE, "Equalizing the amount of weights per vertex to {0} if any of them has more or less.", MAXIMUM_WEIGHTS_PER_VERTEX);
for (Entry<Integer, WeightsAndBoneIndexes> entry : weights.entrySet()) {
maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size());
entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX);
}
if (maximumWeightsPerVertex > MAXIMUM_WEIGHTS_PER_VERTEX) {
LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;// normalization already made at most 'MAXIMUM_WEIGHTS_PER_VERTEX' weights per vertex
}
}
if(maximumWeightsPerVertex == 0) {
LOGGER.fine("No vertex group data nor bone envelopes found to attach vertices to bones!");
return null;
}
LOGGER.fine("Preparing buffers for the mesh.");
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
for (int i = 0; i < indexes.limit(); ++i) {
int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i);
WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index);
if (weightsAndBoneIndexes != null) {
int count = 0;
for (Entry<Integer, Float> entry : weightsAndBoneIndexes.entrySet()) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue());
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue());
++count;
LOGGER.fine("Creating map between bone name and its index.");
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
Bone bone = skeleton.getBone(i);
temporalMesh.addBoneIndex(bone.getName(), i);
}
temporalMesh.applyAfterMeshCreate(this);
} else {
// if no bone is assigned to this vertex then attach it to the 0-indexed root bone
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
}
}
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
}
/**
* A class that gathers the data for mesh bone buffers.
* Added to increase code readability.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class MeshWeightsData {
public final int maximumWeightsPerVertex;
public final VertexBuffer verticesWeights;
public final VertexBuffer verticesWeightsIndices;
public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
this.maximumWeightsPerVertex = maximumWeightsPerVertex;
this.verticesWeights = verticesWeights;
this.verticesWeightsIndices = verticesWeightsIndices;
}
}
/**
* A map between the bone index and the bone's weight.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class WeightsAndBoneIndexes extends HashMap<Integer, Float> {
private static final long serialVersionUID = 2754299007299077459L;
/**
* The method normalizes the weights and bone indexes data.
* First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
* Next it normalizes the weights so that the sum of all verts is 1.
* @param maximumSize
* the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
*/
public void normalize(int maximumSize) {
if (this.size() > maximumSize) {// select only the most significant weights
float lowestWeight = Float.MAX_VALUE;
int lowestWeightIndex = -1;
HashMap<Integer, Float> msw = new HashMap<Integer, Float>(maximumSize);// msw = Most Significant Weight
for (Entry<Integer, Float> entry : this.entrySet()) {
if (msw.size() < maximumSize) {
msw.put(entry.getKey(), entry.getValue());
if (entry.getValue() < lowestWeight) {
lowestWeight = entry.getValue();
lowestWeightIndex = entry.getKey();
}
} else if (entry.getValue() > lowestWeight) {
msw.remove(lowestWeightIndex);
msw.put(lowestWeightIndex, lowestWeight);
// search again for the lowest weight
lowestWeight = Float.MAX_VALUE;
for (Entry<Integer, Float> e : msw.entrySet()) {
if (e.getValue() < lowestWeight) {
lowestWeight = e.getValue();
lowestWeightIndex = e.getKey();
}
}
}
}
// replace current weights with the given ones
this.clear();
this.putAll(msw);
}
// normalizing the weights so that the sum of the values is equal to '1'
float sum = 0;
for (Entry<Integer, Float> entry : this.entrySet()) {
sum += entry.getValue();
}
if (sum != 0 && sum != 1) {
for (Entry<Integer, Float> entry : this.entrySet()) {
entry.setValue(entry.getValue() / sum);
}
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
}
}
}

@ -1,5 +1,11 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
@ -9,32 +15,32 @@ import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
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.FileBlockHeader;
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.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.scene.shape.Curve;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This modifier allows to array modifier to the object.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ArrayModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName());
private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName());
/** Parameters of the modifier. */
private Map<String, Object> modifierData = new HashMap<String, Object>();
private int fittype;
private int count;
private float length;
private float[] offset;
private float[] scale;
private Pointer pOffsetObject;
private Pointer pStartCap;
private Pointer pEndCap;
/**
* This constructor reads array data from the modifier structure. The
@ -54,18 +60,16 @@ import java.util.logging.Logger;
@SuppressWarnings("unchecked")
public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
if (this.validate(modifierStructure, blenderContext)) {
Number fittype = (Number) modifierStructure.getFieldValue("fit_type");
modifierData.put("fittype", fittype);
switch (fittype.intValue()) {
fittype = ((Number) modifierStructure.getFieldValue("fit_type")).intValue();
switch (fittype) {
case 0:// FIXED COUNT
modifierData.put("count", modifierStructure.getFieldValue("count"));
count = ((Number) modifierStructure.getFieldValue("count")).intValue();
break;
case 1:// FIXED LENGTH
modifierData.put("length", modifierStructure.getFieldValue("length"));
length = ((Number) modifierStructure.getFieldValue("length")).floatValue();
break;
case 2:// FITCURVE
Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob");
float length = 0;
if (pCurveOb.isNotNull()) {
Structure curveStructure = pCurveOb.fetchData().get(0);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
@ -88,8 +92,7 @@ import java.util.logging.Logger;
}
}
}
modifierData.put("length", Float.valueOf(length));
modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH
fittype = 1;// treat it like FIXED LENGTH
break;
default:
assert false : "Unknown array modifier fit type: " + fittype;
@ -99,30 +102,19 @@ import java.util.logging.Logger;
int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue();
if ((offsettype & 0x01) != 0) {// Constant offset
DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset");
float[] offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() };
modifierData.put("offset", offset);
offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() };
}
if ((offsettype & 0x02) != 0) {// Relative offset
DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale");
float[] scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() };
modifierData.put("scale", scale);
scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() };
}
if ((offsettype & 0x04) != 0) {// Object offset
Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");
if (pOffsetObject.isNotNull()) {
modifierData.put("offsetob", pOffsetObject);
}
pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");
}
// start cap and end cap
Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");
if (pStartCap.isNotNull()) {
modifierData.put("startcap", pStartCap);
}
Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");
if (pEndCap.isNotNull()) {
modifierData.put("endcap", pEndCap);
}
pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");
pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");
}
}
@ -131,116 +123,105 @@ import java.util.logging.Logger;
if (invalid) {
LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName());
} else {
int fittype = ((Number) modifierData.get("fittype")).intValue();
float[] offset = (float[]) modifierData.get("offset");
if (offset == null) {// the node will be repeated several times in the same place
offset = new float[] { 0.0f, 0.0f, 0.0f };
}
float[] scale = (float[]) modifierData.get("scale");
if (scale == null) {// the node will be repeated several times in the same place
scale = new float[] { 0.0f, 0.0f, 0.0f };
} else {
// getting bounding box
node.updateModelBound();
BoundingVolume boundingVolume = node.getWorldBound();
if (boundingVolume instanceof BoundingBox) {
scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
} else if (boundingVolume instanceof BoundingSphere) {
float radius = ((BoundingSphere) boundingVolume).getRadius();
scale[0] *= radius * 2.0f;
scale[1] *= radius * 2.0f;
scale[2] *= radius * 2.0f;
TemporalMesh temporalMesh = this.getTemporalMesh(node);
if (temporalMesh != null) {
LOGGER.log(Level.FINE, "Applying array modifier to: {0}", temporalMesh);
if (offset == null) {// the node will be repeated several times in the same place
offset = new float[] { 0.0f, 0.0f, 0.0f };
}
if (scale == null) {// the node will be repeated several times in the same place
scale = new float[] { 0.0f, 0.0f, 0.0f };
} else {
throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
// getting bounding box
temporalMesh.updateModelBound();
BoundingVolume boundingVolume = temporalMesh.getWorldBound();
if (boundingVolume instanceof BoundingBox) {
scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
} else if (boundingVolume instanceof BoundingSphere) {
float radius = ((BoundingSphere) boundingVolume).getRadius();
scale[0] *= radius * 2.0f;
scale[1] *= radius * 2.0f;
scale[2] *= radius * 2.0f;
} else {
throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
}
}
}
// adding object's offset
float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f };
Pointer pOffsetObject = (Pointer) modifierData.get("offsetob");
if (pOffsetObject != null) {
FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
try {// we take the structure in case the object was not yet loaded
Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext);
Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation();
objectOffset[0] = translation.x;
objectOffset[1] = translation.y;
objectOffset[2] = translation.z;
} catch (BlenderFileException e) {
LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());
// adding object's offset
float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f };
if (pOffsetObject != null && pOffsetObject.isNotNull()) {
FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
try {// we take the structure in case the object was not yet loaded
Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext);
Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation();
objectOffset[0] = translation.x;
objectOffset[1] = translation.y;
objectOffset[2] = translation.z;
} catch (BlenderFileException e) {
LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());
}
}
}
// getting start and end caps
Node[] caps = new Node[] { null, null };
Pointer[] pCaps = new Pointer[] { (Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap") };
for (int i = 0; i < pCaps.length; ++i) {
if (pCaps[i] != null) {
caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (caps[i] != null) {
caps[i] = (Node) caps[i].clone();
} else {
FileBlockHeader capBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
// getting start and end caps
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
TemporalMesh[] caps = new TemporalMesh[] { null, null };
Pointer[] pCaps = new Pointer[] { pStartCap, pEndCap };
for (int i = 0; i < pCaps.length; ++i) {
if (pCaps[i].isNotNull()) {
FileBlockHeader capBlock = blenderContext.getFileBlock(pCaps[i].getOldMemoryAddress());
try {// we take the structure in case the object was not yet loaded
Structure capStructure = capBlock.getStructure(blenderContext);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
caps[i] = (Node) objectHelper.toObject(capStructure, blenderContext);
if (caps[i] == null) {
LOGGER.log(Level.WARNING, "Cap object ''{0}'' couldn''t be loaded!", capStructure.getName());
}
Pointer pMesh = (Pointer) capStructure.getFieldValue("data");
List<Structure> meshesArray = pMesh.fetchData();
caps[i] = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext);
} catch (BlenderFileException e) {
LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage());
}
}
}
}
Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
if(blenderContext.getBlenderKey().isFixUpAxis()) {
float y = translationVector.y;
translationVector.y = translationVector.z;
translationVector.z = y == 0 ? 0 : -y;
}
// getting/calculating repeats amount
int count = 0;
if (fittype == 0) {// Fixed count
count = ((Number) modifierData.get("count")).intValue() - 1;
} else if (fittype == 1) {// Fixed length
float length = ((Number) modifierData.get("length")).floatValue();
if (translationVector.length() > 0.0f) {
count = (int) (length / translationVector.length()) - 1;
Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
float y = translationVector.y;
translationVector.y = translationVector.z;
translationVector.z = y == 0 ? 0 : -y;
}
} else if (fittype == 2) {// Fit curve
throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
} else {
throw new IllegalStateException("Unknown fit type: " + fittype);
}
// adding translated nodes and caps
if (count > 0) {
Node[] arrayNodes = new Node[count];
Vector3f newTranslation = new Vector3f();
for (int i = 0; i < count; ++i) {
newTranslation.addLocal(translationVector);
Node nodeClone = (Node) node.clone();
nodeClone.setLocalTranslation(newTranslation);
arrayNodes[i] = nodeClone;
// getting/calculating repeats amount
int count = 0;
if (fittype == 0) {// Fixed count
count = this.count - 1;
} else if (fittype == 1) {// Fixed length
float length = this.length;
if (translationVector.length() > 0.0f) {
count = (int) (length / translationVector.length()) - 1;
}
} else if (fittype == 2) {// Fit curve
throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
} else {
throw new IllegalStateException("Unknown fit type: " + fittype);
}
for (Node nodeClone : arrayNodes) {
node.attachChild(nodeClone);
// adding translated nodes and caps
Vector3f totalTranslation = new Vector3f(translationVector);
if (count > 0) {
TemporalMesh originalMesh = temporalMesh.clone();
for (int i = 0; i < count; ++i) {
temporalMesh.append(originalMesh.clone().translate(totalTranslation));
totalTranslation.addLocal(translationVector);
}
}
if (caps[0] != null) {
caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector);
node.attachChild(caps[0]);
temporalMesh.append(caps[0].clone().translate(translationVector.multLocal(-1)));
}
if (caps[1] != null) {
caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector);
node.attachChild(caps[1]);
temporalMesh.append(caps[1].clone().translate(totalTranslation));
}
} else {
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
}
}
}

@ -1,27 +1,16 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
/**
@ -30,21 +19,22 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
* @author Marcin Roguski (Kaelthas)
*/
/* package */class MirrorModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());
private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());
private static final int FLAG_MIRROR_X = 0x08;
private static final int FLAG_MIRROR_Y = 0x10;
private static final int FLAG_MIRROR_Z = 0x20;
private static final int FLAG_MIRROR_U = 0x02;
private static final int FLAG_MIRROR_V = 0x04;
// private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40;
private static final int FLAG_MIRROR_MERGE = 0x80;
private static final int FLAG_MIRROR_X = 0x08;
private static final int FLAG_MIRROR_Y = 0x10;
private static final int FLAG_MIRROR_Z = 0x20;
private static final int FLAG_MIRROR_U = 0x02;
private static final int FLAG_MIRROR_V = 0x04;
private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40;
private static final int FLAG_MIRROR_MERGE = 0x80;
private boolean[] isMirrored;
private boolean mirrorU, mirrorV;
private boolean merge;
private float tolerance;
private Pointer pMirrorObject;
private boolean mirrorVGroup;
/**
* This constructor reads mirror data from the modifier structure. The
@ -74,11 +64,15 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
}
mirrorU = (flag & FLAG_MIRROR_U) != 0;
mirrorV = (flag & FLAG_MIRROR_V) != 0;
// boolean mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0;
mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0;
merge = (flag & FLAG_MIRROR_MERGE) == 0;// in this case we use == instead of != (this is not a mistake)
tolerance = ((Number) modifierStructure.getFieldValue("tolerance")).floatValue();
pMirrorObject = (Pointer) modifierStructure.getFieldValue("mirror_ob");
if(mirrorVGroup) {
LOGGER.warning("Mirroring vertex groups is currently not supported.");
}
}
}
@ -87,156 +81,75 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
if (invalid) {
LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName());
} else {
Vector3f mirrorPlaneCenter = new Vector3f();
if (pMirrorObject.isNotNull()) {
Structure objectStructure;
try {
objectStructure = pMirrorObject.fetchData().get(0);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Node object = (Node) objectHelper.toObject(objectStructure, blenderContext);
if (object != null) {
// compute the mirror object coordinates in node's local space
mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation());
TemporalMesh temporalMesh = this.getTemporalMesh(node);
if (temporalMesh != null) {
LOGGER.log(Level.FINE, "Applying mirror modifier to: {0}", temporalMesh);
Vector3f mirrorPlaneCenter = new Vector3f();
if (pMirrorObject.isNotNull()) {
Structure objectStructure;
try {
objectStructure = pMirrorObject.fetchData().get(0);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Node object = (Node) objectHelper.toObject(objectStructure, blenderContext);
if (object != null) {
// compute the mirror object coordinates in node's local space
mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation());
}
} catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName());
return;
}
} catch (BlenderFileException e) {
LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage());
LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName());
return;
}
}
LOGGER.finest("Allocating temporal variables.");
float d;
Vector3f mirrorPlaneNormal = new Vector3f();
Vector3f shiftVector = new Vector3f();
Vector3f point = new Vector3f();
Vector3f normal = new Vector3f();
Set<Integer> modifiedIndexes = new HashSet<Integer>();
List<Geometry> geometriesToAdd = new ArrayList<Geometry>();
final char[] mirrorNames = new char[] { 'X', 'Y', 'Z' };
LOGGER.fine("Mirroring mesh.");
for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {
if (isMirrored[mirrorIndex]) {
boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0;
if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space
mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex)));
}
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
Mesh mesh = ((Geometry) spatial).getMesh();
Mesh clone = mesh.deepClone();
LOGGER.log(Level.FINEST, "Fetching buffers of cloned spatial: {0}", spatial.getName());
FloatBuffer position = mesh.getFloatBuffer(Type.Position);
FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition);
FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position);
FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition);
FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal);
FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal);
Buffer cloneIndexes = clone.getBuffer(Type.Index).getData();
for (int i = 0; i < cloneIndexes.limit(); ++i) {
int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer) cloneIndexes).get(i) : ((IntBuffer) cloneIndexes).get(i);
if (!modifiedIndexes.contains(index)) {
modifiedIndexes.add(index);
this.get(clonePosition, index, point);
if (mirrorAtPoint0) {
d = Math.abs(point.get(mirrorIndex));
shiftVector.set(0, 0, 0).set(mirrorIndex, -point.get(mirrorIndex));
} else {
d = this.computeDistanceFromPlane(point, mirrorPlaneCenter, mirrorPlaneNormal);
mirrorPlaneNormal.mult(d, shiftVector);
}
if (merge && d <= tolerance) {
point.addLocal(shiftVector);
this.set(index, point, clonePosition, cloneBindPosePosition, position, bindPosePosition);
if (cloneNormals != null) {
this.get(cloneNormals, index, normal);
normal.set(mirrorIndex, 0);
this.set(index, normal, cloneNormals, cloneBindPoseNormals);
}
} else {
point.addLocal(shiftVector.multLocal(2));
LOGGER.finest("Allocating temporal variables.");
float d;
Vector3f mirrorPlaneNormal = new Vector3f();
Vector3f shiftVector = new Vector3f();
LOGGER.fine("Mirroring mesh.");
for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {
if (isMirrored[mirrorIndex]) {
boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0;
if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space
mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex)));
}
this.set(index, point, clonePosition, cloneBindPosePosition);
if (cloneNormals != null) {
this.get(cloneNormals, index, normal);
normal.set(mirrorIndex, -normal.get(mirrorIndex));
this.set(index, normal, cloneNormals, cloneBindPoseNormals);
}
}
}
TemporalMesh mirror = temporalMesh.clone();
for (int i = 0; i < mirror.getVertexCount(); ++i) {
Vector3f vertex = mirror.getVertex(i);
Vector3f normal = mirror.getNormal(i);
if (mirrorAtPoint0) {
d = Math.abs(vertex.get(mirrorIndex));
shiftVector.set(0, 0, 0).set(mirrorIndex, -vertex.get(mirrorIndex));
} else {
d = this.computeDistanceFromPlane(vertex, mirrorPlaneCenter, mirrorPlaneNormal);
mirrorPlaneNormal.mult(d, shiftVector);
}
modifiedIndexes.clear();
LOGGER.finer("Flipping index order.");
switch (mesh.getMode()) {
case Points:
cloneIndexes.flip();
break;
case Lines:
for (int i = 0; i < cloneIndexes.limit(); i += 2) {
if (cloneIndexes instanceof ShortBuffer) {
short index = ((ShortBuffer) cloneIndexes).get(i + 1);
((ShortBuffer) cloneIndexes).put(i + 1, ((ShortBuffer) cloneIndexes).get(i));
((ShortBuffer) cloneIndexes).put(i, index);
} else {
int index = ((IntBuffer) cloneIndexes).get(i + 1);
((IntBuffer) cloneIndexes).put(i + 1, ((IntBuffer) cloneIndexes).get(i));
((IntBuffer) cloneIndexes).put(i, index);
}
}
break;
case Triangles:
for (int i = 0; i < cloneIndexes.limit(); i += 3) {
if (cloneIndexes instanceof ShortBuffer) {
short index = ((ShortBuffer) cloneIndexes).get(i + 2);
((ShortBuffer) cloneIndexes).put(i + 2, ((ShortBuffer) cloneIndexes).get(i + 1));
((ShortBuffer) cloneIndexes).put(i + 1, index);
} else {
int index = ((IntBuffer) cloneIndexes).get(i + 2);
((IntBuffer) cloneIndexes).put(i + 2, ((IntBuffer) cloneIndexes).get(i + 1));
((IntBuffer) cloneIndexes).put(i + 1, index);
}
}
break;
default:
throw new IllegalStateException("Invalid mesh mode: " + mesh.getMode());
if (merge && d <= tolerance) {
vertex.addLocal(shiftVector);
normal.set(mirrorIndex, 0);
temporalMesh.getVertex(i).addLocal(shiftVector);
temporalMesh.getNormal(i).set(mirrorIndex, 0);
} else {
vertex.addLocal(shiftVector.multLocal(2));
normal.set(mirrorIndex, -normal.get(mirrorIndex));
}
}
if (mirrorU && clone.getBuffer(Type.TexCoord) != null) {
LOGGER.finer("Mirroring U coordinates.");
FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
for (int i = 0; i < cloneUVs.limit(); i += 2) {
cloneUVs.put(i, 1.0f - cloneUVs.get(i));
}
}
if (mirrorV && clone.getBuffer(Type.TexCoord) != null) {
LOGGER.finer("Mirroring V coordinates.");
FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
for (int i = 1; i < cloneUVs.limit(); i += 2) {
cloneUVs.put(i, 1.0f - cloneUVs.get(i));
}
}
mirror.flipIndexes();
Geometry geometry = new Geometry(spatial.getName() + " - mirror " + mirrorNames[mirrorIndex], clone);
geometry.setMaterial(((Geometry) spatial).getMaterial());
geometriesToAdd.add(geometry);
if (mirrorU || mirrorV) {
mirror.flipUV(mirrorU, mirrorV);
}
}
LOGGER.log(Level.FINE, "Adding {0} geometries to current node.", geometriesToAdd.size());
for (Geometry geometry : geometriesToAdd) {
node.attachChild(geometry);
temporalMesh.append(mirror);
}
geometriesToAdd.clear();
}
} else {
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
}
}
}
@ -268,41 +181,4 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
private float computeDistanceFromPlane(Vector3f p, Vector3f c, Vector3f n) {
return Math.abs(n.dot(p) - c.dot(n));
}
/**
* Sets the given value (v) into every of the buffers at the given index.
* The index is cosidered to be an index of a vertex of the mesh.
* @param index
* the index of vertex of the mesh
* @param value
* the value to be set
* @param buffers
* the buffers where the value will be set
*/
private void set(int index, Vector3f value, FloatBuffer... buffers) {
index *= 3;
for (FloatBuffer buffer : buffers) {
if (buffer != null) {
buffer.put(index, value.x);
buffer.put(index + 1, value.y);
buffer.put(index + 2, value.z);
}
}
}
/**
* Fetches the vector's value from the given buffer at specified index.
* @param buffer
* the buffer we get the data from
* @param index
* the index of vertex of the mesh
* @param store
* the vector where the result will be set
*/
private void get(FloatBuffer buffer, int index, Vector3f store) {
index *= 3;
store.x = buffer.get(index);
store.y = buffer.get(index + 1);
store.z = buffer.get(index + 2);
}
}

@ -1,9 +1,13 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.util.List;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
/**
* This class represents an object's modifier. The modifier object can be varied
@ -40,6 +44,16 @@ public abstract class Modifier {
*/
public abstract void apply(Node node, BlenderContext blenderContext);
/**
* The method that is called when geometries are already created.
* @param node
* the node that will have the modifier applied
* @param blenderContext
* the blender context
*/
public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
}
/**
* Determines if the modifier can be applied multiple times over one mesh.
* At this moment only armature and object animation modifiers cannot be
@ -67,4 +81,12 @@ public abstract class Modifier {
public boolean isModifying() {
return modifying;
}
protected TemporalMesh getTemporalMesh(Node node) {
List<Spatial> children = node.getChildren();
if (children != null && children.size() == 1 && children.get(0) instanceof TemporalMesh) {
return (TemporalMesh) children.get(0);
}
return null;
}
}

@ -1,5 +1,10 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.shapes.EmitterMeshVertexShape;
import com.jme3.effect.shapes.EmitterShape;
@ -13,13 +18,9 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.particles.ParticlesHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This modifier allows to add particles to the object.
*
@ -55,40 +56,52 @@ import java.util.logging.Logger;
}
@Override
public void apply(Node node, BlenderContext blenderContext) {
if (invalid) {
LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName());
} else {
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
ParticleEmitter emitter = particleEmitter.clone();
public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
LOGGER.log(Level.FINE, "Applying particles modifier to: {0}", node);
// veryfying the alpha function for particles' texture
Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);
if (nameSuffix == 'B' || nameSuffix == 'N') {
alphaFunction = MaterialHelper.ALPHA_MASK_NONE;
}
// removing the type suffix from the name
emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
ParticleEmitter emitter = particleEmitter.clone();
// veryfying the alpha function for particles' texture
Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);
if (nameSuffix == 'B' || nameSuffix == 'N') {
alphaFunction = MaterialHelper.ALPHA_MASK_NONE;
}
// removing the type suffix from the name
emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));
// applying emitter shape
EmitterShape emitterShape = emitter.getShape();
List<Mesh> meshes = new ArrayList<Mesh>();
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
Mesh mesh = ((Geometry) spatial).getMesh();
if (mesh != null) {
meshes.add(mesh);
Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext);
emitter.setMaterial(material);// TODO: divide into several pieces
}
// applying emitter shape
EmitterShape emitterShape = emitter.getShape();
List<Mesh> meshes = new ArrayList<Mesh>();
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
Mesh mesh = ((Geometry) spatial).getMesh();
if (mesh != null) {
meshes.add(mesh);
Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext);
emitter.setMaterial(material);// TODO: divide into several pieces
}
}
if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
}
}
if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
}
node.attachChild(emitter);
}
node.attachChild(emitter);
@Override
public void apply(Node node, BlenderContext blenderContext) {
if (invalid) {
LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName());
} else {
TemporalMesh temporalMesh = this.getTemporalMesh(node);
if(temporalMesh != null) {
temporalMesh.applyAfterMeshCreate(this);
} else {
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
}
}
}
}

@ -0,0 +1,54 @@
package com.jme3.scene.plugins.blender.modifiers;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.scene.Node;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
/**
* The triangulation modifier. It does not take any settings into account so if the result is different than
* in blender then please apply the modifier before importing.
*
* @author Marcin Roguski
*/
public class TriangulateModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(TriangulateModifier.class.getName());
/**
* This constructor reads animation data from the object structore. The
* stored data is the AnimData and additional data is armature's OMA.
*
* @param objectStructure
* the structure of the object
* @param modifierStructure
* the structure of the modifier
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public TriangulateModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
if (this.validate(modifierStructure, blenderContext)) {
LOGGER.warning("Triangulation modifier does not take modifier options into account. If triangulation result is different" + " than the model in blender please apply the modifier before importing!");
}
}
@Override
public void apply(Node node, BlenderContext blenderContext) {
if (invalid) {
LOGGER.log(Level.WARNING, "Triangulate modifier is invalid! Cannot be applied to: {0}", node.getName());
}
TemporalMesh temporalMesh = this.getTemporalMesh(node);
if (temporalMesh != null) {
LOGGER.log(Level.FINE, "Applying triangulation modifier to: {0}", temporalMesh);
temporalMesh.triangulate();
} else {
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
}
}
}

@ -53,7 +53,7 @@ import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.VertexBuffer.Type;
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.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
import com.jme3.scene.plugins.blender.cameras.CameraHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
@ -64,6 +64,7 @@ import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.lights.LightHelper;
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.modifiers.Modifier;
import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
import com.jme3.util.TempVars;
@ -129,7 +130,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
}
LOGGER.fine("Checking if the object has not been already loaded.");
Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
if (loadedResult != null) {
return loadedResult;
}
@ -142,7 +143,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
boolean visible = (restrictflag & 0x01) != 0;
Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedDataType.FEATURE);
if (parent == null && pParent.isNotNull()) {
Structure parentStructure = pParent.fetchData().get(0);
parent = this.toObject(parentStructure, blenderContext);
@ -153,6 +154,11 @@ public class ObjectHelper extends AbstractBlenderHelper {
Node result = null;
try {
switch (objectType) {
case LATTICE:
case METABALL:
case TEXT:
case WAVE:
LOGGER.log(Level.WARNING, "{0} type is not supported but the node will be returned in order to keep parent - child relationship.", objectType);
case EMPTY:
case ARMATURE:
// need to use an empty node to properly create
@ -164,11 +170,9 @@ public class ObjectHelper extends AbstractBlenderHelper {
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
Pointer pMesh = (Pointer) objectStructure.getFieldValue("data");
List<Structure> meshesArray = pMesh.fetchData();
List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);
if (geometries != null) {
for (Geometry geometry : geometries) {
result.attachChild(geometry);
}
TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext);
if(temporalMesh != null) {
result.attachChild(temporalMesh);
}
break;
case SURF:
@ -207,59 +211,68 @@ public class ObjectHelper extends AbstractBlenderHelper {
default:
LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type);
}
} finally {
blenderContext.popParent();
}
if (result != null) {
LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released).");
blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
if (objectType == ObjectType.ARMATURE) {
blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
}
if (result != null) {
LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released).");
Long oma = objectStructure.getOldMemoryAddress();
blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure);
blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result);
result.setLocalTransform(t);
result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
if (parent instanceof Node) {
((Node) parent).attachChild(result);
}
blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
if (objectType == ObjectType.ARMATURE) {
blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
}
if (result.getChildren() != null) {
for (Spatial child : result.getChildren()) {
if (child instanceof Geometry) {
this.flipMeshIfRequired((Geometry) child, child.getWorldScale());
}
result.setLocalTransform(t);
result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
if (parent instanceof Node) {
((Node) parent).attachChild(result);
}
}
LOGGER.fine("Reading and applying object's modifiers.");
ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
for (Modifier modifier : modifiers) {
modifier.apply(result, blenderContext);
}
LOGGER.fine("Reading and applying object's modifiers.");
ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
for (Modifier modifier : modifiers) {
modifier.apply(result, blenderContext);
}
if (result.getChildren() != null && result.getChildren().size() > 0) {
if(result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) {
LOGGER.fine("Converting temporal mesh into jme geometries.");
((TemporalMesh)result.getChild(0)).toGeometries();
}
LOGGER.fine("Applying proper scale to the geometries.");
for (Spatial child : result.getChildren()) {
if (child instanceof Geometry) {
this.flipMeshIfRequired((Geometry) child, child.getWorldScale());
}
}
}
// I prefer do compute bounding box here than read it from the file
result.updateModelBound();
// I prefer do compute bounding box here than read it from the file
result.updateModelBound();
LOGGER.fine("Applying animations to the object if such are defined.");
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod());
LOGGER.fine("Applying animations to the object if such are defined.");
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod());
LOGGER.fine("Loading constraints connected with this object.");
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.loadConstraints(objectStructure, blenderContext);
LOGGER.fine("Loading constraints connected with this object.");
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.loadConstraints(objectStructure, blenderContext);
LOGGER.fine("Loading custom properties.");
if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
Properties properties = this.loadProperties(objectStructure, blenderContext);
// the loaded property is a group property, so we need to get
// each value and set it to Spatial
if (properties != null && properties.getValue() != null) {
this.applyProperties(result, properties);
LOGGER.fine("Loading custom properties.");
if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
Properties properties = this.loadProperties(objectStructure, blenderContext);
// the loaded property is a group property, so we need to get
// each value and set it to Spatial
if (properties != null && properties.getValue() != null) {
this.applyProperties(result, properties);
}
}
}
} finally {
blenderContext.popParent();
}
return result;
}
@ -323,8 +336,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
* @return <b>true</b> if the first given OMA points to a parent of the second one and <b>false</b> otherwise
*/
public boolean isParent(Long supposedParentOMA, Long spatialOMA) {
Spatial supposedParent = (Spatial) blenderContext.getLoadedFeature(supposedParentOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial supposedParent = (Spatial) blenderContext.getLoadedFeature(supposedParentOMA, LoadedDataType.FEATURE);
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(spatialOMA, LoadedDataType.FEATURE);
Spatial parent = spatial.getParent();
while (parent != null) {
@ -350,7 +363,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
Matrix4f parentInv = tempVars.tempMat4;
Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
if (pParent.isNotNull()) {
Structure parentObjectStructure = (Structure) blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_STRUCTURE);
Structure parentObjectStructure = (Structure) blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedDataType.STRUCTURE);
this.getMatrix(parentObjectStructure, "obmat", fixUpAxis, parentInv).invertLocal();
} else {
parentInv.loadIdentity();

@ -5,8 +5,8 @@ import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -17,9 +17,10 @@ import com.jme3.math.Vector2f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement;
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType;
@ -34,6 +35,7 @@ import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureCubeMap;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
/**
@ -132,8 +134,7 @@ public class CombinedTexture {
* @param blenderContext
* the blender context
*/
@SuppressWarnings("unchecked")
public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
public void flatten(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
Mesh mesh = geometry.getMesh();
Texture previousTexture = null;
UVCoordinatesType masterUVCoordinatesType = null;
@ -159,8 +160,8 @@ public class CombinedTexture {
}
masterUserUVSetName = textureData.uvCoordinatesName;
} else {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries);
TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH);
resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, temporalMesh);
}
}
this.blend(resultTexture, textureData.textureBlender, blenderContext);
@ -198,7 +199,7 @@ public class CombinedTexture {
textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
}
} else {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
TemporalMesh geometries = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH);
textureUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries);
}
TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext);
@ -297,7 +298,7 @@ public class CombinedTexture {
for (int i = 0; i < 6; ++i) {
data.add(BufferUtils.clone(sourceData));
}
texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data));
texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data, ColorSpace.Linear));
}
if (result == null) {

@ -9,11 +9,11 @@ import com.jme3.bounding.BoundingBox;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement;
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator;
@ -152,9 +152,8 @@ import com.jme3.util.TempVars;
* the blender context
* @return triangulated texture
*/
@SuppressWarnings("unchecked")
public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) {
List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
TemporalMesh geometries = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH);
int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() };
List<Vector3f> uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries);

@ -51,7 +51,7 @@ import com.jme3.math.Vector2f;
import com.jme3.scene.VertexBuffer.Type;
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.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
@ -69,6 +69,7 @@ import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;
/**
@ -130,7 +131,7 @@ public class TextureHelper extends AbstractBlenderHelper {
* somehow invalid or corrupted
*/
public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException {
Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedDataType.FEATURE);
if (result != null) {
return result;
}
@ -200,7 +201,8 @@ public class TextureHelper extends AbstractBlenderHelper {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() });
}
blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), tex.getName(), tex, result);
blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.STRUCTURE, tex);
blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
}
return result;
}
@ -223,7 +225,7 @@ public class TextureHelper extends AbstractBlenderHelper {
protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress());
Texture result = null;
Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
if (im == null) {
String texturePath = imageStructure.getFieldValue("name").toString();
Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile");
@ -340,7 +342,7 @@ public class TextureHelper extends AbstractBlenderHelper {
int height = maxY - minY;
ByteBuffer data = BufferUtils.createByteBuffer(width * height * (image.getFormat().getBitsPerPixel() >> 3));
Image result = new Image(image.getFormat(), width, height, data);
Image result = new Image(image.getFormat(), width, height, data, ColorSpace.sRGB);
PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
TexturePixel pixel = new TexturePixel();

@ -31,6 +31,10 @@
*/
package com.jme3.scene.plugins.blender.textures;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
@ -41,10 +45,6 @@ import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* This class is used for UV coordinates generation.
@ -90,7 +90,7 @@ public class UVCoordinatesGenerator {
* bounding box)
* @return UV coordinates for the given mesh
*/
public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List<Geometry> geometries) {
public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, Geometry geometries) {
List<Vector2f> result = new ArrayList<Vector2f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc.
@ -166,7 +166,7 @@ public class UVCoordinatesGenerator {
* bounding box)
* @return UV coordinates for the given mesh
*/
public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List<Geometry> geometries) {
public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, Geometry... geometries) {
List<Vector3f> result = new ArrayList<Vector3f>();
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
float[] inputData = null;// positions, normals, reflection vectors, etc.
@ -264,40 +264,24 @@ public class UVCoordinatesGenerator {
* the list of geometries
* @return bounding box of the given geometries
*/
public static BoundingBox getBoundingBox(List<Geometry> geometries) {
public static BoundingBox getBoundingBox(Geometry... geometries) {
BoundingBox result = null;
for (Geometry geometry : geometries) {
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh());
if (result == null) {
result = bb;
geometry.updateModelBound();
BoundingVolume bv = geometry.getModelBound();
if (bv instanceof BoundingBox) {
return (BoundingBox) bv;
} else if (bv instanceof BoundingSphere) {
BoundingSphere bs = (BoundingSphere) bv;
float r = bs.getRadius();
return new BoundingBox(bs.getCenter(), r, r, r);
} else {
result.merge(bb);
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
}
return result;
}
/**
* This method returns the bounding box of the given mesh.
*
* @param mesh
* the mesh
* @return bounding box of the given mesh
*/
/* package */static BoundingBox getBoundingBox(Mesh mesh) {
mesh.updateBound();
BoundingVolume bv = mesh.getBound();
if (bv instanceof BoundingBox) {
return (BoundingBox) bv;
} else if (bv instanceof BoundingSphere) {
BoundingSphere bs = (BoundingSphere) bv;
float r = bs.getRadius();
return new BoundingBox(bs.getCenter(), r, r, r);
} else {
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
}
/**
* This method returns the bounding sphere of the given geometries.
*
@ -305,74 +289,25 @@ public class UVCoordinatesGenerator {
* the list of geometries
* @return bounding sphere of the given geometries
*/
/* package */static BoundingSphere getBoundingSphere(List<Geometry> geometries) {
/* package */static BoundingSphere getBoundingSphere(Geometry... geometries) {
BoundingSphere result = null;
for (Geometry geometry : geometries) {
BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh());
if (result == null) {
result = bs;
geometry.updateModelBound();
BoundingVolume bv = geometry.getModelBound();
if (bv instanceof BoundingBox) {
BoundingBox bb = (BoundingBox) bv;
float r = Math.max(bb.getXExtent(), bb.getYExtent());
r = Math.max(r, bb.getZExtent());
return new BoundingSphere(r, bb.getCenter());
} else if (bv instanceof BoundingSphere) {
return (BoundingSphere) bv;
} else {
result.merge(bs);
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
}
return result;
}
/**
* This method returns the bounding sphere of the given mesh.
*
* @param mesh
* the mesh
* @return bounding sphere of the given mesh
*/
/* package */static BoundingSphere getBoundingSphere(Mesh mesh) {
mesh.updateBound();
BoundingVolume bv = mesh.getBound();
if (bv instanceof BoundingBox) {
BoundingBox bb = (BoundingBox) bv;
float r = Math.max(bb.getXExtent(), bb.getYExtent());
r = Math.max(r, bb.getZExtent());
return new BoundingSphere(r, bb.getCenter());
} else if (bv instanceof BoundingSphere) {
return (BoundingSphere) bv;
} else {
throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
}
}
/**
* This method returns the bounding tube of the given mesh.
*
* @param mesh
* the mesh
* @return bounding tube of the given mesh
*/
/* package */static BoundingTube getBoundingTube(Mesh mesh) {
Vector3f center = new Vector3f();
float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE;
float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE;
float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE;
FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
int limit = positions.limit();
for (int i = 0; i < limit; i += 3) {
float x = positions.get(i);
float y = positions.get(i + 1);
float z = positions.get(i + 2);
center.addLocal(x, y, z);
maxx = x > maxx ? x : maxx;
minx = x < minx ? x : minx;
maxy = y > maxy ? y : maxy;
miny = y < miny ? y : miny;
maxz = z > maxz ? z : maxz;
minz = z < minz ? z : minz;
}
center.divideLocal(limit / 3);
float radius = Math.max(maxx - minx, maxy - miny) * 0.5f;
return new BoundingTube(radius, maxz - minz, center);
}
/**
* This method returns the bounding tube of the given geometries.
*
@ -380,10 +315,15 @@ public class UVCoordinatesGenerator {
* the list of geometries
* @return bounding tube of the given geometries
*/
/* package */static BoundingTube getBoundingTube(List<Geometry> geometries) {
/* package */static BoundingTube getBoundingTube(Geometry... geometries) {
BoundingTube result = null;
for (Geometry geometry : geometries) {
BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh());
BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry);
Vector3f max = bb.getMax(null);
Vector3f min = bb.getMin(null);
float radius = Math.max(max.x - min.x, max.y - min.y) * 0.5f;
BoundingTube bt = new BoundingTube(radius, max.z - min.z, bb.getCenter());
if (result == null) {
result = bt;
} else {
@ -394,7 +334,7 @@ public class UVCoordinatesGenerator {
}
/**
* A very simple bounding tube. Id holds only the basic data bout the
* A very simple bounding tube. It holds only the basic data bout the
* bounding tube and does not provide full functionality of a
* BoundingVolume. Should be replaced with a bounding tube that extends the
* BoundingVolume if it is ever created.
@ -432,7 +372,7 @@ public class UVCoordinatesGenerator {
public BoundingTube merge(BoundingTube boundingTube) {
// get tubes (tube1.radius >= tube2.radius)
BoundingTube tube1, tube2;
if (this.radius >= boundingTube.radius) {
if (radius >= boundingTube.radius) {
tube1 = this;
tube2 = boundingTube;
} else {

Loading…
Cancel
Save