Feature: added automatic action mapping instead of explicit mapping in
BlenderKey.
This commit is contained in:
parent
03f8df05b6
commit
51215a352e
@ -33,11 +33,7 @@ package com.jme3.asset;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.jme3.animation.Animation;
|
||||
@ -126,10 +122,8 @@ public class BlenderKey extends ModelKey {
|
||||
* and textures that in the final result will never be visible - will be discarded.
|
||||
*/
|
||||
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>>();
|
||||
/** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */
|
||||
protected AnimationMatchMethod animationMatchMethod = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH;
|
||||
|
||||
/**
|
||||
* Constructor used by serialization mechanisms.
|
||||
@ -446,6 +440,23 @@ public class BlenderKey extends ModelKey {
|
||||
return optimiseTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the way the animations will be matched with skeletons.
|
||||
*
|
||||
* @param animationMatchMethod
|
||||
* the way the animations will be matched with skeletons
|
||||
*/
|
||||
public void setAnimationMatchMethod(AnimationMatchMethod animationMatchMethod) {
|
||||
this.animationMatchMethod = animationMatchMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the way the animations will be matched with skeletons
|
||||
*/
|
||||
public AnimationMatchMethod getAnimationMatchMethod() {
|
||||
return animationMatchMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
|
||||
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
|
||||
@ -482,58 +493,6 @@ public class BlenderKey extends ModelKey {
|
||||
return defaultMaterial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds spatial animation name for specified node.
|
||||
* @param nodeName
|
||||
* the name of the node
|
||||
* @param animationName
|
||||
* the spatial animation name
|
||||
*/
|
||||
public void addNodeAnimation(String nodeName, String animationName) {
|
||||
List<String> animations = nodeAnimationMap.get(nodeName);
|
||||
if (animations == null) {
|
||||
animations = new ArrayList<String>();
|
||||
nodeAnimationMap.put(nodeName, animations);
|
||||
}
|
||||
animations.add(animationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all spatial animation names for the given node.
|
||||
* @param nodeName
|
||||
* the name of the node
|
||||
* @return all spatial animations names or null if none are defined
|
||||
*/
|
||||
public List<String> getNodeAnimationNames(String nodeName) {
|
||||
return nodeAnimationMap.get(nodeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds bone animation name for specified node.
|
||||
* @param nodeName
|
||||
* the name of the node
|
||||
* @param animationName
|
||||
* the bone animation name
|
||||
*/
|
||||
public void addSkeletonAnimation(String nodeName, String animationName) {
|
||||
List<String> animations = skeletonAnimationMap.get(nodeName);
|
||||
if (animations == null) {
|
||||
animations = new ArrayList<String>();
|
||||
skeletonAnimationMap.put(nodeName, animations);
|
||||
}
|
||||
animations.add(animationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all bone animation names for the given node.
|
||||
* @param nodeName
|
||||
* the name of the node
|
||||
* @return all bone animations names or null if none are defined
|
||||
*/
|
||||
public List<String> getSkeletonAnimationNames(String nodeName) {
|
||||
return skeletonAnimationMap.get(nodeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
super.write(e);
|
||||
@ -553,30 +512,7 @@ public class BlenderKey extends ModelKey {
|
||||
oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
|
||||
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
|
||||
oc.write(optimiseTextures, "optimise-textures", false);
|
||||
|
||||
if (nodeAnimationMap == null) {
|
||||
oc.write(0, "node-anims-map-size", 0);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (skeletonAnimationMap == null) {
|
||||
oc.write(0, "skeleton-anims-map-size", 0);
|
||||
} else {
|
||||
oc.write(skeletonAnimationMap.size(), "skeleton-anims-map-size", 0);
|
||||
int 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;
|
||||
}
|
||||
}
|
||||
oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -598,32 +534,14 @@ public class BlenderKey extends ModelKey {
|
||||
skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
|
||||
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
|
||||
optimiseTextures = ic.readBoolean("optimise-textures", false);
|
||||
|
||||
int animsSize = ic.readInt("node-anims-map-size", 0);
|
||||
nodeAnimationMap = new HashMap<String, List<String>>(animsSize);
|
||||
if (animsSize > 0) {
|
||||
for (int i = 0; i < animsSize; ++i) {
|
||||
String nodeName = ic.readString("node-anim-" + i, null);
|
||||
String[] anims = ic.readStringArray("node-anims-" + i, null);
|
||||
nodeAnimationMap.put(nodeName, new ArrayList<String>(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list
|
||||
}
|
||||
}
|
||||
|
||||
animsSize = ic.readInt("skeleton-anims-map-size", 0);
|
||||
skeletonAnimationMap = new HashMap<String, List<String>>(animsSize);
|
||||
if (animsSize > 0) {
|
||||
for (int i = 0; i < animsSize; ++i) {
|
||||
String nodeName = ic.readString("skeleton-anim-" + i, null);
|
||||
String[] anims = ic.readStringArray("skeleton-anims-" + i, null);
|
||||
skeletonAnimationMap.put(nodeName, new ArrayList<String>(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list
|
||||
}
|
||||
}
|
||||
animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + (animationMatchMethod == null ? 0 : animationMatchMethod.hashCode());
|
||||
result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
|
||||
result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
|
||||
result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
|
||||
@ -637,9 +555,7 @@ public class BlenderKey extends ModelKey {
|
||||
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
|
||||
result = prime * result + maxTextureSize;
|
||||
result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode());
|
||||
result = prime * result + (nodeAnimationMap == null ? 0 : nodeAnimationMap.hashCode());
|
||||
result = prime * result + (optimiseTextures ? 1231 : 1237);
|
||||
result = prime * result + (skeletonAnimationMap == null ? 0 : skeletonAnimationMap.hashCode());
|
||||
result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
|
||||
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
|
||||
result = prime * result + skyGeneratedTextureSize;
|
||||
@ -649,13 +565,13 @@ public class BlenderKey extends ModelKey {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof BlenderKey)) {
|
||||
if (obj instanceof BlenderKey) {
|
||||
return false;
|
||||
}
|
||||
BlenderKey other = (BlenderKey) obj;
|
||||
if (animationMatchMethod != other.animationMatchMethod) {
|
||||
return false;
|
||||
}
|
||||
if (assetRootPath == null) {
|
||||
if (other.assetRootPath != null) {
|
||||
return false;
|
||||
@ -703,23 +619,9 @@ public class BlenderKey extends ModelKey {
|
||||
if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
|
||||
return false;
|
||||
}
|
||||
if (nodeAnimationMap == null) {
|
||||
if (other.nodeAnimationMap != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!nodeAnimationMap.equals(other.nodeAnimationMap)) {
|
||||
return false;
|
||||
}
|
||||
if (optimiseTextures != other.optimiseTextures) {
|
||||
return false;
|
||||
}
|
||||
if (skeletonAnimationMap == null) {
|
||||
if (other.skeletonAnimationMap != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!skeletonAnimationMap.equals(other.skeletonAnimationMap)) {
|
||||
return false;
|
||||
}
|
||||
if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) {
|
||||
return false;
|
||||
}
|
||||
@ -773,6 +675,29 @@ public class BlenderKey extends ModelKey {
|
||||
CUBE, SPHERE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum describes which animations should be attached to which armature.
|
||||
* Blender does not store the mapping between action and armature. That is why the importer
|
||||
* will try to match those by comparing bone name of the armature with the channel names
|
||||
* int the actions.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static enum AnimationMatchMethod {
|
||||
/**
|
||||
* Animation is matched with skeleton when at leas one bone name matches the name of the action channel.
|
||||
* All the bones that do not have their corresponding channel in the animation will not get the proper tracks for
|
||||
* this particulat animation.
|
||||
* Also the channel will not be used for the animation if it does not find the proper bone name.
|
||||
*/
|
||||
AT_LEAST_ONE_NAME_MATCH,
|
||||
/**
|
||||
* Animation is matched when all action names are covered by the target names (bone names or the name of the
|
||||
* animated spatial.
|
||||
*/
|
||||
ALL_NAMES_MATCH;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class holds the loading results according to the given loading flag.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
|
@ -47,6 +47,7 @@ import com.jme3.asset.BlenderKey;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.animations.BlenderAction;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.Constraint;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||
@ -114,6 +115,8 @@ public class BlenderContext {
|
||||
private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>();
|
||||
/** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
|
||||
private Map<String, Map<Object, Object>> markers = new HashMap<String, Map<Object, Object>>();
|
||||
/** A map of blender actions. The key is the action name and the value is the action itself. */
|
||||
private Map<String, BlenderAction> actions = new HashMap<String, BlenderAction>();
|
||||
|
||||
/**
|
||||
* This method sets the blender file version.
|
||||
@ -534,7 +537,8 @@ public class BlenderContext {
|
||||
/**
|
||||
* Returns bone by given name.
|
||||
*
|
||||
* @param skeletonOMA the OMA of the skeleton where the bone will be searched
|
||||
* @param skeletonOMA
|
||||
* the OMA of the skeleton where the bone will be searched
|
||||
* @param name
|
||||
* the name of the bone
|
||||
* @return found bone or null if none bone of a given name exists
|
||||
@ -619,6 +623,22 @@ public class BlenderContext {
|
||||
return markersMap == null ? null : markersMap.get(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds blender action to the context.
|
||||
* @param action
|
||||
* the action loaded from the blend file
|
||||
*/
|
||||
public void addAction(BlenderAction action) {
|
||||
actions.put(action.getName(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a map of blender actions; the key is the action name and the value is action itself
|
||||
*/
|
||||
public Map<String, BlenderAction> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum defines what loaded data type user wants to retreive. It can be
|
||||
* either filled structure or already converted data.
|
||||
|
@ -2,9 +2,10 @@ package com.jme3.scene.plugins.blender.animations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ -14,11 +15,11 @@ 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.asset.BlenderKey.AnimationMatchMethod;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo;
|
||||
import com.jme3.scene.plugins.blender.curves.BezierCurve;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||
@ -34,9 +35,6 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
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);
|
||||
}
|
||||
@ -54,7 +52,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
||||
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));
|
||||
blenderContext.addAction(this.getTracks(actionStructure, blenderContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,25 +61,21 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
||||
* 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
|
||||
* @param animationMatchMethod
|
||||
* the way animation should be matched with node
|
||||
*/
|
||||
public void applyAnimations(Node node, List<String> animationNames) {
|
||||
if (animationNames != null && animationNames.size() > 0) {
|
||||
public void applyAnimations(Node node, AnimationMatchMethod animationMatchMethod) {
|
||||
List<BlenderAction> actions = this.getActions(node, animationMatchMethod);
|
||||
if (actions.size() > 0) {
|
||||
List<Animation> animations = new ArrayList<Animation>();
|
||||
for (String animationName : animationNames) {
|
||||
BlenderAction action = actions.get(animationName);
|
||||
if (action != null) {
|
||||
for (BlenderAction action : actions) {
|
||||
SpatialTrack[] tracks = action.toTracks(node);
|
||||
if (tracks != null && tracks.length > 0) {
|
||||
Animation spatialAnimation = new Animation(animationName, action.getAnimationTime());
|
||||
Animation spatialAnimation = new Animation(action.getName(), 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) {
|
||||
@ -103,29 +97,25 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
||||
* the node where the animations will be applied
|
||||
* @param skeleton
|
||||
* the skeleton of the node
|
||||
* @param animationNames
|
||||
* the names of the skeleton animations
|
||||
* @param animationMatchMethod
|
||||
* the way animation should be matched with skeleton
|
||||
*/
|
||||
public void applyAnimations(Node node, Skeleton skeleton, List<String> animationNames) {
|
||||
public void applyAnimations(Node node, Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
|
||||
node.addControl(new SkeletonControl(skeleton));
|
||||
blenderContext.setNodeForSkeleton(skeleton, node);
|
||||
List<BlenderAction> actions = this.getActions(skeleton, animationMatchMethod);
|
||||
|
||||
if (animationNames != null && animationNames.size() > 0) {
|
||||
if (actions.size() > 0) {
|
||||
List<Animation> animations = new ArrayList<Animation>();
|
||||
for (String animationName : animationNames) {
|
||||
BlenderAction action = actions.get(animationName);
|
||||
if (action != null) {
|
||||
for (BlenderAction action : actions) {
|
||||
BoneTrack[] tracks = action.toTracks(skeleton);
|
||||
if (tracks != null && tracks.length > 0) {
|
||||
Animation boneAnimation = new Animation(animationName, action.getAnimationTime());
|
||||
Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime());
|
||||
boneAnimation.setTracks(tracks);
|
||||
animations.add(boneAnimation);
|
||||
Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
|
||||
blenderContext.addAnimation(animatedNodeOMA, boneAnimation);
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName);
|
||||
}
|
||||
}
|
||||
if (animations.size() > 0) {
|
||||
AnimControl control = new AnimControl(skeleton);
|
||||
@ -230,7 +220,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
||||
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());
|
||||
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
|
||||
int lastFrame = 1;
|
||||
for (Structure actionGroup : actionGroups) {
|
||||
String name = actionGroup.getFieldValue("name").toString();
|
||||
@ -269,7 +259,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
||||
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());
|
||||
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
|
||||
int lastFrame = 1;
|
||||
for (Structure bActionChannel : actionChannels) {
|
||||
String animatedFeatureName = bActionChannel.getFieldValue("name").toString();
|
||||
@ -321,104 +311,77 @@ public class AnimationHelper extends AbstractBlenderHelper {
|
||||
if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) {
|
||||
return Ipo.OB_ROT_X + arrayIndex;
|
||||
}
|
||||
LOGGER.warning("Unknown curve rna path: " + rnaPath);
|
||||
LOGGER.log(Level.WARNING, "Unknown curve rna path: {0}", 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.
|
||||
* The method returns the actions for the given skeleton. The actions represent armature animation in blender.
|
||||
* @param skeleton
|
||||
* the skeleton that will be animated
|
||||
* @return the bone tracks for the node
|
||||
* the skeleton we fetch the actions for
|
||||
* @param animationMatchMethod
|
||||
* the method of animation matching
|
||||
* @return a list of animations for the specified skeleton
|
||||
*/
|
||||
public BoneTrack[] toTracks(Skeleton skeleton) {
|
||||
List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
|
||||
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
|
||||
int boneIndex = skeleton.getBoneIndex(entry.getKey());
|
||||
tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false));
|
||||
private List<BlenderAction> getActions(Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
|
||||
List<BlenderAction> result = new ArrayList<BlenderAction>();
|
||||
|
||||
// first get a set of bone names
|
||||
Set<String> boneNames = new HashSet<String>();
|
||||
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
|
||||
String boneName = skeleton.getBone(i).getName();
|
||||
if (boneName != null && boneName.length() > 0) {
|
||||
boneNames.add(skeleton.getBone(i).getName());
|
||||
}
|
||||
return tracks.toArray(new BoneTrack[tracks.size()]);
|
||||
}
|
||||
|
||||
// finding matches
|
||||
Set<String> matchingNames = new HashSet<String>();
|
||||
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
|
||||
// compute how many action tracks match the skeleton bones' names
|
||||
for (String boneName : boneNames) {
|
||||
if (actionEntry.getValue().hasTrackName(boneName)) {
|
||||
matchingNames.add(boneName);
|
||||
}
|
||||
}
|
||||
|
||||
BlenderAction action = null;
|
||||
if (animationMatchMethod == AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH && matchingNames.size() > 0) {
|
||||
action = actionEntry.getValue();
|
||||
} else if (matchingNames.size() == actionEntry.getValue().getTracksCount()) {
|
||||
action = actionEntry.getValue();
|
||||
}
|
||||
|
||||
if (action != null) {
|
||||
// remove the tracks that do not match the bone names if the matching method is different from ALL_NAMES_MATCH
|
||||
if (animationMatchMethod != AnimationMatchMethod.ALL_NAMES_MATCH) {
|
||||
action = action.clone();
|
||||
action.removeTracksThatAreNotInTheCollection(matchingNames);
|
||||
}
|
||||
result.add(action);
|
||||
}
|
||||
|
||||
matchingNames.clear();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time of animations (in seconds)
|
||||
* The method returns the actions for the given node. The actions represent object animation in blender.
|
||||
* @param node
|
||||
* the node we fetch the actions for
|
||||
* @param animationMatchMethod
|
||||
* the method of animation matching
|
||||
* @return a list of animations for the specified node
|
||||
*/
|
||||
public float getAnimationTime() {
|
||||
return (stopFrame - 1) / (float) fps;
|
||||
}
|
||||
}
|
||||
private List<BlenderAction> getActions(Node node, AnimationMatchMethod animationMatchMethod) {
|
||||
List<BlenderAction> result = new ArrayList<BlenderAction>();
|
||||
|
||||
/**
|
||||
* 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!");
|
||||
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
|
||||
if (actionEntry.getValue().hasTrackName(node.getName())) {
|
||||
result.add(actionEntry.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,134 @@
|
||||
package com.jme3.scene.plugins.blender.animations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.jme3.animation.BoneTrack;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.animation.SpatialTrack;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
public class BlenderAction implements Cloneable {
|
||||
/** The action name. */
|
||||
/* package */final String name;
|
||||
/** Animation speed - frames per second. */
|
||||
/* package */int fps;
|
||||
/**
|
||||
* The last frame of the animation (the last ipo curve node position is
|
||||
* used as a last frame).
|
||||
*/
|
||||
/* package */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.
|
||||
*/
|
||||
/* package */Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
|
||||
|
||||
public BlenderAction(String name, int fps) {
|
||||
this.name = name;
|
||||
this.fps = fps;
|
||||
}
|
||||
|
||||
public void removeTracksThatAreNotInTheCollection(Collection<String> trackNames) {
|
||||
Map<String, Ipo> newTracks = new HashMap<String, Ipo>();
|
||||
for (String trackName : trackNames) {
|
||||
if (featuresTracks.containsKey(trackName)) {
|
||||
newTracks.put(trackName, featuresTracks.get(trackName));
|
||||
}
|
||||
}
|
||||
featuresTracks = newTracks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlenderAction clone() {
|
||||
BlenderAction result = new BlenderAction(name, fps);
|
||||
result.stopFrame = stopFrame;
|
||||
result.featuresTracks = new HashMap<String, Ipo>(featuresTracks);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
int boneIndex = skeleton.getBoneIndex(entry.getKey());
|
||||
tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false));
|
||||
}
|
||||
return tracks.toArray(new BoneTrack[tracks.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the action
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time of animations (in seconds)
|
||||
*/
|
||||
public float getAnimationTime() {
|
||||
return (stopFrame - 1) / (float) fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current action has a track of a given name.
|
||||
* CAUTION! The names are case sensitive.
|
||||
*
|
||||
* @param name
|
||||
* the name of the track
|
||||
* @return <B>true</b> if the track of a given name exists for the
|
||||
* action and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean hasTrackName(String name) {
|
||||
return featuresTracks.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the amount of tracks in current action
|
||||
*/
|
||||
public int getTracksCount() {
|
||||
return featuresTracks.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlenderTrack [name = " + name + "; tracks = [" + featuresTracks.keySet() + "]]";
|
||||
}
|
||||
}
|
@ -257,4 +257,43 @@ public class Ipo {
|
||||
|
||||
return calculatedTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
/* package */static 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ import com.jme3.util.BufferUtils;
|
||||
}
|
||||
|
||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getSkeletonAnimationNames(node.getName()));
|
||||
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
|
||||
node.updateModelBound();
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
|
||||
|
||||
LOGGER.fine("Applying animations to the object if such are defined.");
|
||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||
animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getNodeAnimationNames(name));
|
||||
animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod());
|
||||
|
||||
LOGGER.fine("Loading constraints connected with this object.");
|
||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||
|
Loading…
x
Reference in New Issue
Block a user