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
This commit is contained in:
parent
e4e3e0906d
commit
dd1fdd6bce
@ -33,9 +33,14 @@ package com.jme3.asset;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import com.jme3.animation.Animation;
|
||||||
import com.jme3.bounding.BoundingVolume;
|
import com.jme3.bounding.BoundingVolume;
|
||||||
import com.jme3.collision.Collidable;
|
import com.jme3.collision.Collidable;
|
||||||
import com.jme3.collision.CollisionResults;
|
import com.jme3.collision.CollisionResults;
|
||||||
@ -52,7 +57,6 @@ import com.jme3.scene.LightNode;
|
|||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.SceneGraphVisitor;
|
import com.jme3.scene.SceneGraphVisitor;
|
||||||
import com.jme3.scene.Spatial;
|
import com.jme3.scene.Spatial;
|
||||||
import com.jme3.scene.plugins.blender.animations.AnimationData;
|
|
||||||
import com.jme3.texture.Texture;
|
import com.jme3.texture.Texture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,66 +65,70 @@ import com.jme3.texture.Texture;
|
|||||||
*/
|
*/
|
||||||
public class BlenderKey extends ModelKey {
|
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
|
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
|
||||||
* between the frames.
|
* 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.
|
* 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. */
|
/** 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. */
|
/** 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. */
|
/** 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). */
|
/** 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
|
* 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.
|
* 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
|
* 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).
|
* 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. */
|
/** 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.
|
* 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.
|
* 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. */
|
/** 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.
|
* 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>.
|
* 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. */
|
/** 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. */
|
/** 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
|
* 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.
|
* 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. */
|
/** 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. */
|
/** 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
|
* 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.
|
* 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.
|
* Constructor used by serialization mechanisms.
|
||||||
@ -473,6 +481,58 @@ public class BlenderKey extends ModelKey {
|
|||||||
return defaultMaterial;
|
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
|
@Override
|
||||||
public void write(JmeExporter e) throws IOException {
|
public void write(JmeExporter e) throws IOException {
|
||||||
super.write(e);
|
super.write(e);
|
||||||
@ -492,6 +552,22 @@ public class BlenderKey extends ModelKey {
|
|||||||
oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
|
oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
|
||||||
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
|
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
|
||||||
oc.write(optimiseTextures, "optimise-textures", false);
|
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
|
@Override
|
||||||
@ -513,6 +589,26 @@ public class BlenderKey extends ModelKey {
|
|||||||
skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
|
skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
|
||||||
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
|
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
|
||||||
optimiseTextures = ic.readBoolean("optimise-textures", false);
|
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
|
@Override
|
||||||
@ -532,7 +628,9 @@ public class BlenderKey extends ModelKey {
|
|||||||
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
|
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
|
||||||
result = prime * result + maxTextureSize;
|
result = prime * result + maxTextureSize;
|
||||||
result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode());
|
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 + (optimiseTextures ? 1231 : 1237);
|
||||||
|
result = prime * result + (skeletonAnimationMap == null ? 0 : skeletonAnimationMap.hashCode());
|
||||||
result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
|
result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
|
||||||
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
|
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
|
||||||
result = prime * result + skyGeneratedTextureSize;
|
result = prime * result + skyGeneratedTextureSize;
|
||||||
@ -545,10 +643,7 @@ public class BlenderKey extends ModelKey {
|
|||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!super.equals(obj)) {
|
if (!(obj instanceof BlenderKey)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this.getClass() != obj.getClass()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BlenderKey other = (BlenderKey) obj;
|
BlenderKey other = (BlenderKey) obj;
|
||||||
@ -599,9 +694,23 @@ public class BlenderKey extends ModelKey {
|
|||||||
if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
|
if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (nodeAnimationMap == null) {
|
||||||
|
if (other.nodeAnimationMap != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!nodeAnimationMap.equals(other.nodeAnimationMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (optimiseTextures != other.optimiseTextures) {
|
if (optimiseTextures != other.optimiseTextures) {
|
||||||
return false;
|
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)) {
|
if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -662,28 +771,28 @@ public class BlenderKey extends ModelKey {
|
|||||||
public static class LoadingResults extends Spatial {
|
public static class LoadingResults extends Spatial {
|
||||||
|
|
||||||
/** Bitwise mask of features that are to be loaded. */
|
/** Bitwise mask of features that are to be loaded. */
|
||||||
private final int featuresToLoad;
|
private final int featuresToLoad;
|
||||||
/** The scenes from the file. */
|
/** The scenes from the file. */
|
||||||
private List<Node> scenes;
|
private List<Node> scenes;
|
||||||
/** Objects from all scenes. */
|
/** Objects from all scenes. */
|
||||||
private List<Node> objects;
|
private List<Node> objects;
|
||||||
/** Materials from all objects. */
|
/** Materials from all objects. */
|
||||||
private List<Material> materials;
|
private List<Material> materials;
|
||||||
/** Textures from all objects. */
|
/** Textures from all objects. */
|
||||||
private List<Texture> textures;
|
private List<Texture> textures;
|
||||||
/** Animations of all objects. */
|
/** Animations of all objects. */
|
||||||
private List<AnimationData> animations;
|
private List<Animation> animations;
|
||||||
/** All cameras from the file. */
|
/** All cameras from the file. */
|
||||||
private List<CameraNode> cameras;
|
private List<CameraNode> cameras;
|
||||||
/** All lights from the file. */
|
/** All lights from the file. */
|
||||||
private List<LightNode> lights;
|
private List<LightNode> lights;
|
||||||
/** Loaded sky. */
|
/** 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
|
* 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.
|
* 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
|
* 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) {
|
if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {
|
||||||
animations = new ArrayList<AnimationData>();
|
animations = new ArrayList<Animation>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
|
if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
|
||||||
@ -839,7 +948,7 @@ public class BlenderKey extends ModelKey {
|
|||||||
/**
|
/**
|
||||||
* @return all loaded animations
|
* @return all loaded animations
|
||||||
*/
|
*/
|
||||||
public List<AnimationData> getAnimations() {
|
public List<Animation> getAnimations() {
|
||||||
return animations;
|
return animations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import com.jme3.animation.Animation;
|
||||||
import com.jme3.animation.Bone;
|
import com.jme3.animation.Bone;
|
||||||
import com.jme3.animation.Skeleton;
|
import com.jme3.animation.Skeleton;
|
||||||
import com.jme3.asset.AssetManager;
|
import com.jme3.asset.AssetManager;
|
||||||
@ -46,7 +47,6 @@ import com.jme3.asset.BlenderKey;
|
|||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.scene.Node;
|
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.animations.BoneContext;
|
||||||
import com.jme3.scene.plugins.blender.constraints.Constraint;
|
import com.jme3.scene.plugins.blender.constraints.Constraint;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||||
@ -100,8 +100,8 @@ public class BlenderContext {
|
|||||||
private Stack<Structure> parentStack = new Stack<Structure>();
|
private Stack<Structure> parentStack = new Stack<Structure>();
|
||||||
/** A list of constraints for the specified object. */
|
/** A list of constraints for the specified object. */
|
||||||
protected Map<Long, List<Constraint>> constraints = new HashMap<Long, List<Constraint>>();
|
protected Map<Long, List<Constraint>> constraints = new HashMap<Long, List<Constraint>>();
|
||||||
/** Anim data loaded for features. */
|
/** Animations loaded for features. */
|
||||||
private Map<Long, AnimationData> animData = new HashMap<Long, AnimationData>();
|
private Map<Long, List<Animation>> animations = new HashMap<Long, List<Animation>>();
|
||||||
/** Loaded skeletons. */
|
/** Loaded skeletons. */
|
||||||
private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>();
|
private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>();
|
||||||
/** A map between skeleton and node it modifies. */
|
/** A map between skeleton and node it modifies. */
|
||||||
@ -407,15 +407,20 @@ public class BlenderContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @param ownerOMA
|
||||||
* the owner's old memory address
|
* the owner's old memory address
|
||||||
* @param animData
|
* @param animation
|
||||||
* the animation data for the feature specified by ownerOMA
|
* the animation for the feature specified by ownerOMA
|
||||||
*/
|
*/
|
||||||
public void setAnimData(Long ownerOMA, AnimationData animData) {
|
public void addAnimation(Long ownerOMA, Animation animation) {
|
||||||
this.animData.put(ownerOMA, animData);
|
List<Animation> animList = animations.get(ownerOMA);
|
||||||
|
if(animList == null) {
|
||||||
|
animList = new ArrayList<Animation>();
|
||||||
|
animations.put(ownerOMA, animList);
|
||||||
|
}
|
||||||
|
animations.put(ownerOMA, animList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -423,10 +428,10 @@ public class BlenderContext {
|
|||||||
*
|
*
|
||||||
* @param ownerOMA
|
* @param ownerOMA
|
||||||
* the old memory address of the animation data owner
|
* 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) {
|
public List<Animation> getAnimations(Long ownerOMA) {
|
||||||
return animData.get(ownerOMA);
|
return animations.get(ownerOMA);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,8 +47,7 @@ import com.jme3.scene.CameraNode;
|
|||||||
import com.jme3.scene.LightNode;
|
import com.jme3.scene.LightNode;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.Spatial;
|
import com.jme3.scene.Spatial;
|
||||||
import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
|
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
||||||
import com.jme3.scene.plugins.blender.animations.IpoHelper;
|
|
||||||
import com.jme3.scene.plugins.blender.cameras.CameraHelper;
|
import com.jme3.scene.plugins.blender.cameras.CameraHelper;
|
||||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||||
import com.jme3.scene.plugins.blender.curves.CurvesHelper;
|
import com.jme3.scene.plugins.blender.curves.CurvesHelper;
|
||||||
@ -85,6 +84,10 @@ public class BlenderLoader implements AssetLoader {
|
|||||||
List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
|
List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
|
||||||
BlenderKey blenderKey = blenderContext.getBlenderKey();
|
BlenderKey blenderKey = blenderContext.getBlenderKey();
|
||||||
LoadingResults loadingResults = blenderKey.prepareLoadingResults();
|
LoadingResults loadingResults = blenderKey.prepareLoadingResults();
|
||||||
|
|
||||||
|
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||||
|
animationHelper.loadAnimations();
|
||||||
|
|
||||||
for (FileBlockHeader block : blocks) {
|
for (FileBlockHeader block : blocks) {
|
||||||
switch (block.getCode()) {
|
switch (block.getCode()) {
|
||||||
case FileBlockHeader.BLOCK_OB00:// Object
|
case FileBlockHeader.BLOCK_OB00:// Object
|
||||||
@ -240,7 +243,7 @@ public class BlenderLoader implements AssetLoader {
|
|||||||
blenderContext.setBlenderKey(blenderKey);
|
blenderContext.setBlenderKey(blenderKey);
|
||||||
|
|
||||||
// creating helpers
|
// 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(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
@ -250,7 +253,6 @@ public class BlenderLoader implements AssetLoader {
|
|||||||
blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(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(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad;
|
|||||||
import com.jme3.scene.LightNode;
|
import com.jme3.scene.LightNode;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.Spatial;
|
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.constraints.ConstraintHelper;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||||
@ -62,6 +63,9 @@ public class BlenderModelLoader extends BlenderLoader {
|
|||||||
try {
|
try {
|
||||||
this.setup(assetInfo);
|
this.setup(assetInfo);
|
||||||
|
|
||||||
|
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||||
|
animationHelper.loadAnimations();
|
||||||
|
|
||||||
BlenderKey blenderKey = blenderContext.getBlenderKey();
|
BlenderKey blenderKey = blenderContext.getBlenderKey();
|
||||||
List<Node> rootObjects = new ArrayList<Node>();
|
List<Node> rootObjects = new ArrayList<Node>();
|
||||||
for (FileBlockHeader block : blocks) {
|
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(), 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<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(), 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<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
|
// first get the bone matrix in its armature space
|
||||||
globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
|
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
|
if(blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||||
globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX);
|
// 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);
|
Spatial armature = (Spatial) objectHelper.toObject(blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext), blenderContext);
|
||||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
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
|
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;
|
int yIndex = 1, zIndex = 2;
|
||||||
if (spatialTrack && fixUpAxis) {
|
boolean swapAxes = spatialTrack && fixUpAxis;
|
||||||
|
if (swapAxes) {
|
||||||
yIndex = 2;
|
yIndex = 2;
|
||||||
zIndex = 1;
|
zIndex = 1;
|
||||||
}
|
}
|
||||||
@ -164,8 +165,7 @@ public class Ipo {
|
|||||||
// calculating track data
|
// calculating track data
|
||||||
for (int frame = startFrame; frame <= stopFrame; ++frame) {
|
for (int frame = startFrame; frame <= stopFrame; ++frame) {
|
||||||
int index = frame - startFrame;
|
int index = frame - startFrame;
|
||||||
times[index] = index * timeBetweenFrames;// start + (frame - 1)
|
times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames;
|
||||||
// * timeBetweenFrames;
|
|
||||||
for (int j = 0; j < bezierCurves.length; ++j) {
|
for (int j = 0; j < bezierCurves.length; ++j) {
|
||||||
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
|
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
|
||||||
switch (bezierCurves[j].getType()) {
|
switch (bezierCurves[j].getType()) {
|
||||||
@ -174,7 +174,7 @@ public class Ipo {
|
|||||||
translation[0] = (float) value;
|
translation[0] = (float) value;
|
||||||
break;
|
break;
|
||||||
case AC_LOC_Y:
|
case AC_LOC_Y:
|
||||||
if (fixUpAxis && value != 0) {
|
if (swapAxes && value != 0) {
|
||||||
value = -value;
|
value = -value;
|
||||||
}
|
}
|
||||||
translation[yIndex] = (float) value;
|
translation[yIndex] = (float) value;
|
||||||
@ -188,7 +188,7 @@ public class Ipo {
|
|||||||
objectRotation[0] = (float) value * degreeToRadiansFactor;
|
objectRotation[0] = (float) value * degreeToRadiansFactor;
|
||||||
break;
|
break;
|
||||||
case OB_ROT_Y:
|
case OB_ROT_Y:
|
||||||
if (fixUpAxis && value != 0) {
|
if (swapAxes && value != 0) {
|
||||||
value = -value;
|
value = -value;
|
||||||
}
|
}
|
||||||
objectRotation[yIndex] = (float) value * degreeToRadiansFactor;
|
objectRotation[yIndex] = (float) value * degreeToRadiansFactor;
|
||||||
@ -202,10 +202,10 @@ public class Ipo {
|
|||||||
scale[0] = (float) value;
|
scale[0] = (float) value;
|
||||||
break;
|
break;
|
||||||
case AC_SIZE_Y:
|
case AC_SIZE_Y:
|
||||||
scale[fixUpAxis ? 2 : 1] = (float) value;
|
scale[yIndex] = (float) value;
|
||||||
break;
|
break;
|
||||||
case AC_SIZE_Z:
|
case AC_SIZE_Z:
|
||||||
scale[fixUpAxis ? 1 : 2] = (float) value;
|
scale[zIndex] = (float) value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// QUATERNION ROTATION (used with bone animation)
|
// QUATERNION ROTATION (used with bone animation)
|
||||||
@ -216,7 +216,7 @@ public class Ipo {
|
|||||||
quaternionRotation[0] = (float) value;
|
quaternionRotation[0] = (float) value;
|
||||||
break;
|
break;
|
||||||
case AC_QUAT_Y:
|
case AC_QUAT_Y:
|
||||||
if (fixUpAxis && value != 0) {
|
if (swapAxes && value != 0) {
|
||||||
value = -value;
|
value = -value;
|
||||||
}
|
}
|
||||||
quaternionRotation[yIndex] = (float) 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.Spatial;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
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.BoneContext;
|
||||||
import com.jme3.scene.plugins.blender.animations.Ipo;
|
import com.jme3.scene.plugins.blender.animations.Ipo;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
|
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constraint applied on the bone.
|
* 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
|
// the second part of the if expression verifies if the found node
|
||||||
// (if any) is an armature 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()) {
|
if (subtargetName.trim().isEmpty()) {
|
||||||
LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name);
|
LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name);
|
||||||
return false;
|
return false;
|
||||||
|
@ -17,10 +17,9 @@ import com.jme3.scene.Spatial;
|
|||||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
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.BoneContext;
|
||||||
import com.jme3.scene.plugins.blender.animations.Ipo;
|
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.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
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 {
|
public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||||
LOGGER.fine("Loading constraints.");
|
LOGGER.fine("Loading constraints.");
|
||||||
// reading influence ipos for the 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>>();
|
Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
|
||||||
Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
|
Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
|
||||||
if (pActions.isNotNull()) {
|
if (pActions.isNotNull()) {
|
||||||
@ -79,7 +78,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
|
|||||||
Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
|
Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
|
||||||
if (pIpo.isNotNull()) {
|
if (pIpo.isNotNull()) {
|
||||||
String constraintName = constraintChannel.getFieldValue("name").toString();
|
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);
|
ipos.put(constraintName, ipo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +106,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
|
|||||||
Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName);
|
Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName);
|
||||||
if (ipo == null) {
|
if (ipo == null) {
|
||||||
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
|
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
|
||||||
ipo = ipoHelper.fromValue(enforce);
|
ipo = animationHelper.fromValue(enforce);
|
||||||
}
|
}
|
||||||
constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext));
|
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;
|
Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null;
|
||||||
if (ipo == null) {
|
if (ipo == null) {
|
||||||
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
|
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));
|
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) {
|
public Transform getTransform(Long oma, String subtargetName, Space space) {
|
||||||
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
|
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) {
|
if (isArmature) {
|
||||||
blenderContext.getSkeleton(oma).updateWorldVectors();
|
blenderContext.getSkeleton(oma).updateWorldVectors();
|
||||||
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
|
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) {
|
public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
|
||||||
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
|
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, 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) {
|
if (isArmature) {
|
||||||
Skeleton skeleton = blenderContext.getSkeleton(oma);
|
Skeleton skeleton = blenderContext.getSkeleton(oma);
|
||||||
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
|
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
|
||||||
|
@ -25,7 +25,6 @@ import com.jme3.scene.Node;
|
|||||||
import com.jme3.scene.Spatial;
|
import com.jme3.scene.Spatial;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
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.BoneContext;
|
||||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||||
import com.jme3.util.TempVars;
|
import com.jme3.util.TempVars;
|
||||||
@ -95,7 +94,7 @@ public class SimulationNode {
|
|||||||
private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
|
private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
|
||||||
this.blenderContext = blenderContext;
|
this.blenderContext = blenderContext;
|
||||||
Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE);
|
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);
|
skeleton = blenderContext.getSkeleton(featureOMA);
|
||||||
|
|
||||||
Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton);
|
Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton);
|
||||||
@ -136,9 +135,9 @@ public class SimulationNode {
|
|||||||
// each bone of the skeleton has the same anim data applied
|
// each bone of the skeleton has the same anim data applied
|
||||||
BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(1));
|
BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(1));
|
||||||
Long boneOma = boneContext.getBoneOma();
|
Long boneOma = boneContext.getBoneOma();
|
||||||
animations = blenderContext.getAnimData(boneOma) == null ? null : blenderContext.getAnimData(boneOma).anims;
|
animations = blenderContext.getAnimations(boneOma);
|
||||||
} else {
|
} else {
|
||||||
animations = blenderContext.getAnimData(featureOMA) == null ? null : blenderContext.getAnimData(featureOMA).anims;
|
animations = blenderContext.getAnimations(featureOMA);
|
||||||
for (Spatial child : spatial.getChildren()) {
|
for (Spatial child : spatial.getChildren()) {
|
||||||
if (child instanceof Node) {
|
if (child instanceof Node) {
|
||||||
children.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, child), blenderContext, false));
|
children.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, child), blenderContext, false));
|
||||||
@ -274,11 +273,10 @@ public class SimulationNode {
|
|||||||
skeleton.updateWorldVectors();
|
skeleton.updateWorldVectors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ... and then apply constraints from the root bone to the last child ...
|
// ... and then apply constraints from the root bone to the last child ...
|
||||||
for (Bone rootBone : skeleton.getRoots()) {
|
for (Bone rootBone : skeleton.getRoots()) {
|
||||||
if(skeleton.getBoneIndex(rootBone) > 0) {
|
if (skeleton.getBoneIndex(rootBone) > 0) {
|
||||||
//ommit the 0 - indexed root bone as it is the bone added by importer
|
// ommit the 0 - indexed root bone as it is the bone added by importer
|
||||||
this.applyConstraints(rootBone, alteredOmas, frame);
|
this.applyConstraints(rootBone, alteredOmas, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,7 +417,7 @@ public class SimulationNode {
|
|||||||
private List<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) {
|
private List<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) {
|
||||||
List<Constraint> result = new ArrayList<Constraint>();
|
List<Constraint> result = new ArrayList<Constraint>();
|
||||||
List<Constraint> constraints = blenderContext.getConstraints(ownerOMA);
|
List<Constraint> constraints = blenderContext.getConstraints(ownerOMA);
|
||||||
if(constraints != null) {
|
if (constraints != null) {
|
||||||
for (Constraint constraint : constraints) {
|
for (Constraint constraint : constraints) {
|
||||||
if (constraint.isImplemented() && constraint.validate()) {
|
if (constraint.isImplemented() && constraint.validate()) {
|
||||||
result.add(constraint);
|
result.add(constraint);
|
||||||
|
@ -11,14 +11,8 @@ import java.util.TreeMap;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.jme3.animation.AnimControl;
|
|
||||||
import com.jme3.animation.Animation;
|
|
||||||
import com.jme3.animation.Bone;
|
import com.jme3.animation.Bone;
|
||||||
import com.jme3.animation.BoneTrack;
|
|
||||||
import com.jme3.animation.Skeleton;
|
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.Geometry;
|
||||||
import com.jme3.scene.Mesh;
|
import com.jme3.scene.Mesh;
|
||||||
import com.jme3.scene.Node;
|
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.VertexBuffer.Usage;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
||||||
import com.jme3.scene.plugins.blender.animations.AnimationData;
|
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
||||||
import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
|
|
||||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
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.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
import com.jme3.scene.plugins.blender.meshes.MeshContext;
|
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.BufferUtils;
|
||||||
import com.jme3.util.TempVars;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This modifier allows to add bone animation to the object.
|
* This modifier allows to add bone animation to the object.
|
||||||
@ -56,8 +44,6 @@ import com.jme3.util.TempVars;
|
|||||||
private Structure objectStructure;
|
private Structure objectStructure;
|
||||||
private Structure meshStructure;
|
private Structure meshStructure;
|
||||||
|
|
||||||
/** Loaded animation data. */
|
|
||||||
private AnimationData animationData;
|
|
||||||
/** Old memory address of the mesh that will have the skeleton applied. */
|
/** Old memory address of the mesh that will have the skeleton applied. */
|
||||||
private Long meshOMA;
|
private Long meshOMA;
|
||||||
|
|
||||||
@ -80,8 +66,6 @@ import com.jme3.util.TempVars;
|
|||||||
if (this.validate(modifierStructure, blenderContext)) {
|
if (this.validate(modifierStructure, blenderContext)) {
|
||||||
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
|
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
|
||||||
if (pArmatureObject.isNotNull()) {
|
if (pArmatureObject.isNotNull()) {
|
||||||
ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class);
|
|
||||||
|
|
||||||
armatureObject = pArmatureObject.fetchData().get(0);
|
armatureObject = pArmatureObject.fetchData().get(0);
|
||||||
|
|
||||||
// load skeleton
|
// load skeleton
|
||||||
@ -89,7 +73,7 @@ import com.jme3.util.TempVars;
|
|||||||
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
|
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
|
||||||
List<Bone> bonesList = new ArrayList<Bone>();
|
List<Bone> bonesList = new ArrayList<Bone>();
|
||||||
for (int i = 0; i < bonebase.size(); ++i) {
|
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(""));
|
bonesList.add(0, new Bone(""));
|
||||||
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
|
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
|
||||||
@ -100,81 +84,72 @@ import com.jme3.util.TempVars;
|
|||||||
|
|
||||||
// read mesh indexes
|
// read mesh indexes
|
||||||
meshOMA = meshStructure.getOldMemoryAddress();
|
meshOMA = meshStructure.getOldMemoryAddress();
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
modifying = false;
|
modifying = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void apply(Node node, BlenderContext blenderContext) {
|
public void apply(Node node, BlenderContext blenderContext) {
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
|
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
|
||||||
}// if invalid, animData will be null
|
}// if invalid, animData will be null
|
||||||
if (animationData != null && skeleton != null) {
|
if (skeleton != null) {
|
||||||
// setting weights for bones
|
// setting weights for bones
|
||||||
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
|
List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
|
||||||
MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
|
MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
|
||||||
@ -209,65 +184,9 @@ import com.jme3.util.TempVars;
|
|||||||
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
|
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
|
||||||
invalid = true;
|
invalid = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!invalid) {
|
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||||
// applying animations
|
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getSkeletonAnimationNames(node.getName()));
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
node.updateModelBound();
|
node.updateModelBound();
|
||||||
}
|
}
|
||||||
@ -288,9 +207,8 @@ import com.jme3.util.TempVars;
|
|||||||
* somehow invalid or corrupted
|
* somehow invalid or corrupted
|
||||||
*/
|
*/
|
||||||
private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException {
|
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");
|
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());
|
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 {
|
private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap) throws BlenderFileException {
|
||||||
bonesGroups[0] = 0;
|
bonesGroups[0] = 0;
|
||||||
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert
|
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
|
||||||
// =
|
|
||||||
// DeformVERTices
|
|
||||||
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||||
ByteBuffer indicesData = BufferUtils.createByteBuffer(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();
|
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;
|
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.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -47,6 +39,11 @@ import java.util.Set;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
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.
|
* 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;
|
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(), 1, 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.AbstractBlenderHelper;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
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.cameras.CameraHelper;
|
||||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||||
import com.jme3.scene.plugins.blender.curves.CurvesHelper;
|
import com.jme3.scene.plugins.blender.curves.CurvesHelper;
|
||||||
@ -74,9 +74,10 @@ import com.jme3.util.TempVars;
|
|||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
public class ObjectHelper extends AbstractBlenderHelper {
|
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.
|
* 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).");
|
LOGGER.fine("Applying markers (those will be removed before the final result is released).");
|
||||||
blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
|
blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
|
||||||
if (objectType == ObjectType.ARMATURE) {
|
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.");
|
LOGGER.fine("Loading constraints connected with this object.");
|
||||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||||
constraintHelper.loadConstraints(objectStructure, blenderContext);
|
constraintHelper.loadConstraints(objectStructure, blenderContext);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user