use of com.jme3.animation.BoneTrack in project jmonkeyengine by jMonkeyEngine.
the class SimulationNode method computeAnimationTimeBoundaries.
/**
* Computes the maximum frame and time for the animation. Different tracks
* can have different lengths so here the maximum one is being found.
*
* @param animation
* the animation
* @return maximum frame and time of the animation
*/
private float[] computeAnimationTimeBoundaries(Animation animation) {
int maxFrame = Integer.MIN_VALUE;
float maxTime = -Float.MAX_VALUE;
for (Track track : animation.getTracks()) {
if (track instanceof BoneTrack) {
maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length);
maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]);
} else if (track instanceof SpatialTrack) {
maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length);
maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]);
} else {
throw new IllegalStateException("Unsupported track type for simuation: " + track);
}
}
return new float[] { maxFrame, maxTime };
}
use of com.jme3.animation.BoneTrack in project jmonkeyengine by jMonkeyEngine.
the class Ipo method calculateTrack.
/**
* This method calculates the value of the curves as a bone track between
* the specified frames.
*
* @param targetIndex
* the index of the target for which the method calculates the
* tracks IMPORTANT! Aet to -1 (or any negative number) if you
* want to load spatial animation.
* @param localTranslation
* the local translation of the object/bone that will be animated by
* the track
* @param localRotation
* the local rotation of the object/bone that will be animated by
* the track
* @param localScale
* the local scale of the object/bone that will be animated by
* the track
* @param startFrame
* the first frame of tracks (inclusive)
* @param stopFrame
* the last frame of the tracks (inclusive)
* @param fps
* frame rate (frames per second)
* @param spatialTrack
* this flag indicates if the track belongs to a spatial or to a
* bone; the difference is important because it appears that bones
* in blender have the same type of coordinate system (Y as UP)
* as jme while other features have different one (Z is UP)
* @return bone track for the specified bone
*/
public Track calculateTrack(int targetIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
if (calculatedTrack == null) {
// preparing data for track
int framesAmount = stopFrame - startFrame;
float timeBetweenFrames = 1.0f / fps;
float[] times = new float[framesAmount + 1];
Vector3f[] translations = new Vector3f[framesAmount + 1];
float[] translation = new float[3];
Quaternion[] rotations = new Quaternion[framesAmount + 1];
float[] quaternionRotation = new float[] { localRotation.getX(), localRotation.getY(), localRotation.getZ(), localRotation.getW() };
float[] eulerRotation = localRotation.toAngles(null);
Vector3f[] scales = new Vector3f[framesAmount + 1];
float[] scale = new float[] { localScale.x, localScale.y, localScale.z };
float degreeToRadiansFactor = 1;
if (blenderVersion < 250) {
// in blender earlier than 2.50 the values are stored in degrees
// the values in blender are divided by 10, so we need to mult it here
degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;
}
int yIndex = 1, zIndex = 2;
boolean swapAxes = spatialTrack && fixUpAxis;
if (swapAxes) {
yIndex = 2;
zIndex = 1;
}
boolean eulerRotationUsed = false, queternionRotationUsed = false;
// calculating track data
for (int frame = startFrame; frame <= stopFrame; ++frame) {
boolean translationSet = false;
translation[0] = translation[1] = translation[2] = 0;
int index = frame - startFrame;
// start + (frame - 1) * timeBetweenFrames;
times[index] = index * timeBetweenFrames;
for (int j = 0; j < bezierCurves.length; ++j) {
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
switch(bezierCurves[j].getType()) {
// LOCATION
case AC_LOC_X:
translation[0] = (float) value;
translationSet = true;
break;
case AC_LOC_Y:
if (swapAxes && value != 0) {
value = -value;
}
translation[yIndex] = (float) value;
translationSet = true;
break;
case AC_LOC_Z:
translation[zIndex] = (float) value;
translationSet = true;
break;
// EULER ROTATION
case OB_ROT_X:
eulerRotationUsed = true;
eulerRotation[0] = (float) value * degreeToRadiansFactor;
break;
case OB_ROT_Y:
eulerRotationUsed = true;
if (swapAxes && value != 0) {
value = -value;
}
eulerRotation[yIndex] = (float) value * degreeToRadiansFactor;
break;
case OB_ROT_Z:
eulerRotationUsed = true;
eulerRotation[zIndex] = (float) value * degreeToRadiansFactor;
break;
// SIZE
case AC_SIZE_X:
scale[0] = (float) value;
break;
case AC_SIZE_Y:
scale[yIndex] = (float) value;
break;
case AC_SIZE_Z:
scale[zIndex] = (float) value;
break;
// QUATERNION ROTATION (used with bone animation)
case AC_QUAT_W:
queternionRotationUsed = true;
quaternionRotation[3] = (float) value;
break;
case AC_QUAT_X:
queternionRotationUsed = true;
quaternionRotation[0] = (float) value;
break;
case AC_QUAT_Y:
queternionRotationUsed = true;
if (swapAxes && value != 0) {
value = -value;
}
quaternionRotation[yIndex] = (float) value;
break;
case AC_QUAT_Z:
quaternionRotation[zIndex] = (float) value;
break;
default:
LOGGER.log(Level.WARNING, "Unknown ipo curve type: {0}.", bezierCurves[j].getType());
}
}
if (translationSet) {
translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
} else {
translations[index] = new Vector3f();
}
if (boneContext != null) {
if (boneContext.getBone().getParent() == null && boneContext.is(BoneContext.NO_LOCAL_LOCATION)) {
float temp = translations[index].z;
translations[index].z = -translations[index].y;
translations[index].y = temp;
}
}
if (queternionRotationUsed) {
rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
} else {
rotations[index] = new Quaternion().fromAngles(eulerRotation);
}
scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
}
if (spatialTrack) {
calculatedTrack = new SpatialTrack(times, translations, rotations, scales);
} else {
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
}
if (queternionRotationUsed && eulerRotationUsed) {
LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!");
}
}
return calculatedTrack;
}
use of com.jme3.animation.BoneTrack in project jmonkeyengine by jMonkeyEngine.
the class SceneLoader method buildAnimations.
private void buildAnimations() {
if (skeleton == null)
return;
if (animList == null || animList.list.size() == 0) {
animList = new AnimationList();
for (long layerId : alayerMap.keySet()) {
FbxObject layer = alayerMap.get(layerId);
animList.add(layer.name, layer.name, 0, -1);
}
}
// Extract aminations
HashMap<String, Animation> anims = new HashMap<String, Animation>();
for (AnimInverval animInfo : animList.list) {
float realLength = 0;
float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate;
float animStart = animInfo.firstFrame / this.animFrameRate;
float animStop = animInfo.lastFrame / this.animFrameRate;
Animation anim = new Animation(animInfo.name, length);
// Search source layer for animation nodes
long sourceLayerId = 0L;
for (long layerId : alayerMap.keySet()) {
FbxObject layer = alayerMap.get(layerId);
if (layer.name.equals(animInfo.layerName)) {
sourceLayerId = layerId;
break;
}
}
// Build bone tracks
for (FbxNode limb : limbMap.values()) {
// Animation channels may have different keyframes (non-baked animation).
// So we have to restore intermediate values for all channels cause of JME requires
// a bone track as a single channel with collective transformation for each keyframe
// Sorted unique timestamps
Set<Long> stamps = new TreeSet<Long>();
FbxAnimNode animTranslation = limb.animTranslation(sourceLayerId);
FbxAnimNode animRotation = limb.animRotation(sourceLayerId);
FbxAnimNode animScale = limb.animScale(sourceLayerId);
boolean haveTranslation = haveAnyChannel(animTranslation);
boolean haveRotation = haveAnyChannel(animRotation);
boolean haveScale = haveAnyChannel(animScale);
// Collect keyframes stamps
if (haveTranslation)
animTranslation.exportTimes(stamps);
if (haveRotation)
animRotation.exportTimes(stamps);
if (haveScale)
animScale.exportTimes(stamps);
if (stamps.isEmpty())
continue;
long[] keyTimes = new long[stamps.size()];
int cnt = 0;
for (long t : stamps) keyTimes[cnt++] = t;
// Calculate keys interval by animation time interval
int firstKeyIndex = 0;
int lastKeyIndex = keyTimes.length - 1;
for (int i = 0; i < keyTimes.length; ++i) {
// Translate into seconds
float time = (float) (((double) keyTimes[i]) * secondsPerUnit);
if (time <= animStart)
firstKeyIndex = i;
if (time >= animStop && animStop >= 0) {
lastKeyIndex = i;
break;
}
}
int keysCount = lastKeyIndex - firstKeyIndex + 1;
if (keysCount <= 0)
continue;
float[] times = new float[keysCount];
Vector3f[] translations = new Vector3f[keysCount];
Quaternion[] rotations = new Quaternion[keysCount];
Vector3f[] scales = null;
// Calculate keyframes times
for (int i = 0; i < keysCount; ++i) {
int keyIndex = firstKeyIndex + i;
// Translate into seconds
float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit);
times[i] = time - animStart;
realLength = Math.max(realLength, times[i]);
}
// Load keyframes from animation curves
if (haveTranslation) {
for (int i = 0; i < keysCount; ++i) {
int keyIndex = firstKeyIndex + i;
FbxAnimNode n = animTranslation;
// Why do it here but not in other places? FBX magic?
Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value).subtractLocal(n.value);
translations[i] = tvec.divideLocal(unitSize);
}
} else {
for (int i = 0; i < keysCount; ++i) translations[i] = Vector3f.ZERO;
}
RotationOrder ro = RotationOrder.EULER_XYZ;
if (haveRotation) {
for (int i = 0; i < keysCount; ++i) {
int keyIndex = firstKeyIndex + i;
FbxAnimNode n = animRotation;
Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value);
rotations[i] = ro.rotate(tvec);
}
} else {
for (int i = 0; i < keysCount; ++i) rotations[i] = Quaternion.IDENTITY;
}
if (haveScale) {
scales = new Vector3f[keysCount];
for (int i = 0; i < keysCount; ++i) {
int keyIndex = firstKeyIndex + i;
FbxAnimNode n = animScale;
Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value);
scales[i] = tvec;
}
}
BoneTrack track = null;
if (haveScale)
track = new BoneTrack(limb.boneIndex, times, translations, rotations, scales);
else
track = new BoneTrack(limb.boneIndex, times, translations, rotations);
anim.addTrack(track);
}
if (realLength != length && animInfo.lastFrame == -1) {
Track[] tracks = anim.getTracks();
if (tracks == null || tracks.length == 0)
continue;
anim = new Animation(animInfo.name, realLength);
for (Track track : tracks) anim.addTrack(track);
}
anims.put(anim.getName(), anim);
}
animControl.setAnimations(anims);
}
use of com.jme3.animation.BoneTrack in project jmonkeyengine by jMonkeyEngine.
the class SkeletonLoader method endElement.
public void endElement(String uri, String name, String qName) {
if (qName.equals("translate") || qName.equals("position") || qName.equals("scale")) {
} else if (qName.equals("axis")) {
} else if (qName.equals("rotate") || qName.equals("rotation")) {
rotation = new Quaternion();
axis.normalizeLocal();
rotation.fromAngleNormalAxis(angle, axis);
angle = 0;
axis = null;
} else if (qName.equals("bone")) {
bone.setBindTransforms(position, rotation, scale);
bone = null;
position = null;
rotation = null;
scale = null;
} else if (qName.equals("bonehierarchy")) {
Bone[] bones = new Bone[indexToBone.size()];
// also assign the bones to the bonelist
for (Map.Entry<Integer, Bone> entry : indexToBone.entrySet()) {
Bone bone = entry.getValue();
bones[entry.getKey()] = bone;
}
indexToBone.clear();
skeleton = new Skeleton(bones);
} else if (qName.equals("animation")) {
animations.add(animation);
animation = null;
} else if (qName.equals("track")) {
if (track != null) {
// if track has keyframes
tracks.add(track);
track = null;
}
} else if (qName.equals("tracks")) {
BoneTrack[] trackList = tracks.toArray(new BoneTrack[tracks.size()]);
animation.setTracks(trackList);
tracks.clear();
} else if (qName.equals("keyframe")) {
assert time >= 0;
assert position != null;
assert rotation != null;
times.add(time);
translations.add(position);
rotations.add(rotation);
if (scale != null) {
scales.add(scale);
} else {
scales.add(new Vector3f(1, 1, 1));
}
time = -1;
position = null;
rotation = null;
scale = null;
} else if (qName.equals("keyframes")) {
if (times.size() > 0) {
float[] timesArray = new float[times.size()];
for (int i = 0; i < timesArray.length; i++) {
timesArray[i] = times.get(i);
}
Vector3f[] transArray = translations.toArray(new Vector3f[translations.size()]);
Quaternion[] rotArray = rotations.toArray(new Quaternion[rotations.size()]);
Vector3f[] scalesArray = scales.toArray(new Vector3f[scales.size()]);
track.setKeyframes(timesArray, transArray, rotArray, scalesArray);
//track.setKeyframes(timesArray, transArray, rotArray);
} else {
track = null;
}
times.clear();
translations.clear();
rotations.clear();
scales.clear();
} else if (qName.equals("skeleton")) {
nameToBone.clear();
}
assert elementStack.peek().equals(qName);
elementStack.pop();
}
Aggregations