From 0e71583048ebea89c6dc253b77ec5bcb1dca59b0 Mon Sep 17 00:00:00 2001 From: DaveDubUK Date: Wed, 24 Jun 2015 19:41:54 +0700 Subject: [PATCH] code review changes for walk.js 1.25 --- examples/html/walkSettings.html | 2 - examples/html/walkStyle.css | 2 +- examples/libraries/walkApi.js | 48 ++++++++--------- examples/libraries/walkFilters.js | 10 ++-- examples/libraries/walkSettings.js | 35 +++++------- examples/walk.js | 86 +++++++++++++++--------------- 6 files changed, 86 insertions(+), 97 deletions(-) diff --git a/examples/html/walkSettings.html b/examples/html/walkSettings.html index cc9e8793f7..7c9b7fbbf7 100644 --- a/examples/html/walkSettings.html +++ b/examples/html/walkSettings.html @@ -37,11 +37,9 @@ } }); } - elArmsFree.addEventListener("change", emitUpdate); elFootstepSounds.addEventListener("change", emitUpdate); elBlenderPreRotations.addEventListener("change", emitUpdate); - elPower.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "powerToggle" diff --git a/examples/html/walkStyle.css b/examples/html/walkStyle.css index 26fb085393..22f465411a 100644 --- a/examples/html/walkStyle.css +++ b/examples/html/walkStyle.css @@ -45,4 +45,4 @@ input[type=button] { border: 0; color: #fff; font-weight: bold; -} \ No newline at end of file +} diff --git a/examples/libraries/walkApi.js b/examples/libraries/walkApi.js index 3144b9df45..94357b97dc 100644 --- a/examples/libraries/walkApi.js +++ b/examples/libraries/walkApi.js @@ -2,10 +2,10 @@ // walkApi.js // version 1.3 // -// Created by David Wooldridge, Autumn 2014 -// Copyright © 2015 High Fidelity, Inc. +// Created by David Wooldridge, June 2015 +// Copyright © 2014 - 2015 High Fidelity, Inc. // -// Exposes API for use by walk.js version 1.2+. +// Exposes API for use by walk.js version 1.2+. // // Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools // @@ -32,7 +32,7 @@ const SAWTOOTH = 1; const TRIANGLE = 2; const SQUARE = 4; -// constants used by walk.js and walkApi.js +// 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; @@ -62,7 +62,7 @@ Avatar = function() { const HYDRA_TRIGGERS = 2; const HYDRA_CONTROLLERS_PER_TRIGGER = 2; var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - if (numberOfButtons == HYDRA_BUTTONS && + if (numberOfButtons == HYDRA_BUTTONS && numberOfTriggers == HYDRA_TRIGGERS && controllersPerTrigger == HYDRA_CONTROLLERS_PER_TRIGGER) { print('walk.js info: Razer Hydra detected. Setting arms free (not controlled by script)'); @@ -75,7 +75,7 @@ Avatar = function() { // settings this.headFree = true; this.armsFree = this.hydraCheck(); // automatically sets true to enable Hydra support - temporary fix - this.makesFootStepSounds = false; + this.makesFootStepSounds = false; this.isBlenderExport = false; // temporary fix this.animationSet = undefined; // currently just one animation set this.setAnimationSet = function(animationSet) { @@ -121,8 +121,8 @@ Avatar = function() { } } this.calibration.hipsToFeet = MyAvatar.getJointPosition("Hips").y - MyAvatar.getJointPosition("RightToeBase").y; - - // maybe measuring before Blender pre-rotations have been applied? + + // maybe measuring before Blender pre-rotations have been applied? if (this.calibration.hipsToFeet < 0 && this.isBlenderExport) { this.calibration.hipsToFeet *= -1; } @@ -165,7 +165,7 @@ 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() { @@ -229,7 +229,7 @@ Motion = function() { // orientation, locomotion and timing this.velocity = {x:0, y:0, z:0}; this.acceleration = {x:0, y:0, z:0}; - this.yaw = Quat.safeEulerAngles(MyAvatar.orientation).y; // MyAvatar.orientation.y; // + this.yaw = Quat.safeEulerAngles(MyAvatar.orientation).y; this.yawDelta = 0; this.yawDeltaAcceleration = 0; this.direction = FORWARDS; @@ -241,12 +241,12 @@ Motion = function() { this.lastYaw = Quat.safeEulerAngles(MyAvatar.orientation).y; this.lastYawDelta = 0; this.lastYawDeltaAcceleration = 0; - + // Quat.safeEulerAngles(MyAvatar.orientation).y tends to repeat values between frames, so values are filtered var YAW_SMOOTHING = 22; - this.yawFilter = filter.createAveragingFilter(YAW_SMOOTHING); //createButterworthFilter(); // - this.deltaTimeFilter = filter.createAveragingFilter(YAW_SMOOTHING); //createButterworthFilter(); // - this.yawDeltaAccelerationFilter = filter.createAveragingFilter(YAW_SMOOTHING); //createButterworthFilter(); // + this.yawFilter = filter.createAveragingFilter(YAW_SMOOTHING); + this.deltaTimeFilter = filter.createAveragingFilter(YAW_SMOOTHING); + this.yawDeltaAccelerationFilter = filter.createAveragingFilter(YAW_SMOOTHING); // assess locomotion state this.assess = function(deltaTime) { @@ -350,16 +350,16 @@ Motion = function() { var surfaceMotion = isOnSurface && this.isMoving; var acceleratingAndAirborne = this.isAccelerating && !isOnSurface; var goingTooFastToWalk = !this.isDecelerating && this.isFlyingSpeed; - var movingDirectlyUpOrDown = (this.direction === UP || this.direction === DOWN) // && lateralVelocity < MOVE_THRESHOLD; + var movingDirectlyUpOrDown = (this.direction === UP || this.direction === DOWN) var maybeBouncing = Math.abs(this.acceleration.y > BOUNCE_ACCELERATION_THRESHOLD) ? true : false; // we now have enough information to set the appropriate locomotion mode switch (this.state) { case STATIC: - var staticToAirMotion = this.isMoving && (acceleratingAndAirborne || goingTooFastToWalk || + var staticToAirMotion = this.isMoving && (acceleratingAndAirborne || goingTooFastToWalk || (movingDirectlyUpOrDown && !isOnSurface)); var staticToSurfaceMotion = surfaceMotion && !motion.isComingToHalt && !movingDirectlyUpOrDown && - !this.isDecelerating && lateralVelocity > MOVE_THRESHOLD; + !this.isDecelerating && lateralVelocity > MOVE_THRESHOLD; if (staticToAirMotion) { this.nextState = AIR_MOTION; } else if (staticToSurfaceMotion) { @@ -370,8 +370,8 @@ Motion = function() { break; case SURFACE_MOTION: - var surfaceMotionToStatic = !this.isMoving || - (this.isDecelerating && motion.lastDirection !== DOWN && surfaceMotion && + var surfaceMotionToStatic = !this.isMoving || + (this.isDecelerating && motion.lastDirection !== DOWN && surfaceMotion && !maybeBouncing && Vec3.length(this.velocity) < MAX_WALK_SPEED); var surfaceMotionToAirMotion = (acceleratingAndAirborne || goingTooFastToWalk || movingDirectlyUpOrDown) && (!surfaceMotion && isTakingOff) || @@ -391,8 +391,7 @@ Motion = function() { case AIR_MOTION: var airMotionToSurfaceMotion = (surfaceMotion || aboutToLand) && !movingDirectlyUpOrDown; - var airMotionToStatic = !this.isMoving && this.direction === this.lastDirection; //|| - //this.isDeceleratingFast || isOnSurface; + var airMotionToStatic = !this.isMoving && this.direction === this.lastDirection; if (airMotionToSurfaceMotion){ this.nextState = SURFACE_MOTION; } else if (airMotionToStatic) { @@ -484,7 +483,7 @@ animationOperations = (function() { } else { jointTranslations.z = joint.thrust * Math.sin (filter.degToRad(modifiers.thrustFrequencyMultiplier * ft + joint.thrustPhase)) + joint.thrustOffset; - } + } return jointTranslations; }, @@ -780,7 +779,7 @@ Transition = function(nextAnimation, lastAnimation, lastTransition, playTransiti if (this.lastTransition !== nullTransition) { this.lastTransition.incrementRecursion(); } - + // end of transition initialisation. begin Transition public methods // keep up the pace for the frequency time wheel for the last animation @@ -788,7 +787,7 @@ Transition = function(nextAnimation, lastAnimation, lastTransition, playTransiti var wheelAdvance = undefined; if (this.lastAnimation === avatar.selectedWalkBlend && - this.nextAnimation === avatar.selectedIdle) { + this.nextAnimation === avatar.selectedIdle) { if (this.degreesRemaining <= 0) { // stop continued motion wheelAdvance = 0; @@ -855,7 +854,6 @@ Transition = function(nextAnimation, lastAnimation, lastTransition, playTransiti // update transition progress this.filteredProgress = filter.bezier(this.progress, this.parameters.easingLower, this.parameters.easingUpper); - //if (this.progress >= 1) walkTools.toLog(this.lastAnimation.name + ' to '+ this.nextAnimation.name + ': done'); return this.progress >= 1 ? TRANSITION_COMPLETE : false; }; diff --git a/examples/libraries/walkFilters.js b/examples/libraries/walkFilters.js index 1734c81414..9f8c947b1f 100644 --- a/examples/libraries/walkFilters.js +++ b/examples/libraries/walkFilters.js @@ -3,7 +3,7 @@ // version 1.1 // // Created by David Wooldridge, Autumn 2014 -// Copyright © 2015 High Fidelity, Inc. +// Copyright © 2014 - 2015 High Fidelity, Inc. // // Provides a variety of filters for use by the walk.js script v1.2+ // @@ -131,9 +131,9 @@ HarmonicsFilter = function(magnitudes, phaseAngles) { // the main filter object literal filter = (function() { - + const HALF_CYCLE = 180; - + // Bezier private variables var _C1 = {x:0, y:0}; var _C4 = {x:1, y:1}; @@ -158,7 +158,7 @@ filter = (function() { }, // these filters need instantiating, as they hold arrays of previous values - + // simple averaging (LP) filter for damping / smoothing createAveragingFilter: function(length) { var newAveragingFilter = new AveragingFilter(length); @@ -184,7 +184,7 @@ filter = (function() { }, // the following filters do not need separate instances, as they hold no previous values - + // Bezier response curve shaping for more natural transitions bezier: function(input, C2, C3) { // based on script by Dan Pupius (www.pupius.net) http://13thparallel.com/archive/bezier-curves/ diff --git a/examples/libraries/walkSettings.js b/examples/libraries/walkSettings.js index 6175d8b9fe..2657eca605 100644 --- a/examples/libraries/walkSettings.js +++ b/examples/libraries/walkSettings.js @@ -2,8 +2,8 @@ // walkSettings.js // version 0.1 // -// Created by David Wooldridge, Summer 2015 -// Copyright © 2015 High Fidelity, Inc. +// Created by David Wooldridge, June 2015 +// Copyright © 2014 - 2015 High Fidelity, Inc. // // Presents settings for walk.js // @@ -20,14 +20,14 @@ WalkSettings = function() { const MARGIN_TOP = 145; const ICON_SIZE = 50; const ICON_ALPHA = 0.9; - + var minimisedTab = Overlays.addOverlay("image", { x: _innerWidth - MARGIN_RIGHT, y: Window.innerHeight - MARGIN_TOP, width: ICON_SIZE, height: ICON_SIZE, imageURL: pathToAssets + 'overlay-images/ddpa-minimised-ddpa-tab.png', visible: true, alpha: ICON_ALPHA }); - + function mousePressEvent(event) { if (Overlays.getOverlayAtPoint(event) === minimisedTab) { _visible = !_visible; @@ -35,43 +35,36 @@ WalkSettings = function() { } } Controller.mousePressEvent.connect(mousePressEvent); - + Script.update.connect(function(deltaTime) { if (window.innerWidth !== _innerWidth) { _innerWidth = window.innerWidth; Overlays.EditOverlay(minimisedTab, {x: _innerWidth - MARGIN_RIGHT}); } }); - + function cleanup() { Overlays.deleteOverlay(minimisedTab); } Script.scriptEnding.connect(cleanup); - - var _control = false; + var _shift = false; function keyPressEvent(event) { - if (event.text === "CONTROL") { - _control = true; - } if (event.text === "SHIFT") { _shift = true; - } + } if (_shift && (event.text === 'o' || event.text === 'O')) { _visible = !_visible; _webView.setVisible(_visible); } } function keyReleaseEvent(event) { - if (event.text === "CONTROL") { - _control = false; - } if (event.text === "SHIFT") { _shift = false; - } - } - Controller.keyPressEvent.connect(keyPressEvent); - Controller.keyReleaseEvent.connect(keyReleaseEvent); + } + } + Controller.keyPressEvent.connect(keyPressEvent); + Controller.keyReleaseEvent.connect(keyReleaseEvent); // web window var _url = Script.resolvePath('html/walkSettings.html'); @@ -84,7 +77,7 @@ WalkSettings = function() { data = JSON.parse(data); if (data.type == "init") { - // send the current settings to the dialog + // send the current settings to the window _webView.eventBridge.emitScriptEvent(JSON.stringify({ type: "update", armsFree: avatar.armsFree, @@ -94,7 +87,7 @@ WalkSettings = function() { } else if (data.type == "powerToggle") { motion.isLive = !motion.isLive; } else if (data.type == "update") { - // receive settings from the dialog + // receive settings from the window avatar.armsFree = data.armsFree; avatar.makesFootStepSounds = data.footstepSounds; avatar.isBlenderExport = data.blenderPreRotations; diff --git a/examples/walk.js b/examples/walk.js index 0b57eb2ece..0c2e978eea 100644 --- a/examples/walk.js +++ b/examples/walk.js @@ -2,11 +2,11 @@ // walk.js // version 1.25 // -// Created by David Wooldridge, May 2015 -// Copyright © 2015 High Fidelity, Inc. +// Created by David Wooldridge, June 2015 +// Copyright © 2014 - 2015 High Fidelity, Inc. // // Animates an avatar using procedural animation techniques. -// +// // Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools // // Distributed under the Apache License, Version 2.0. @@ -49,7 +49,7 @@ Script.include([ pathToAssets + "walkAssets.js" ]); -// construct Avatar and Motion +// construct Avatar and Motion var avatar = new Avatar(); var motion = new Motion(); @@ -73,7 +73,7 @@ var flyBackwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING); Script.update.connect(function(deltaTime) { if (motion.isLive) { - + // assess current locomotion state motion.assess(deltaTime); @@ -90,7 +90,7 @@ Script.update.connect(function(deltaTime) { renderMotion(); // record this frame's parameters - motion.saveHistory(); + motion.saveHistory(); } }); @@ -98,29 +98,29 @@ Script.update.connect(function(deltaTime) { function setTransition(nextAnimation, playTransitionReachPoses) { var lastTransition = motion.currentTransition; var lastAnimation = avatar.currentAnimation; - + // if already transitioning from a blended walk need to maintain the previous walk's direction if (lastAnimation.lastDirection) { switch(lastAnimation.lastDirection) { - + case FORWARDS: lastAnimation = avatar.selectedWalk; break; - - case BACKWARDS: + + case BACKWARDS: lastAnimation = avatar.selectedWalkBackwards; break; - + case LEFT: lastAnimation = avatar.selectedSideStepLeft; break; - - case RIGHT: + + case RIGHT: lastAnimation = avatar.selectedSideStepRight; - break; + break; } } - + motion.currentTransition = new Transition(nextAnimation, lastAnimation, lastTransition, playTransitionReachPoses); avatar.currentAnimation = nextAnimation; @@ -137,10 +137,10 @@ function selectAnimation() { // select appropriate animation. create transitions where appropriate switch (motion.nextState) { case STATIC: { - if (avatar.distanceFromSurface < ON_SURFACE_THRESHOLD && - avatar.currentAnimation !== avatar.selectedIdle) { + if (avatar.distanceFromSurface < ON_SURFACE_THRESHOLD && + avatar.currentAnimation !== avatar.selectedIdle) { setTransition(avatar.selectedIdle, playTransitionReachPoses); - } else if (!(avatar.distanceFromSurface < ON_SURFACE_THRESHOLD) && + } else if (!(avatar.distanceFromSurface < ON_SURFACE_THRESHOLD) && avatar.currentAnimation !== avatar.selectedHover) { setTransition(avatar.selectedHover, playTransitionReachPoses); } @@ -151,13 +151,13 @@ function selectAnimation() { case SURFACE_MOTION: { // walk transition reach poses are currently only specified for starting to walk forwards - playTransitionReachPoses = (motion.direction === FORWARDS); + playTransitionReachPoses = (motion.direction === FORWARDS); var isAlreadyWalking = (avatar.currentAnimation === avatar.selectedWalkBlend); - + switch (motion.direction) { case FORWARDS: if (avatar.selectedWalkBlend.lastDirection !== FORWARDS) { - animationOperations.deepCopy(avatar.selectedWalk, avatar.selectedWalkBlend); + animationOperations.deepCopy(avatar.selectedWalk, avatar.selectedWalkBlend); avatar.calibration.strideLength = avatar.selectedWalk.calibration.strideLength; } avatar.selectedWalkBlend.lastDirection = FORWARDS; @@ -165,7 +165,7 @@ function selectAnimation() { case BACKWARDS: if (avatar.selectedWalkBlend.lastDirection !== BACKWARDS) { - animationOperations.deepCopy(avatar.selectedWalkBackwards, avatar.selectedWalkBlend); + animationOperations.deepCopy(avatar.selectedWalkBackwards, avatar.selectedWalkBlend); avatar.calibration.strideLength = avatar.selectedWalkBackwards.calibration.strideLength; } avatar.selectedWalkBlend.lastDirection = BACKWARDS; @@ -182,7 +182,7 @@ function selectAnimation() { avatar.selectedWalkBlend.lastDirection = RIGHT; avatar.calibration.strideLength = avatar.selectedSideStepRight.calibration.strideLength; break; - + default: // condition occurs when the avi goes through the floor due to collision hull errors animationOperations.deepCopy(avatar.selectedWalk, avatar.selectedWalkBlend); @@ -190,7 +190,7 @@ function selectAnimation() { avatar.calibration.strideLength = avatar.selectedWalk.calibration.strideLength; break; } - + if (!isAlreadyWalking && !motion.isComingToHalt) { setTransition(avatar.selectedWalkBlend, playTransitionReachPoses); } @@ -206,18 +206,18 @@ function selectAnimation() { var velocityMagnitude = Vec3.length(motion.velocity); var verticalProportion = motion.velocity.y / velocityMagnitude; var thrustProportion = motion.velocity.z / velocityMagnitude / 2; - + // directional components var upComponent = motion.velocity.y > 0 ? verticalProportion : 0; var downComponent = motion.velocity.y < 0 ? -verticalProportion : 0; var forwardComponent = motion.velocity.z < 0 ? -thrustProportion : 0; var backwardComponent = motion.velocity.z > 0 ? thrustProportion : 0; - + // smooth / damp directional components to add visual 'weight' upComponent = flyUpFilter.process(upComponent); downComponent = flyDownFilter.process(downComponent); forwardComponent = flyForwardFilter.process(forwardComponent); - backwardComponent = flyBackwardFilter.process(backwardComponent); + backwardComponent = flyBackwardFilter.process(backwardComponent); // normalise directional components var normaliser = upComponent + downComponent + forwardComponent + backwardComponent; @@ -225,7 +225,7 @@ function selectAnimation() { downComponent = downComponent / normaliser; forwardComponent = forwardComponent / normaliser; backwardComponent = backwardComponent / normaliser; - + // blend animations proportionally if (upComponent > 0) { animationOperations.blendAnimation(avatar.selectedFlyUp, @@ -274,7 +274,7 @@ function determineStride() { wheelAdvance = filter.radToDeg(motion.deltaTime * ftWheelAngularVelocity); } else { // turn the frequency time wheel by the amount specified for this animation - wheelAdvance = filter.radToDeg(avatar.currentAnimation.calibration.frequency * motion.deltaTime); + wheelAdvance = filter.radToDeg(avatar.currentAnimation.calibration.frequency * motion.deltaTime); } if (motion.currentTransition !== nullTransition) { @@ -291,20 +291,20 @@ function determineStride() { } motion.currentTransition.advancePreviousFrequencyTimeWheel(motion.deltaTime); } - + // avoid unnaturally fast walking when landing at speed - simulates skimming / skidding if (Math.abs(wheelAdvance) > MAX_FT_WHEEL_INCREMENT) { wheelAdvance = 0; } - + // advance the walk wheel the appropriate amount motion.advanceFrequencyTimeWheel(wheelAdvance); // walking? then see if it's a good time to measure the stride length (needs to be at least 97% of max walking speed) const ALMOST_ONE = 0.97; - if (avatar.currentAnimation === avatar.selectedWalkBlend && + if (avatar.currentAnimation === avatar.selectedWalkBlend && (Vec3.length(motion.velocity) / MAX_WALK_SPEED > ALMOST_ONE)) { - + var strideMaxAt = avatar.currentAnimation.calibration.strideMaxAt; const TOLERANCE = 1.0; @@ -348,7 +348,7 @@ function updateTransitions() { // helper function for renderMotion(). calculate the amount to lean forwards (or backwards) based on the avi's velocity function getLeanPitch() { var leanProgress = 0; - + if (motion.direction === DOWN || motion.direction === FORWARDS || motion.direction === BACKWARDS) { @@ -364,7 +364,7 @@ function getLeanRoll() { var leanRollProgress = 0; var linearContribution = 0; const LOG_SCALER = 8; - + if (Vec3.length(motion.velocity) > 0) { linearContribution = (Math.log(Vec3.length(motion.velocity) / TOP_SPEED) + LOG_SCALER) / LOG_SCALER; } @@ -375,7 +375,7 @@ function getLeanRoll() { leanRollProgress = filter.bezier(leanRollProgress, {x: 1, y: 0}, {x: 1, y: 0}); // which way to lean? var turnSign = (motion.yawDelta >= 0) ? 1 : -1; - + if (motion.direction === BACKWARDS || motion.direction === LEFT) { turnSign *= -1; @@ -409,7 +409,7 @@ function renderMotion() { // factor any leaning into the hips offset hipsTranslations.z += avatar.calibration.hipsToFeet * Math.sin(filter.degToRad(leanPitch)); hipsTranslations.x += avatar.calibration.hipsToFeet * Math.sin(filter.degToRad(leanRoll)); - + // ensure skeleton offsets are within the 1m limit hipsTranslations.x = hipsTranslations.x > 1 ? 1 : hipsTranslations.x; hipsTranslations.x = hipsTranslations.x < -1 ? -1 : hipsTranslations.x; @@ -422,7 +422,7 @@ function renderMotion() { // play footfall sound? var producingFootstepSounds = (avatar.currentAnimation === avatar.selectedWalkBlend) && avatar.makesFootStepSounds; - + if (motion.currentTransition !== nullTransition && avatar.makesFootStepSounds) { if (motion.currentTransition.nextAnimation === avatar.selectedWalkBlend || motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) { @@ -433,8 +433,8 @@ function renderMotion() { const QUARTER_CYCLE = 90; const THREE_QUARTER_CYCLE = 270; var ftWheelPosition = motion.frequencyTimeWheelPos; - - if (motion.currentTransition !== nullTransition && + + if (motion.currentTransition !== nullTransition && motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) { ftWheelPosition = motion.currentTransition.lastFrequencyTimeWheelPos; } @@ -453,7 +453,7 @@ function renderMotion() { // ignore arms / head rotations if options are selected in the settings if (avatar.armsFree && (joint.IKChain === "LeftArm" || joint.IKChain === "RightArm")) { continue; - } + } if (avatar.headFree && joint.IKChain === "Head") { continue; } @@ -469,13 +469,13 @@ function renderMotion() { motion.frequencyTimeWheelPos, motion.direction); } - + // apply angular velocity and speed induced leaning if (jointName === "Hips") { jointRotations.x += leanPitch; jointRotations.z += leanRoll; } - + // apply rotations MyAvatar.setJointData(jointName, Quat.fromVec3Degrees(jointRotations)); }