/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
******************************************************************************/
package com.badlogic.gdx.graphics.g3d.model.skeleton;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
public class Skeleton {
/** each joint is a root joint in the hierachy **/
public final Array<SkeletonJoint> hierarchy = new Array<SkeletonJoint>();
/** the names of each joint in breadth first order **/
public final Array<String> jointNames = new Array<String>();
/** names to indices **/
public final Map<String, Integer> namesToIndices = new HashMap<String, Integer>();
/** the bind pose joints in breadth first order **/
public final Array<SkeletonKeyframe> bindPoseJoints = new Array<SkeletonKeyframe>();
/** the joints in breadth first order for the last calculates animation pose **/
public final Array<SkeletonKeyframe> animPoseJoints = new Array<SkeletonKeyframe>();
/** the offset matrices for each joint in the same order as the bindPoseJoints **/
public final Array<Matrix4> offsetMatrices = new Array<Matrix4>();
/** the scene matrices for each joint in the same order as bindPoseJoints **/
public final Array<Matrix4> sceneMatrices = new Array<Matrix4>();
/** combined scene and offset matrices **/
public final Array<Matrix4> combinedMatrices = new Array<Matrix4>();
/** map of animations, indexed by name **/
public final ObjectMap<String, SkeletonAnimation> animations = new ObjectMap<String, SkeletonAnimation>();
private static final Matrix4 IDENTITY = new Matrix4();
private final Matrix4 rotMatrix = new Matrix4();
/**
* Fills the baseJoints, offsetMatrices and sceneMatrices Array instances with joints and Matrix4 instances in an
* breadth first order. This allows one to iterate over the joint arrays instead of recursing over the hierarchy
* when calculating the scene matrices.
*/
public void buildFromHierarchy() {
jointNames.clear();
namesToIndices.clear();
bindPoseJoints.clear();
animPoseJoints.clear();
offsetMatrices.clear();
sceneMatrices.clear();
for (int i = 0; i < hierarchy.size; i++) {
recursiveFill(hierarchy.get(i));
}
calculateMatrices(bindPoseJoints);
calculateOffsetMatrices();
}
private void recursiveFill(SkeletonJoint joint) {
joint.index = bindPoseJoints.size;
joint.parentIndex = joint.parent != null ? joint.parent.index : -1;
SkeletonKeyframe keyFrame = new SkeletonKeyframe();
keyFrame.position.set(joint.position);
keyFrame.scale.set(joint.scale);
keyFrame.rotation.set(joint.rotation);
keyFrame.parentIndex = joint.parentIndex;
jointNames.add(joint.name);
namesToIndices.put(joint.name, joint.index);
bindPoseJoints.add(keyFrame);
SkeletonKeyframe animKeyframe = new SkeletonKeyframe();
animKeyframe.parentIndex = joint.parentIndex;
animPoseJoints.add(animKeyframe);
offsetMatrices.add(new Matrix4());
sceneMatrices.add(new Matrix4());
combinedMatrices.add(new Matrix4());
int len = joint.children.size;
for (int i = 0; i < len; i++) {
recursiveFill(joint.children.get(i));
}
}
protected void calculateOffsetMatrices() {
for (int i = 0; i < offsetMatrices.size; i++) {
offsetMatrices.get(i).set(sceneMatrices.get(i)).inv();
}
}
protected void calculateMatrices(Array<SkeletonKeyframe> joints) {
for (int i = 0; i < joints.size; i++) {
SkeletonKeyframe joint = joints.get(i);
Matrix4 sceneMatrix = sceneMatrices.get(i);
Matrix4 parentMatrix = joint.parentIndex != -1 ? sceneMatrices.get(joint.parentIndex) : IDENTITY;
Matrix4 combinedMatrix = combinedMatrices.get(i);
joint.rotation.toMatrix(rotMatrix.val);
rotMatrix.trn(joint.position);
rotMatrix.scl(joint.scale);
sceneMatrix.set(parentMatrix);
sceneMatrix.mul(rotMatrix);
combinedMatrix.set(sceneMatrix);
combinedMatrix.mul(offsetMatrices.get(i));
}
}
public void setAnimation(String name, float time) {
SkeletonAnimation anim = animations.get(name);
if (anim == null)
throw new IllegalArgumentException("Animation with name '" + name + "' does not exist");
if (time < 0 || time > anim.totalDuration)
throw new IllegalArgumentException("time must be 0 <= time <= animation duration");
int len = anim.perJointkeyFrames.length;
for (int i = 0; i < len; i++) {
SkeletonKeyframe[] jointTrack = anim.perJointkeyFrames[i];
int idx = 0;
int len2 = jointTrack.length;
for (int j = 0; j < len2; j++) {
SkeletonKeyframe jointFrame = jointTrack[j];
if (jointFrame.timeStamp >= time) {
idx = Math.max(0, j - 1);
break;
}
}
SkeletonKeyframe startFrame = jointTrack[idx];
SkeletonKeyframe endFrame = idx + 1 == len2 ? startFrame : jointTrack[idx + 1];
float alpha = 0;
if (startFrame != endFrame) {
alpha = Math.min(1, (time - startFrame.timeStamp) / (endFrame.timeStamp - startFrame.timeStamp));
}
SkeletonKeyframe animFrame = animPoseJoints.get(i);
animFrame.position.set(startFrame.position).lerp(endFrame.position, alpha);
animFrame.scale.set(startFrame.scale).lerp(endFrame.scale, alpha);
animFrame.rotation.set(startFrame.rotation).slerp(endFrame.rotation, alpha);
}
calculateMatrices(animPoseJoints);
}
public void setBindPose() {
calculateMatrices(bindPoseJoints);
}
}