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-0572b91ccdcaexperimental
parent
e4e3e0906d
commit
dd1fdd6bce
@ -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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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!"); |
||||
} |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue