From dd1fdd6bce08dcd661f292ce358f1507163033f2 Mon Sep 17 00:00:00 2001 From: "Kae..pl" Date: Mon, 13 Jan 2014 19:02:35 +0000 Subject: [PATCH] Bugfix: fixed an issue with skeleton animations (the models did not behave as they should). Refactoring: animations method changes, less classes, more simple code Feature: BlenderKey will now require setting the mapping between animation (skeleton or spatial) and the animated model. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10985 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../blender/com/jme3/asset/BlenderKey.java | 181 ++++++-- .../scene/plugins/blender/BlenderContext.java | 33 +- .../scene/plugins/blender/BlenderLoader.java | 12 +- .../plugins/blender/BlenderModelLoader.java | 4 + .../blender/animations/AnimationData.java | 29 -- .../blender/animations/AnimationHelper.java | 420 ++++++++++++++++++ .../blender/animations/ArmatureHelper.java | 276 ------------ .../blender/animations/BoneContext.java | 6 +- .../scene/plugins/blender/animations/Ipo.java | 16 +- .../plugins/blender/animations/IpoHelper.java | 194 -------- .../blender/constraints/BoneConstraint.java | 6 +- .../blender/constraints/ConstraintHelper.java | 15 +- .../blender/constraints/SimulationNode.java | 14 +- .../blender/modifiers/ArmatureModifier.java | 250 +++++------ .../blender/modifiers/ModifierHelper.java | 83 +--- .../modifiers/ObjectAnimationModifier.java | 89 ---- .../plugins/blender/objects/ObjectHelper.java | 13 +- 17 files changed, 744 insertions(+), 897 deletions(-) delete mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationData.java create mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationHelper.java delete mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java delete mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java delete mode 100644 engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java diff --git a/engine/src/blender/com/jme3/asset/BlenderKey.java b/engine/src/blender/com/jme3/asset/BlenderKey.java index dd153e393..30bbd212b 100644 --- a/engine/src/blender/com/jme3/asset/BlenderKey.java +++ b/engine/src/blender/com/jme3/asset/BlenderKey.java @@ -33,9 +33,14 @@ package com.jme3.asset; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Queue; +import com.jme3.animation.Animation; import com.jme3.bounding.BoundingVolume; import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; @@ -52,7 +57,6 @@ import com.jme3.scene.LightNode; import com.jme3.scene.Node; import com.jme3.scene.SceneGraphVisitor; import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.animations.AnimationData; import com.jme3.texture.Texture; /** @@ -61,66 +65,70 @@ import com.jme3.texture.Texture; */ public class BlenderKey extends ModelKey { - protected static final int DEFAULT_FPS = 25; + protected static final int DEFAULT_FPS = 25; /** * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time * between the frames. */ - protected int fps = DEFAULT_FPS; + protected int fps = DEFAULT_FPS; /** * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. */ - protected int featuresToLoad = FeaturesToLoad.ALL; + protected int featuresToLoad = FeaturesToLoad.ALL; /** This variable determines if assets that are not linked to the objects should be loaded. */ - protected boolean loadUnlinkedAssets; + protected boolean loadUnlinkedAssets; /** The root path for all the assets. */ - protected String assetRootPath; + protected String assetRootPath; /** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */ - protected boolean fixUpAxis = true; + protected boolean fixUpAxis = true; /** Generated textures resolution (PPU - Pixels Per Unit). */ - protected int generatedTexturePPU = 128; + protected int generatedTexturePPU = 128; /** * The name of world settings that the importer will use. If not set or specified name does not occur in the file * then the first world settings in the file will be used. */ - protected String usedWorld; + protected String usedWorld; /** * User's default material that is set fo objects that have no material definition in blender. The default value is * null. If the value is null the importer will use its own default material (gray color - like in blender). */ - protected Material defaultMaterial; + protected Material defaultMaterial; /** Face cull mode. By default it is disabled. */ - protected FaceCullMode faceCullMode = FaceCullMode.Back; + protected FaceCullMode faceCullMode = FaceCullMode.Back; /** * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded. * If set to -1 then the current layer will be loaded. */ - protected int layersToLoad = -1; + protected int layersToLoad = -1; /** A variable that toggles the object custom properties loading. */ - protected boolean loadObjectProperties = true; + protected boolean loadObjectProperties = true; /** * Maximum texture size. Might be dependant on the graphic card. * This value is taken from org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE. */ - protected int maxTextureSize = 8192; + protected int maxTextureSize = 8192; /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */ - protected boolean loadGeneratedTextures; + protected boolean loadGeneratedTextures; /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */ - protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; + protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; /** * If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated * textures will get their proper size. */ - protected int skyGeneratedTextureSize = 1000; + protected int skyGeneratedTextureSize = 1000; /** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */ - protected float skyGeneratedTextureRadius = 1; + protected float skyGeneratedTextureRadius = 1; /** The shape against which the generated texture for the sky will be created. */ - protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE; + protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE; /** * This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together * and textures that in the final result will never be visible - will be discarded. */ - protected boolean optimiseTextures; + protected boolean optimiseTextures; + /** A map between node name and its animation names. */ + protected Map> nodeAnimationMap = new HashMap>(); + /** A map between node name and its skeleton animation names. */ + protected Map> skeletonAnimationMap = new HashMap>(); /** * Constructor used by serialization mechanisms. @@ -473,6 +481,58 @@ public class BlenderKey extends ModelKey { return defaultMaterial; } + /** + * Adds spatial animation name for specified node. + * @param nodeName + * the name of the node + * @param animationName + * the spatial animation name + */ + public void addNodeAnimation(String nodeName, String animationName) { + List animations = nodeAnimationMap.get(nodeName); + if (animations == null) { + animations = new ArrayList(); + nodeAnimationMap.put(nodeName, animations); + } + animations.add(animationName); + } + + /** + * Returns all spatial animation names for the given node. + * @param nodeName + * the name of the node + * @return all spatial animations names or null if none are defined + */ + public List getNodeAnimationNames(String nodeName) { + return nodeAnimationMap.get(nodeName); + } + + /** + * Adds bone animation name for specified node. + * @param nodeName + * the name of the node + * @param animationName + * the bone animation name + */ + public void addSkeletonAnimation(String nodeName, String animationName) { + List animations = skeletonAnimationMap.get(nodeName); + if (animations == null) { + animations = new ArrayList(); + skeletonAnimationMap.put(nodeName, animations); + } + animations.add(animationName); + } + + /** + * Returns all bone animation names for the given node. + * @param nodeName + * the name of the node + * @return all bone animations names or null if none are defined + */ + public List getSkeletonAnimationNames(String nodeName) { + return skeletonAnimationMap.get(nodeName); + } + @Override public void write(JmeExporter e) throws IOException { super.write(e); @@ -492,6 +552,22 @@ public class BlenderKey extends ModelKey { oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f); oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE); oc.write(optimiseTextures, "optimise-textures", false); + + oc.write(nodeAnimationMap.size(), "node-anims-map-size", 0); + int counter = 0; + for (Entry> entry : nodeAnimationMap.entrySet()) { + oc.write(entry.getKey(), "node-anim-" + counter, null); + oc.write(entry.getValue().toArray(new String[entry.getValue().size()]), "node-anims-" + counter, null); + ++counter; + } + + oc.write(skeletonAnimationMap.size(), "skeleton-anims-map-size", 0); + counter = 0; + for (Entry> entry : skeletonAnimationMap.entrySet()) { + oc.write(entry.getKey(), "skeleton-anim-" + counter, null); + oc.write(entry.getValue().toArray(new String[entry.getValue().size()]), "skeleton-anims-" + counter, null); + ++counter; + } } @Override @@ -513,6 +589,26 @@ public class BlenderKey extends ModelKey { skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f); skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE); optimiseTextures = ic.readBoolean("optimise-textures", false); + + int animsSize = ic.readInt("node-anims-map-size", 0); + nodeAnimationMap = new HashMap>(animsSize); + if (animsSize > 0) { + for (int i = 0; i < animsSize; ++i) { + String nodeName = ic.readString("node-anim-" + i, null); + String[] anims = ic.readStringArray("node-anims-" + i, null); + nodeAnimationMap.put(nodeName, new ArrayList(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list + } + } + + animsSize = ic.readInt("skeleton-anims-map-size", 0); + skeletonAnimationMap = new HashMap>(animsSize); + if (animsSize > 0) { + for (int i = 0; i < animsSize; ++i) { + String nodeName = ic.readString("skeleton-anim-" + i, null); + String[] anims = ic.readStringArray("skeleton-anims-" + i, null); + skeletonAnimationMap.put(nodeName, new ArrayList(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list + } + } } @Override @@ -532,7 +628,9 @@ public class BlenderKey extends ModelKey { result = prime * result + (loadUnlinkedAssets ? 1231 : 1237); result = prime * result + maxTextureSize; result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode()); + result = prime * result + (nodeAnimationMap == null ? 0 : nodeAnimationMap.hashCode()); result = prime * result + (optimiseTextures ? 1231 : 1237); + result = prime * result + (skeletonAnimationMap == null ? 0 : skeletonAnimationMap.hashCode()); result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius); result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode()); result = prime * result + skyGeneratedTextureSize; @@ -545,10 +643,7 @@ public class BlenderKey extends ModelKey { if (this == obj) { return true; } - if (!super.equals(obj)) { - return false; - } - if (this.getClass() != obj.getClass()) { + if (!(obj instanceof BlenderKey)) { return false; } BlenderKey other = (BlenderKey) obj; @@ -599,9 +694,23 @@ public class BlenderKey extends ModelKey { if (mipmapGenerationMethod != other.mipmapGenerationMethod) { return false; } + if (nodeAnimationMap == null) { + if (other.nodeAnimationMap != null) { + return false; + } + } else if (!nodeAnimationMap.equals(other.nodeAnimationMap)) { + return false; + } if (optimiseTextures != other.optimiseTextures) { return false; } + if (skeletonAnimationMap == null) { + if (other.skeletonAnimationMap != null) { + return false; + } + } else if (!skeletonAnimationMap.equals(other.skeletonAnimationMap)) { + return false; + } if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) { return false; } @@ -662,28 +771,28 @@ public class BlenderKey extends ModelKey { public static class LoadingResults extends Spatial { /** Bitwise mask of features that are to be loaded. */ - private final int featuresToLoad; + private final int featuresToLoad; /** The scenes from the file. */ - private List scenes; + private List scenes; /** Objects from all scenes. */ - private List objects; + private List objects; /** Materials from all objects. */ - private List materials; + private List materials; /** Textures from all objects. */ - private List textures; + private List textures; /** Animations of all objects. */ - private List animations; + private List animations; /** All cameras from the file. */ - private List cameras; + private List cameras; /** All lights from the file. */ - private List lights; + private List lights; /** Loaded sky. */ - private Spatial sky; + private Spatial sky; /** * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color * is set to default (as in blender editor. */ - private ColorRGBA backgroundColor = ColorRGBA.Gray; + private ColorRGBA backgroundColor = ColorRGBA.Gray; /** * Private constructor prevents users to create an instance of this class from outside the @@ -705,7 +814,7 @@ public class BlenderKey extends ModelKey { } } if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { - animations = new ArrayList(); + animations = new ArrayList(); } } if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { @@ -839,7 +948,7 @@ public class BlenderKey extends ModelKey { /** * @return all loaded animations */ - public List getAnimations() { + public List getAnimations() { return animations; } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java index 290a578b9..17b312a37 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java @@ -39,6 +39,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Stack; +import com.jme3.animation.Animation; import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; import com.jme3.asset.AssetManager; @@ -46,7 +47,6 @@ import com.jme3.asset.BlenderKey; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.animations.AnimationData; import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.constraints.Constraint; import com.jme3.scene.plugins.blender.file.BlenderInputStream; @@ -100,8 +100,8 @@ public class BlenderContext { private Stack parentStack = new Stack(); /** A list of constraints for the specified object. */ protected Map> constraints = new HashMap>(); - /** Anim data loaded for features. */ - private Map animData = new HashMap(); + /** Animations loaded for features. */ + private Map> animations = new HashMap>(); /** Loaded skeletons. */ private Map skeletons = new HashMap(); /** A map between skeleton and node it modifies. */ @@ -405,28 +405,33 @@ public class BlenderContext { } return result; } - + /** - * This method sets the anim data for the specified OMA of its owner. + * This method adds the animation for the specified OMA of its owner. * * @param ownerOMA * the owner's old memory address - * @param animData - * the animation data for the feature specified by ownerOMA - */ - public void setAnimData(Long ownerOMA, AnimationData animData) { - this.animData.put(ownerOMA, animData); + * @param animation + * the animation for the feature specified by ownerOMA + */ + public void addAnimation(Long ownerOMA, Animation animation) { + List animList = animations.get(ownerOMA); + if(animList == null) { + animList = new ArrayList(); + animations.put(ownerOMA, animList); + } + animations.put(ownerOMA, animList); } - + /** * This method returns the animation data for the specified owner. * * @param ownerOMA * the old memory address of the animation data owner - * @return the animation data or null if none exists + * @return the animation or null if none exists */ - public AnimationData getAnimData(Long ownerOMA) { - return animData.get(ownerOMA); + public List getAnimations(Long ownerOMA) { + return animations.get(ownerOMA); } /** diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java index 4b398d166..fa5566560 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java @@ -47,8 +47,7 @@ import com.jme3.scene.CameraNode; import com.jme3.scene.LightNode; import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.animations.ArmatureHelper; -import com.jme3.scene.plugins.blender.animations.IpoHelper; +import com.jme3.scene.plugins.blender.animations.AnimationHelper; import com.jme3.scene.plugins.blender.cameras.CameraHelper; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; import com.jme3.scene.plugins.blender.curves.CurvesHelper; @@ -85,6 +84,10 @@ public class BlenderLoader implements AssetLoader { List sceneBlocks = new ArrayList(); BlenderKey blenderKey = blenderContext.getBlenderKey(); LoadingResults loadingResults = blenderKey.prepareLoadingResults(); + + AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); + animationHelper.loadAnimations(); + for (FileBlockHeader block : blocks) { switch (block.getCode()) { case FileBlockHeader.BLOCK_OB00:// Object @@ -240,7 +243,7 @@ public class BlenderLoader implements AssetLoader { blenderContext.setBlenderKey(blenderKey); // creating helpers - blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderContext)); + blenderContext.putHelper(AnimationHelper.class, new AnimationHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderContext)); @@ -250,10 +253,9 @@ public class BlenderLoader implements AssetLoader { blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext)); - + // reading the blocks (dna block is automatically saved in the blender context when found) FileBlockHeader sceneFileBlock = null; do { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java index 1a856df6e..021a6082f 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java @@ -43,6 +43,7 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.scene.LightNode; import com.jme3.scene.Node; import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.animations.AnimationHelper; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.FileBlockHeader; @@ -62,6 +63,9 @@ public class BlenderModelLoader extends BlenderLoader { try { this.setup(assetInfo); + AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); + animationHelper.loadAnimations(); + BlenderKey blenderKey = blenderContext.getBlenderKey(); List rootObjects = new ArrayList(); for (FileBlockHeader block : blocks) { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationData.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationData.java deleted file mode 100644 index 05f405fc1..000000000 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationData.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jme3.scene.plugins.blender.animations; - -import java.util.List; - -import com.jme3.animation.Animation; -import com.jme3.animation.Skeleton; - -/** - * A simple class that sotres animation data. - * If skeleton is null then we deal with object animation. - * - * @author Marcin Roguski (Kaelthas) - */ -public class AnimationData { - /** The skeleton. */ - public final Skeleton skeleton; - /** The animations list. */ - public final List anims; - - public AnimationData(List anims) { - this.anims = anims; - skeleton = null; - } - - public AnimationData(Skeleton skeleton, List anims) { - this.skeleton = skeleton; - this.anims = anims; - } -} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationHelper.java new file mode 100644 index 000000000..19c51b422 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/AnimationHelper.java @@ -0,0 +1,420 @@ +package com.jme3.scene.plugins.blender.animations; + +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.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.animation.SpatialTrack; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +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.curves.BezierCurve; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +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.objects.ObjectHelper; + +/** + * The helper class that helps in animations loading. + * @author Marcin Roguski (Kaelthas) + */ +public class AnimationHelper extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName()); + + /** A map of blender actions. */ + private Map actions = new HashMap(); + + public AnimationHelper(String blenderVersion, BlenderContext blenderContext) { + super(blenderVersion, blenderContext); + } + + /** + * Loads all animations that are stored in the blender file. The animations are not yet applied to the scene features. + * This should be called before objects are loaded. + * @throws BlenderFileException + * an exception is thrown when problems with blender file reading occur + */ + public void loadAnimations() throws BlenderFileException { + LOGGER.info("Loading animations that will be later applied to scene features."); + List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); + if (actionHeaders != null) { + for (FileBlockHeader header : actionHeaders) { + Structure actionStructure = header.getStructure(blenderContext); + LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName()); + actions.put(actionStructure.getName(), this.getTracks(actionStructure, blenderContext)); + } + } + } + + /** + * The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file. + * @param node + * the node to whom the animations will be applied + * @param animationNames + * the names of the animations to be applied + */ + public void applyAnimations(Node node, List animationNames) { + if (animationNames != null && animationNames.size() > 0) { + List animations = new ArrayList(); + for (String animationName : animationNames) { + BlenderAction action = actions.get(animationName); + if (action != null) { + SpatialTrack[] tracks = action.toTracks(node); + if (tracks != null && tracks.length > 0) { + Animation spatialAnimation = new Animation(animationName, action.getAnimationTime()); + spatialAnimation.setTracks(tracks); + animations.add(spatialAnimation); + blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation); + } + } else { + LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName); + } + } + + if (animations.size() > 0) { + AnimControl control = new AnimControl(); + HashMap anims = new HashMap(animations.size()); + for (int i = 0; i < animations.size(); ++i) { + Animation animation = animations.get(i); + anims.put(animation.getName(), animation); + } + control.setAnimations(anims); + node.addControl(control); + } + } + } + + /** + * The method applies skeleton animations to the given node. + * @param node + * the node where the animations will be applied + * @param skeleton + * the skeleton of the node + * @param animationNames + * the names of the skeleton animations + */ + public void applyAnimations(Node node, Skeleton skeleton, List animationNames) { + node.addControl(new SkeletonControl(skeleton)); + blenderContext.setNodeForSkeleton(skeleton, node); + + if (animationNames != null && animationNames.size() > 0) { + List animations = new ArrayList(); + for (String animationName : animationNames) { + BlenderAction action = actions.get(animationName); + if (action != null) { + BoneTrack[] tracks = action.toTracks(skeleton); + if (tracks != null && tracks.length > 0) { + Animation boneAnimation = new Animation(animationName, action.getAnimationTime()); + boneAnimation.setTracks(tracks); + animations.add(boneAnimation); + for (BoneTrack track : tracks) { + Bone bone = skeleton.getBone(track.getTargetBoneIndex()); + BoneContext boneContext = blenderContext.getBoneContext(bone); + blenderContext.addAnimation(boneContext.getBoneOma(), boneAnimation); + } + } + } else { + LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName); + } + } + if (animations.size() > 0) { + AnimControl control = new AnimControl(skeleton); + HashMap anims = new HashMap(animations.size()); + for (int i = 0; i < animations.size(); ++i) { + Animation animation = animations.get(i); + anims.put(animation.getName(), animation); + } + control.setAnimations(anims); + node.addControl(control); + } + } + } + + /** + * This method creates an ipo object used for interpolation calculations. + * + * @param ipoStructure + * the structure with ipo definition + * @param blenderContext + * the blender context + * @return the ipo object + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { + Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); + + // preparing bezier curves + Ipo result = null; + List curves = curvebase.evaluateListBase();// IpoCurve + if (curves.size() > 0) { + BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; + int frame = 0; + for (Structure curve : curves) { + Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(); + int type = ((Number) curve.getFieldValue("adrcode")).intValue(); + bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); + } + curves.clear(); + result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); + } + return result; + } + + /** + * This method creates an ipo with only a single value. No track type is + * specified so do not use it for calculating tracks. + * + * @param constValue + * the value of this ipo + * @return constant ipo + */ + public Ipo fromValue(float constValue) { + return new ConstIpo(constValue); + } + + /** + * This method retuns the bone tracks for animation. + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + private BlenderAction getTracks(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { + if (blenderVersion < 250) { + return this.getTracks249(actionStructure, blenderContext); + } else { + return this.getTracks250(actionStructure, blenderContext); + } + } + + /** + * This method retuns the bone tracks for animation for blender version 2.50 + * and higher. + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + private BlenderAction getTracks250(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Getting tracks!"); + Structure groups = (Structure) actionStructure.getFieldValue("groups"); + List actionGroups = groups.evaluateListBase();// bActionGroup + BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps()); + int lastFrame = 1; + for (Structure actionGroup : actionGroups) { + String name = actionGroup.getFieldValue("name").toString(); + List channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(); + BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; + int channelCounter = 0; + for (Structure c : channels) { + int type = this.getCurveType(c, blenderContext); + Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(); + bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); + } + + Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + lastFrame = Math.max(lastFrame, ipo.getLastFrame()); + blenderAction.featuresTracks.put(name, ipo); + } + blenderAction.stopFrame = lastFrame; + return blenderAction; + } + + /** + * This method retuns the bone tracks for animation for blender version 2.49 + * (and probably several lower versions too). + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + private BlenderAction getTracks249(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Getting tracks!"); + Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); + List actionChannels = chanbase.evaluateListBase();// bActionChannel + BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps()); + int lastFrame = 1; + for (Structure bActionChannel : actionChannels) { + String animatedFeatureName = bActionChannel.getFieldValue("name").toString(); + Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); + if (!p.isNull()) { + Structure ipoStructure = p.fetchData().get(0); + Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext); + lastFrame = Math.max(lastFrame, ipo.getLastFrame()); + blenderAction.featuresTracks.put(animatedFeatureName, ipo); + } + } + blenderAction.stopFrame = lastFrame; + return blenderAction; + } + + /** + * This method returns the type of the ipo curve. + * + * @param structure + * the structure must contain the 'rna_path' field and + * 'array_index' field (the type is not important here) + * @param blenderContext + * the blender context + * @return the type of the curve + */ + public int getCurveType(Structure structure, BlenderContext blenderContext) { + // reading rna path first + BlenderInputStream bis = blenderContext.getInputStream(); + int currentPosition = bis.getPosition(); + Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); + bis.setPosition(dataFileBlock.getBlockPosition()); + String rnaPath = bis.readString(); + bis.setPosition(currentPosition); + int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); + + // determining the curve type + if (rnaPath.endsWith("location")) { + return Ipo.AC_LOC_X + arrayIndex; + } + if (rnaPath.endsWith("rotation_quaternion")) { + return Ipo.AC_QUAT_W + arrayIndex; + } + if (rnaPath.endsWith("scale")) { + return Ipo.AC_SIZE_X + arrayIndex; + } + if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { + return Ipo.OB_ROT_X + arrayIndex; + } + LOGGER.warning("Unknown curve rna path: " + rnaPath); + return -1; + } + + /** + * An abstract representation of animation. The data stored here is mainly a raw action data loaded from blender. + * It can later be transformed into bone or spatial animation and applied to the specified node. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class BlenderAction { + /** Animation speed - frames per second. */ + private int fps; + /** The last frame of the animation (the last ipo curve node position is used as a last frame). */ + private int stopFrame; + /** + * Tracks of the features. In case of bone animation the keys are the names of the bones. In case of spatial animation - the node's name + * is used. A single ipo contains all tracks for location, rotation and scales. + */ + private Map featuresTracks = new HashMap(); + + public BlenderAction(int fps) { + this.fps = fps; + } + + /** + * Converts the action into JME spatial animation tracks. + * @param node + * the node that will be animated + * @return the spatial tracks for the node + */ + public SpatialTrack[] toTracks(Node node) { + List tracks = new ArrayList(featuresTracks.size()); + for (Entry entry : featuresTracks.entrySet()) { + tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true)); + } + return tracks.toArray(new SpatialTrack[tracks.size()]); + } + + /** + * Converts the action into JME bone animation tracks. + * @param skeleton + * the skeleton that will be animated + * @return the bone tracks for the node + */ + public BoneTrack[] toTracks(Skeleton skeleton) { + List tracks = new ArrayList(featuresTracks.size()); + for (Entry entry : featuresTracks.entrySet()) { + Bone bone = skeleton.getBone(entry.getKey()); + int boneIndex = skeleton.getBoneIndex(entry.getKey()); + tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 1, stopFrame, fps, false)); + } + return tracks.toArray(new BoneTrack[tracks.size()]); + } + + /** + * @return the time of animations (in seconds) + */ + public float getAnimationTime() { + return (stopFrame - 1) / (float) fps; + } + } + + /** + * Ipo constant curve. This is a curve with only one value and no specified + * type. This type of ipo cannot be used to calculate tracks. It should only + * be used to calculate single value for a given frame. + * + * @author Marcin Roguski (Kaelthas) + */ + private class ConstIpo extends Ipo { + + /** The constant value of this ipo. */ + private float constValue; + + /** + * Constructor. Stores the constant value of this ipo. + * + * @param constValue + * the constant value of this ipo + */ + public ConstIpo(float constValue) { + super(null, false, 0);// the version is not important here + this.constValue = constValue; + } + + @Override + public float calculateValue(int frame) { + return constValue; + } + + @Override + public float calculateValue(int frame, int curveIndex) { + return constValue; + } + + @Override + public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) { + throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); + } + } +} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java deleted file mode 100644 index 9370cdc8c..000000000 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.animations; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.animation.Bone; -import com.jme3.animation.BoneTrack; -import com.jme3.animation.Skeleton; -import com.jme3.math.Quaternion; -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.curves.BezierCurve; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class defines the methods to calculate certain aspects of animation and - * armature functionalities. - * - * @author Marcin Roguski (Kaelthas) - */ -public class ArmatureHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); - - public static final String ARMATURE_NODE_MARKER = "armature-node"; - - /** - * This constructor parses the given blender version and stores the result. - * Some functionalities may differ in different blender versions. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public ArmatureHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * 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 - */ - public 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); - } - - /** - * This method returns a map where the key is the object's group index that - * is used by a bone and the key is the bone index in the armature. - * - * @param defBaseStructure - * a bPose structure of the object - * @return bone group-to-index map - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Map getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException { - Map result = null; - if (skeleton.getBoneCount() != 0) { - result = new HashMap(); - List deformGroups = defBaseStructure.evaluateListBase();// bDeformGroup - int groupIndex = 0; - for (Structure deformGroup : deformGroups) { - String deformGroupName = deformGroup.getFieldValue("name").toString(); - int boneIndex = skeleton.getBoneIndex(deformGroupName); - if (boneIndex >= 0) { - result.put(groupIndex, boneIndex); - } - ++groupIndex; - } - } - return result; - } - - /** - * This method retuns the bone tracks for animation. - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { - if (blenderVersion < 250) { - return this.getTracks249(actionStructure, skeleton, blenderContext); - } else { - return this.getTracks250(actionStructure, skeleton, blenderContext); - } - } - - /** - * This method retuns the bone tracks for animation for blender version 2.50 - * and higher. - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Getting tracks!"); - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - int fps = blenderContext.getBlenderKey().getFps(); - Structure groups = (Structure) actionStructure.getFieldValue("groups"); - List actionGroups = groups.evaluateListBase();// bActionGroup - List tracks = new ArrayList(); - for (Structure actionGroup : actionGroups) { - String name = actionGroup.getFieldValue("name").toString(); - int boneIndex = skeleton.getBoneIndex(name); - if (boneIndex >= 0) { - List channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(); - BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; - int channelCounter = 0; - for (Structure c : channels) { - int type = ipoHelper.getCurveType(c, blenderContext); - Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(); - bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); - } - - Bone bone = skeleton.getBone(boneIndex); - Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 1, ipo.getLastFrame(), fps, false)); - } - } - this.equaliseBoneTracks(tracks); - return tracks.toArray(new BoneTrack[tracks.size()]); - } - - /** - * This method retuns the bone tracks for animation for blender version 2.49 - * (and probably several lower versions too). - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Getting tracks!"); - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - int fps = blenderContext.getBlenderKey().getFps(); - Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); - List actionChannels = chanbase.evaluateListBase();// bActionChannel - List tracks = new ArrayList(); - for (Structure bActionChannel : actionChannels) { - String name = bActionChannel.getFieldValue("name").toString(); - int boneIndex = skeleton.getBoneIndex(name); - if (boneIndex >= 0) { - Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); - if (!p.isNull()) { - Structure ipoStructure = p.fetchData().get(0); - - Bone bone = skeleton.getBone(boneIndex); - Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); - if (ipo != null) { - tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 1, ipo.getLastFrame(), fps, false)); - } - } - } - } - this.equaliseBoneTracks(tracks); - return tracks.toArray(new BoneTrack[tracks.size()]); - } - - /** - * The method makes all the tracks to have equal frame lengths. - * @param tracks - * the tracks to be equalized - */ - private void equaliseBoneTracks(List tracks) { - // first compute the maximum amount of frames - int maximumFrameCount = -1; - float[] maximumTrackTimes = null; - for (BoneTrack track : tracks) { - if (track.getTimes().length > maximumFrameCount) { - maximumTrackTimes = track.getTimes(); - maximumFrameCount = maximumTrackTimes.length; - } - } - - // now widen all the tracks that have less frames by repeating the last values in the frame - for (BoneTrack track : tracks) { - int currentTrackLength = track.getTimes().length; - if (currentTrackLength < maximumFrameCount) { - Vector3f[] translations = new Vector3f[maximumFrameCount]; - Quaternion[] rotations = new Quaternion[maximumFrameCount]; - Vector3f[] scales = new Vector3f[maximumFrameCount]; - - Vector3f[] currentTranslations = track.getTranslations(); - Quaternion[] currentRotations = track.getRotations(); - Vector3f[] currentScales = track.getScales(); - for (int i = 0; i < currentTrackLength; ++i) { - translations[i] = currentTranslations[i]; - rotations[i] = currentRotations[i]; - scales[i] = currentScales[i]; - } - - for (int i = currentTrackLength; i < maximumFrameCount; ++i) { - translations[i] = currentTranslations[currentTranslations.length - 1]; - rotations[i] = currentRotations[currentRotations.length - 1]; - scales[i] = currentScales[currentScales.length - 1]; - } - - track.setKeyframes(maximumTrackTimes, translations, rotations, scales); - } - } - } -} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java index 6b14d8097..6fd91f143 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -99,8 +99,10 @@ public class BoneContext { // first get the bone matrix in its armature space globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis()); - // then make sure it is rotated in a proper way to fit the jme bone transformation conventions - globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX); + if(blenderContext.getBlenderKey().isFixUpAxis()) { + // then make sure it is rotated in a proper way to fit the jme bone transformation conventions + globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX); + } Spatial armature = (Spatial) objectHelper.toObject(blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext), blenderContext); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java index a2b5e3708..8a0bf9176 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java @@ -156,7 +156,8 @@ public class Ipo { degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here } int yIndex = 1, zIndex = 2; - if (spatialTrack && fixUpAxis) { + boolean swapAxes = spatialTrack && fixUpAxis; + if (swapAxes) { yIndex = 2; zIndex = 1; } @@ -164,8 +165,7 @@ public class Ipo { // calculating track data for (int frame = startFrame; frame <= stopFrame; ++frame) { int index = frame - startFrame; - times[index] = index * timeBetweenFrames;// start + (frame - 1) - // * timeBetweenFrames; + times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames; for (int j = 0; j < bezierCurves.length; ++j) { double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); switch (bezierCurves[j].getType()) { @@ -174,7 +174,7 @@ public class Ipo { translation[0] = (float) value; break; case AC_LOC_Y: - if (fixUpAxis && value != 0) { + if (swapAxes && value != 0) { value = -value; } translation[yIndex] = (float) value; @@ -188,7 +188,7 @@ public class Ipo { objectRotation[0] = (float) value * degreeToRadiansFactor; break; case OB_ROT_Y: - if (fixUpAxis && value != 0) { + if (swapAxes && value != 0) { value = -value; } objectRotation[yIndex] = (float) value * degreeToRadiansFactor; @@ -202,10 +202,10 @@ public class Ipo { scale[0] = (float) value; break; case AC_SIZE_Y: - scale[fixUpAxis ? 2 : 1] = (float) value; + scale[yIndex] = (float) value; break; case AC_SIZE_Z: - scale[fixUpAxis ? 1 : 2] = (float) value; + scale[zIndex] = (float) value; break; // QUATERNION ROTATION (used with bone animation) @@ -216,7 +216,7 @@ public class Ipo { quaternionRotation[0] = (float) value; break; case AC_QUAT_Y: - if (fixUpAxis && value != 0) { + if (swapAxes && value != 0) { value = -value; } quaternionRotation[yIndex] = (float) value; diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java deleted file mode 100644 index 260a59575..000000000 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.jme3.scene.plugins.blender.animations; - -import java.util.List; -import java.util.logging.Logger; - -import com.jme3.animation.BoneTrack; -import com.jme3.math.Quaternion; -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.curves.BezierCurve; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class helps to compute values from interpolation curves for features - * like animation or constraint influence. The curves are 3rd degree bezier - * curves. - * - * @author Marcin Roguski - */ -public class IpoHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(IpoHelper.class.getName()); - - /** - * This constructor parses the given blender version and stores the result. - * Some functionalities may differ in different blender versions. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public IpoHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * This method creates an ipo object used for interpolation calculations. - * - * @param ipoStructure - * the structure with ipo definition - * @param blenderContext - * the blender context - * @return the ipo object - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { - Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); - - // preparing bezier curves - Ipo result = null; - List curves = curvebase.evaluateListBase();// IpoCurve - if (curves.size() > 0) { - BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; - int frame = 0; - for (Structure curve : curves) { - Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(); - int type = ((Number) curve.getFieldValue("adrcode")).intValue(); - bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); - } - curves.clear(); - result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); - } - return result; - } - - /** - * This method creates an ipo object used for interpolation calculations. It - * should be called for blender version 2.50 and higher. - * - * @param actionStructure - * the structure with action definition - * @param blenderContext - * the blender context - * @return the ipo object - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Ipo fromAction(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { - Ipo result = null; - List curves = ((Structure) actionStructure.getFieldValue("curves")).evaluateListBase();// FCurve - if (curves.size() > 0) { - BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; - int frame = 0; - for (Structure curve : curves) { - Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(); - int type = this.getCurveType(curve, blenderContext); - bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); - } - curves.clear(); - result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - } - return result; - } - - /** - * This method returns the type of the ipo curve. - * - * @param structure - * the structure must contain the 'rna_path' field and - * 'array_index' field (the type is not important here) - * @param blenderContext - * the blender context - * @return the type of the curve - */ - public int getCurveType(Structure structure, BlenderContext blenderContext) { - // reading rna path first - BlenderInputStream bis = blenderContext.getInputStream(); - int currentPosition = bis.getPosition(); - Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); - bis.setPosition(dataFileBlock.getBlockPosition()); - String rnaPath = bis.readString(); - bis.setPosition(currentPosition); - int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); - - // determining the curve type - if (rnaPath.endsWith("location")) { - return Ipo.AC_LOC_X + arrayIndex; - } - if (rnaPath.endsWith("rotation_quaternion")) { - return Ipo.AC_QUAT_W + arrayIndex; - } - if (rnaPath.endsWith("scale")) { - return Ipo.AC_SIZE_X + arrayIndex; - } - if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { - return Ipo.OB_ROT_X + arrayIndex; - } - LOGGER.warning("Unknown curve rna path: " + rnaPath); - return -1; - } - - /** - * This method creates an ipo with only a single value. No track type is - * specified so do not use it for calculating tracks. - * - * @param constValue - * the value of this ipo - * @return constant ipo - */ - public Ipo fromValue(float constValue) { - return new ConstIpo(constValue); - } - - /** - * Ipo constant curve. This is a curve with only one value and no specified - * type. This type of ipo cannot be used to calculate tracks. It should only - * be used to calculate single value for a given frame. - * - * @author Marcin Roguski (Kaelthas) - */ - private class ConstIpo extends Ipo { - - /** The constant value of this ipo. */ - private float constValue; - - /** - * Constructor. Stores the constant value of this ipo. - * - * @param constValue - * the constant value of this ipo - */ - public ConstIpo(float constValue) { - super(null, false, 0);// the version is not important here - this.constValue = constValue; - } - - @Override - public float calculateValue(int frame) { - return constValue; - } - - @Override - public float calculateValue(int frame, int curveIndex) { - return constValue; - } - - @Override - public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) { - throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); - } - } -} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java index 284cb7da4..232f30508 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java @@ -6,11 +6,11 @@ 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.animations.ArmatureHelper; import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.animations.Ipo; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.objects.ObjectHelper; /** * Constraint applied on the bone. @@ -48,7 +48,7 @@ import com.jme3.scene.plugins.blender.file.Structure; } // the second part of the if expression verifies if the found node // (if any) is an armature node - if (blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) { + if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) { if (subtargetName.trim().isEmpty()) { LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name); return false; @@ -64,7 +64,7 @@ import com.jme3.scene.plugins.blender.file.Structure; } return true; } - + @Override public void apply(int frame) { super.apply(frame); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java index 97b6889ff..68f723183 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java @@ -17,10 +17,9 @@ 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.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.AnimationHelper; import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.animations.IpoHelper; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; @@ -63,7 +62,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { LOGGER.fine("Loading constraints."); // reading influence ipos for the constraints - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); Map> constraintsIpos = new HashMap>(); Pointer pActions = (Pointer) objectStructure.getFieldValue("action"); if (pActions.isNotNull()) { @@ -79,7 +78,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo"); if (pIpo.isNotNull()) { String constraintName = constraintChannel.getFieldValue("name").toString(); - Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData().get(0), blenderContext); + Ipo ipo = animationHelper.fromIpoStructure(pIpo.fetchData().get(0), blenderContext); ipos.put(constraintName, ipo); } } @@ -107,7 +106,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName); if (ipo == null) { float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); - ipo = ipoHelper.fromValue(enforce); + ipo = animationHelper.fromValue(enforce); } constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext)); } @@ -130,7 +129,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null; if (ipo == null) { float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); - ipo = ipoHelper.fromValue(enforce); + ipo = animationHelper.fromValue(enforce); } constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); @@ -219,7 +218,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { */ public Transform getTransform(Long oma, String subtargetName, Space space) { Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); - boolean isArmature = blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, feature) != null; + boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null; if (isArmature) { blenderContext.getSkeleton(oma).updateWorldVectors(); BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName); @@ -301,7 +300,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { */ public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) { Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); - boolean isArmature = blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, feature) != null; + boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null; if (isArmature) { Skeleton skeleton = blenderContext.getSkeleton(oma); BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java index f656150fa..088e9c889 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java @@ -25,7 +25,6 @@ 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.animations.ArmatureHelper; import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.objects.ObjectHelper; import com.jme3.util.TempVars; @@ -95,7 +94,7 @@ public class SimulationNode { private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) { this.blenderContext = blenderContext; Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE); - if (blenderContext.getMarkerValue(ArmatureHelper.ARMATURE_NODE_MARKER, spatial) != null) { + if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, spatial) != null) { skeleton = blenderContext.getSkeleton(featureOMA); Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton); @@ -136,9 +135,9 @@ public class SimulationNode { // each bone of the skeleton has the same anim data applied BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(1)); Long boneOma = boneContext.getBoneOma(); - animations = blenderContext.getAnimData(boneOma) == null ? null : blenderContext.getAnimData(boneOma).anims; + animations = blenderContext.getAnimations(boneOma); } else { - animations = blenderContext.getAnimData(featureOMA) == null ? null : blenderContext.getAnimData(featureOMA).anims; + animations = blenderContext.getAnimations(featureOMA); for (Spatial child : spatial.getChildren()) { if (child instanceof Node) { children.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, child), blenderContext, false)); @@ -273,12 +272,11 @@ public class SimulationNode { track.setTime(time, 1, animControl, animChannel, vars); skeleton.updateWorldVectors(); } - // ... and then apply constraints from the root bone to the last child ... for (Bone rootBone : skeleton.getRoots()) { - if(skeleton.getBoneIndex(rootBone) > 0) { - //ommit the 0 - indexed root bone as it is the bone added by importer + if (skeleton.getBoneIndex(rootBone) > 0) { + // ommit the 0 - indexed root bone as it is the bone added by importer this.applyConstraints(rootBone, alteredOmas, frame); } } @@ -419,7 +417,7 @@ public class SimulationNode { private List findConstraints(Long ownerOMA, BlenderContext blenderContext) { List result = new ArrayList(); List constraints = blenderContext.getConstraints(ownerOMA); - if(constraints != null) { + if (constraints != null) { for (Constraint constraint : constraints) { if (constraint.isImplemented() && constraint.validate()) { result.add(constraint); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java index 258a2c8aa..395097a96 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java @@ -11,14 +11,8 @@ import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; import com.jme3.animation.Bone; -import com.jme3.animation.BoneTrack; import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; -import com.jme3.math.Matrix4f; -import com.jme3.math.Transform; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.Node; @@ -28,19 +22,13 @@ 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.AnimationData; -import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.AnimationHelper; 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; import com.jme3.scene.plugins.blender.file.BlenderFileException; -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.MeshContext; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; import com.jme3.util.BufferUtils; -import com.jme3.util.TempVars; /** * This modifier allows to add bone animation to the object. @@ -56,8 +44,6 @@ import com.jme3.util.TempVars; private Structure objectStructure; private Structure meshStructure; - /** Loaded animation data. */ - private AnimationData animationData; /** Old memory address of the mesh that will have the skeleton applied. */ private Long meshOMA; @@ -80,8 +66,6 @@ import com.jme3.util.TempVars; if (this.validate(modifierStructure, blenderContext)) { Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); if (pArmatureObject.isNotNull()) { - ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); - armatureObject = pArmatureObject.fetchData().get(0); // load skeleton @@ -89,7 +73,7 @@ import com.jme3.util.TempVars; List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(); List bonesList = new ArrayList(); for (int i = 0; i < bonebase.size(); ++i) { - armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext); + this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext); } bonesList.add(0, new Bone("")); Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); @@ -100,72 +84,63 @@ import com.jme3.util.TempVars; // read mesh indexes meshOMA = meshStructure.getOldMemoryAddress(); + } else { + modifying = false; + } + } + } - // read animations - ArrayList animations = new ArrayList(); - List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); - if (actionHeaders != null) {// it may happen that the model has - // armature with no actions - for (FileBlockHeader header : actionHeaders) { - Structure actionStructure = header.getStructure(blenderContext); - String actionName = actionStructure.getName(); - - BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); - if (tracks != null && tracks.length > 0) { - // determining the animation time - float maximumTrackLength = 0; - for (BoneTrack track : tracks) { - float length = track.getLength(); - if (length > maximumTrackLength) { - maximumTrackLength = length; - } - } - - Animation boneAnimation = new Animation(actionName, maximumTrackLength); - boneAnimation.setTracks(tracks); - animations.add(boneAnimation); - } - } - } - // fetching action defined in object - Pointer pAction = (Pointer) objectStructure.getFieldValue("action"); - if (pAction.isNotNull()) { - Structure actionStructure = pAction.fetchData().get(0); - String actionName = actionStructure.getName(); - - BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); - if (tracks != null && tracks.length > 0) { - // determining the animation time - float maximumTrackLength = 0; - for (BoneTrack track : tracks) { - float length = track.getLength(); - if (length > maximumTrackLength) { - maximumTrackLength = length; - } - } - - Animation boneAnimation = new Animation(actionName, maximumTrackLength); - boneAnimation.setTracks(tracks); - animations.add(boneAnimation); - } - } - - animationData = new AnimationData(skeleton, animations); + /** + * 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); + } - // store the animation data for each bone - for (Bone bone : bones) { - if (bone.getName().length() > 0) { - BoneContext boneContext = blenderContext.getBoneContext(bone); - Long boneOma = boneContext.getBoneOma(); - if (boneOma != null) { - blenderContext.setAnimData(boneOma, animationData); - } - } + /** + * This method returns a map where the key is the object's group index that + * is used by a bone and the key is the bone index in the armature. + * + * @param defBaseStructure + * a bPose structure of the object + * @return bone group-to-index map + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Map getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException { + Map result = null; + if (skeleton.getBoneCount() != 0) { + result = new HashMap(); + List deformGroups = defBaseStructure.evaluateListBase();// bDeformGroup + int groupIndex = 0; + for (Structure deformGroup : deformGroups) { + String deformGroupName = deformGroup.getFieldValue("name").toString(); + int boneIndex = skeleton.getBoneIndex(deformGroupName); + if (boneIndex >= 0) { + result.put(groupIndex, boneIndex); } - } else { - modifying = false; + ++groupIndex; } } + return result; } @Override @@ -174,7 +149,7 @@ import com.jme3.util.TempVars; if (invalid) { LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); }// if invalid, animData will be null - if (animationData != null && skeleton != null) { + if (skeleton != null) { // setting weights for bones List geomList = (List) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE); MeshContext meshContext = blenderContext.getMeshContext(meshOMA); @@ -209,65 +184,9 @@ import com.jme3.util.TempVars; LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); invalid = true; } - } - - if (!invalid) { - // applying animations - AnimControl control = new AnimControl(animationData.skeleton); - List animList = animationData.anims; - if (animList != null && animList.size() > 0) { - HashMap anims = new HashMap(animList.size()); - for (int i = 0; i < animList.size(); ++i) { - Animation animation = animList.get(i); - anims.put(animation.getName(), animation); - } - control.setAnimations(anims); - } - node.addControl(control); - node.addControl(new SkeletonControl(animationData.skeleton)); - blenderContext.setNodeForSkeleton(skeleton, node); - - TempVars tempVars = TempVars.get(); - try { - Pointer pPose = (Pointer) armatureObject.getFieldValue("pose"); - if (pPose.isNotNull()) { - LOGGER.fine("Loading the pose of the armature."); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - - Structure pose = pPose.fetchData().get(0); - Structure chanbase = (Structure) pose.getFieldValue("chanbase"); - List chans = chanbase.evaluateListBase(); - Transform transform = new Transform(); - for (Structure poseChannel : chans) { - Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); - if (pBone.isNull()) { - throw new BlenderFileException("Cannot find bone for pose channel named: " + poseChannel.getName()); - } - BoneContext boneContext = blenderContext.getBoneContext(pBone.getOldMemoryAddress()); - - LOGGER.log(Level.FINEST, "Getting the global pose transformation for bone: {0}", boneContext); - Matrix4f poseMat = objectHelper.getMatrix(poseChannel, "pose_mat", blenderContext.getBlenderKey().isFixUpAxis()); - poseMat.multLocal(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX); - Matrix4f armatureWorldMat = objectHelper.getMatrix(armatureObject, "obmat", blenderContext.getBlenderKey().isFixUpAxis()); - Matrix4f boneWorldMat = armatureWorldMat.multLocal(poseMat); - - boneWorldMat.toTranslationVector(tempVars.vect1); - boneWorldMat.toRotationQuat(tempVars.quat1); - boneWorldMat.toScaleVector(tempVars.vect2); - transform.setTranslation(tempVars.vect1); - transform.setRotation(tempVars.quat1); - transform.setScale(tempVars.vect2); - - constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform); - } - } - } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Problems occured during pose loading: {0}.", e.getLocalizedMessage()); - } finally { - tempVars.release(); - } + AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); + animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getSkeletonAnimationNames(node.getName())); node.updateModelBound(); } @@ -288,9 +207,8 @@ import com.jme3.util.TempVars; * somehow invalid or corrupted */ private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException { - ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); - Map groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton); + Map groupToBoneIndexMap = this.getGroupToBoneIndexMap(defBase, skeleton); MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress()); @@ -326,9 +244,7 @@ import com.jme3.util.TempVars; */ private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map> vertexReferenceMap, Map groupToBoneIndexMap) throws BlenderFileException { bonesGroups[0] = 0; - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert - // = - // DeformVERTices + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); @@ -461,4 +377,52 @@ import com.jme3.util.TempVars; } weightsFloatData.rewind(); } + +// This method is now not used because it broke animations. +// Perhaps in the future I will find a solution to this problem. +// I store it here for future use. +// +// private void loadBonePoses() { +// TempVars tempVars = TempVars.get(); +// try { +// Pointer pPose = (Pointer) armatureObject.getFieldValue("pose"); +// if (pPose.isNotNull()) { +// LOGGER.fine("Loading the pose of the armature."); +// ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); +// ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); +// +// Structure pose = pPose.fetchData().get(0); +// Structure chanbase = (Structure) pose.getFieldValue("chanbase"); +// List chans = chanbase.evaluateListBase(); +// Transform transform = new Transform(); +// for (Structure poseChannel : chans) { +// Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); +// if (pBone.isNull()) { +// throw new BlenderFileException("Cannot find bone for pose channel named: " + poseChannel.getName()); +// } +// BoneContext boneContext = blenderContext.getBoneContext(pBone.getOldMemoryAddress()); +// +// LOGGER.log(Level.FINEST, "Getting the global pose transformation for bone: {0}", boneContext); +// Matrix4f poseMat = objectHelper.getMatrix(poseChannel, "pose_mat", blenderContext.getBlenderKey().isFixUpAxis()); +// poseMat.multLocal(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX); +// +// Matrix4f armatureWorldMat = objectHelper.getMatrix(armatureObject, "obmat", blenderContext.getBlenderKey().isFixUpAxis()); +// Matrix4f boneWorldMat = armatureWorldMat.multLocal(poseMat); +// +// boneWorldMat.toTranslationVector(tempVars.vect1); +// boneWorldMat.toRotationQuat(tempVars.quat1); +// boneWorldMat.toScaleVector(tempVars.vect2); +// transform.setTranslation(tempVars.vect1); +// transform.setRotation(tempVars.quat1); +// transform.setScale(tempVars.vect2); +// +// constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform); +// } +// } +// } catch (BlenderFileException e) { +// LOGGER.log(Level.WARNING, "Problems occured during pose loading: {0}.", e.getLocalizedMessage()); +// } finally { +// tempVars.release(); +// } +// } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java index a0ad5c8ef..6a28e433d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java @@ -31,14 +31,6 @@ */ package com.jme3.scene.plugins.blender.modifiers; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.animations.IpoHelper; -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 java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -47,6 +39,11 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.scene.plugins.blender.AbstractBlenderHelper; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.Structure; + /** * A class that is used in modifiers calculations. * @@ -113,76 +110,6 @@ public class ModifierHelper extends AbstractBlenderHelper { } } } - - // at the end read object's animation modifier (object animation is - // either described by action or by ipo of the object) - Modifier modifier; - if (blenderVersion <= 249) { - modifier = this.readAnimationModifier249(objectStructure, blenderContext); - } else { - modifier = this.readAnimationModifier250(objectStructure, blenderContext); - } - if (modifier != null) { - result.add(modifier); - } - return result; - } - - /** - * This method reads the object's animation modifier for blender version - * 2.49 and lower. - * - * @param objectStructure - * the object's structure - * @param blenderContext - * the blender context - * @return loaded modifier - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Modifier result = null; - Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo"); - if (pIpo.isNotNull()) { - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - Structure ipoStructure = pIpo.fetchData().get(0); - Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); - if (ipo != null) { - result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); - } - } - return result; - } - - /** - * This method reads the object's animation modifier for blender version - * 2.50 and higher. - * - * @param objectStructure - * the object's structure - * @param blenderContext - * the blender context - * @return loaded modifier - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Modifier result = null; - Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt"); - if (pAnimData.isNotNull()) { - Structure animData = pAnimData.fetchData().get(0); - Pointer pAction = (Pointer) animData.getFieldValue("action"); - if (pAction.isNotNull()) { - Structure actionStructure = pAction.fetchData().get(0); - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext); - if (ipo != null) {// ipo can be null if it has no curves applied, ommit such modifier then - result = new ObjectAnimationModifier(ipo, actionStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); - } - } - } return result; } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java deleted file mode 100644 index 805260202..000000000 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.SpatialTrack; -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.animations.AnimationData; -import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.file.BlenderFileException; - -/** - * This modifier allows to add animation to the object. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ObjectAnimationModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(ObjectAnimationModifier.class.getName()); - - /** Loaded animation data. */ - private AnimationData animationData; - - /** - * This constructor reads animation of the object itself (without bones) and - * stores it as an ArmatureModifierData modifier. The animation is returned - * as a modifier. It should be later applied regardless other modifiers. The - * reason for this is that object may not have modifiers added but it's - * animation should be working. The stored modifier is an anim data and - * additional data is given object's OMA. - * - * @param ipo - * the object's interpolation curves - * @param objectAnimationName - * the name of object's animation - * @param objectOMA - * the OMA of the object - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException { - int fps = blenderContext.getBlenderKey().getFps(); - - Spatial object = (Spatial) blenderContext.getLoadedFeature(objectOMA, LoadedFeatureDataType.LOADED_FEATURE); - // calculating track - SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, object.getLocalTranslation(), object.getLocalRotation(), object.getLocalScale(), 1, ipo.getLastFrame(), fps, true); - - Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float) fps); - animation.setTracks(new SpatialTrack[] { track }); - ArrayList animations = new ArrayList(1); - animations.add(animation); - - animationData = new AnimationData(animations); - blenderContext.setAnimData(objectOMA, animationData); - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); - }// if invalid, animData will be null - if (animationData != null) { - // INFO: constraints for this modifier are applied in the - // ObjectHelper when the whole object is loaded - List animList = animationData.anims; - if (animList != null && animList.size() > 0) { - HashMap anims = new HashMap(); - for (int i = 0; i < animList.size(); ++i) { - Animation animation = animList.get(i); - anims.put(animation.getName(), animation); - } - - AnimControl control = new AnimControl(null); - control.setAnimations(anims); - node.addControl(control); - } - } - } -} diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index cf1835095..4bc111d8e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -54,7 +54,7 @@ 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.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.AnimationHelper; import com.jme3.scene.plugins.blender.cameras.CameraHelper; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; import com.jme3.scene.plugins.blender.curves.CurvesHelper; @@ -74,9 +74,10 @@ import com.jme3.util.TempVars; * @author Marcin Roguski (Kaelthas) */ public class ObjectHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); - public static final String OMA_MARKER = "oma"; + public static final String OMA_MARKER = "oma"; + public static final String ARMATURE_NODE_MARKER = "armature-node"; /** * This constructor parses the given blender version and stores the result. @@ -236,9 +237,13 @@ public class ObjectHelper extends AbstractBlenderHelper { LOGGER.fine("Applying markers (those will be removed before the final result is released)."); blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress()); if (objectType == ObjectType.ARMATURE) { - blenderContext.addMarker(ArmatureHelper.ARMATURE_NODE_MARKER, result, Boolean.TRUE); + blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE); } + LOGGER.fine("Applying animations to the object if such are defined."); + AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); + animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getNodeAnimationNames(name)); + LOGGER.fine("Loading constraints connected with this object."); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); constraintHelper.loadConstraints(objectStructure, blenderContext);