Feature: new triangulation modifier.
This commit is contained in:
parent
124b5e51da
commit
6e21b0527c
@ -124,7 +124,9 @@ 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.
|
||||
*/
|
||||
@ -456,6 +458,22 @@ public class BlenderKey extends ModelKey {
|
||||
public AnimationMatchMethod getAnimationMatchMethod() {
|
||||
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
|
||||
@ -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,129 +117,15 @@ 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);
|
||||
|
||||
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);
|
||||
}
|
||||
geometries.add(geometry);
|
||||
meshContext.putGeometry(materialIndex, geometry);
|
||||
}
|
||||
}
|
||||
|
||||
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<Structure> defs = defbase.evaluateListBase();
|
||||
for (Structure def : defs) {
|
||||
meshContext.addVertexGroup(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) {
|
||||
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();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return geometries;
|
||||
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, meshStructure);
|
||||
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH, temporalMesh);
|
||||
return temporalMesh.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,7 +141,190 @@ public class MeshHelper extends AbstractBlenderHelper {
|
||||
return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull();
|
||||
}
|
||||
|
||||
private synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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>>();
|
||||
|
||||
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) {
|
||||
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();
|
||||
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);
|
||||
|
||||
weightsForVertex.put(groupName, weight);
|
||||
}
|
||||
}
|
||||
result.add(weightsForVertex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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,17 +44,16 @@ 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);
|
||||
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
|
||||
@ -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
|
||||
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
|
||||
@SuppressWarnings("unchecked")
|
||||
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);
|
||||
|
||||
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;
|
||||
if (modifying) {
|
||||
TemporalMesh temporalMesh = this.getTemporalMesh(node);
|
||||
if (temporalMesh != null) {
|
||||
LOGGER.log(Level.FINE, "Applying armature modifier to: {0}", temporalMesh);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
// 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 {
|
||||
FileBlockHeader capBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
|
||||
throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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());
|
||||
}
|
||||
} 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));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (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));
|
||||
}
|
||||
}
|
||||
|
||||
Geometry geometry = new Geometry(spatial.getName() + " - mirror " + mirrorNames[mirrorIndex], clone);
|
||||
geometry.setMaterial(((Geometry) spatial).getMaterial());
|
||||
geometriesToAdd.add(geometry);
|
||||
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;
|
||||
}
|
||||
|
||||
LOGGER.log(Level.FINE, "Adding {0} geometries to current node.", geometriesToAdd.size());
|
||||
for (Geometry geometry : geometriesToAdd) {
|
||||
node.attachChild(geometry);
|
||||
}
|
||||
geometriesToAdd.clear();
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
mirror.flipIndexes();
|
||||
|
||||
if (mirrorU || mirrorV) {
|
||||
mirror.flipUV(mirrorU, mirrorV);
|
||||
}
|
||||
|
||||
temporalMesh.append(mirror);
|
||||
}
|
||||
}
|
||||
} 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.
|
||||
*
|
||||
@ -53,42 +54,54 @@ import java.util.logging.Logger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
|
||||
LOGGER.log(Level.FINE, "Applying particles modifier to: {0}", node);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
|
||||
((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
|
||||
((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
|
||||
}
|
||||
|
||||
node.attachChild(emitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
@ -137,12 +138,12 @@ public class ObjectHelper extends AbstractBlenderHelper {
|
||||
blenderContext.pushParent(objectStructure);
|
||||
String name = objectStructure.getName();
|
||||
LOGGER.log(Level.FINE, "Loading obejct: {0}", name);
|
||||
|
||||
|
||||
int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue();
|
||||
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).");
|
||||
Long oma = objectStructure.getOldMemoryAddress();
|
||||
blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure);
|
||||
blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, 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).");
|
||||
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);
|
||||
}
|
||||
result.setLocalTransform(t);
|
||||
result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
|
||||
if (parent instanceof Node) {
|
||||
((Node) parent).attachChild(result);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.getChildren() != null) {
|
||||
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();
|
||||
|
||||
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 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("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);
|
||||
}
|
||||
|
||||
// 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("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);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
@ -56,7 +56,7 @@ import com.jme3.util.TempVars;
|
||||
private final Structure mTex;
|
||||
/** Texture generateo for the specified texture type. */
|
||||
private final TextureGenerator textureGenerator;
|
||||
/**
|
||||
/**
|
||||
* The generated texture cast functions. They are used to cas a given point on a plane to a specified shape in 3D space.
|
||||
* The functions should be ordered as the ordinal of a BlenderKey.CastFunction enums.
|
||||
*/
|
||||
@ -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.
|
||||
@ -55,7 +55,7 @@ public class UVCoordinatesGenerator {
|
||||
private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName());
|
||||
|
||||
public static enum UVCoordinatesType {
|
||||
TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128),
|
||||
TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128),
|
||||
TEXCO_STICKY(256), TEXCO_OSA(512), TEXCO_WINDOW(1024), NEED_UV(2048), TEXCO_TANGENT(4096),
|
||||
TEXCO_PARTICLE_OR_STRAND(8192), //TEXCO_PARTICLE (since blender 2.6x) has also the value of: 8192 but is used for halo materials instead of normal materials
|
||||
TEXCO_STRESS(16384), TEXCO_SPEED(32768);
|
||||
@ -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…
x
Reference in New Issue
Block a user