diff --git a/examples/html/walkSettings.html b/examples/html/walkSettings.html index 7c9b7fbbf7..7e65277977 100644 --- a/examples/html/walkSettings.html +++ b/examples/html/walkSettings.html @@ -2,16 +2,16 @@ @@ -62,7 +62,7 @@
- +
diff --git a/examples/libraries/walkApi.js b/examples/libraries/walkApi.js index 94357b97dc..ab9f5071b1 100644 --- a/examples/libraries/walkApi.js +++ b/examples/libraries/walkApi.js @@ -3,7 +3,7 @@ // version 1.3 // // Created by David Wooldridge, June 2015 -// Copyright © 2014 - 2015 High Fidelity, Inc. +// Copyright © 2014 - 2015 High Fidelity, Inc. // // Exposes API for use by walk.js version 1.2+. // @@ -13,46 +13,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// locomotion states -const STATIC = 1; -const SURFACE_MOTION = 2; -const AIR_MOTION = 4; - -// directions -const UP = 1; -const DOWN = 2; -const LEFT = 4; -const RIGHT = 8; -const FORWARDS = 16; -const BACKWARDS = 32; -const NONE = 64; - -// waveshapes -const SAWTOOTH = 1; -const TRIANGLE = 2; -const SQUARE = 4; - -// constants used by walk.js and walkApi.js -const MAX_WALK_SPEED = 2.9; // peak, by observation -const MAX_FT_WHEEL_INCREMENT = 25; // avoid fast walk when landing -const TOP_SPEED = 300; -const ON_SURFACE_THRESHOLD = 0.1; // height above surface to be considered as on the surface -const TRANSITION_COMPLETE = 1000; -const HIFI_PUBLIC_BUCKET = "https://hifi-public.s3.amazonaws.com/"; - -// constants used by walkApi.js -const MOVE_THRESHOLD = 0.075; -const ACCELERATION_THRESHOLD = 0.2; // detect stop to walking -const DECELERATION_THRESHOLD = -6; // detect walking to stop -const FAST_DECELERATION_THRESHOLD = -150; // detect flying to stop -const BOUNCE_ACCELERATION_THRESHOLD = 25; // used to ignore gravity influence fluctuations after landing -const GRAVITY_THRESHOLD = 3.0; // height above surface where gravity is in effect -const OVERCOME_GRAVITY_SPEED = 0.5; // reaction sensitivity to jumping under gravity -const LANDING_THRESHOLD = 0.35; // metres from a surface below which need to prepare for impact -const MAX_TRANSITION_RECURSION = 10; // how many nested transitions are permitted +// included here to ensure walkApi.js can be used as an API, separate from walk.js +Script.include("./libraries/walkConstants.js"); Avatar = function() { - // if Hydras are connected, the only way to enable use is by never setting any rotations on the arm joints + // if Hydras are connected, the only way to enable use is to never set any arm joint rotation this.hydraCheck = function() { // function courtesy of Thijs Wenker (frisbee.js) var numberOfButtons = Controller.getNumberOfButtons(); @@ -75,8 +40,8 @@ Avatar = function() { // settings this.headFree = true; this.armsFree = this.hydraCheck(); // automatically sets true to enable Hydra support - temporary fix - this.makesFootStepSounds = false; - this.isBlenderExport = false; // temporary fix + this.makesFootStepSounds = true; + this.blenderPreRotations = false; // temporary fix this.animationSet = undefined; // currently just one animation set this.setAnimationSet = function(animationSet) { this.animationSet = animationSet; @@ -123,7 +88,7 @@ Avatar = function() { this.calibration.hipsToFeet = MyAvatar.getJointPosition("Hips").y - MyAvatar.getJointPosition("RightToeBase").y; // maybe measuring before Blender pre-rotations have been applied? - if (this.calibration.hipsToFeet < 0 && this.isBlenderExport) { + if (this.calibration.hipsToFeet < 0 && this.blenderPreRotations) { this.calibration.hipsToFeet *= -1; } @@ -165,7 +130,6 @@ Avatar = function() { // footsteps this.nextStep = RIGHT; // the first step is right, because the waveforms say so - this.leftAudioInjector = null; this.rightAudioInjector = null; this.makeFootStepSound = function() { @@ -173,16 +137,18 @@ Avatar = function() { const SPEED_THRESHOLD = 0.4; const VOLUME_ATTENUATION = 0.8; const MIN_VOLUME = 0.5; + var volume = Vec3.length(motion.velocity) > SPEED_THRESHOLD ? + VOLUME_ATTENUATION * Vec3.length(motion.velocity) / MAX_WALK_SPEED : MIN_VOLUME; + volume = volume > 1 ? 1 : volume; // occurs when landing at speed - can walk faster than max walking speed var options = { position: Vec3.sum(MyAvatar.position, {x:0, y: -this.calibration.hipsToFeet, z:0}), - volume: Vec3.length(motion.velocity) > SPEED_THRESHOLD ? - VOLUME_ATTENUATION * Vec3.length(motion.velocity) / MAX_WALK_SPEED : MIN_VOLUME + volume: volume }; if (this.nextStep === RIGHT) { if (this.rightAudioInjector === null) { this.rightAudioInjector = Audio.playSound(walkAssets.footsteps[0], options); } else { - //this.rightAudioInjector.setOptions(options); + this.rightAudioInjector.setOptions(options); this.rightAudioInjector.restart(); } this.nextStep = LEFT; @@ -190,7 +156,7 @@ Avatar = function() { if (this.leftAudioInjector === null) { this.leftAudioInjector = Audio.playSound(walkAssets.footsteps[1], options); } else { - //this.leftAudioInjector.setOptions(options); + this.leftAudioInjector.setOptions(options); this.leftAudioInjector.restart(); } this.nextStep = RIGHT; @@ -200,6 +166,7 @@ Avatar = function() { // constructor for the Motion object Motion = function() { + this.isLive = true; // locomotion status this.state = STATIC; this.nextState = STATIC; @@ -212,14 +179,6 @@ Motion = function() { this.isComingToHalt = false; this.directedAcceleration = 0; - // settings - this.isLive = true; - this.calibration = { - PITCH_MAX: 60, - ROLL_MAX: 80, - DELTA_YAW_MAX: 1.7 - } - // used to make sure at least one step has been taken when transitioning from a walk cycle this.elapsedFTDegrees = 0; @@ -360,6 +319,7 @@ Motion = function() { (movingDirectlyUpOrDown && !isOnSurface)); var staticToSurfaceMotion = surfaceMotion && !motion.isComingToHalt && !movingDirectlyUpOrDown && !this.isDecelerating && lateralVelocity > MOVE_THRESHOLD; + if (staticToAirMotion) { this.nextState = AIR_MOTION; } else if (staticToSurfaceMotion) { @@ -376,6 +336,7 @@ Motion = function() { var surfaceMotionToAirMotion = (acceleratingAndAirborne || goingTooFastToWalk || movingDirectlyUpOrDown) && (!surfaceMotion && isTakingOff) || (!surfaceMotion && this.isMoving && !isComingInToLand); + if (surfaceMotionToStatic) { // working on the assumption that stopping is now inevitable if (!motion.isComingToHalt && isOnSurface) { @@ -392,6 +353,7 @@ Motion = function() { case AIR_MOTION: var airMotionToSurfaceMotion = (surfaceMotion || aboutToLand) && !movingDirectlyUpOrDown; var airMotionToStatic = !this.isMoving && this.direction === this.lastDirection; + if (airMotionToSurfaceMotion){ this.nextState = SURFACE_MOTION; } else if (airMotionToStatic) { @@ -492,7 +454,7 @@ animationOperations = (function() { var joint = animation.joints[jointName]; var jointRotations = {x:0, y:0, z:0}; - if (avatar.isBlenderExport) { + if (avatar.blenderPreRotations) { jointRotations = Vec3.sum(jointRotations, walkAssets.blenderPreRotations.joints[jointName]); } @@ -670,7 +632,7 @@ TransitionParameters = function() { this.duration = 0.5; this.easingLower = {x:0.25, y:0.75}; this.easingUpper = {x:0.75, y:0.25}; - this.reachPoseNames = []; + this.reachPoses = []; } const QUARTER_CYCLE = 90; @@ -710,8 +672,8 @@ Transition = function(nextAnimation, lastAnimation, lastTransition, playTransiti walkAssets.getTransitionParameters(lastAnimation, nextAnimation, this.parameters); // fire up any reach poses for this transition if (playTransitionReachPoses) { - for (poseName in this.parameters.reachPoseNames) { - this.liveReachPoses.push(new ReachPose(this.parameters.reachPoseNames[poseName])); + for (poseName in this.parameters.reachPoses) { + this.liveReachPoses.push(new ReachPose(this.parameters.reachPoses[poseName])); } } } diff --git a/examples/libraries/walkConstants.js b/examples/libraries/walkConstants.js new file mode 100644 index 0000000000..abc9a4bc5f --- /dev/null +++ b/examples/libraries/walkConstants.js @@ -0,0 +1,54 @@ +// +// walkConstants.js +// version 1.0 +// +// Created by David Wooldridge, June 2015 +// Copyright © 2015 High Fidelity, Inc. +// +// Provides constants necessary for the operation of the walk.js script and the walkApi.js script +// +// Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// locomotion states +STATIC = 1; +SURFACE_MOTION = 2; +AIR_MOTION = 4; + +// directions +UP = 1; +DOWN = 2; +LEFT = 4; +RIGHT = 8; +FORWARDS = 16; +BACKWARDS = 32; +NONE = 64; + +// waveshapes +SAWTOOTH = 1; +TRIANGLE = 2; +SQUARE = 4; + +// used by walk.js and walkApi.js +MAX_WALK_SPEED = 2.9; // peak, by observation +MAX_FT_WHEEL_INCREMENT = 25; // avoid fast walk when landing +TOP_SPEED = 300; +ON_SURFACE_THRESHOLD = 0.1; // height above surface to be considered as on the surface +TRANSITION_COMPLETE = 1000; +PITCH_MAX = 60; // maximum speed induced pitch +ROLL_MAX = 80; // maximum speed induced leaning / banking +DELTA_YAW_MAX = 1.7; // maximum change in yaw in rad/s + +// used by walkApi.js only +MOVE_THRESHOLD = 0.075; // movement dead zone +ACCELERATION_THRESHOLD = 0.2; // detect stop to walking +DECELERATION_THRESHOLD = -6; // detect walking to stop +FAST_DECELERATION_THRESHOLD = -150; // detect flying to stop +BOUNCE_ACCELERATION_THRESHOLD = 25; // used to ignore gravity influence fluctuations after landing +GRAVITY_THRESHOLD = 3.0; // height above surface where gravity is in effect +OVERCOME_GRAVITY_SPEED = 0.5; // reaction sensitivity to jumping under gravity +LANDING_THRESHOLD = 0.35; // metres from a surface below which need to prepare for impact +MAX_TRANSITION_RECURSION = 10; // how many nested transitions are permitted \ No newline at end of file diff --git a/examples/libraries/walkFilters.js b/examples/libraries/walkFilters.js index 9f8c947b1f..1f8077d9eb 100644 --- a/examples/libraries/walkFilters.js +++ b/examples/libraries/walkFilters.js @@ -2,8 +2,8 @@ // walkFilters.js // version 1.1 // -// Created by David Wooldridge, Autumn 2014 -// Copyright © 2014 - 2015 High Fidelity, Inc. +// Created by David Wooldridge, June 2015 +// Copyright © 2014 - 2015 High Fidelity, Inc. // // Provides a variety of filters for use by the walk.js script v1.2+ // diff --git a/examples/libraries/walkSettings.js b/examples/libraries/walkSettings.js index 2657eca605..e997e4f29e 100644 --- a/examples/libraries/walkSettings.js +++ b/examples/libraries/walkSettings.js @@ -3,7 +3,7 @@ // version 0.1 // // Created by David Wooldridge, June 2015 -// Copyright © 2014 - 2015 High Fidelity, Inc. +// Copyright © 2015 High Fidelity, Inc. // // Presents settings for walk.js // @@ -31,7 +31,7 @@ WalkSettings = function() { function mousePressEvent(event) { if (Overlays.getOverlayAtPoint(event) === minimisedTab) { _visible = !_visible; - _webView.setVisible(_visible); + _webWindow.setVisible(_visible); } } Controller.mousePressEvent.connect(mousePressEvent); @@ -55,7 +55,7 @@ WalkSettings = function() { } if (_shift && (event.text === 'o' || event.text === 'O')) { _visible = !_visible; - _webView.setVisible(_visible); + _webWindow.setVisible(_visible); } } function keyReleaseEvent(event) { @@ -67,30 +67,29 @@ WalkSettings = function() { Controller.keyReleaseEvent.connect(keyReleaseEvent); // web window - var _url = Script.resolvePath('html/walkSettings.html'); const PANEL_WIDTH = 200; const PANEL_HEIGHT = 180; - var _webView = new WebWindow('Walk Settings', _url, PANEL_WIDTH, PANEL_HEIGHT, false); - _webView.setVisible(false); - - _webView.eventBridge.webEventReceived.connect(function(data) { + var _url = Script.resolvePath('html/walkSettings.html'); + var _webWindow = new WebWindow('Walk Settings', _url, PANEL_WIDTH, PANEL_HEIGHT, false); + _webWindow.setVisible(false); + _webWindow.eventBridge.webEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type == "init") { // send the current settings to the window - _webView.eventBridge.emitScriptEvent(JSON.stringify({ + _webWindow.eventBridge.emitScriptEvent(JSON.stringify({ type: "update", armsFree: avatar.armsFree, - footstepSounds: avatar.makesFootStepSounds, - blenderPreRotations: avatar.isBlenderExport + makesFootStepSounds: avatar.makesFootStepSounds, + blenderPreRotations: avatar.blenderPreRotations })); } else if (data.type == "powerToggle") { motion.isLive = !motion.isLive; } else if (data.type == "update") { // receive settings from the window avatar.armsFree = data.armsFree; - avatar.makesFootStepSounds = data.footstepSounds; - avatar.isBlenderExport = data.blenderPreRotations; + avatar.makesFootStepSounds = data.makesFootStepSounds; + avatar.blenderPreRotations = data.blenderPreRotations; } }); }; diff --git a/examples/walk.js b/examples/walk.js index 0c2e978eea..8d54fecc92 100644 --- a/examples/walk.js +++ b/examples/walk.js @@ -3,7 +3,7 @@ // version 1.25 // // Created by David Wooldridge, June 2015 -// Copyright © 2014 - 2015 High Fidelity, Inc. +// Copyright © 2014 - 2015 High Fidelity, Inc. // // Animates an avatar using procedural animation techniques. // @@ -13,61 +13,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// locomotion states -var STATIC = 1; -var SURFACE_MOTION = 2; -var AIR_MOTION = 4; - -// directions -var UP = 1; -var DOWN = 2; -var LEFT = 4; -var RIGHT = 8; -var FORWARDS = 16; -var BACKWARDS = 32; -var NONE = 64; - -// waveshapes -var SAWTOOTH = 1; -var TRIANGLE = 2; -var SQUARE = 4; - -// constants -const MAX_WALK_SPEED = 2.9; // peak, by observation -const MAX_FT_WHEEL_INCREMENT = 25; // avoid fast walk when landing -const TOP_SPEED = 300; -const ON_SURFACE_THRESHOLD = 0.1; // height above surface to be considered as on the surface -const TRANSITION_COMPLETE = 1000; +// animations, reach poses, reach pose parameters, transitions, transition parameters, sounds, image/s and reference files const HIFI_PUBLIC_BUCKET = "https://hifi-public.s3.amazonaws.com/"; - -// path to animations, reach-poses, reachPoses, transitions, overlay images and reference files var pathToAssets = HIFI_PUBLIC_BUCKET + "procedural-animator/assets/"; Script.include([ + "./libraries/walkConstants.js", "./libraries/walkFilters.js", "./libraries/walkApi.js", pathToAssets + "walkAssets.js" ]); -// construct Avatar and Motion +// construct Avatar, Motion and (null) Transition var avatar = new Avatar(); var motion = new Motion(); - -// create settings dialog -Script.include("./libraries/walkSettings.js"); - -// create and initialise Transition var nullTransition = new Transition(); motion.currentTransition = nullTransition; -// motion smoothing / damping filters -var FLY_BLEND_DAMPING = 50; -var leanPitchSmoothingFilter = filter.createButterworthFilter(); -var leanRollSmoothingFilter = filter.createButterworthFilter(); -var flyUpFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); -var flyDownFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); -var flyForwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); -var flyBackwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); +// create settings (gets initial values from avatar) +Script.include("./libraries/walkSettings.js"); // Main loop Script.update.connect(function(deltaTime) { @@ -80,8 +44,8 @@ Script.update.connect(function(deltaTime) { // decide which animation should be playing selectAnimation(); - // turn the frequency time wheels and determine stride length - determineStride(); + // advance the animation cycle/s by the correct amount/s + advanceAnimations(); // update the progress of any live transitions updateTransitions(); @@ -89,7 +53,7 @@ Script.update.connect(function(deltaTime) { // apply translation and rotations renderMotion(); - // record this frame's parameters + // save this frame's parameters motion.saveHistory(); } }); @@ -130,6 +94,13 @@ function setTransition(nextAnimation, playTransitionReachPoses) { } } +// fly animation blending: smoothing / damping filters +const FLY_BLEND_DAMPING = 50; +var flyUpFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); +var flyDownFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); +var flyForwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); +var flyBackwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); + // select / blend the appropriate animation for the current state of motion function selectAnimation() { var playTransitionReachPoses = true; @@ -259,7 +230,7 @@ function selectAnimation() { } // determine the length of stride. advance the frequency time wheels. advance frequency time wheels for any live transitions -function determineStride() { +function advanceAnimations() { var wheelAdvance = 0; // turn the frequency time wheel @@ -338,7 +309,6 @@ function updateTransitions() { } } } - // update the Transition progress if (motion.currentTransition.updateProgress() === TRANSITION_COMPLETE) { motion.currentTransition = nullTransition; } @@ -346,6 +316,7 @@ function updateTransitions() { } // helper function for renderMotion(). calculate the amount to lean forwards (or backwards) based on the avi's velocity +var leanPitchSmoothingFilter = filter.createButterworthFilter(); function getLeanPitch() { var leanProgress = 0; @@ -356,10 +327,11 @@ function getLeanPitch() { } // use filters to shape the walking acceleration response leanProgress = leanPitchSmoothingFilter.process(leanProgress); - return motion.calibration.PITCH_MAX * leanProgress; + return PITCH_MAX * leanProgress; } // helper function for renderMotion(). calculate the angle at which to bank into corners whilst turning +var leanRollSmoothingFilter = filter.createButterworthFilter(); function getLeanRoll() { var leanRollProgress = 0; var linearContribution = 0; @@ -368,7 +340,7 @@ function getLeanRoll() { if (Vec3.length(motion.velocity) > 0) { linearContribution = (Math.log(Vec3.length(motion.velocity) / TOP_SPEED) + LOG_SCALER) / LOG_SCALER; } - var angularContribution = Math.abs(motion.yawDelta) / motion.calibration.DELTA_YAW_MAX; + var angularContribution = Math.abs(motion.yawDelta) / DELTA_YAW_MAX; leanRollProgress = linearContribution; leanRollProgress *= angularContribution; // shape the response curve @@ -382,7 +354,7 @@ function getLeanRoll() { } // filter progress leanRollProgress = leanRollSmoothingFilter.process(turnSign * leanRollProgress); - return motion.calibration.ROLL_MAX * leanRollProgress; + return ROLL_MAX * leanRollProgress; } // animate the avatar using sine waves, geometric waveforms and harmonic generators