|
| 1 | +package org.andresoviedo.app.model3D.animation; |
| 2 | + |
| 3 | +import android.opengl.Matrix; |
| 4 | +import android.os.SystemClock; |
| 5 | + |
| 6 | +import java.util.HashMap; |
| 7 | +import java.util.Map; |
| 8 | + |
| 9 | +import org.andresoviedo.app.model3D.model.AnimatedModel; |
| 10 | +import org.andresoviedo.app.model3D.model.Object3DData; |
| 11 | +import org.andresoviedo.app.model3D.services.collada.entities.Joint; |
| 12 | + |
| 13 | + |
| 14 | +/** |
| 15 | + * |
| 16 | + * This class contains all the functionality to apply an animation to an |
| 17 | + * animated entity. An Animator instance is associated with just one |
| 18 | + * {@link AnimatedModel}. It also keeps track of the running time (in seconds) |
| 19 | + * of the current animation, along with a reference to the currently playing |
| 20 | + * animation for the corresponding entity. |
| 21 | + * |
| 22 | + * An Animator instance needs to be updated every frame, in order for it to keep |
| 23 | + * updating the animation pose of the associated entity. The currently playing |
| 24 | + * animation can be changed at any time using the doAnimation() method. The |
| 25 | + * Animator will keep looping the current animation until a new animation is |
| 26 | + * chosen. |
| 27 | + * |
| 28 | + * The Animator calculates the desired current animation pose by interpolating |
| 29 | + * between the previous and next keyframes of the animation (based on the |
| 30 | + * current animation time). The Animator then updates the transforms all of the |
| 31 | + * joints each frame to match the current desired animation pose. |
| 32 | + * |
| 33 | + * @author Karl |
| 34 | + * |
| 35 | + */ |
| 36 | +public class Animator { |
| 37 | + |
| 38 | + private float animationTime = 0; |
| 39 | + |
| 40 | + public Animator() { |
| 41 | + } |
| 42 | + |
| 43 | + /** |
| 44 | + * This method should be called each frame to update the animation currently |
| 45 | + * being played. This increases the animation time (and loops it back to |
| 46 | + * zero if necessary), finds the pose that the entity should be in at that |
| 47 | + * time of the animation, and then applies that pose to all the model's |
| 48 | + * joints by setting the joint transforms. |
| 49 | + */ |
| 50 | + public void update(Object3DData obj) { |
| 51 | + if (!(obj instanceof AnimatedModel)) { |
| 52 | + return; |
| 53 | + } |
| 54 | + // if (true) return; |
| 55 | + AnimatedModel animatedModel = (AnimatedModel)obj; |
| 56 | + if (animatedModel.getAnimation() == null) return; |
| 57 | + |
| 58 | + increaseAnimationTime((AnimatedModel)obj); |
| 59 | + Map<String, float[]> currentPose = calculateCurrentAnimationPose(animatedModel); |
| 60 | + float parentTransform[] = new float[16]; |
| 61 | + Matrix.setIdentityM(parentTransform,0); |
| 62 | + applyPoseToJoints(currentPose, (animatedModel).getRootJoint(), parentTransform); |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * Increases the current animation time which allows the animation to |
| 67 | + * progress. If the current animation has reached the end then the timer is |
| 68 | + * reset, causing the animation to loop. |
| 69 | + */ |
| 70 | + private void increaseAnimationTime(AnimatedModel obj) { |
| 71 | + animationTime += SystemClock.uptimeMillis(); |
| 72 | + if (animationTime > obj.getAnimation().getLength()) { |
| 73 | + this.animationTime %= obj.getAnimation().getLength(); |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * This method returns the current animation pose of the entity. It returns |
| 79 | + * the desired local-space transforms for all the joints in a map, indexed |
| 80 | + * by the name of the joint that they correspond to. |
| 81 | + * |
| 82 | + * The pose is calculated based on the previous and next keyframes in the |
| 83 | + * current animation. Each keyframe provides the desired pose at a certain |
| 84 | + * time in the animation, so the animated pose for the current time can be |
| 85 | + * calculated by interpolating between the previous and next keyframe. |
| 86 | + * |
| 87 | + * This method first finds the preious and next keyframe, calculates how far |
| 88 | + * between the two the current animation is, and then calculated the pose |
| 89 | + * for the current animation time by interpolating between the transforms at |
| 90 | + * those keyframes. |
| 91 | + * |
| 92 | + * @return The current pose as a map of the desired local-space transforms |
| 93 | + * for all the joints. The transforms are indexed by the name ID of |
| 94 | + * the joint that they should be applied to. |
| 95 | + */ |
| 96 | + private Map<String, float[]> calculateCurrentAnimationPose(AnimatedModel obj) { |
| 97 | + KeyFrame[] frames = getPreviousAndNextFrames(obj); |
| 98 | + float progression = calculateProgression(frames[0], frames[1]); |
| 99 | + return interpolatePoses(frames[0], frames[1], progression); |
| 100 | + } |
| 101 | + |
| 102 | + /** |
| 103 | + * This is the method where the animator calculates and sets those all- |
| 104 | + * important "joint transforms" that I talked about so much in the tutorial. |
| 105 | + * |
| 106 | + * This method applies the current pose to a given joint, and all of its |
| 107 | + * descendants. It does this by getting the desired local-transform for the |
| 108 | + * current joint, before applying it to the joint. Before applying the |
| 109 | + * transformations it needs to be converted from local-space to model-space |
| 110 | + * (so that they are relative to the model's origin, rather than relative to |
| 111 | + * the parent joint). This can be done by multiplying the local-transform of |
| 112 | + * the joint with the model-space transform of the parent joint. |
| 113 | + * |
| 114 | + * The same thing is then done to all the child joints. |
| 115 | + * |
| 116 | + * Finally the inverse of the joint's bind transform is multiplied with the |
| 117 | + * model-space transform of the joint. This basically "subtracts" the |
| 118 | + * joint's original bind (no animation applied) transform from the desired |
| 119 | + * pose transform. The result of this is then the transform required to move |
| 120 | + * the joint from its original model-space transform to it's desired |
| 121 | + * model-space posed transform. This is the transform that needs to be |
| 122 | + * loaded up to the vertex shader and used to transform the vertices into |
| 123 | + * the current pose. |
| 124 | + * |
| 125 | + * @param currentPose |
| 126 | + * - a map of the local-space transforms for all the joints for |
| 127 | + * the desired pose. The map is indexed by the name of the joint |
| 128 | + * which the transform corresponds to. |
| 129 | + * @param joint |
| 130 | + * - the current joint which the pose should be applied to. |
| 131 | + * @param parentTransform |
| 132 | + * - the desired model-space transform of the parent joint for |
| 133 | + * the pose. |
| 134 | + */ |
| 135 | + private void applyPoseToJoints(Map<String, float[]> currentPose, Joint joint, float[] parentTransform) { |
| 136 | + float[] currentLocalTransform = currentPose.get(joint.name); |
| 137 | + float[] currentTransform = new float[16]; |
| 138 | + Matrix.multiplyMM(currentTransform,0,parentTransform,0,currentLocalTransform,0); |
| 139 | + for (Joint childJoint : joint.children) { |
| 140 | + applyPoseToJoints(currentPose, childJoint, currentTransform); |
| 141 | + } |
| 142 | + Matrix.multiplyMM(currentTransform,0,joint.getInverseBindTransform(), 0, currentTransform, 0); |
| 143 | + joint.setAnimationTransform(currentTransform); |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Finds the previous keyframe in the animation and the next keyframe in the |
| 148 | + * animation, and returns them in an array of length 2. If there is no |
| 149 | + * previous frame (perhaps current animation time is 0.5 and the first |
| 150 | + * keyframe is at time 1.5) then the first keyframe is used as both the |
| 151 | + * previous and next keyframe. The last keyframe is used for both next and |
| 152 | + * previous if there is no next keyframe. |
| 153 | + * |
| 154 | + * @return The previous and next keyframes, in an array which therefore will |
| 155 | + * always have a length of 2. |
| 156 | + */ |
| 157 | + private KeyFrame[] getPreviousAndNextFrames(AnimatedModel obj) { |
| 158 | + KeyFrame[] allFrames = obj.getAnimation().getKeyFrames(); |
| 159 | + KeyFrame previousFrame = allFrames[0]; |
| 160 | + KeyFrame nextFrame = allFrames[0]; |
| 161 | + for (int i = 1; i < allFrames.length; i++) { |
| 162 | + nextFrame = allFrames[i]; |
| 163 | + if (nextFrame.getTimeStamp() > animationTime) { |
| 164 | + break; |
| 165 | + } |
| 166 | + previousFrame = allFrames[i]; |
| 167 | + } |
| 168 | + return new KeyFrame[] { previousFrame, nextFrame }; |
| 169 | + } |
| 170 | + |
| 171 | + /** |
| 172 | + * Calculates how far between the previous and next keyframe the current |
| 173 | + * animation time is, and returns it as a value between 0 and 1. |
| 174 | + * |
| 175 | + * @param previousFrame |
| 176 | + * - the previous keyframe in the animation. |
| 177 | + * @param nextFrame |
| 178 | + * - the next keyframe in the animation. |
| 179 | + * @return A number between 0 and 1 indicating how far between the two |
| 180 | + * keyframes the current animation time is. |
| 181 | + */ |
| 182 | + private float calculateProgression(KeyFrame previousFrame, KeyFrame nextFrame) { |
| 183 | + float totalTime = nextFrame.getTimeStamp() - previousFrame.getTimeStamp(); |
| 184 | + float currentTime = animationTime - previousFrame.getTimeStamp(); |
| 185 | + return currentTime / totalTime; |
| 186 | + } |
| 187 | + |
| 188 | + /** |
| 189 | + * Calculates all the local-space joint transforms for the desired current |
| 190 | + * pose by interpolating between the transforms at the previous and next |
| 191 | + * keyframes. |
| 192 | + * |
| 193 | + * @param previousFrame |
| 194 | + * - the previous keyframe in the animation. |
| 195 | + * @param nextFrame |
| 196 | + * - the next keyframe in the animation. |
| 197 | + * @param progression |
| 198 | + * - a number between 0 and 1 indicating how far between the |
| 199 | + * previous and next keyframes the current animation time is. |
| 200 | + * @return The local-space transforms for all the joints for the desired |
| 201 | + * current pose. They are returned in a map, indexed by the name of |
| 202 | + * the joint to which they should be applied. |
| 203 | + */ |
| 204 | + private Map<String, float[]> interpolatePoses(KeyFrame previousFrame, KeyFrame nextFrame, float progression) { |
| 205 | + Map<String, float[]> currentPose = new HashMap<String, float[]>(); |
| 206 | + for (String jointName : previousFrame.getJointKeyFrames().keySet()) { |
| 207 | + JointTransform previousTransform = previousFrame.getJointKeyFrames().get(jointName); |
| 208 | + JointTransform nextTransform = nextFrame.getJointKeyFrames().get(jointName); |
| 209 | + JointTransform currentTransform = JointTransform.interpolate(previousTransform, nextTransform, progression); |
| 210 | + currentPose.put(jointName, currentTransform.getLocalTransform()); |
| 211 | + } |
| 212 | + return currentPose; |
| 213 | + } |
| 214 | + |
| 215 | +} |
0 commit comments