Updating blender sources to the most recent state.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/branches/gradle-restructure@10999 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
experimental
Kae..pl 11 years ago
parent af58df2fc0
commit f71490c7dd
  1. 181
      jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java
  2. 33
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
  3. 12
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java
  4. 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java
  5. 29
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationData.java
  6. 420
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java
  7. 276
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java
  8. 6
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java
  9. 16
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java
  10. 194
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/IpoHelper.java
  11. 6
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java
  12. 15
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
  13. 14
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java
  14. 250
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  15. 83
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java
  16. 89
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java
  17. 13
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.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 <b>org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE</b>.
*/
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<String, List<String>> nodeAnimationMap = new HashMap<String, List<String>>();
/** A map between node name and its skeleton animation names. */
protected Map<String, List<String>> skeletonAnimationMap = new HashMap<String, List<String>>();
/**
* 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<String> animations = nodeAnimationMap.get(nodeName);
if (animations == null) {
animations = new ArrayList<String>();
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<String> 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<String> animations = skeletonAnimationMap.get(nodeName);
if (animations == null) {
animations = new ArrayList<String>();
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<String> 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<String, List<String>> 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<String, List<String>> 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<String, List<String>>(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<String>(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list
}
}
animsSize = ic.readInt("skeleton-anims-map-size", 0);
skeletonAnimationMap = new HashMap<String, List<String>>(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<String>(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<Node> scenes;
private List<Node> scenes;
/** Objects from all scenes. */
private List<Node> objects;
private List<Node> objects;
/** Materials from all objects. */
private List<Material> materials;
private List<Material> materials;
/** Textures from all objects. */
private List<Texture> textures;
private List<Texture> textures;
/** Animations of all objects. */
private List<AnimationData> animations;
private List<Animation> animations;
/** All cameras from the file. */
private List<CameraNode> cameras;
private List<CameraNode> cameras;
/** All lights from the file. */
private List<LightNode> lights;
private List<LightNode> 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<AnimationData>();
animations = new ArrayList<Animation>();
}
}
if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
@ -839,7 +948,7 @@ public class BlenderKey extends ModelKey {
/**
* @return all loaded animations
*/
public List<AnimationData> getAnimations() {
public List<Animation> getAnimations() {
return animations;
}

@ -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<Structure> parentStack = new Stack<Structure>();
/** A list of constraints for the specified object. */
protected Map<Long, List<Constraint>> constraints = new HashMap<Long, List<Constraint>>();
/** Anim data loaded for features. */
private Map<Long, AnimationData> animData = new HashMap<Long, AnimationData>();
/** Animations loaded for features. */
private Map<Long, List<Animation>> animations = new HashMap<Long, List<Animation>>();
/** Loaded skeletons. */
private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>();
/** 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<Animation> animList = animations.get(ownerOMA);
if(animList == null) {
animList = new ArrayList<Animation>();
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<Animation> getAnimations(Long ownerOMA) {
return animations.get(ownerOMA);
}
/**

@ -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<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
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 {

@ -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<Node> rootObjects = new ArrayList<Node>();
for (FileBlockHeader block : blocks) {

@ -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<Animation> anims;
public AnimationData(List<Animation> anims) {
this.anims = anims;
skeleton = null;
}
public AnimationData(Skeleton skeleton, List<Animation> anims) {
this.skeleton = skeleton;
this.anims = anims;
}
}

@ -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<String, BlenderAction> actions = new HashMap<String, BlenderAction>();
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<FileBlockHeader> 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<String> animationNames) {
if (animationNames != null && animationNames.size() > 0) {
List<Animation> animations = new ArrayList<Animation>();
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<String, Animation> anims = new HashMap<String, Animation>(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<String> animationNames) {
node.addControl(new SkeletonControl(skeleton));
blenderContext.setNodeForSkeleton(skeleton, node);
if (animationNames != null && animationNames.size() > 0) {
List<Animation> animations = new ArrayList<Animation>();
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<String, Animation> anims = new HashMap<String, Animation>(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<Structure> 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<Structure> 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<Structure> 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<Structure> 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<Structure> 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<Structure> 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<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
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<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
for (Entry<String, Ipo> 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<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
for (Entry<String, Ipo> 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!");
}
}
}

@ -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<Bone> 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<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException {
Map<Integer, Integer> result = null;
if (skeleton.getBoneCount() != 0) {
result = new HashMap<Integer, Integer>();
List<Structure> 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<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
List<BoneTrack> tracks = new ArrayList<BoneTrack>();
for (Structure actionGroup : actionGroups) {
String name = actionGroup.getFieldValue("name").toString();
int boneIndex = skeleton.getBoneIndex(name);
if (boneIndex >= 0) {
List<Structure> 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<Structure> 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(), 0, 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<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
List<BoneTrack> tracks = new ArrayList<BoneTrack>();
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(), 0, 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<BoneTrack> 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);
}
}
}
}

@ -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);

@ -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;

@ -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<Structure> 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<Structure> 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<Structure> 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<Structure> 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!");
}
}
}

@ -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);

@ -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<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
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);

@ -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<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) {
List<Constraint> result = new ArrayList<Constraint>();
List<Constraint> constraints = blenderContext.getConstraints(ownerOMA);
if(constraints != null) {
if (constraints != null) {
for (Constraint constraint : constraints) {
if (constraint.isImplemented() && constraint.validate()) {
result.add(constraint);

@ -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<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
List<Bone> bonesList = new ArrayList<Bone>();
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<Animation> animations = new ArrayList<Animation>();
List<FileBlockHeader> 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<Bone> 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<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException {
Map<Integer, Integer> result = null;
if (skeleton.getBoneCount() != 0) {
result = new HashMap<Integer, Integer>();
List<Structure> 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<Geometry> geomList = (List<Geometry>) 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<Animation> animList = animationData.anims;
if (animList != null && animList.size() > 0) {
HashMap<String, Animation> anims = new HashMap<String, Animation>(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<Structure> 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<Integer, Integer> groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton);
Map<Integer, Integer> 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<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> 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<Structure> 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();
// }
// }
}

@ -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;
}
}

@ -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(), 0, ipo.getLastFrame(), fps, true);
Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float) fps);
animation.setTracks(new SpatialTrack[] { track });
ArrayList<Animation> animations = new ArrayList<Animation>(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<Animation> animList = animationData.anims;
if (animList != null && animList.size() > 0) {
HashMap<String, Animation> anims = new HashMap<String, Animation>();
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);
}
}
}
}

@ -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);

Loading…
Cancel
Save