From 6e21b0527c1d129c656d14644c6ca49fae211d7c Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sat, 30 Aug 2014 11:52:38 +0200 Subject: [PATCH] Feature: new triangulation modifier. --- .../main/java/com/jme3/asset/BlenderKey.java | 26 +- .../blender/AbstractBlenderHelper.java | 2 +- .../scene/plugins/blender/BlenderContext.java | 70 +- .../blender/animations/AnimationHelper.java | 5 +- .../blender/animations/BoneContext.java | 7 +- .../blender/constraints/BoneConstraint.java | 4 +- .../blender/constraints/ConstraintHelper.java | 10 +- .../blender/constraints/SimulationNode.java | 4 +- .../constraints/SpatialConstraint.java | 4 +- .../definitions/ConstraintDefinition.java | 4 +- .../ConstraintDefinitionTransLike.java | 4 +- .../plugins/blender/lights/LightHelper.java | 6 +- .../blender/materials/MaterialContext.java | 4 +- .../blender/materials/MaterialHelper.java | 8 +- .../scene/plugins/blender/meshes/Edge.java | 222 +++++++ .../scene/plugins/blender/meshes/Face.java | 533 ++++++++++++++++ .../plugins/blender/meshes/IndexesLoop.java | 258 ++++++++ .../plugins/blender/meshes/MeshBuffers.java | 364 +++++++++++ .../plugins/blender/meshes/MeshContext.java | 185 ------ .../plugins/blender/meshes/MeshHelper.java | 328 ++++++---- .../scene/plugins/blender/meshes/Point.java | 88 +++ .../plugins/blender/meshes/TemporalMesh.java | 599 ++++++++++++++++++ .../meshes/builders/FaceMeshBuilder.java | 586 ----------------- .../meshes/builders/LineMeshBuilder.java | 160 ----- .../blender/meshes/builders/MeshBuilder.java | 161 ----- .../meshes/builders/PointMeshBuilder.java | 171 ----- .../blender/modifiers/ArmatureModifier.java | 340 +--------- .../blender/modifiers/ArrayModifier.java | 237 ++++--- .../blender/modifiers/MirrorModifier.java | 266 +++----- .../plugins/blender/modifiers/Modifier.java | 22 + .../blender/modifiers/ParticlesModifier.java | 81 ++- .../modifiers/TriangulateModifier.java | 54 ++ .../plugins/blender/objects/ObjectHelper.java | 123 ++-- .../blender/textures/CombinedTexture.java | 17 +- .../blender/textures/GeneratedTexture.java | 9 +- .../blender/textures/TextureHelper.java | 12 +- .../textures/UVCoordinatesGenerator.java | 134 ++-- 37 files changed, 2788 insertions(+), 2320 deletions(-) create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java delete mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java delete mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java delete mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java delete mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java delete mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java diff --git a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java index 555150ea2..8a2206ed1 100644 --- a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java +++ b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java @@ -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; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java index 4338d08d4..eff993f76 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java @@ -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 propertyNames = properties.getSubPropertiesNames(); if (propertyNames != null && propertyNames.size() > 0) { for (String propertyName : propertyNames) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java index 67241e54e..cde38e327 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java @@ -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 loadedFeatures = new HashMap(); - /** - * 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 loadedFeaturesByName = new HashMap(); + private Map> loadedFeatures = new HashMap>(); /** A stack that hold the parent structure of currently loaded feature. */ private Stack parentStack = new Stack(); /** A list of constraints for the specified object. */ @@ -107,8 +99,6 @@ public class BlenderContext { private Map skeletons = new HashMap(); /** A map between skeleton and node it modifies. */ private Map nodesWithSkeletons = new HashMap(); - /** A map of mesh contexts. */ - protected Map meshContexts = new HashMap(); /** A map of bone contexts. */ protected Map boneContexts = new HashMap(); /** 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 map = loadedFeatures.get(oldMemoryAddress); + if(map == null) { + map = new HashMap(); + 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 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 null 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; } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java index b058ae665..1b5a40e79 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java @@ -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; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java index 8cd8dba67..b081431a6 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -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(); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java index bfbc57d86..bab8f843d 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java @@ -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; diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java index 2e8505d12..1624e36cb 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java @@ -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); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java index 156cd6bad..1e1b2caa4 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java @@ -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); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java index 887986c4a..5284a800e 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java @@ -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; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java index 7c51c8204..8b3f9fd15 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java @@ -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()); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java index 826982ac5..d58907ea9 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java @@ -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); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java index 914d6f6b1..1880a6542 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java @@ -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; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java index 96cb36d49..8fec0afec 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java @@ -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> userDefinedUVCoordinates, BlenderContext blenderContext) { + public void applyMaterial(Geometry geometry, Long geometriesOMA, Map> userDefinedUVCoordinates, BlenderContext blenderContext) { Material material = null; if (shadeless) { material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java index f4fff839c..5fcccc389 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java @@ -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; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java new file mode 100644 index 000000000..c968feeac --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java @@ -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 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 true 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 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 result = new ArrayList(); + + Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); + + if (pMEdge.isNotNull()) { + List 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; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java new file mode 100644 index 000000000..0aa9ae1d1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java @@ -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 { + 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> faceUVCoords; + /** The vertex colors of the face. */ + private List 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> faceUVCoords, List 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>(faceUVCoords.size()); + for (Entry> entry : faceUVCoords.entrySet()) { + List uvs = new ArrayList(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(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 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 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 detachedFaces = new ArrayList(); + + 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 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> 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> 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> 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 triangulate(List vertices, List normals) { + LOGGER.fine("Triangulating face."); + assert indexes.size() >= 3 : "Invalid indexes amount for face. 3 is the required minimum!"; + List result = new ArrayList(); + + List facesToTriangulate = new ArrayList(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 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> faceUVS = new HashMap>(); + for (Entry> entry : faceUVCoords.entrySet()) { + List uvs = new ArrayList(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 vertexColors = null; + if (this.vertexColors != null) { + vertexColors = new ArrayList(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 true if the face is smooth and false 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 getVertexColors() { + return vertexColors; + } + + @Override + public String toString() { + return "Face " + indexes; + } + + /** + * The method finds the closest vertex to the one specified by index. + * 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 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 true if the given edge is contained within the face and false 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 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 loadAll(Structure meshStructure, Map> userUVGroups, List verticesColors, TemporalMesh temporalMesh, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Loading all faces from mesh: {0}", meshStructure.getName()); + List result = new ArrayList(); + 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 polys = pMPoly.fetchData(); + List 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> uvCoords = new HashMap>(); + for (Entry> entry : userUVGroups.entrySet()) { + List uvs = entry.getValue().subList(loopStart, loopStart + totLoop); + uvCoords.put(entry.getKey(), new ArrayList(uvs)); + } + + List vertexColors = null; + if (verticesColors != null && verticesColors.size() > 0) { + vertexColors = new ArrayList(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 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> faceUVCoords = new HashMap>(); + for (Entry> entry : userUVGroups.entrySet()) { + List uvCoordsForASingleFace = new ArrayList(vertCount); + for (int j = 0; j < vertCount; ++j) { + uvCoordsForASingleFace.add(entry.getValue().get(i * 4 + j)); + } + faceUVCoords.put(entry.getKey(), uvCoordsForASingleFace); + } + + List vertexColors = null; + if (verticesColors != null && verticesColors.size() > 0) { + vertexColors = new ArrayList(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); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java new file mode 100644 index 000000000..27244e2d1 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java @@ -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, Iterable { + /** The indexes. */ + private List nodes; + /** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */ + private Map> edges = new HashMap>(); + + /** + * 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(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 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(Arrays.asList(nodes.get(i + 1)))); + } + } + edges.put(nodes.get(nodes.size() - 1), new ArrayList(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 true if the edge was removed and false 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 true if the given indexes are neighbours and false 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 nodes = new ArrayList(this.nodes.size()); + for (Integer node : this.nodes) { + nodes.add(node + shift); + } + + Map> edges = new HashMap>(); + for (Entry> entry : this.edges.entrySet()) { + List neighbours = new ArrayList(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 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 getAll() { + return new ArrayList(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 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 findPath(Integer start, Integer end) { + List result = new ArrayList(); + Integer node = start; + while (!node.equals(end)) { + result.add(node); + List 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 iterator() { + return nodes.iterator(); + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java new file mode 100644 index 000000000..0ec120bad --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java @@ -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 verts = new ArrayList(); + /** The normals. */ + private List normals = new ArrayList(); + /** The UV coordinate sets. */ + private Map> uvCoords = new HashMap>(); + /** The vertex colors. */ + private List vertColors = new ArrayList(); + /** The indexes. */ + private List indexes = new ArrayList(); + /** 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> boneWeightAndIndexes = new ArrayList>(); + + /** + * 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 boneBuffersData : boneWeightAndIndexes) { + if (boneBuffersData.size() > 0) { + int count = 0; + for (Entry 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> getUvCoords() { + return uvCoords; + } + + /** + * @return true if vertex colors are used and false 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 true if indexes can be shorts' and false 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> uvCoords, byte[][] vertColors, List> 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 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> entry : uvCoords.entrySet()) { + if (this.uvCoords.containsKey(entry.getKey())) { + this.uvCoords.get(entry.getKey()).add(entry.getValue().get(i)); + } else { + List uvs = new ArrayList(); + uvs.add(entry.getValue().get(i)); + this.uvCoords.put(entry.getKey(), uvs); + } + } + } + + if (vertexGroups != null && vertexGroups.size() > 0) { + Map group = vertexGroups.get(i); + maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, group.size()); + boneWeightAndIndexes.add(new TreeMap(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 getUVsForVertex(int vertexIndex, Map> uvs) { + if (uvs == null || uvs.size() == 0) { + return null; + } + Map result = new HashMap(uvs.size()); + for (Entry> 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 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 entry : uvCoords.entrySet()) { + List 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 group : boneWeightAndIndexes) { + if (group.size() > maximumSize) { + NavigableMap descendingWeights = group.descendingMap(); + while (descendingWeights.size() > maximumSize) { + descendingWeights.pollLastEntry(); + } + } + + // normalizing the weights so that the sum of the values is equal to '1' + TreeMap normalizedGroup = new TreeMap(); + float sum = 0; + for (Entry entry : group.entrySet()) { + sum += entry.getKey(); + } + + if (sum != 0 && sum != 1) { + for (Entry 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; + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java deleted file mode 100644 index 5bdd23b6b..000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java +++ /dev/null @@ -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> geometries = new HashMap>(); - /** The vertex reference map. */ - private Map>> 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 vertexGroups = new LinkedHashMap(); - - /** - * 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 geomList = geometries.get(materialIndex); - if (geomList == null) { - geomList = new ArrayList(); - 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> 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> getVertexReferenceMap(int materialIndex) { - return vertexReferenceMap.get(materialIndex); - } - - /** - * This method sets the vertex reference map. - * - * @param vertexReferenceMap - * the vertex reference map - */ - public void setVertexReferenceMap(Map>> 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 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 { - 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; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index d9bdd9a55..14ced2026 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -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 toMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - List geometries = (List) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); - if (geometries != null) { - List copiedGeometries = new ArrayList(geometries.size()); - for (Geometry geometry : geometries) { - copiedGeometries.add(geometry.clone()); - } - return copiedGeometries; + public TemporalMesh toTemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Loading temporal mesh named: {0}.", meshStructure.getName()); + TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH); + if (temporalMesh != null) { + LOGGER.fine("The mesh is already loaded. Returning its clone."); + return temporalMesh.clone(); } String name = meshStructure.getName(); - MeshContext meshContext = new MeshContext(); LOGGER.log(Level.FINE, "Reading mesh: {0}.", name); + temporalMesh = new TemporalMesh(meshStructure, blenderContext); LOGGER.fine("Loading materials."); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); @@ -120,145 +117,214 @@ public class MeshHelper extends AbstractBlenderHelper { if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { materials = materialHelper.getMaterials(meshStructure, blenderContext); } - - LOGGER.fine("Reading vertices."); - MeshBuilder meshBuilder = new MeshBuilder(meshStructure, materials, blenderContext); - if (meshBuilder.isEmpty()) { - LOGGER.fine("The geometry is empty."); - geometries = new ArrayList(0); - blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries); - blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext); - return geometries; - } - meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap()); + temporalMesh.setMaterials(materials); LOGGER.fine("Reading custom properties."); Properties properties = this.loadProperties(meshStructure, blenderContext); + temporalMesh.setProperties(properties); + + blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, meshStructure); + blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH, temporalMesh); + return temporalMesh.clone(); + } - LOGGER.fine("Generating meshes."); - Map> meshes = meshBuilder.buildMeshes(); - geometries = new ArrayList(meshes.size()); - for (Entry> meshEntry : meshes.entrySet()) { - int materialIndex = meshEntry.getKey(); - for (Mesh mesh : meshEntry.getValue()) { - LOGGER.fine("Preparing the result part."); - Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); - if (properties != null && properties.getValue() != null) { - this.applyProperties(geometry, properties); + /** + * Tells if the given mesh structure supports BMesh. + * + * @param meshStructure + * the mesh structure + * @return true if BMesh is supported and false otherwise + */ + public boolean isBMeshCompatible(Structure meshStructure) { + Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); + Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); + return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); + } + + /** + * This method returns the vertices. + * + * @param meshStructure + * the structure containing the mesh data + * @return a list of two - element arrays, the first element is the vertex and the second - its normal + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + @SuppressWarnings("unchecked") + public void loadVerticesAndNormals(Structure meshStructure, List vertices, List 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 mVerts = pMVert.fetchData(); + Vector3f co = null, no = null; + if (fixUpAxis) { + for (int i = 0; i < count; ++i) { + DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); + co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); + vertices.add(co); + + DynamicArray norm = (DynamicArray) 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 coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); + co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); + vertices.add(co); + + DynamicArray norm = (DynamicArray) mVerts.get(i).getFieldValue("no"); + no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(1).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f); + normals.add(no); } - geometries.add(geometry); - meshContext.putGeometry(materialIndex, geometry); } } + LOGGER.log(Level.FINE, "Loaded {0} vertices and normals.", vertices.size()); + } + + /** + * This method returns the vertices colors. Each vertex is stored in byte[4] array. + * + * @param meshStructure + * the structure containing the mesh data + * @param blenderContext + * the blender context + * @return a list of vertices colors, each color belongs to a single vertex or empty list of colors are not specified + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + public List 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 verticesColors = new ArrayList(); + // 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 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> loadUVCoordinates(Structure meshStructure) throws BlenderFileException { + LOGGER.log(Level.FINE, "Loading UV coordinates from mesh: {0}.", meshStructure.getName()); + LinkedHashMap> result = new LinkedHashMap>(); + 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 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 uvsStructures = p.fetchData(); + List uvs = new ArrayList(uvsStructures.size()); + for (Structure uvStructure : uvsStructures) { + DynamicArray loopUVS = (DynamicArray) 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 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 uvsStructures = p.fetchData(); + List uvs = new ArrayList(uvsStructures.size()); + for (Structure uvStructure : uvsStructures) { + DynamicArray mFaceUVs = (DynamicArray) 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> loadVerticesGroups(Structure meshStructure) throws BlenderFileException { + LOGGER.log(Level.FINE, "Loading vertices groups from mesh: {0}.", meshStructure.getName()); + List> result = new ArrayList>(); - 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 groupNames = new ArrayList(); List defs = defbase.evaluateListBase(); for (Structure def : defs) { - meshContext.addVertexGroup(def.getFieldValue("name").toString()); + groupNames.add(def.getFieldValue("name").toString()); } Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices if (pDvert.isNotNull()) {// assigning weights and bone indices List dverts = pDvert.fetchData(); - int blenderVertexIndex = 0; for (Structure dvert : dverts) { + Map weightsForVertex = new HashMap(); Pointer pDW = (Pointer) dvert.getFieldValue("dw"); if (pDW.isNotNull()) { List dw = pDW.fetchData(); for (Structure deformWeight : dw) { int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue(); float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); + String groupName = groupNames.get(groupIndex); - // we need to use JME vertex index here and NOT blender vertex index - for (Entry>> vertexReferenceMap : meshBuilder.getVertexReferenceMap().entrySet()) {// iterate through the meshes [key is the material index] - for (Entry> 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> 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 uvCoordsBuffer = null; - if (meshBuilder.hasUVCoordinates()) { - Map> uvs = meshBuilder.getUVCoordinates(0); - if (uvs != null && uvs.size() > 0) { - uvCoordsBuffer = new ArrayList(uvs.size()); - int uvIndex = 0; - for (Entry> entry : uvs.entrySet()) { - VertexBuffer buffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[uvIndex++]); - buffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(entry.getValue().toArray(new Vector2f[uvs.size()]))); - uvCoordsBuffer.add(buffer); - } - } - } - - for (Geometry geometry : geometries) { - Mode mode = geometry.getMesh().getMode(); - if (mode != Mode.Triangles && mode != Mode.TriangleFan && mode != Mode.TriangleStrip) { - geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext)); - } else { - Material defaultMaterial = blenderContext.getDefaultMaterial(); - if (geometry.getMesh().getBuffer(Type.Color) != null) { - defaultMaterial = defaultMaterial.clone(); - defaultMaterial.setBoolean("VertexColor", true); - } - geometry.setMaterial(defaultMaterial); - } - if (uvCoordsBuffer != null) { - for (VertexBuffer buffer : uvCoordsBuffer) { - geometry.getMesh().setBuffer(buffer); + weightsForVertex.put(groupName, weight); } } + result.add(weightsForVertex); } } - - return geometries; + return result; } /** - * Tells if the given mesh structure supports BMesh. - * - * @param meshStructure - * the mesh structure - * @return true if BMesh is supported and false otherwise + * Returns the black unshaded material. It is used for lines and points because that is how blender + * renders it. + * @param blenderContext + * the blender context + * @return black unshaded material */ - public boolean isBMeshCompatible(Structure meshStructure) { - Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); - Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); - return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); - } - - private synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) { + public synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) { if (blackUnshadedMaterial == null) { blackUnshadedMaterial = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); blackUnshadedMaterial.setColor("Color", ColorRGBA.Black); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java new file mode 100644 index 000000000..90bfb72cd --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java @@ -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 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 result = new ArrayList(); + + Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); + if (pMEdge.isNotNull()) { + int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); + Set unusedVertices = new HashSet(count); + for (int i = 0; i < count; ++i) { + unusedVertices.add(i); + } + + List 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; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java new file mode 100644 index 000000000..049217742 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java @@ -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 vertices = new ArrayList(); + /** Loaded normals. */ + protected List normals = new ArrayList(); + /** Loaded vertex groups. */ + protected List> vertexGroups = new ArrayList>(); + /** Loaded vertex colors. */ + protected List verticesColors = new ArrayList(); + + /** Materials used by the mesh. */ + protected MaterialContext[] materials; + /** The properties of the mesh. */ + protected Properties properties; + /** The bone indexes. */ + protected Map boneIndexes = new HashMap(); + /** The modifiers that should be applied after the mesh has been created. */ + protected List postMeshCreationModifiers = new ArrayList(); + + /** The faces of the mesh. */ + protected List faces = new ArrayList(); + /** The edges of the mesh. */ + protected List edges = new ArrayList(); + /** The points of the mesh. */ + protected List points = new ArrayList(); + + /** 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> 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 group : vertexGroups) { + result.vertexGroups.add(new HashMap(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 getVertices() { + return vertices; + } + + /** + * @return the normals of the mesh + */ + protected List 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 triangulatedFaces = new ArrayList(); + 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 result = new ArrayList(); + 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 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> boneBuffers = new ArrayList>(3); + + LOGGER.log(Level.FINE, "Appending {0} faces to mesh buffers.", faces.size()); + Map faceMeshes = new HashMap(); + for (Face face : faces) { + MeshBuffers meshBuffers = faceMeshes.get(face.getMaterialNumber()); + if (meshBuffers == null) { + meshBuffers = new MeshBuffers(face.getMaterialNumber()); + faceMeshes.put(face.getMaterialNumber(), meshBuffers); + } + + List indexes = face.getIndexes(); + List 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 boneBuffersForVertex = new HashMap(); + Map vertexGroupsForVertex = vertexGroups.get(vertIndex); + for (Entry 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 geometryToBuffersMap = new HashMap(); + for (Entry 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 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 result, MeshHelper meshHelper) { + if (edges.size() > 0) { + LOGGER.fine("Preparing lines geometries."); + + List> separateEdges = new ArrayList>(); + List edges = new ArrayList(this.edges); + while (edges.size() > 0) { + boolean edgeAppended = false; + int edgeIndex = 0; + for (List 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(Arrays.asList(edge.getFirstIndex(), edge.getSecondIndex()))); + } + } + + for (List 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 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() + "]"; + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java deleted file mode 100644 index 0d694ed80..000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java +++ /dev/null @@ -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 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>> globalVertexReferenceMap; - /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ - private Map> normalMap = new HashMap>(); - /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ - private Map> vertexMap = new HashMap>(); - /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */ - private Map> vertexColorsMap = new HashMap>(); - /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */ - private Map> indexMap = new HashMap>(); - /** 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>>(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 buildMeshes() { - Map result = new HashMap(indexMap.size()); - - for (Entry> 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 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 uvCoordinatesForFace = new HashMap(); - - if (pMPoly.isNotNull() && pMLoop.isNotNull()) { - Map> uvs = this.loadUVCoordinates(meshStructure, true); - List polys = pMPoly.fetchData(); - List 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> 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 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> uvs = this.loadUVCoordinates(meshStructure, false); - Map uvCoordinatesForFace = new HashMap(); - 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> 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> 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 uvsForFace, int[] vertexColorIndex) { - if (uvsForFace != null && uvsForFace.size() > 0) { - for (Entry 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 indexList = indexMap.get(materialNumber); - if (indexList == null) { - indexList = new ArrayList(); - indexMap.put(materialNumber, indexList); - } - List vertexList = vertexMap.get(materialNumber); - if (vertexList == null) { - vertexList = new ArrayList(); - vertexMap.put(materialNumber, vertexList); - } - List vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null; - if (vertexColorsList == null && vertexColorsMap != null) { - vertexColorsList = new ArrayList(); - vertexColorsMap.put(materialNumber, vertexColorsList); - } - List normalList = normalMap.get(materialNumber); - if (normalList == null) { - normalList = new ArrayList(); - normalMap.put(materialNumber, normalList); - } - Map> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber); - if (vertexReferenceMap == null) { - vertexReferenceMap = new HashMap>(); - 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 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 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 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 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> loadUVCoordinates(Structure meshStructure, boolean useBMesh) throws BlenderFileException { - Map> result = new HashMap>(); - 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 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 uvsStructures = p.fetchData(); - List uvs = new ArrayList(uvsStructures.size()); - for (Structure uvStructure : uvsStructures) { - DynamicArray loopUVS = (DynamicArray) 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 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 uvsStructures = p.fetchData(); - List uvs = new ArrayList(uvsStructures.size()); - for (Structure uvStructure : uvsStructures) { - DynamicArray mFaceUVs = (DynamicArray) 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 getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol"); - List 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 mCol = pMCol.fetchData(); - verticesColors = new ArrayList(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>> 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 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> getUVCoordinates(int materialNumber) { - return userUVCollection.getUVCoordinates(materialNumber); - } - - /** - * @return indicates if the mesh has UV coordinates - */ - public boolean hasUVCoordinates() { - return userUVCollection.hasUVCoordinates(); - } - - /** - * @return true if the mesh has no vertices and false 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> vertexReferenceMap) { - List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); - if (referenceList == null) { - referenceList = new ArrayList(); - vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); - } - referenceList.add(Integer.valueOf(resultIndex)); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java deleted file mode 100644 index 4191484d4..000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java +++ /dev/null @@ -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 vertices = new ArrayList(); - /** The normals of the mesh. */ - private List normals = new ArrayList(); - - /** - * 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> 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>(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 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 buildMeshes() { - LOGGER.fine("Building line mesh."); - Map result = new HashMap(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 true if the mesh has no vertices and false otherwise - */ - public boolean isEmpty() { - return vertices == null; - } - - public Map> 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> vertexReferenceMap) { - List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); - if (referenceList == null) { - referenceList = new ArrayList(); - vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); - } - referenceList.add(Integer.valueOf(resultIndex)); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java deleted file mode 100644 index 12cfe4920..000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java +++ /dev/null @@ -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>> globalVertexReferenceMap = new HashMap>>(); - - 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> buildMeshes() { - Map> result = new HashMap>(); - - Map meshes = faceMeshBuilder.buildMeshes(); - for (Entry entry : meshes.entrySet()) { - List meshList = new ArrayList(); - meshList.add(entry.getValue()); - result.put(entry.getKey(), meshList); - } - - meshes = lineMeshBuilder.buildMeshes(); - for (Entry entry : meshes.entrySet()) { - List meshList = result.get(entry.getKey()); - if (meshList == null) { - meshList = new ArrayList(); - result.put(entry.getKey(), meshList); - } - meshList.add(entry.getValue()); - } - - meshes = pointMeshBuilder.buildMeshes(); - for (Entry entry : meshes.entrySet()) { - List meshList = result.get(entry.getKey()); - if (meshList == null) { - meshList = new ArrayList(); - 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 true if the mesh has no vertices and false 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>> 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> 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 mVerts = pMVert.fetchData(); - if (fixUpAxis) { - for (int i = 0; i < count; ++i) { - DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); - result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); - - DynamicArray normals = (DynamicArray) 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 coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); - result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); - - DynamicArray normals = (DynamicArray) 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 true if the material has at least one generated component and false otherwise - */ - private boolean areGeneratedTexturesPresent(MaterialContext[] materials) { - if (materials != null) { - for (MaterialContext material : materials) { - if (material != null && material.hasGeneratedTextures()) { - return true; - } - } - } - return false; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java deleted file mode 100644 index 57e99100b..000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java +++ /dev/null @@ -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 vertices = new ArrayList(); - /** The normals of the mesh. */ - private List normals = new ArrayList(); - - /** - * 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> 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>(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 usedVertices = new HashSet(count); - List 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(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 buildMeshes() { - LOGGER.fine("Building point mesh."); - Map result = new HashMap(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 true if the mesh has no vertices and false otherwise - */ - public boolean isEmpty() { - return vertices == null; - } - - public Map> 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> vertexReferenceMap) { - List referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex)); - if (referenceList == null) { - referenceList = new ArrayList(); - vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList); - } - referenceList.add(Integer.valueOf(resultIndex)); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java index f12a3cb39..8e1051150 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java @@ -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 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 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 geomList = (List) 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> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex); - - Map vertexGroups = new HashMap(); - 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> entry : vertexReferenceMap.entrySet()) { - List 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 weights = new HashMap();// [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 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 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 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 { - 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 msw = new HashMap(maximumSize);// msw = Most Significant Weight - for (Entry 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 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 entry : this.entrySet()) { - sum += entry.getValue(); - } - - if (sum != 0 && sum != 1) { - for (Entry 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); } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java index 1c6b52b5f..4a34a84d5 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java @@ -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 modifierData = new HashMap(); + 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 offsetArray = (DynamicArray) 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 scaleArray = (DynamicArray) modifierStructure.getFieldValue("scale"); - float[] scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() }; - modifierData.put("scale", scale); + scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() }; } if ((offsettype & 0x04) != 0) {// Object offset - Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); - if (pOffsetObject.isNotNull()) { - modifierData.put("offsetob", pOffsetObject); - } + pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); } // start cap and end cap - Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); - if (pStartCap.isNotNull()) { - modifierData.put("startcap", pStartCap); - } - Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); - if (pEndCap.isNotNull()) { - modifierData.put("endcap", pEndCap); - } + pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); + pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); } } @@ -131,116 +123,105 @@ import java.util.logging.Logger; if (invalid) { LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName()); } else { - int fittype = ((Number) modifierData.get("fittype")).intValue(); - float[] offset = (float[]) modifierData.get("offset"); - if (offset == null) {// the node will be repeated several times in the same place - offset = new float[] { 0.0f, 0.0f, 0.0f }; - } - float[] scale = (float[]) modifierData.get("scale"); - if (scale == null) {// the node will be repeated several times in the same place - scale = new float[] { 0.0f, 0.0f, 0.0f }; - } else { - // getting bounding box - node.updateModelBound(); - BoundingVolume boundingVolume = node.getWorldBound(); - if (boundingVolume instanceof BoundingBox) { - scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f; - scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f; - scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f; - } else if (boundingVolume instanceof BoundingSphere) { - float radius = ((BoundingSphere) boundingVolume).getRadius(); - scale[0] *= radius * 2.0f; - scale[1] *= radius * 2.0f; - scale[2] *= radius * 2.0f; + TemporalMesh temporalMesh = this.getTemporalMesh(node); + if (temporalMesh != null) { + LOGGER.log(Level.FINE, "Applying array modifier to: {0}", temporalMesh); + if (offset == null) {// the node will be repeated several times in the same place + offset = new float[] { 0.0f, 0.0f, 0.0f }; + } + if (scale == null) {// the node will be repeated several times in the same place + scale = new float[] { 0.0f, 0.0f, 0.0f }; } else { - throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName()); + // getting bounding box + temporalMesh.updateModelBound(); + BoundingVolume boundingVolume = temporalMesh.getWorldBound(); + if (boundingVolume instanceof BoundingBox) { + scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f; + scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f; + scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f; + } else if (boundingVolume instanceof BoundingSphere) { + float radius = ((BoundingSphere) boundingVolume).getRadius(); + scale[0] *= radius * 2.0f; + scale[1] *= radius * 2.0f; + scale[2] *= radius * 2.0f; + } else { + throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName()); + } } - } - - // adding object's offset - float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f }; - Pointer pOffsetObject = (Pointer) modifierData.get("offsetob"); - if (pOffsetObject != null) { - FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - try {// we take the structure in case the object was not yet loaded - Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext); - Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation(); - objectOffset[0] = translation.x; - objectOffset[1] = translation.y; - objectOffset[2] = translation.z; - } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage()); + + // adding object's offset + float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f }; + if (pOffsetObject != null && pOffsetObject.isNotNull()) { + FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + try {// we take the structure in case the object was not yet loaded + Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext); + Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation(); + objectOffset[0] = translation.x; + objectOffset[1] = translation.y; + objectOffset[2] = translation.z; + } catch (BlenderFileException e) { + LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage()); + } } - } - - // getting start and end caps - Node[] caps = new Node[] { null, null }; - Pointer[] pCaps = new Pointer[] { (Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap") }; - for (int i = 0; i < pCaps.length; ++i) { - if (pCaps[i] != null) { - caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); - if (caps[i] != null) { - caps[i] = (Node) caps[i].clone(); - } else { - FileBlockHeader capBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); + + // getting start and end caps + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + TemporalMesh[] caps = new TemporalMesh[] { null, null }; + Pointer[] pCaps = new Pointer[] { pStartCap, pEndCap }; + for (int i = 0; i < pCaps.length; ++i) { + if (pCaps[i].isNotNull()) { + FileBlockHeader capBlock = blenderContext.getFileBlock(pCaps[i].getOldMemoryAddress()); try {// we take the structure in case the object was not yet loaded Structure capStructure = capBlock.getStructure(blenderContext); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - caps[i] = (Node) objectHelper.toObject(capStructure, blenderContext); - if (caps[i] == null) { - LOGGER.log(Level.WARNING, "Cap object ''{0}'' couldn''t be loaded!", capStructure.getName()); - } + Pointer pMesh = (Pointer) capStructure.getFieldValue("data"); + List 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); } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java index aba576020..67fc453fe 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java @@ -1,27 +1,16 @@ package com.jme3.scene.plugins.blender.modifiers; -import java.nio.Buffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.math.Matrix4f; import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.objects.ObjectHelper; /** @@ -30,21 +19,22 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; * @author Marcin Roguski (Kaelthas) */ /* package */class MirrorModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); + private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); - private static final int FLAG_MIRROR_X = 0x08; - private static final int FLAG_MIRROR_Y = 0x10; - private static final int FLAG_MIRROR_Z = 0x20; - private static final int FLAG_MIRROR_U = 0x02; - private static final int FLAG_MIRROR_V = 0x04; - // private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40; - private static final int FLAG_MIRROR_MERGE = 0x80; + private static final int FLAG_MIRROR_X = 0x08; + private static final int FLAG_MIRROR_Y = 0x10; + private static final int FLAG_MIRROR_Z = 0x20; + private static final int FLAG_MIRROR_U = 0x02; + private static final int FLAG_MIRROR_V = 0x04; + private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40; + private static final int FLAG_MIRROR_MERGE = 0x80; private boolean[] isMirrored; private boolean mirrorU, mirrorV; private boolean merge; private float tolerance; private Pointer pMirrorObject; + private boolean mirrorVGroup; /** * This constructor reads mirror data from the modifier structure. The @@ -74,11 +64,15 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; } mirrorU = (flag & FLAG_MIRROR_U) != 0; mirrorV = (flag & FLAG_MIRROR_V) != 0; - // boolean mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0; + mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0; merge = (flag & FLAG_MIRROR_MERGE) == 0;// in this case we use == instead of != (this is not a mistake) tolerance = ((Number) modifierStructure.getFieldValue("tolerance")).floatValue(); pMirrorObject = (Pointer) modifierStructure.getFieldValue("mirror_ob"); + + if(mirrorVGroup) { + LOGGER.warning("Mirroring vertex groups is currently not supported."); + } } } @@ -87,156 +81,75 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; if (invalid) { LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); } else { - Vector3f mirrorPlaneCenter = new Vector3f(); - if (pMirrorObject.isNotNull()) { - Structure objectStructure; - try { - objectStructure = pMirrorObject.fetchData().get(0); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Node object = (Node) objectHelper.toObject(objectStructure, blenderContext); - if (object != null) { - // compute the mirror object coordinates in node's local space - mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation()); + TemporalMesh temporalMesh = this.getTemporalMesh(node); + if (temporalMesh != null) { + LOGGER.log(Level.FINE, "Applying mirror modifier to: {0}", temporalMesh); + Vector3f mirrorPlaneCenter = new Vector3f(); + if (pMirrorObject.isNotNull()) { + Structure objectStructure; + try { + objectStructure = pMirrorObject.fetchData().get(0); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Node object = (Node) objectHelper.toObject(objectStructure, blenderContext); + if (object != null) { + // compute the mirror object coordinates in node's local space + mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation()); + } + } catch (BlenderFileException e) { + LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage()); + LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName()); + return; } - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage()); - LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName()); - return; } - } - - LOGGER.finest("Allocating temporal variables."); - float d; - Vector3f mirrorPlaneNormal = new Vector3f(); - Vector3f shiftVector = new Vector3f(); - Vector3f point = new Vector3f(); - Vector3f normal = new Vector3f(); - Set modifiedIndexes = new HashSet(); - List geometriesToAdd = new ArrayList(); - final char[] mirrorNames = new char[] { 'X', 'Y', 'Z' }; - - LOGGER.fine("Mirroring mesh."); - for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) { - if (isMirrored[mirrorIndex]) { - boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0; - if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space - mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex))); - } - - for (Spatial spatial : node.getChildren()) { - if (spatial instanceof Geometry) { - Mesh mesh = ((Geometry) spatial).getMesh(); - Mesh clone = mesh.deepClone(); - LOGGER.log(Level.FINEST, "Fetching buffers of cloned spatial: {0}", spatial.getName()); - FloatBuffer position = mesh.getFloatBuffer(Type.Position); - FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition); - - FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position); - FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition); - FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal); - FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal); - Buffer cloneIndexes = clone.getBuffer(Type.Index).getData(); - - for (int i = 0; i < cloneIndexes.limit(); ++i) { - int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer) cloneIndexes).get(i) : ((IntBuffer) cloneIndexes).get(i); - if (!modifiedIndexes.contains(index)) { - modifiedIndexes.add(index); - - this.get(clonePosition, index, point); - if (mirrorAtPoint0) { - d = Math.abs(point.get(mirrorIndex)); - shiftVector.set(0, 0, 0).set(mirrorIndex, -point.get(mirrorIndex)); - } else { - d = this.computeDistanceFromPlane(point, mirrorPlaneCenter, mirrorPlaneNormal); - mirrorPlaneNormal.mult(d, shiftVector); - } - - if (merge && d <= tolerance) { - point.addLocal(shiftVector); - - this.set(index, point, clonePosition, cloneBindPosePosition, position, bindPosePosition); - if (cloneNormals != null) { - this.get(cloneNormals, index, normal); - normal.set(mirrorIndex, 0); - this.set(index, normal, cloneNormals, cloneBindPoseNormals); - } - } else { - point.addLocal(shiftVector.multLocal(2)); + LOGGER.finest("Allocating temporal variables."); + float d; + Vector3f mirrorPlaneNormal = new Vector3f(); + Vector3f shiftVector = new Vector3f(); + + LOGGER.fine("Mirroring mesh."); + for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) { + if (isMirrored[mirrorIndex]) { + boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0; + if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space + mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex))); + } - this.set(index, point, clonePosition, cloneBindPosePosition); - if (cloneNormals != null) { - this.get(cloneNormals, index, normal); - normal.set(mirrorIndex, -normal.get(mirrorIndex)); - this.set(index, normal, cloneNormals, cloneBindPoseNormals); - } - } - } + TemporalMesh mirror = temporalMesh.clone(); + for (int i = 0; i < mirror.getVertexCount(); ++i) { + Vector3f vertex = mirror.getVertex(i); + Vector3f normal = mirror.getNormal(i); + + if (mirrorAtPoint0) { + d = Math.abs(vertex.get(mirrorIndex)); + shiftVector.set(0, 0, 0).set(mirrorIndex, -vertex.get(mirrorIndex)); + } else { + d = this.computeDistanceFromPlane(vertex, mirrorPlaneCenter, mirrorPlaneNormal); + mirrorPlaneNormal.mult(d, shiftVector); } - modifiedIndexes.clear(); - LOGGER.finer("Flipping index order."); - switch (mesh.getMode()) { - case Points: - cloneIndexes.flip(); - break; - case Lines: - for (int i = 0; i < cloneIndexes.limit(); i += 2) { - if (cloneIndexes instanceof ShortBuffer) { - short index = ((ShortBuffer) cloneIndexes).get(i + 1); - ((ShortBuffer) cloneIndexes).put(i + 1, ((ShortBuffer) cloneIndexes).get(i)); - ((ShortBuffer) cloneIndexes).put(i, index); - } else { - int index = ((IntBuffer) cloneIndexes).get(i + 1); - ((IntBuffer) cloneIndexes).put(i + 1, ((IntBuffer) cloneIndexes).get(i)); - ((IntBuffer) cloneIndexes).put(i, index); - } - } - break; - case Triangles: - for (int i = 0; i < cloneIndexes.limit(); i += 3) { - if (cloneIndexes instanceof ShortBuffer) { - short index = ((ShortBuffer) cloneIndexes).get(i + 2); - ((ShortBuffer) cloneIndexes).put(i + 2, ((ShortBuffer) cloneIndexes).get(i + 1)); - ((ShortBuffer) cloneIndexes).put(i + 1, index); - } else { - int index = ((IntBuffer) cloneIndexes).get(i + 2); - ((IntBuffer) cloneIndexes).put(i + 2, ((IntBuffer) cloneIndexes).get(i + 1)); - ((IntBuffer) cloneIndexes).put(i + 1, index); - } - } - break; - default: - throw new IllegalStateException("Invalid mesh mode: " + mesh.getMode()); + if (merge && d <= tolerance) { + vertex.addLocal(shiftVector); + normal.set(mirrorIndex, 0); + temporalMesh.getVertex(i).addLocal(shiftVector); + temporalMesh.getNormal(i).set(mirrorIndex, 0); + } else { + vertex.addLocal(shiftVector.multLocal(2)); + normal.set(mirrorIndex, -normal.get(mirrorIndex)); } + } - if (mirrorU && clone.getBuffer(Type.TexCoord) != null) { - LOGGER.finer("Mirroring U coordinates."); - FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData(); - for (int i = 0; i < cloneUVs.limit(); i += 2) { - cloneUVs.put(i, 1.0f - cloneUVs.get(i)); - } - } - if (mirrorV && clone.getBuffer(Type.TexCoord) != null) { - LOGGER.finer("Mirroring V coordinates."); - FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData(); - for (int i = 1; i < cloneUVs.limit(); i += 2) { - cloneUVs.put(i, 1.0f - cloneUVs.get(i)); - } - } + mirror.flipIndexes(); - Geometry geometry = new Geometry(spatial.getName() + " - mirror " + mirrorNames[mirrorIndex], clone); - geometry.setMaterial(((Geometry) spatial).getMaterial()); - geometriesToAdd.add(geometry); + if (mirrorU || mirrorV) { + mirror.flipUV(mirrorU, mirrorV); } - } - LOGGER.log(Level.FINE, "Adding {0} geometries to current node.", geometriesToAdd.size()); - for (Geometry geometry : geometriesToAdd) { - node.attachChild(geometry); + temporalMesh.append(mirror); } - geometriesToAdd.clear(); } + } else { + LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); } } } @@ -268,41 +181,4 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; private float computeDistanceFromPlane(Vector3f p, Vector3f c, Vector3f n) { return Math.abs(n.dot(p) - c.dot(n)); } - - /** - * Sets the given value (v) into every of the buffers at the given index. - * The index is cosidered to be an index of a vertex of the mesh. - * @param index - * the index of vertex of the mesh - * @param value - * the value to be set - * @param buffers - * the buffers where the value will be set - */ - private void set(int index, Vector3f value, FloatBuffer... buffers) { - index *= 3; - for (FloatBuffer buffer : buffers) { - if (buffer != null) { - buffer.put(index, value.x); - buffer.put(index + 1, value.y); - buffer.put(index + 2, value.z); - } - } - } - - /** - * Fetches the vector's value from the given buffer at specified index. - * @param buffer - * the buffer we get the data from - * @param index - * the index of vertex of the mesh - * @param store - * the vector where the result will be set - */ - private void get(FloatBuffer buffer, int index, Vector3f store) { - index *= 3; - store.x = buffer.get(index); - store.y = buffer.get(index + 1); - store.z = buffer.get(index + 2); - } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java index e1755999e..889000056 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java @@ -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 children = node.getChildren(); + if (children != null && children.size() == 1 && children.get(0) instanceof TemporalMesh) { + return (TemporalMesh) children.get(0); + } + return null; + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java index bda881a05..c01d269bb 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java @@ -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 meshes = new ArrayList(); + 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; - } - // 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 meshes = new ArrayList(); - 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); + 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); } - - node.attachChild(emitter); } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java new file mode 100644 index 000000000..375565975 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java @@ -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); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index 49fb475ad..8c0acf9d4 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -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 meshesArray = pMesh.fetchData(); - List geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext); - if (geometries != null) { - for (Geometry geometry : geometries) { - result.attachChild(geometry); - } + TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext); + if(temporalMesh != null) { + result.attachChild(temporalMesh); } break; case SURF: @@ -207,59 +211,68 @@ public class ObjectHelper extends AbstractBlenderHelper { default: LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type); } - } finally { - blenderContext.popParent(); - } - - if (result != null) { - LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released)."); - blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); - blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress()); - if (objectType == ObjectType.ARMATURE) { - blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE); - } + + if (result != null) { + LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released)."); + Long oma = objectStructure.getOldMemoryAddress(); + blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure); + blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result); + + 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); + } - if (result.getChildren() != null) { - for (Spatial child : result.getChildren()) { - if (child instanceof Geometry) { - this.flipMeshIfRequired((Geometry) child, child.getWorldScale()); + LOGGER.fine("Reading and applying object's modifiers."); + ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); + Collection 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()); + } } } - } - LOGGER.fine("Reading and applying object's modifiers."); - ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); - Collection 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(); - // I prefer do compute bounding box here than read it from the file - result.updateModelBound(); + LOGGER.fine("Applying animations to the object if such are defined."); + AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); + animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod()); - LOGGER.fine("Applying animations to the object if such are defined."); - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); - animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod()); + LOGGER.fine("Loading constraints connected with this object."); + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper.loadConstraints(objectStructure, blenderContext); - LOGGER.fine("Loading constraints connected with this object."); - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.loadConstraints(objectStructure, blenderContext); - - LOGGER.fine("Loading custom properties."); - if (blenderContext.getBlenderKey().isLoadObjectProperties()) { - Properties properties = this.loadProperties(objectStructure, blenderContext); - // the loaded property is a group property, so we need to get - // each value and set it to Spatial - if (properties != null && properties.getValue() != null) { - this.applyProperties(result, properties); + LOGGER.fine("Loading custom properties."); + if (blenderContext.getBlenderKey().isLoadObjectProperties()) { + Properties properties = this.loadProperties(objectStructure, blenderContext); + // the loaded property is a group property, so we need to get + // each value and set it to Spatial + if (properties != null && properties.getValue() != null) { + this.applyProperties(result, properties); + } } } + } finally { + blenderContext.popParent(); } return result; } @@ -323,8 +336,8 @@ public class ObjectHelper extends AbstractBlenderHelper { * @return true if the first given OMA points to a parent of the second one and false 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(); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java index 344d65e31..855d79e97 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java @@ -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> userDefinedUVCoordinates, BlenderContext blenderContext) { + public void flatten(Geometry geometry, Long geometriesOMA, Map> 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 geometries = (List) 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 geometries = (List) 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) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java index 8202953f2..772db0c3c 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java @@ -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 geometries = (List) 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 uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java index 1fdcfbb6e..ad35a2ebc 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java @@ -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(); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java index f3d192008..a922b2588 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java @@ -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 generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List geometries) { + public static List generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, Geometry geometries) { List result = new ArrayList(); 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 generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List geometries) { + public static List generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, Geometry... geometries) { List result = new ArrayList(); 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 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 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 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 {