// // walkObjects.js // // version 1.003 // // Created by David Wooldridge, Autumn 2014 // // Motion, state and Transition objects for use by the walk.js script v1.12 // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // // constructor for the Motion object Motion = function() { // selected animations this.selectedWalk = undefined; this.selectedStand = undefined; this.selectedFly = undefined; this.selectedFlyUp = undefined; this.selectedFlyDown = undefined; this.selectedFlyBlend = undefined; this.selectedHovering = undefined; this.selectedSideStepLeft = undefined; this.selectedSideStepRight = undefined; // if Hydras are connected, the only way to enable use is by never setting any rotations on the arm joints this.hydraCheck = function() { // function courtesy of Thijs Wenker (frisbee.js) var numberOfButtons = Controller.getNumberOfButtons(); var numberOfTriggers = Controller.getNumberOfTriggers(); var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; hydrasConnected = (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2); return hydrasConnected; } // settings this.armsFree = this.hydraCheck(); // automatically sets true for Hydra support - temporary fix this.makesFootStepSounds = true; this.avatarGender = undefined; this.setGender = function(gender) { this.avatarGender = gender; switch(this.avatarGender) { case MALE: this.selectedWalk = walkAssets.maleStandardWalk; this.selectedStand = walkAssets.maleStandOne; this.selectedFlyUp = walkAssets.maleFlyingUp; this.selectedFly = walkAssets.maleFlying; this.selectedFlyDown = walkAssets.maleFlyingDown; this.selectedFlyBlend = walkAssets.maleFlyingBlend; this.selectedHovering = walkAssets.maleHovering; this.selectedSideStepLeft = walkAssets.maleSideStepLeft; this.selectedSideStepRight = walkAssets.maleSideStepRight; this.currentAnimation = this.selectedStand; return; case FEMALE: this.selectedWalk = walkAssets.femaleStandardWalk; this.selectedStand = walkAssets.femaleStandOne; this.selectedFlyUp = walkAssets.femaleFlyingUp; this.selectedFly = walkAssets.femaleFlying; this.selectedFlyDown = walkAssets.femaleFlyingDown; this.selectedFlyBlend = walkAssets.femaleFlyingBlend; this.selectedHovering = walkAssets.femaleHovering; this.selectedSideStepLeft = walkAssets.femaleSideStepLeft; this.selectedSideStepRight = walkAssets.femaleSideStepRight; this.currentAnimation = this.selectedStand; return; } } this.setGender(MALE); // calibration this.calibration = { pitchMax: 8, maxWalkAcceleration: 5, maxWalkDeceleration: 25, rollMax: 80, angularVelocityMax: 70, hipsToFeet: MyAvatar.getJointPosition("Hips").y - MyAvatar.getJointPosition("RightFoot").y, } // used to make sure at least one step has been taken when transitioning from a walk cycle this.elapsedFTDegrees = 0; // the current animation and transition this.currentAnimation = this.selectedStand; this.currentTransition = null; // zero out avi's joints and pose the fingers this.avatarJointNames = MyAvatar.getJointNames(); this.poseFingers = function() { for (var i = 0; i < this.avatarJointNames.length; i++) { if (i > 17 || i < 34) { // left hand fingers MyAvatar.setJointData(this.avatarJointNames[i], Quat.fromPitchYawRollDegrees(16, 0, 0)); } else if (i > 33 || i < 38) { // left hand thumb MyAvatar.setJointData(this.avatarJointNames[i], Quat.fromPitchYawRollDegrees(4, 0, 0)); } else if (i > 41 || i < 58) { // right hand fingers MyAvatar.setJointData(this.avatarJointNames[i], Quat.fromPitchYawRollDegrees(16, 0, 0)); } else if (i > 57 || i < 62) { // right hand thumb MyAvatar.setJointData(this.avatarJointNames[i], Quat.fromPitchYawRollDegrees(4, 0, 0)); } } }; if (!this.armsFree) { this.poseFingers(); } // frequency time wheel (foot / ground speed matching) this.direction = FORWARDS; this.strideLength = this.selectedWalk.calibration.strideLengthForwards; this.frequencyTimeWheelPos = 0; this.frequencyTimeWheelRadius = 0.5; this.recentFrequencyTimeIncrements = []; for(var i = 0; i < 8; i++) { this.recentFrequencyTimeIncrements.push(0); } this.averageFrequencyTimeIncrement = 0; this.advanceFrequencyTimeWheel = function(angle){ this.elapsedFTDegrees += angle; this.recentFrequencyTimeIncrements.push(angle); this.recentFrequencyTimeIncrements.shift(); for(increment in this.recentFrequencyTimeIncrements) { this.averageFrequencyTimeIncrement += this.recentFrequencyTimeIncrements[increment]; } this.averageFrequencyTimeIncrement /= this.recentFrequencyTimeIncrements.length; this.frequencyTimeWheelPos += angle; if (this.frequencyTimeWheelPos >= 360) { this.frequencyTimeWheelPos = this.frequencyTimeWheelPos % 360; } } // currently disabled due to breaking changes in js audio native objects this.playFootStepSound = function(side) { //var options = new AudioInjectionOptions(); //options.position = Camera.getPosition(); //options.volume = 0.3; //var soundNumber = 2; // 0 to 2 //if (side === RIGHT && motion.makesFootStepSounds) { // Audio.playS ound(walkAssets.footsteps[soundNumber + 1], options); //} else if (side === LEFT && motion.makesFootStepSounds) { // Audio.playSound(walkAssets.footsteps[soundNumber], options); //} } // history this.lastDirection = 0; this.lastSpeed = 0; this.transitionCount = 0; this.lastDistanceToVoxels = 0; }; // end Motion constructor spatialInformation = (function() { return { distanceToVoxels: function() { // use the blocking version of findRayIntersection to avoid errors var pickRay = {origin: MyAvatar.position, direction: {x:0, y:-1, z:0}}; // TODO: change this to use entities? return false; // Voxels.findRayIntersectionBlocking(pickRay).distance - motion.calibration.hipsToFeet;; } } })(); // end spatialInformation object literal // animation file manipulation animation = (function() { return { zeroAnimation: function(animation) { for (i in animation.joints) { animation.joints[i].pitch = 0; animation.joints[i].yaw = 0; animation.joints[i].roll = 0; animation.joints[i].pitchPhase = 0; animation.joints[i].yawPhase = 0; animation.joints[i].rollPhase = 0; animation.joints[i].pitchOffset = 0; animation.joints[i].yawOffset = 0; animation.joints[i].rollOffset = 0; if (i === 0) { // Hips only animation.joints[i].thrust = 0; animation.joints[i].sway = 0; animation.joints[i].bob = 0; animation.joints[i].thrustPhase = 0; animation.joints[i].swayPhase = 0; animation.joints[i].bobPhase = 0; } } }, blendAnimation: function(sourceAnimation, targetAnimation, percent) { for (i in targetAnimation.joints) { targetAnimation.joints[i].pitch += percent * sourceAnimation.joints[i].pitch; targetAnimation.joints[i].yaw += percent * sourceAnimation.joints[i].yaw; targetAnimation.joints[i].roll += percent * sourceAnimation.joints[i].roll; targetAnimation.joints[i].pitchPhase += percent * sourceAnimation.joints[i].pitchPhase; targetAnimation.joints[i].yawPhase += percent * sourceAnimation.joints[i].yawPhase; targetAnimation.joints[i].rollPhase += percent * sourceAnimation.joints[i].rollPhase; targetAnimation.joints[i].pitchOffset += percent * sourceAnimation.joints[i].pitchOffset; targetAnimation.joints[i].yawOffset += percent * sourceAnimation.joints[i].yawOffset; targetAnimation.joints[i].rollOffset += percent * sourceAnimation.joints[i].rollOffset; if (i === 0) { // Hips only targetAnimation.joints[i].thrust += percent * sourceAnimation.joints[i].thrust; targetAnimation.joints[i].sway += percent * sourceAnimation.joints[i].sway; targetAnimation.joints[i].bob += percent * sourceAnimation.joints[i].bob; targetAnimation.joints[i].thrustPhase += percent * sourceAnimation.joints[i].thrustPhase; targetAnimation.joints[i].swayPhase += percent * sourceAnimation.joints[i].swayPhase; targetAnimation.joints[i].bobPhase += percent * sourceAnimation.joints[i].bobPhase; } } } } })(); // end animation object literal // finite state machine. Avatar locomotion modes represent states in the FSM state = (function () { return { // the finite list of states STANDING: 1, WALKING: 2, SIDE_STEP: 3, FLYING: 4, currentState: this.STANDING, // status vars powerOn: true, setInternalState: function(newInternalState) { motion.elapsedFTDegrees = 0; switch (newInternalState) { case this.WALKING: this.currentState = this.WALKING; return; case this.FLYING: this.currentState = this.FLYING; return; case this.SIDE_STEP: this.currentState = this.SIDE_STEP; return; case this.STANDING: default: this.currentState = this.STANDING; return; } } } })(); // end state object literal // constructor for animation Transition Transition = function(nextAnimation, lastAnimation, lastTransition) { // record the current state of animation this.nextAnimation = nextAnimation; this.lastAnimation = lastAnimation; this.lastTransition = lastTransition; // deal with transition recursion (overlapping transitions) this.id = motion.transitionCount++; // serial number for this transition this.recursionDepth = 0; this.incrementRecursion = function() { this.recursionDepth += 1; if(this.lastTransition !== nullTransition) { this.lastTransition.incrementRecursion(); if(this.lastTransition.recursionDepth > 5) { delete this.lastTransition; this.lastTransition = nullTransition; } } } if(lastTransition !== nullTransition) { this.lastTransition.incrementRecursion(); } this.reachPoses = []; // placeholder / stub: work in progress - array of reach poses for squash and stretch techniques this.transitionDuration = 0.5; // length of transition (seconds) this.easingLower = {x:0.60, y:0.03}; // Bezier curve handle this.easingUpper = {x:0.87, y:1.35}; // Bezier curve handle this.startTime = new Date().getTime(); // Starting timestamp (seconds) this.progress = 0; // how far are we through the transition? this.lastDirection = motion.lastDirection; // collect information about the currently playing animation this.lastDirection = motion.lastDirection; this.lastFrequencyTimeWheelPos = motion.frequencyTimeWheelPos; this.lastFrequencyTimeIncrement = motion.averageFrequencyTimeIncrement; this.lastFrequencyTimeWheelRadius = motion.frequencyTimeWheelRadius; this.stopAngle = 0; // what angle should we stop turning this frequency time wheel? this.percentToMove = 0; // if we need to keep moving to complete a step, this will be set to 100 this.lastElapsedFTDegrees = motion.elapsedFTDegrees; this.advancePreviousFrequencyTimeWheel = function() { this.lastFrequencyTimeWheelPos += this.lastFrequencyTimeIncrement; if (this.lastFrequencyTimeWheelPos >= 360) { this.lastFrequencyTimeWheelPos = this.lastFrequencyTimeWheelPos % 360; } if(this.lastTransition !== nullTransition) { this.lastTransition.advancePreviousFrequencyTimeWheel(); } }; this.updateProgress = function() { var elapasedTime = (new Date().getTime() - this.startTime) / 1000; this.progress = elapasedTime / this.transitionDuration; this.progress = filter.bezier((1 - this.progress), {x: 0, y: 0}, this.easingLower, this.easingUpper, {x: 1, y: 1}).y; // ensure there is at least some progress if(this.progress <= 0) { this.progress = VERY_SHORT_TIME; } if(this.lastTransition !== nullTransition) { if(this.lastTransition.updateProgress() >= 1) { // the previous transition is now complete delete this.lastTransition; this.lastTransition = nullTransition; } } return this.progress; }; this.blendTranslations = function(frequencyTimeWheelPos, direction) { var lastTranslations = {x:0, y:0, z:0}; if(!isDefined(this.nextAnimation)) { return lastTranslations; } var nextTranslations = calculateTranslations(this.nextAnimation, frequencyTimeWheelPos, direction); // are we blending with a previous, still live transition? if(this.lastTransition !== nullTransition) { lastTranslations = this.lastTransition.blendTranslations(this.lastFrequencyTimeWheelPos, this.lastDirection); } else { lastTranslations = calculateTranslations(this.lastAnimation, this.lastFrequencyTimeWheelPos, this.lastDirection); } nextTranslations.x = this.progress * nextTranslations.x + (1 - this.progress) * lastTranslations.x; nextTranslations.y = this.progress * nextTranslations.y + (1 - this.progress) * lastTranslations.y; nextTranslations.z = this.progress * nextTranslations.z + (1 - this.progress) * lastTranslations.z; return nextTranslations; }; this.blendRotations = function(jointName, frequencyTimeWheelPos, direction) { var lastRotations = {x:0, y:0, z:0}; var nextRotations = calculateRotations(jointName, this.nextAnimation, frequencyTimeWheelPos, direction); // are we blending with a previous, still live transition? if(this.lastTransition !== nullTransition) { lastRotations = this.lastTransition.blendRotations(jointName, this.lastFrequencyTimeWheelPos, this.lastDirection); } else { lastRotations = calculateRotations(jointName, this.lastAnimation, this.lastFrequencyTimeWheelPos, this.lastDirection); } nextRotations.x = this.progress * nextRotations.x + (1 - this.progress) * lastRotations.x; nextRotations.y = this.progress * nextRotations.y + (1 - this.progress) * lastRotations.y; nextRotations.z = this.progress * nextRotations.z + (1 - this.progress) * lastRotations.z; return nextRotations; }; }; // end Transition constructor // individual joint modiers (mostly used to provide symmetry between left and right limbs) JointModifiers = function(joint, direction) { // gather modifiers and multipliers this.pitchFrequencyMultiplier = 1; this.pitchPhaseModifier = 0; this.pitchReverseModifier = 0; this.yawReverseModifier = 0; this.rollReverseModifier = 0; this.pitchSign = 1; // for sidestepping and incorrectly rigged Ron ToeBases this.yawSign = 1; this.rollSign = 1; this.pitchReverseInvert = 1; this.pitchOffsetSign = 1; this.yawOffsetSign = 1; this.rollOffsetSign = 1; this.bobReverseModifier = 0; this.bobFrequencyMultiplier = 1; this.thrustFrequencyMultiplier = 1; if (isDefined(joint.pitchFrequencyMultiplier)) { this.pitchFrequencyMultiplier = joint.pitchFrequencyMultiplier; } if (isDefined(joint.pitchPhaseModifier)) { this.pitchPhaseModifier = joint.pitchPhaseModifier; } if (isDefined(joint.pitchSign)) { this.pitchSign = joint.pitchSign; } if (isDefined(joint.yawSign)) { this.yawSign = joint.yawSign; } if (isDefined(joint.rollSign)) { this.rollSign = joint.rollSign; } if (isDefined(joint.pitchReverseInvert) && direction === BACKWARDS) { this.pitchReverseInvert = joint.pitchReverseInvert; } if (isDefined(joint.pitchReverseModifier) && direction === BACKWARDS) { this.pitchReverseModifier = joint.pitchReverseModifier; } if (isDefined(joint.yawReverseModifier) && direction === BACKWARDS) { this.yawReverseModifier = joint.yawReverseModifier; } if (isDefined(joint.rollReverseModifier) && direction === BACKWARDS) { this.rollReverseModifier = joint.rollReverseModifier; } if (isDefined(joint.pitchOffsetSign)) { this.pitchOffsetSign = joint.pitchOffsetSign; } if (isDefined(joint.yawOffsetSign)) { this.yawOffsetSign = joint.yawOffsetSign; } if (isDefined(joint.rollOffsetSign)) { this.rollOffsetSign = joint.rollOffsetSign; } if (isDefined(joint.bobReverseModifier) && direction === BACKWARDS) { this.bobReverseModifier = joint.bobReverseModifier; } if (isDefined(joint.bobFrequencyMultiplier)) { this.bobFrequencyMultiplier = joint.bobFrequencyMultiplier; } if (isDefined(joint.thrustFrequencyMultiplier)) { this.thrustFrequencyMultiplier = joint.thrustFrequencyMultiplier; } }; walkAssets = (function () { // load the sounds - currently disabled due to breaking changes in js audio native objects //var _pathToSounds = 'https://s3.amazonaws.com/hifi-public/sounds/Footsteps/'; //var _footsteps = []; //_footsteps.push(new Sound(_pathToSounds+"FootstepW2Left-12db.wav")); //_footsteps.push(new Sound(_pathToSounds+"FootstepW2Right-12db.wav")); //_footsteps.push(new Sound(_pathToSounds+"FootstepW3Left-12db.wav")); //_footsteps.push(new Sound(_pathToSounds+"FootstepW3Right-12db.wav")); //_footsteps.push(new Sound(_pathToSounds+"FootstepW5Left-12db.wav")); //_footsteps.push(new Sound(_pathToSounds+"FootstepW5Right-12db.wav")); // load the animation datafiles Script.include(pathToAssets+"animations/dd-female-standard-walk-animation.js"); Script.include(pathToAssets+"animations/dd-female-standing-one-animation.js"); Script.include(pathToAssets+"animations/dd-female-flying-up-animation.js"); Script.include(pathToAssets+"animations/dd-female-flying-animation.js"); Script.include(pathToAssets+"animations/dd-female-flying-down-animation.js"); Script.include(pathToAssets+"animations/dd-female-flying-blend-animation.js"); Script.include(pathToAssets+"animations/dd-female-hovering-animation.js"); Script.include(pathToAssets+"animations/dd-female-sidestep-left-animation.js"); Script.include(pathToAssets+"animations/dd-female-sidestep-right-animation.js"); Script.include(pathToAssets+"animations/dd-male-standard-walk-animation.js"); Script.include(pathToAssets+"animations/dd-male-standing-one-animation.js"); Script.include(pathToAssets+"animations/dd-male-flying-up-animation.js"); Script.include(pathToAssets+"animations/dd-male-flying-animation.js"); Script.include(pathToAssets+"animations/dd-male-flying-down-animation.js"); Script.include(pathToAssets+"animations/dd-male-flying-blend-animation.js"); Script.include(pathToAssets+"animations/dd-male-hovering-animation.js"); Script.include(pathToAssets+"animations/dd-male-sidestep-left-animation.js"); Script.include(pathToAssets+"animations/dd-male-sidestep-right-animation.js"); Script.include(pathToAssets+"animations/dd-animation-reference.js"); var _femaleStandardWalk = new FemaleStandardWalk(); var _femaleFlyingUp = new FemaleFlyingUp(); var _femaleFlying = new FemaleFlying(); var _femaleFlyingDown = new FemaleFlyingDown(); var _femaleStandOne = new FemaleStandingOne(); var _femaleSideStepLeft = new FemaleSideStepLeft(); var _femaleSideStepRight = new FemaleSideStepRight(); var _femaleFlyingBlend = new FemaleFlyingBlend(); var _femaleHovering = new FemaleHovering(); var _maleStandardWalk = new MaleStandardWalk(filter); var _maleStandOne = new MaleStandingOne(); var _maleSideStepLeft = new MaleSideStepLeft(); var _maleSideStepRight = new MaleSideStepRight(); var _maleFlying = new MaleFlying(); var _maleFlyingDown = new MaleFlyingDown(); var _maleFlyingUp = new MaleFlyingUp(); var _maleFlyingBlend = new MaleFlyingBlend(); var _maleHovering = new MaleHovering(); var _animationReference = new AnimationReference(); return { // expose the sound assets //footsteps: _footsteps, // expose the animation assets femaleStandardWalk: _femaleStandardWalk, femaleFlyingUp: _femaleFlyingUp, femaleFlying: _femaleFlying, femaleFlyingDown: _femaleFlyingDown, femaleFlyingBlend: _femaleFlyingBlend, femaleHovering: _femaleHovering, femaleStandOne: _femaleStandOne, femaleSideStepLeft: _femaleSideStepLeft, femaleSideStepRight: _femaleSideStepRight, maleStandardWalk: _maleStandardWalk, maleFlyingUp: _maleFlyingUp, maleFlying: _maleFlying, maleFlyingDown: _maleFlyingDown, maleFlyingBlend: _maleFlyingBlend, maleHovering: _maleHovering, maleStandOne: _maleStandOne, maleSideStepLeft: _maleSideStepLeft, maleSideStepRight: _maleSideStepRight, animationReference: _animationReference, } })();