git-svn-id: https://jmonkeyengine.googlecode.com/svn/branches/gradle-restructure@10999 75d07b2b-3a1a-0410-a2c5-0572b91ccdcaexperimental
parent
af58df2fc0
commit
f71490c7dd
@ -1,29 +0,0 @@ |
|||||||
package com.jme3.scene.plugins.blender.animations; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import com.jme3.animation.Animation; |
|
||||||
import com.jme3.animation.Skeleton; |
|
||||||
|
|
||||||
/** |
|
||||||
* A simple class that sotres animation data. |
|
||||||
* If skeleton is null then we deal with object animation. |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
public class AnimationData { |
|
||||||
/** The skeleton. */ |
|
||||||
public final Skeleton skeleton; |
|
||||||
/** The animations list. */ |
|
||||||
public final List<Animation> anims; |
|
||||||
|
|
||||||
public AnimationData(List<Animation> anims) { |
|
||||||
this.anims = anims; |
|
||||||
skeleton = null; |
|
||||||
} |
|
||||||
|
|
||||||
public AnimationData(Skeleton skeleton, List<Animation> anims) { |
|
||||||
this.skeleton = skeleton; |
|
||||||
this.anims = anims; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,420 @@ |
|||||||
|
package com.jme3.scene.plugins.blender.animations; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Map.Entry; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
import com.jme3.animation.AnimControl; |
||||||
|
import com.jme3.animation.Animation; |
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.animation.SkeletonControl; |
||||||
|
import com.jme3.animation.SpatialTrack; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; |
||||||
|
import com.jme3.scene.plugins.blender.BlenderContext; |
||||||
|
import com.jme3.scene.plugins.blender.curves.BezierCurve; |
||||||
|
import com.jme3.scene.plugins.blender.file.BlenderFileException; |
||||||
|
import com.jme3.scene.plugins.blender.file.BlenderInputStream; |
||||||
|
import com.jme3.scene.plugins.blender.file.FileBlockHeader; |
||||||
|
import com.jme3.scene.plugins.blender.file.Pointer; |
||||||
|
import com.jme3.scene.plugins.blender.file.Structure; |
||||||
|
import com.jme3.scene.plugins.blender.objects.ObjectHelper; |
||||||
|
|
||||||
|
/** |
||||||
|
* The helper class that helps in animations loading. |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
public class AnimationHelper extends AbstractBlenderHelper { |
||||||
|
private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName()); |
||||||
|
|
||||||
|
/** A map of blender actions. */ |
||||||
|
private Map<String, BlenderAction> actions = new HashMap<String, BlenderAction>(); |
||||||
|
|
||||||
|
public AnimationHelper(String blenderVersion, BlenderContext blenderContext) { |
||||||
|
super(blenderVersion, blenderContext); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Loads all animations that are stored in the blender file. The animations are not yet applied to the scene features. |
||||||
|
* This should be called before objects are loaded. |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when problems with blender file reading occur |
||||||
|
*/ |
||||||
|
public void loadAnimations() throws BlenderFileException { |
||||||
|
LOGGER.info("Loading animations that will be later applied to scene features."); |
||||||
|
List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); |
||||||
|
if (actionHeaders != null) { |
||||||
|
for (FileBlockHeader header : actionHeaders) { |
||||||
|
Structure actionStructure = header.getStructure(blenderContext); |
||||||
|
LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName()); |
||||||
|
actions.put(actionStructure.getName(), this.getTracks(actionStructure, blenderContext)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file. |
||||||
|
* @param node |
||||||
|
* the node to whom the animations will be applied |
||||||
|
* @param animationNames |
||||||
|
* the names of the animations to be applied |
||||||
|
*/ |
||||||
|
public void applyAnimations(Node node, List<String> animationNames) { |
||||||
|
if (animationNames != null && animationNames.size() > 0) { |
||||||
|
List<Animation> animations = new ArrayList<Animation>(); |
||||||
|
for (String animationName : animationNames) { |
||||||
|
BlenderAction action = actions.get(animationName); |
||||||
|
if (action != null) { |
||||||
|
SpatialTrack[] tracks = action.toTracks(node); |
||||||
|
if (tracks != null && tracks.length > 0) { |
||||||
|
Animation spatialAnimation = new Animation(animationName, action.getAnimationTime()); |
||||||
|
spatialAnimation.setTracks(tracks); |
||||||
|
animations.add(spatialAnimation); |
||||||
|
blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation); |
||||||
|
} |
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (animations.size() > 0) { |
||||||
|
AnimControl control = new AnimControl(); |
||||||
|
HashMap<String, Animation> anims = new HashMap<String, Animation>(animations.size()); |
||||||
|
for (int i = 0; i < animations.size(); ++i) { |
||||||
|
Animation animation = animations.get(i); |
||||||
|
anims.put(animation.getName(), animation); |
||||||
|
} |
||||||
|
control.setAnimations(anims); |
||||||
|
node.addControl(control); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The method applies skeleton animations to the given node. |
||||||
|
* @param node |
||||||
|
* the node where the animations will be applied |
||||||
|
* @param skeleton |
||||||
|
* the skeleton of the node |
||||||
|
* @param animationNames |
||||||
|
* the names of the skeleton animations |
||||||
|
*/ |
||||||
|
public void applyAnimations(Node node, Skeleton skeleton, List<String> animationNames) { |
||||||
|
node.addControl(new SkeletonControl(skeleton)); |
||||||
|
blenderContext.setNodeForSkeleton(skeleton, node); |
||||||
|
|
||||||
|
if (animationNames != null && animationNames.size() > 0) { |
||||||
|
List<Animation> animations = new ArrayList<Animation>(); |
||||||
|
for (String animationName : animationNames) { |
||||||
|
BlenderAction action = actions.get(animationName); |
||||||
|
if (action != null) { |
||||||
|
BoneTrack[] tracks = action.toTracks(skeleton); |
||||||
|
if (tracks != null && tracks.length > 0) { |
||||||
|
Animation boneAnimation = new Animation(animationName, action.getAnimationTime()); |
||||||
|
boneAnimation.setTracks(tracks); |
||||||
|
animations.add(boneAnimation); |
||||||
|
for (BoneTrack track : tracks) { |
||||||
|
Bone bone = skeleton.getBone(track.getTargetBoneIndex()); |
||||||
|
BoneContext boneContext = blenderContext.getBoneContext(bone); |
||||||
|
blenderContext.addAnimation(boneContext.getBoneOma(), boneAnimation); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName); |
||||||
|
} |
||||||
|
} |
||||||
|
if (animations.size() > 0) { |
||||||
|
AnimControl control = new AnimControl(skeleton); |
||||||
|
HashMap<String, Animation> anims = new HashMap<String, Animation>(animations.size()); |
||||||
|
for (int i = 0; i < animations.size(); ++i) { |
||||||
|
Animation animation = animations.get(i); |
||||||
|
anims.put(animation.getName(), animation); |
||||||
|
} |
||||||
|
control.setAnimations(anims); |
||||||
|
node.addControl(control); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method creates an ipo object used for interpolation calculations. |
||||||
|
* |
||||||
|
* @param ipoStructure |
||||||
|
* the structure with ipo definition |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @return the ipo object |
||||||
|
* @throws BlenderFileException |
||||||
|
* this exception is thrown when the blender file is somehow |
||||||
|
* corrupted |
||||||
|
*/ |
||||||
|
public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { |
||||||
|
Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); |
||||||
|
|
||||||
|
// preparing bezier curves
|
||||||
|
Ipo result = null; |
||||||
|
List<Structure> curves = curvebase.evaluateListBase();// IpoCurve
|
||||||
|
if (curves.size() > 0) { |
||||||
|
BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; |
||||||
|
int frame = 0; |
||||||
|
for (Structure curve : curves) { |
||||||
|
Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); |
||||||
|
List<Structure> bezTriples = pBezTriple.fetchData(); |
||||||
|
int type = ((Number) curve.getFieldValue("adrcode")).intValue(); |
||||||
|
bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); |
||||||
|
} |
||||||
|
curves.clear(); |
||||||
|
result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); |
||||||
|
blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method creates an ipo with only a single value. No track type is |
||||||
|
* specified so do not use it for calculating tracks. |
||||||
|
* |
||||||
|
* @param constValue |
||||||
|
* the value of this ipo |
||||||
|
* @return constant ipo |
||||||
|
*/ |
||||||
|
public Ipo fromValue(float constValue) { |
||||||
|
return new ConstIpo(constValue); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method retuns the bone tracks for animation. |
||||||
|
* |
||||||
|
* @param actionStructure |
||||||
|
* the structure containing the tracks |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @return a list of tracks for the specified animation |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when there are problems with the blend |
||||||
|
* file |
||||||
|
*/ |
||||||
|
private BlenderAction getTracks(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { |
||||||
|
if (blenderVersion < 250) { |
||||||
|
return this.getTracks249(actionStructure, blenderContext); |
||||||
|
} else { |
||||||
|
return this.getTracks250(actionStructure, blenderContext); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method retuns the bone tracks for animation for blender version 2.50 |
||||||
|
* and higher. |
||||||
|
* |
||||||
|
* @param actionStructure |
||||||
|
* the structure containing the tracks |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @return a list of tracks for the specified animation |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when there are problems with the blend |
||||||
|
* file |
||||||
|
*/ |
||||||
|
private BlenderAction getTracks250(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.FINE, "Getting tracks!"); |
||||||
|
Structure groups = (Structure) actionStructure.getFieldValue("groups"); |
||||||
|
List<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
|
||||||
|
BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps()); |
||||||
|
int lastFrame = 1; |
||||||
|
for (Structure actionGroup : actionGroups) { |
||||||
|
String name = actionGroup.getFieldValue("name").toString(); |
||||||
|
List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(); |
||||||
|
BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; |
||||||
|
int channelCounter = 0; |
||||||
|
for (Structure c : channels) { |
||||||
|
int type = this.getCurveType(c, blenderContext); |
||||||
|
Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); |
||||||
|
List<Structure> bezTriples = pBezTriple.fetchData(); |
||||||
|
bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); |
||||||
|
} |
||||||
|
|
||||||
|
Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); |
||||||
|
lastFrame = Math.max(lastFrame, ipo.getLastFrame()); |
||||||
|
blenderAction.featuresTracks.put(name, ipo); |
||||||
|
} |
||||||
|
blenderAction.stopFrame = lastFrame; |
||||||
|
return blenderAction; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method retuns the bone tracks for animation for blender version 2.49 |
||||||
|
* (and probably several lower versions too). |
||||||
|
* |
||||||
|
* @param actionStructure |
||||||
|
* the structure containing the tracks |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @return a list of tracks for the specified animation |
||||||
|
* @throws BlenderFileException |
||||||
|
* an exception is thrown when there are problems with the blend |
||||||
|
* file |
||||||
|
*/ |
||||||
|
private BlenderAction getTracks249(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { |
||||||
|
LOGGER.log(Level.FINE, "Getting tracks!"); |
||||||
|
Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); |
||||||
|
List<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
|
||||||
|
BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps()); |
||||||
|
int lastFrame = 1; |
||||||
|
for (Structure bActionChannel : actionChannels) { |
||||||
|
String animatedFeatureName = bActionChannel.getFieldValue("name").toString(); |
||||||
|
Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); |
||||||
|
if (!p.isNull()) { |
||||||
|
Structure ipoStructure = p.fetchData().get(0); |
||||||
|
Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext); |
||||||
|
lastFrame = Math.max(lastFrame, ipo.getLastFrame()); |
||||||
|
blenderAction.featuresTracks.put(animatedFeatureName, ipo); |
||||||
|
} |
||||||
|
} |
||||||
|
blenderAction.stopFrame = lastFrame; |
||||||
|
return blenderAction; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This method returns the type of the ipo curve. |
||||||
|
* |
||||||
|
* @param structure |
||||||
|
* the structure must contain the 'rna_path' field and |
||||||
|
* 'array_index' field (the type is not important here) |
||||||
|
* @param blenderContext |
||||||
|
* the blender context |
||||||
|
* @return the type of the curve |
||||||
|
*/ |
||||||
|
public int getCurveType(Structure structure, BlenderContext blenderContext) { |
||||||
|
// reading rna path first
|
||||||
|
BlenderInputStream bis = blenderContext.getInputStream(); |
||||||
|
int currentPosition = bis.getPosition(); |
||||||
|
Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); |
||||||
|
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); |
||||||
|
bis.setPosition(dataFileBlock.getBlockPosition()); |
||||||
|
String rnaPath = bis.readString(); |
||||||
|
bis.setPosition(currentPosition); |
||||||
|
int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); |
||||||
|
|
||||||
|
// determining the curve type
|
||||||
|
if (rnaPath.endsWith("location")) { |
||||||
|
return Ipo.AC_LOC_X + arrayIndex; |
||||||
|
} |
||||||
|
if (rnaPath.endsWith("rotation_quaternion")) { |
||||||
|
return Ipo.AC_QUAT_W + arrayIndex; |
||||||
|
} |
||||||
|
if (rnaPath.endsWith("scale")) { |
||||||
|
return Ipo.AC_SIZE_X + arrayIndex; |
||||||
|
} |
||||||
|
if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { |
||||||
|
return Ipo.OB_ROT_X + arrayIndex; |
||||||
|
} |
||||||
|
LOGGER.warning("Unknown curve rna path: " + rnaPath); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* An abstract representation of animation. The data stored here is mainly a raw action data loaded from blender. |
||||||
|
* It can later be transformed into bone or spatial animation and applied to the specified node. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
private static class BlenderAction { |
||||||
|
/** Animation speed - frames per second. */ |
||||||
|
private int fps; |
||||||
|
/** The last frame of the animation (the last ipo curve node position is used as a last frame). */ |
||||||
|
private int stopFrame; |
||||||
|
/** |
||||||
|
* Tracks of the features. In case of bone animation the keys are the names of the bones. In case of spatial animation - the node's name |
||||||
|
* is used. A single ipo contains all tracks for location, rotation and scales. |
||||||
|
*/ |
||||||
|
private Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>(); |
||||||
|
|
||||||
|
public BlenderAction(int fps) { |
||||||
|
this.fps = fps; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts the action into JME spatial animation tracks. |
||||||
|
* @param node |
||||||
|
* the node that will be animated |
||||||
|
* @return the spatial tracks for the node |
||||||
|
*/ |
||||||
|
public SpatialTrack[] toTracks(Node node) { |
||||||
|
List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size()); |
||||||
|
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) { |
||||||
|
tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true)); |
||||||
|
} |
||||||
|
return tracks.toArray(new SpatialTrack[tracks.size()]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts the action into JME bone animation tracks. |
||||||
|
* @param skeleton |
||||||
|
* the skeleton that will be animated |
||||||
|
* @return the bone tracks for the node |
||||||
|
*/ |
||||||
|
public BoneTrack[] toTracks(Skeleton skeleton) { |
||||||
|
List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size()); |
||||||
|
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) { |
||||||
|
Bone bone = skeleton.getBone(entry.getKey()); |
||||||
|
int boneIndex = skeleton.getBoneIndex(entry.getKey()); |
||||||
|
tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 1, stopFrame, fps, false)); |
||||||
|
} |
||||||
|
return tracks.toArray(new BoneTrack[tracks.size()]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the time of animations (in seconds) |
||||||
|
*/ |
||||||
|
public float getAnimationTime() { |
||||||
|
return (stopFrame - 1) / (float) fps; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Ipo constant curve. This is a curve with only one value and no specified |
||||||
|
* type. This type of ipo cannot be used to calculate tracks. It should only |
||||||
|
* be used to calculate single value for a given frame. |
||||||
|
* |
||||||
|
* @author Marcin Roguski (Kaelthas) |
||||||
|
*/ |
||||||
|
private class ConstIpo extends Ipo { |
||||||
|
|
||||||
|
/** The constant value of this ipo. */ |
||||||
|
private float constValue; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. Stores the constant value of this ipo. |
||||||
|
* |
||||||
|
* @param constValue |
||||||
|
* the constant value of this ipo |
||||||
|
*/ |
||||||
|
public ConstIpo(float constValue) { |
||||||
|
super(null, false, 0);// the version is not important here
|
||||||
|
this.constValue = constValue; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public float calculateValue(int frame) { |
||||||
|
return constValue; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public float calculateValue(int frame, int curveIndex) { |
||||||
|
return constValue; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) { |
||||||
|
throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,276 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (c) 2009-2012 jMonkeyEngine |
|
||||||
* All rights reserved. |
|
||||||
* |
|
||||||
* Redistribution and use in source and binary forms, with or without |
|
||||||
* modification, are permitted provided that the following conditions are |
|
||||||
* met: |
|
||||||
* |
|
||||||
* * Redistributions of source code must retain the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer. |
|
||||||
* |
|
||||||
* * Redistributions in binary form must reproduce the above copyright |
|
||||||
* notice, this list of conditions and the following disclaimer in the |
|
||||||
* documentation and/or other materials provided with the distribution. |
|
||||||
* |
|
||||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
|
||||||
* may be used to endorse or promote products derived from this software |
|
||||||
* without specific prior written permission. |
|
||||||
* |
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
|
||||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
||||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|
||||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|
||||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
||||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
||||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
||||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
||||||
*/ |
|
||||||
package com.jme3.scene.plugins.blender.animations; |
|
||||||
|
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.logging.Level; |
|
||||||
import java.util.logging.Logger; |
|
||||||
|
|
||||||
import com.jme3.animation.Bone; |
|
||||||
import com.jme3.animation.BoneTrack; |
|
||||||
import com.jme3.animation.Skeleton; |
|
||||||
import com.jme3.math.Quaternion; |
|
||||||
import com.jme3.math.Vector3f; |
|
||||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; |
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext; |
|
||||||
import com.jme3.scene.plugins.blender.curves.BezierCurve; |
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException; |
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer; |
|
||||||
import com.jme3.scene.plugins.blender.file.Structure; |
|
||||||
|
|
||||||
/** |
|
||||||
* This class defines the methods to calculate certain aspects of animation and |
|
||||||
* armature functionalities. |
|
||||||
* |
|
||||||
* @author Marcin Roguski (Kaelthas) |
|
||||||
*/ |
|
||||||
public class ArmatureHelper extends AbstractBlenderHelper { |
|
||||||
private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); |
|
||||||
|
|
||||||
public static final String ARMATURE_NODE_MARKER = "armature-node"; |
|
||||||
|
|
||||||
/** |
|
||||||
* This constructor parses the given blender version and stores the result. |
|
||||||
* Some functionalities may differ in different blender versions. |
|
||||||
* |
|
||||||
* @param blenderVersion |
|
||||||
* the version read from the blend file |
|
||||||
* @param blenderContext |
|
||||||
* the blender context |
|
||||||
*/ |
|
||||||
public ArmatureHelper(String blenderVersion, BlenderContext blenderContext) { |
|
||||||
super(blenderVersion, blenderContext); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method builds the object's bones structure. |
|
||||||
* |
|
||||||
* @param armatureObjectOMA |
|
||||||
* the OMa of the armature node |
|
||||||
* @param boneStructure |
|
||||||
* the structure containing the bones' data |
|
||||||
* @param parent |
|
||||||
* the parent bone |
|
||||||
* @param result |
|
||||||
* the list where the newly created bone will be added |
|
||||||
* @param spatialOMA |
|
||||||
* the OMA of the spatial that will own the skeleton |
|
||||||
* @param blenderContext |
|
||||||
* the blender context |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception is thrown when there is problem with the blender |
|
||||||
* file |
|
||||||
*/ |
|
||||||
public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException { |
|
||||||
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext); |
|
||||||
bc.buildBone(result, spatialOMA, blenderContext); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method returns a map where the key is the object's group index that |
|
||||||
* is used by a bone and the key is the bone index in the armature. |
|
||||||
* |
|
||||||
* @param defBaseStructure |
|
||||||
* a bPose structure of the object |
|
||||||
* @return bone group-to-index map |
|
||||||
* @throws BlenderFileException |
|
||||||
* this exception is thrown when the blender file is somehow |
|
||||||
* corrupted |
|
||||||
*/ |
|
||||||
public Map<Integer, Integer> getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton) throws BlenderFileException { |
|
||||||
Map<Integer, Integer> result = null; |
|
||||||
if (skeleton.getBoneCount() != 0) { |
|
||||||
result = new HashMap<Integer, Integer>(); |
|
||||||
List<Structure> deformGroups = defBaseStructure.evaluateListBase();// bDeformGroup
|
|
||||||
int groupIndex = 0; |
|
||||||
for (Structure deformGroup : deformGroups) { |
|
||||||
String deformGroupName = deformGroup.getFieldValue("name").toString(); |
|
||||||
int boneIndex = skeleton.getBoneIndex(deformGroupName); |
|
||||||
if (boneIndex >= 0) { |
|
||||||
result.put(groupIndex, boneIndex); |
|
||||||
} |
|
||||||
++groupIndex; |
|
||||||
} |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method retuns the bone tracks for animation. |
|
||||||
* |
|
||||||
* @param actionStructure |
|
||||||
* the structure containing the tracks |
|
||||||
* @param blenderContext |
|
||||||
* the blender context |
|
||||||
* @return a list of tracks for the specified animation |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception is thrown when there are problems with the blend |
|
||||||
* file |
|
||||||
*/ |
|
||||||
public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { |
|
||||||
if (blenderVersion < 250) { |
|
||||||
return this.getTracks249(actionStructure, skeleton, blenderContext); |
|
||||||
} else { |
|
||||||
return this.getTracks250(actionStructure, skeleton, blenderContext); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method retuns the bone tracks for animation for blender version 2.50 |
|
||||||
* and higher. |
|
||||||
* |
|
||||||
* @param actionStructure |
|
||||||
* the structure containing the tracks |
|
||||||
* @param blenderContext |
|
||||||
* the blender context |
|
||||||
* @return a list of tracks for the specified animation |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception is thrown when there are problems with the blend |
|
||||||
* file |
|
||||||
*/ |
|
||||||
private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { |
|
||||||
LOGGER.log(Level.FINE, "Getting tracks!"); |
|
||||||
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); |
|
||||||
int fps = blenderContext.getBlenderKey().getFps(); |
|
||||||
Structure groups = (Structure) actionStructure.getFieldValue("groups"); |
|
||||||
List<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
|
|
||||||
List<BoneTrack> tracks = new ArrayList<BoneTrack>(); |
|
||||||
for (Structure actionGroup : actionGroups) { |
|
||||||
String name = actionGroup.getFieldValue("name").toString(); |
|
||||||
int boneIndex = skeleton.getBoneIndex(name); |
|
||||||
if (boneIndex >= 0) { |
|
||||||
List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(); |
|
||||||
BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; |
|
||||||
int channelCounter = 0; |
|
||||||
for (Structure c : channels) { |
|
||||||
int type = ipoHelper.getCurveType(c, blenderContext); |
|
||||||
Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); |
|
||||||
List<Structure> bezTriples = pBezTriple.fetchData(); |
|
||||||
bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); |
|
||||||
} |
|
||||||
|
|
||||||
Bone bone = skeleton.getBone(boneIndex); |
|
||||||
Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); |
|
||||||
tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 0, ipo.getLastFrame(), fps, false)); |
|
||||||
} |
|
||||||
} |
|
||||||
this.equaliseBoneTracks(tracks); |
|
||||||
return tracks.toArray(new BoneTrack[tracks.size()]); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method retuns the bone tracks for animation for blender version 2.49 |
|
||||||
* (and probably several lower versions too). |
|
||||||
* |
|
||||||
* @param actionStructure |
|
||||||
* the structure containing the tracks |
|
||||||
* @param blenderContext |
|
||||||
* the blender context |
|
||||||
* @return a list of tracks for the specified animation |
|
||||||
* @throws BlenderFileException |
|
||||||
* an exception is thrown when there are problems with the blend |
|
||||||
* file |
|
||||||
*/ |
|
||||||
private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { |
|
||||||
LOGGER.log(Level.FINE, "Getting tracks!"); |
|
||||||
IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); |
|
||||||
int fps = blenderContext.getBlenderKey().getFps(); |
|
||||||
Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); |
|
||||||
List<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
|
|
||||||
List<BoneTrack> tracks = new ArrayList<BoneTrack>(); |
|
||||||
for (Structure bActionChannel : actionChannels) { |
|
||||||
String name = bActionChannel.getFieldValue("name").toString(); |
|
||||||
int boneIndex = skeleton.getBoneIndex(name); |
|
||||||
if (boneIndex >= 0) { |
|
||||||
Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); |
|
||||||
if (!p.isNull()) { |
|
||||||
Structure ipoStructure = p.fetchData().get(0); |
|
||||||
|
|
||||||
Bone bone = skeleton.getBone(boneIndex); |
|
||||||
Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); |
|
||||||
if (ipo != null) { |
|
||||||
tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale(), 0, ipo.getLastFrame(), fps, false)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
this.equaliseBoneTracks(tracks); |
|
||||||
return tracks.toArray(new BoneTrack[tracks.size()]); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The method makes all the tracks to have equal frame lengths. |
|
||||||
* @param tracks |
|
||||||
* the tracks to be equalized |
|
||||||
*/ |
|
||||||
private void equaliseBoneTracks(List<BoneTrack> tracks) { |
|
||||||
// first compute the maximum amount of frames
|
|
||||||
int maximumFrameCount = -1; |
|
||||||
float[] maximumTrackTimes = null; |
|
||||||
for (BoneTrack track : tracks) { |
|
||||||
if (track.getTimes().length > maximumFrameCount) { |
|
||||||
maximumTrackTimes = track.getTimes(); |
|
||||||
maximumFrameCount = maximumTrackTimes.length; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// now widen all the tracks that have less frames by repeating the last values in the frame
|
|
||||||
for (BoneTrack track : tracks) { |
|
||||||
int currentTrackLength = track.getTimes().length; |
|
||||||
if (currentTrackLength < maximumFrameCount) { |
|
||||||
Vector3f[] translations = new Vector3f[maximumFrameCount]; |
|
||||||
Quaternion[] rotations = new Quaternion[maximumFrameCount]; |
|
||||||
Vector3f[] scales = new Vector3f[maximumFrameCount]; |
|
||||||
|
|
||||||
Vector3f[] currentTranslations = track.getTranslations(); |
|
||||||
Quaternion[] currentRotations = track.getRotations(); |
|
||||||
Vector3f[] currentScales = track.getScales(); |
|
||||||
for (int i = 0; i < currentTrackLength; ++i) { |
|
||||||
translations[i] = currentTranslations[i]; |
|
||||||
rotations[i] = currentRotations[i]; |
|
||||||
scales[i] = currentScales[i]; |
|
||||||
} |
|
||||||
|
|
||||||
for (int i = currentTrackLength; i < maximumFrameCount; ++i) { |
|
||||||
translations[i] = currentTranslations[currentTranslations.length - 1]; |
|
||||||
rotations[i] = currentRotations[currentRotations.length - 1]; |
|
||||||
scales[i] = currentScales[currentScales.length - 1]; |
|
||||||
} |
|
||||||
|
|
||||||
track.setKeyframes(maximumTrackTimes, translations, rotations, scales); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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(), 0, ipo.getLastFrame(), fps, true); |
|
||||||
|
|
||||||
Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float) fps); |
|
||||||
animation.setTracks(new SpatialTrack[] { track }); |
|
||||||
ArrayList<Animation> animations = new ArrayList<Animation>(1); |
|
||||||
animations.add(animation); |
|
||||||
|
|
||||||
animationData = new AnimationData(animations); |
|
||||||
blenderContext.setAnimData(objectOMA, animationData); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void apply(Node node, BlenderContext blenderContext) { |
|
||||||
if (invalid) { |
|
||||||
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); |
|
||||||
}// if invalid, animData will be null
|
|
||||||
if (animationData != null) { |
|
||||||
// INFO: constraints for this modifier are applied in the
|
|
||||||
// ObjectHelper when the whole object is loaded
|
|
||||||
List<Animation> animList = animationData.anims; |
|
||||||
if (animList != null && animList.size() > 0) { |
|
||||||
HashMap<String, Animation> anims = new HashMap<String, Animation>(); |
|
||||||
for (int i = 0; i < animList.size(); ++i) { |
|
||||||
Animation animation = animList.get(i); |
|
||||||
anims.put(animation.getName(), animation); |
|
||||||
} |
|
||||||
|
|
||||||
AnimControl control = new AnimControl(null); |
|
||||||
control.setAnimations(anims); |
|
||||||
node.addControl(control); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue