// // proceduralAnimation.js // hifi // // Created by Ben Arnold on 7/29/14. // Copyright (c) 2014 HighFidelity, Inc. All rights reserved. // // This is a Procedural Animation API for creating procedural animations in JS. // To include it in your JS files, simply use the following line at the top: // Script.include("proceduralAnimation.js"); // You can see a usage example in proceduralBot.js // The current implementation is quite simple. If you would like a feature // to be added or expanded, you can contact Ben at brb555@vols.utk.edu ProcAnimAPI = function() { // generateKeyFrames(rightAngles, leftAngles, middleAngles, numFrames) // // Parameters: // rightAngles - An array of tuples. The angles in degrees for the joints // on the right side of the body // leftAngles - An array of tuples. The angles in degrees for the joints // on the left side of the body // middleAngles - An array of tuples. The angles in degrees for the joints // on the left side of the body // numFrames - The number of frames in the animation, before mirroring. // for a 4 frame walk animation, simply supply 2 frames // and generateKeyFrames will return 4 frames. // // Return Value: // Returns an array of KeyFrames. Each KeyFrame has an array of quaternions // for each of the joints, generated from the input angles. They will be ordered // R,L,R,L,...M,M,M,M where R ~ rightAngles, L ~ leftAngles, M ~ middlesAngles. // The size of the returned array will be numFrames * 2 this.generateKeyframes = function(rightAngles, leftAngles, middleAngles, numFrames) { if (rightAngles.length != leftAngles.length) { print("ERROR: generateKeyFrames(...) rightAngles and leftAngles must have equal length."); } //for mirrored joints, such as the arms or legs var rightQuats = []; var leftQuats = []; //for non mirrored joints such as the spine var middleQuats = []; for (var i = 0; i < numFrames; i++) { rightQuats[i] = []; leftQuats[i] = []; middleQuats[i] = []; } var finalKeyFrames = []; //Generate quaternions for (var i = 0; i < rightAngles.length; i++) { for (var j = 0; j < rightAngles[i].length; j++) { rightQuats[i][j] = Quat.fromPitchYawRollDegrees(rightAngles[i][j][0], rightAngles[i][j][1], rightAngles[i][j][2]); leftQuats[i][j] = Quat.fromPitchYawRollDegrees(leftAngles[i][j][0], -leftAngles[i][j][1], -leftAngles[i][j][2]); } } for (var i = 0; i < middleAngles.length; i++) { for (var j = 0; j < middleAngles[i].length; j++) { middleQuats[i][j] = Quat.fromPitchYawRollDegrees(middleAngles[i][j][0], middleAngles[i][j][1], middleAngles[i][j][2]); } } for (var i = 0; i < numFrames; i++) { finalKeyFrames[i] = new this.KeyFrame(rightQuats[i], leftQuats[i], middleQuats[i]); } //Generate mirrored quaternions for the other half of the animation for (var i = 0; i < rightAngles.length; i++) { for (var j = 0; j < rightAngles[i].length; j++) { rightQuats[i][j] = Quat.fromPitchYawRollDegrees(rightAngles[i][j][0], -rightAngles[i][j][1], -rightAngles[i][j][2]); leftQuats[i][j] = Quat.fromPitchYawRollDegrees(leftAngles[i][j][0], leftAngles[i][j][1], leftAngles[i][j][2]); } } for (var i = 0; i < middleAngles.length; i++) { for (var j = 0; j < middleAngles[i].length; j++) { middleQuats[i][j] = Quat.fromPitchYawRollDegrees(-middleAngles[i][j][0], -middleAngles[i][j][1], -middleAngles[i][j][2]); } } for (var i = 0; i < numFrames; i++) { finalKeyFrames[numFrames + i] = new this.KeyFrame(leftQuats[i], rightQuats[i], middleQuats[i]); } //Generate control points this.computeBezierControlPoints(finalKeyFrames); return finalKeyFrames; }; //Computes 2 controlPoints to each keyframe to be used in the bezier evaluation. //Technique is described at: //https://www.cs.tcd.ie/publications/tech-reports/reports.94/TCD-CS-94-18.pdf this.computeBezierControlPoints = function(keyFrames) { //Hook up pointers to the next keyframe for (var i = 0; i < keyFrames.length - 1; i++) { keyFrames[i].nextFrame = keyFrames[i+1]; } keyFrames[keyFrames.length-1].nextFrame = keyFrames[0]; //Set up all C1 for (var i = 0; i < keyFrames.length; i++) { keyFrames[i].nextFrame.controlPoints = []; for (var j = 0; j < keyFrames[i].rotations.length; j++) { keyFrames[i].nextFrame.controlPoints[j] = []; var R = Quat.slerp(keyFrames[i].rotations[j], keyFrames[i].nextFrame.rotations[j], 2.0); var T = Quat.slerp(R, keyFrames[i].nextFrame.nextFrame.rotations[j], 0.5); keyFrames[i].nextFrame.controlPoints[j][0] = Quat.slerp(keyFrames[i].nextFrame.rotations[j], T, 0.33333); } } //Set up all C2 for (var i = 0; i < keyFrames.length; i++) { for (var j = 0; j < keyFrames[i].rotations.length; j++) { keyFrames[i].controlPoints[j][1] = Quat.slerp(keyFrames[i].nextFrame.rotations[j], keyFrames[i].nextFrame.controlPoints[j][0], -1.0); } } }; // Animation KeyFrame constructor. rightJoints and leftJoints must be the same size this.KeyFrame = function(rightJoints, leftJoints, middleJoints) { this.rotations = []; for (var i = 0; i < rightJoints.length; i++) { this.rotations[this.rotations.length] = rightJoints[i]; this.rotations[this.rotations.length] = leftJoints[i]; } for (var i = 0; i < middleJoints.length; i++) { this.rotations[this.rotations.length] = middleJoints[i]; } }; // DeCasteljau evaluation to evaluate the bezier curve. // This is a very natural looking interpolation this.deCasteljau = function(k1, k2, c1, c2, f) { var a = Quat.slerp(k1, c1, f); var b = Quat.slerp(c1, c2, f); var c = Quat.slerp(c2, k2, f); var d = Quat.slerp(a, b, f); var e = Quat.slerp(b, c, f); return Quat.slerp(d, e, f); }; }