diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72eab372cd..4f1153b4f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -The project embraces distributed development and if you'd like to help, we'll pay you -- find out more at Worklist.net. If you find a small bug and have a fix, pull requests are welcome. If you'd like to get paid for your work, make sure you report the bug via a job on Worklist.net. +The project embraces distributed development and if you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net). If you find a small bug and have a fix, pull requests are welcome. If you'd like to get paid for your work, make sure you report the bug via a job on Worklist.net. We're hiring! We're looking for skilled developers; send your resume to hiring@highfidelity.io @@ -16,7 +16,7 @@ Contributing git checkout -b new_branch_name ``` 4. Code - * Follow the [coding standard](https://github.com/highfidelity/hifi/wiki/Coding-Standard) + * Follow the [coding standard](http://docs.highfidelity.io/v1.0/docs/coding-standard) 5. Commit * Use [well formed commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 6. Update your branch @@ -38,11 +38,11 @@ Contributing Reporting Bugs === -1. Always update to the latest code on master, it is possible the bug has already been fixed! -2. Search the [repository issues](https://github.com/highfidelity/hifi/issues) to make sure that somebody has not already reported the same bug. -3. Open an [issue on GitHub](https://github.com/highfidelity/hifi/issues) including information about your system and how to reproduce the bug. +1. Always update to the latest code on master, we make many merges every day and it is possible the bug has already been fixed! +2. Search jobs [on Worklist](https://worklist.net) to make sure that somebody has not already reported the same bug. +3. Add a [job on Worklist](https://worklist.net/job/add) including information about your system and how to reproduce the bug. Requesting a feature === -1. Search the [repository issues](https://github.com/highfidelity/hifi/issues) to make sure that somebody has not already requested the same feature. If you find a matching request, feel free to add any additional comments to the existing issue. -2. Submit an [issue on GitHub](https://github.com/highfidelity/hifi/issues) that is tagged as a feature. \ No newline at end of file +1. Search the [the Worklist](https://worklist.net) to make sure that somebody has not already requested the same feature. If you find a matching request, feel free to add any additional comments to the existing issue. +2. Add a [job on Worklist](https://worklist.net/job/add) that is labeled as a Feature (and select any other appropriate Labels) and includes a detailed description of your request. diff --git a/README.md b/README.md index d5fa8de1b4..90f1ca0dc7 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ lab experimenting with Virtual Worlds and VR. In this repository you'll find the source to many of the components in our alpha-stage virtual world. The project embraces distributed development -and if you'd like to help, we'll pay you -- find out more at Worklist.net. +and if you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net). If you find a small bug and have a fix, pull requests are welcome. If you'd like to get paid for your work, make sure you report the bug via a job on -Worklist.net. +[Worklist.net](https://worklist.net). We're hiring! We're looking for skilled developers; send your resume to hiring@highfidelity.io @@ -14,6 +14,10 @@ send your resume to hiring@highfidelity.io ##### Chat with us Come chat with us in [our Gitter](http://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi! +Documentation +========= +Documentation is available at [docs.highfidelity.io](http://docs.highfidelity.io), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project). + Build Instructions ========= All information required to build is found in the [build guide](BUILD.md). diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ea49b90852..140742ce88 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -206,7 +206,7 @@ void Agent::run() { scriptURL = QUrl(_payload); } - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply *reply = networkAccessManager.get(QNetworkRequest(scriptURL)); QNetworkDiskCache* cache = new QNetworkDiskCache(); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 24b6127d63..b30cd355d1 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -59,6 +59,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : const QString ASSIGNMENT_POOL_OPTION = "pool"; const QString ASSIGNMENT_WALLET_DESTINATION_ID_OPTION = "wallet"; const QString CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION = "a"; + const QString CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION = "p"; Assignment::Type requestAssignmentType = Assignment::AllTypes; @@ -87,17 +88,29 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : // create a NodeList as an unassigned client NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned); + + unsigned short assignmentServerPort = DEFAULT_DOMAIN_SERVER_PORT; + + // check for an overriden assignment server port + if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION)) { + assignmentServerPort = + argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt(); + } + + HifiSockAddr assignmentServerSocket(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, assignmentServerPort); // check for an overriden assignment server hostname if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION)) { _assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString(); - // set the custom assignment socket on our NodeList - HifiSockAddr customAssignmentSocket = HifiSockAddr(_assignmentServerHostname, DEFAULT_DOMAIN_SERVER_PORT); - - nodeList->setAssignmentServerSocket(customAssignmentSocket); + // change the hostname for our assignment server + assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerSocket.getPort()); } + + nodeList->setAssignmentServerSocket(assignmentServerSocket); + qDebug() << "Assignment server socket is" << assignmentServerSocket; + // call a timer function every ASSIGNMENT_REQUEST_INTERVAL_MSECS to ask for assignment, if required qDebug() << "Waiting for assignment -" << _requestAssignment; diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 681cc58cb3..37d89f3790 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -364,7 +364,6 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData* l const float ZERO_DB = 1.0f; const float NEGATIVE_ONE_DB = 0.891f; const float NEGATIVE_THREE_DB = 0.708f; - const float NEGATIVE_SIX_DB = 0.501f; const float FILTER_GAIN_AT_0 = ZERO_DB; // source is in front const float FILTER_GAIN_AT_90 = NEGATIVE_ONE_DB; // source is incident to left or right ear diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 5833d8a764..42af8ff1b9 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -940,7 +940,7 @@ void OctreeServer::run() { qDebug("--statusHost=%s", statusHost); _statusHost = statusHost; } else { - _statusHost = QHostAddress(getHostOrderLocalAddress()).toString(); + _statusHost = getLocalAddress().toString(); } qDebug("statusHost=%s", qPrintable(_statusHost)); diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 2fbe33a4e1..568931e074 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -33,6 +33,14 @@ "label": "None: use the network information I have entered for this domain at data.highfidelity.io" } ] + }, + { + "name": "local_port", + "label": "Local UDP Port", + "help": "This is the local port your domain-server binds to for UDP connections.
Depending on your router, this may need to be changed to run multiple full automatic networking domain-servers in the same network.", + "default": "40102", + "type": "int", + "advanced": true } ] }, diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index 48b3b15e8f..fb6901b34d 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -46,6 +46,11 @@ var viewHelpers = { form_group += "" } else { + + if (input_type == 'integer') { + input_type = "text" + } + form_group += "" @@ -292,7 +297,7 @@ function chooseFromHighFidelityDomains(clickedButton) { modal_buttons["success"] = { label: 'Create new domain', callback: function() { - window.open("https://data.highfidelity.io/domains", '_blank'); + window.open("https://data.highfidelity.io/user/domains", '_blank'); } } modal_body = "

You do not have any domains in your High Fidelity account." + diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 61310cad75..fa7a0fe012 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -57,6 +57,10 @@ DomainServer::DomainServer(int argc, char* argv[]) : setApplicationName("domain-server"); QSettings::setDefaultFormat(QSettings::IniFormat); + // make sure we have a fresh AccountManager instance + // (need this since domain-server can restart itself and maintain static variables) + AccountManager::getInstance(true); + _settingsManager.setupConfigMap(arguments()); installNativeEventFilter(&_shutdownEventListener); @@ -82,10 +86,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : void DomainServer::restart() { qDebug() << "domain-server is restarting."; - // make sure all static instances are reset - LimitedNodeList::getInstance()->reset(); - AccountManager::getInstance(true); - exit(DomainServer::EXIT_CODE_REBOOT); } @@ -189,17 +189,20 @@ bool DomainServer::optionallySetupOAuth() { const QString DOMAIN_CONFIG_ID_KEY = "id"; +const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking"; +const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; +const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; +const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; + void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { - const QString CUSTOM_PORT_OPTION = "port"; - unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT; + const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; + + QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); + unsigned short domainServerPort = (unsigned short) localPortValue.toUInt(); QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - if (settingsMap.contains(CUSTOM_PORT_OPTION)) { - domainServerPort = (unsigned short) settingsMap.value(CUSTOM_PORT_OPTION).toUInt(); - } - unsigned short domainServerDTLSPort = 0; if (_isUsingDTLS) { @@ -310,12 +313,7 @@ bool DomainServer::optionallySetupAssignmentPayment() { return true; } -const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; -const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; -const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; - void DomainServer::setupAutomaticNetworking() { - const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking"; if (!didSetupAccountManagerWithAccessToken()) { qDebug() << "Cannot setup domain-server automatic networking without an access token."; @@ -357,7 +355,8 @@ void DomainServer::setupAutomaticNetworking() { connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates); iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); - // call our sendHeartbeaToIceServer immediately anytime a public address changes + // call our sendHeartbeaToIceServer immediately anytime a local or public socket changes + connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer); connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer); // tell the data server which type of automatic networking we are using diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 28b1151f2d..7c92f1cc51 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -254,11 +254,14 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ QString settingType = groupObject[SETTING_DESCRIPTION_TYPE_KEY].toString(); const QString INPUT_DOUBLE_TYPE = "double"; + const QString INPUT_INTEGER_TYPE = "int"; // make sure the resulting json value has the right type if (settingType == INPUT_DOUBLE_TYPE) { settingsVariant[key] = rootValue.toString().toDouble(); + } else if (settingType == INPUT_INTEGER_TYPE) { + settingsVariant[key] = rootValue.toString().toInt(); } else { settingsVariant[key] = rootValue.toString(); } diff --git a/examples/editModels.js b/examples/editModels.js index 9cdba8098f..2741bdccdd 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1215,19 +1215,44 @@ var toolBar = (function () { Overlays.editOverlay(loadFileMenuItem, { visible: active }); } + var RESIZE_INTERVAL = 50; + var RESIZE_TIMEOUT = 20000; + var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL; function addModel(url) { var position; position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); if (position.x > 0 && position.y > 0 && position.z > 0) { - Entities.addEntity({ + var entityId = Entities.addEntity({ type: "Model", position: position, dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, modelURL: url }); print("Model added: " + url); + + var checkCount = 0; + function resize() { + var entityProperties = Entities.getEntityProperties(entityId); + var naturalDimensions = entityProperties.naturalDimensions; + + checkCount++; + + if (naturalDimensions.x == 0 && naturalDimensions.y == 0 && naturalDimensions.z == 0) { + if (checkCount < RESIZE_MAX_CHECKS) { + Script.setTimeout(resize, RESIZE_INTERVAL); + } else { + print("Resize failed: timed out waiting for model (" + url + ") to load"); + } + } else { + entityProperties.dimensions = naturalDimensions; + Entities.editEntity(entityId, entityProperties); + } + } + + Script.setTimeout(resize, RESIZE_INTERVAL); + } else { print("Can't add model: Model would be out of bounds."); } diff --git a/examples/leapHands.js b/examples/leapHands.js index 1095a9f4dc..222c0e4cf1 100644 --- a/examples/leapHands.js +++ b/examples/leapHands.js @@ -30,8 +30,6 @@ var leapHands = (function () { CALIBRATING = 1, CALIBRATED = 2, CALIBRATION_TIME = 1000, // milliseconds - PI = 3.141593, - isWindows, avatarScale, avatarFaceModelURL, avatarSkeletonModelURL, @@ -132,9 +130,6 @@ var leapHands = (function () { if (hands[0].controller.isActive() && hands[1].controller.isActive()) { leapHandHeight = (hands[0].controller.getAbsTranslation().y + hands[1].controller.getAbsTranslation().y) / 2.0; - - // TODO: Temporary detection of Windows to work around Leap Controller problem. - isWindows = (hands[1].controller.getAbsRotation().z > (0.25 * PI)); } else { calibrationStatus = UNCALIBRATED; return; @@ -318,11 +313,7 @@ var leapHands = (function () { j, side, handOffset, - handRoll, - handPitch, - handYaw, handRotation, - wristAbsRotation, locRotation, cameraOrientation, inverseAvatarOrientation; @@ -361,20 +352,22 @@ var leapHands = (function () { handOffset.x = -handOffset.x; // Hand rotation in camera coordinates ... - // TODO: 2.0* scale factors should not be necessary; Leap Motion controller code needs investigating. - handRoll = 2.0 * -hands[h].controller.getAbsRotation().z; - wristAbsRotation = wrists[h].controller.getAbsRotation(); - handPitch = 2.0 * wristAbsRotation.x - PI / 2.0; - handYaw = 2.0 * -wristAbsRotation.y; - // TODO: Roll values only work if hand is upside down; Leap Motion controller code needs investigating. - handRoll = PI + handRoll; + handRotation = wrists[h].controller.getAbsRotation(); + handRotation = { + x: handRotation.z, + y: handRotation.y, + z: handRotation.x, + w: handRotation.w + }; if (h === 0) { - handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }), - Quat.fromVec3Radians({ x: handRoll, y: handYaw, z: -handPitch })); + handRotation.x = -handRotation.x; + handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 1, y: 0, z: 0 }), handRotation); + handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 0, z: 1 }), handRotation); } else { - handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }), - Quat.fromVec3Radians({ x: -handRoll, y: handYaw, z: handPitch })); + handRotation.z = -handRotation.z; + handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 1, y: 0, z: 0 }), handRotation); + handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 0, z: 1 }), handRotation); } // Hand rotation in avatar coordinates ... @@ -392,25 +385,22 @@ var leapHands = (function () { z: hands[h].zeroPosition.z - handOffset.z }; - // TODO: 2.0* scale factors should not be necessary; Leap Motion controller code needs investigating. - handRoll = 2.0 * -hands[h].controller.getAbsRotation().z; - wristAbsRotation = wrists[h].controller.getAbsRotation(); - handPitch = 2.0 * -wristAbsRotation.x; - handYaw = 2.0 * wristAbsRotation.y; + handRotation = wrists[h].controller.getAbsRotation(); + handRotation = { + x: handRotation.z, + y: handRotation.y, + z: handRotation.x, + w: handRotation.w + }; - // TODO: Leap Motion controller's right-hand roll calculation only works if physical hand is upside down. - // Approximate fix is to add a fudge factor. - if (h === 1 && isWindows) { - handRoll = handRoll + 0.6 * PI; - } - - // Hand position and orientation ... if (h === 0) { + handRotation.x = -handRotation.x; handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }), - Quat.fromVec3Radians({ x: handRoll, y: handYaw, z: -handPitch })); + handRotation); } else { + handRotation.z = -handRotation.z; handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }), - Quat.fromVec3Radians({ x: -handRoll, y: handYaw, z: handPitch })); + handRotation); } } diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index e07fb752e5..d9cf2c54fd 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -169,7 +169,14 @@ SelectionDisplay = (function () { alpha: 0.2, solid: true, visible: false, - rotation: yawOverlayRotation + rotation: yawOverlayRotation, + hasTickMarks: true, + majorTickMarksAngle: 12.5, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, }); var rotateOverlayOuter = Overlays.addOverlay("circle3d", { @@ -179,7 +186,15 @@ SelectionDisplay = (function () { alpha: 0.2, solid: true, visible: false, - rotation: yawOverlayRotation + rotation: yawOverlayRotation, + + hasTickMarks: true, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + majorTickMarksColor: { red: 0, green: 0, blue: 0 }, + minorTickMarksColor: { red: 0, green: 0, blue: 0 }, }); var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { @@ -571,7 +586,7 @@ SelectionDisplay = (function () { innerRadius: 0.9, startAt: 0, endAt: 360, - alpha: outerAlpha + alpha: outerAlpha, }); Overlays.editOverlay(rotateOverlayCurrent, @@ -584,7 +599,7 @@ SelectionDisplay = (function () { size: outerRadius, startAt: 0, endAt: 0, - innerRadius: 0.9 + innerRadius: 0.9, }); // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden diff --git a/examples/libraries/globals.js b/examples/libraries/globals.js index 211e0d56b6..1bd851af77 100644 --- a/examples/libraries/globals.js +++ b/examples/libraries/globals.js @@ -8,4 +8,4 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var HIFI_PUBLIC_BUCKET = "https://s3.amazonaws.com/hifi-public/"; \ No newline at end of file +HIFI_PUBLIC_BUCKET = "https://s3.amazonaws.com/hifi-public/"; diff --git a/examples/overlaysExample.js b/examples/overlaysExample.js index 2161b1c903..fef502c761 100644 --- a/examples/overlaysExample.js +++ b/examples/overlaysExample.js @@ -64,8 +64,8 @@ var text = Overlays.addOverlay("text", { y: 100, width: 150, height: 50, - color: { red: 0, green: 0, blue: 0}, - textColor: { red: 255, green: 0, blue: 0}, + backgroundColor: { red: 255, green: 255, blue: 255}, + color: { red: 255, green: 0, blue: 0}, topMargin: 4, leftMargin: 4, text: "Here is some text.\nAnd a second line." diff --git a/examples/walk.js b/examples/walk.js new file mode 100644 index 0000000000..a9e8f401d6 --- /dev/null +++ b/examples/walk.js @@ -0,0 +1,3851 @@ +// +// walk.js +// +// version 1.007b +// +// Created by Davedub, August / September 2014 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// path to the animation files +var pathToAnimFiles = 'http://s3-us-west-1.amazonaws.com/highfidelity-public/procedural-animator/animation-files/'; // working (but only without https) + +// path to the images used for the overlays +var pathToOverlays = 'http://s3-us-west-1.amazonaws.com/highfidelity-public/procedural-animator/overlays/'; // working (but only without https) + +// path to the sounds used for the footsteps +var pathToSounds = 'http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Footsteps/'; + +// load the animation datafiles +Script.include(pathToAnimFiles+"dd-female-strut-walk-animation.js"); +Script.include(pathToAnimFiles+"dd-female-flying-up-animation.js"); +Script.include(pathToAnimFiles+"dd-female-flying-animation.js"); +Script.include(pathToAnimFiles+"dd-female-flying-down-animation.js"); +Script.include(pathToAnimFiles+"dd-female-standing-one-animation.js"); +Script.include(pathToAnimFiles+"dd-female-sidestep-left-animation.js"); +Script.include(pathToAnimFiles+"dd-female-sidestep-right-animation.js"); +Script.include(pathToAnimFiles+"dd-male-strut-walk-animation.js"); +Script.include(pathToAnimFiles+"dd-male-flying-up-animation.js"); +Script.include(pathToAnimFiles+"dd-male-flying-animation.js"); +Script.include(pathToAnimFiles+"dd-male-flying-down-animation.js"); +Script.include(pathToAnimFiles+"dd-male-standing-one-animation.js"); +Script.include(pathToAnimFiles+"dd-male-sidestep-left-animation.js"); +Script.include(pathToAnimFiles+"dd-male-sidestep-right-animation.js"); + +// read in the data from the animation files +var FemaleStrutWalkFile = new FemaleStrutWalk(); +var femaleStrutWalk = FemaleStrutWalkFile.loadAnimation(); +var FemaleFlyingUpFile = new FemaleFlyingUp(); +var femaleFlyingUp = FemaleFlyingUpFile.loadAnimation(); +var FemaleFlyingFile = new FemaleFlying(); +var femaleFlying = FemaleFlyingFile.loadAnimation(); +var FemaleFlyingDownFile = new FemaleFlyingDown(); +var femaleFlyingDown = FemaleFlyingDownFile.loadAnimation(); +var FemaleStandOneFile = new FemaleStandingOne(); +var femaleStandOne = FemaleStandOneFile.loadAnimation(); +var FemaleSideStepLeftFile = new FemaleSideStepLeft(); +var femaleSideStepLeft = FemaleSideStepLeftFile.loadAnimation(); +var FemaleSideStepRightFile = new FemaleSideStepRight(); +var femaleSideStepRight = FemaleSideStepRightFile.loadAnimation(); +var MaleStrutWalkFile = new MaleStrutWalk(); +var maleStrutWalk = MaleStrutWalkFile.loadAnimation(); +var MaleFlyingUpFile = new MaleFlyingUp(); +var maleFlyingUp = MaleFlyingUpFile.loadAnimation(); +var MaleFlyingFile = new MaleFlying(); +var maleFlying = MaleFlyingFile.loadAnimation(); +var MaleFlyingDownFile = new MaleFlyingDown(); +var maleFlyingDown = MaleFlyingDownFile.loadAnimation(); +var MaleStandOneFile = new MaleStandingOne(); +var maleStandOne = MaleStandOneFile.loadAnimation(); +var MaleSideStepLeftFile = new MaleSideStepLeft(); +var maleSideStepLeft = MaleSideStepLeftFile.loadAnimation(); +var MaleSideStepRightFile = new MaleSideStepRight(); +var maleSideStepRight = MaleSideStepRightFile.loadAnimation(); + +// read in the sounds +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")); + +// all slider controls have a range (with the exception of phase controls that are always +-180) so we store them all here +var sliderRanges = {"joints":[{"name":"hips","pitchRange":25,"yawRange":25,"rollRange":25,"pitchOffsetRange":25,"yawOffsetRange":25,"rollOffsetRange":25,"thrustRange":0.01,"bobRange":0.02,"swayRange":0.01},{"name":"upperLegs","pitchRange":90,"yawRange":35,"rollRange":35,"pitchOffsetRange":60,"yawOffsetRange":20,"rollOffsetRange":20},{"name":"lowerLegs","pitchRange":90,"yawRange":20,"rollRange":20,"pitchOffsetRange":90,"yawOffsetRange":20,"rollOffsetRange":20},{"name":"feet","pitchRange":60,"yawRange":20,"rollRange":20,"pitchOffsetRange":60,"yawOffsetRange":50,"rollOffsetRange":50},{"name":"toes","pitchRange":90,"yawRange":20,"rollRange":20,"pitchOffsetRange":90,"yawOffsetRange":20,"rollOffsetRange":20},{"name":"spine","pitchRange":40,"yawRange":40,"rollRange":40,"pitchOffsetRange":90,"yawOffsetRange":50,"rollOffsetRange":50},{"name":"spine1","pitchRange":20,"yawRange":40,"rollRange":20,"pitchOffsetRange":90,"yawOffsetRange":50,"rollOffsetRange":50},{"name":"spine2","pitchRange":20,"yawRange":40,"rollRange":20,"pitchOffsetRange":90,"yawOffsetRange":50,"rollOffsetRange":50},{"name":"shoulders","pitchRange":35,"yawRange":40,"rollRange":20,"pitchOffsetRange":180,"yawOffsetRange":180,"rollOffsetRange":180},{"name":"upperArms","pitchRange":90,"yawRange":90,"rollRange":90,"pitchOffsetRange":180,"yawOffsetRange":180,"rollOffsetRange":180},{"name":"lowerArms","pitchRange":90,"yawRange":90,"rollRange":120,"pitchOffsetRange":180,"yawOffsetRange":180,"rollOffsetRange":180},{"name":"hands","pitchRange":90,"yawRange":180,"rollRange":90,"pitchOffsetRange":180,"yawOffsetRange":180,"rollOffsetRange":180},{"name":"head","pitchRange":20,"yawRange":20,"rollRange":20,"pitchOffsetRange":90,"yawOffsetRange":90,"rollOffsetRange":90}]}; + +// internal state (FSM based) constants +var STANDING = 1; +var WALKING = 2; +var SIDE_STEPPING = 3; +var FLYING = 4; +var CONFIG_WALK_STYLES = 5; +var CONFIG_WALK_TWEAKS = 6; +var CONFIG_WALK_JOINTS = 7; +var CONFIG_STANDING = 8; +var CONFIG_FLYING = 9; +var CONFIG_FLYING_UP = 10; +var CONFIG_FLYING_DOWN = 11; +var CONFIG_SIDESTEP_LEFT = 12; +var CONFIG_SIDESTEP_RIGHT = 14; +var INTERNAL_STATE = STANDING; + +// status +var powerOn = true; +var paused = false; // pause animation playback whilst adjusting certain parameters +var minimised = true; +var armsFree = true; // set true for hydra support - temporary fix for Hydras +var statsOn = false; +var playFootStepSounds = true; + +// constants +var MAX_WALK_SPEED = 1257; // max oscillation speed +var FLYING_SPEED = 6.4;// 12.4; // m/s - real humans can't run any faster than 12.4 m/s +var TERMINAL_VELOCITY = 300; // max speed imposed by Interface +var DIRECTION_UP = 1; +var DIRECTION_DOWN = 2; +var DIRECTION_LEFT = 4; +var DIRECTION_RIGHT = 8; +var DIRECTION_FORWARDS = 16; +var DIRECTION_BACKWARDS = 32; +var DIRECTION_NONE = 64; +var MALE = 1; +var FEMALE = 2; + +// start of animation control section +var cumulativeTime = 0.0; +var lastOrientation; + +// avi gender and default animations +var avatarGender = MALE; +var selectedWalk = maleStrutWalk; // the currently selected animation walk file (to edit any animation, paste it's name here and select walk editing mode) +var selectedStand = maleStandOne; +var selectedFlyUp = maleFlyingUp; +var selectedFly = maleFlying; +var selectedFlyDown = maleFlyingDown; +var selectedSideStepLeft = maleSideStepLeft; +var selectedSideStepRight = maleSideStepRight; +if(avatarGender===FEMALE) { + + // to make toggling the default quick + selectedWalk = femaleStrutWalk; + selectedStand = femaleStandOne; + selectedFlyUp = femaleFlyingUp; + selectedFly = femaleFlying; + selectedFlyDown = femaleFlyingDown; + selectedSideStepLeft = femaleSideStepLeft; + selectedSideStepRight = femaleSideStepRight; +} +var currentAnimation = selectedStand; // the current animation +var selectedJointIndex = 0; // the index of the joint currently selected for editing +var currentTransition = null; // used as a pointer to a Transition + +// walkwheel (foot / ground speed matching) +var sideStepCycleStartLeft = 270; +var sideStepCycleStartRight = 90; +var walkWheelPosition = 0; +var nextStep = DIRECTION_RIGHT; // first step is always right, because the sine waves say so. Unless you're mirrored. +var nFrames = 0; // counts number of frames +var strideLength = 0; // stride calibration +var aviFootSize = {x:0.1, y:0.1, z:0.25}; // experimental values for addition to stride length - TODO: analyse and confirm is increasing smaller stride lengths accuracy once we have better ground detection + +// stats +var frameStartTime = 0; // when the frame first starts we take a note of the time +var frameExecutionTimeMax = 0; // keep track of the longest frame execution time + +// constructor for recent RecentMotion (i.e. frame data) class +function RecentMotion(velocity, acceleration, principleDirection, state) { + this.velocity = velocity; + this.acceleration = acceleration; + this.principleDirection = principleDirection; + this.state = state; +} + +// constructor for the FramesHistory object +function FramesHistory() { + this.recentMotions = []; + for(var i = 0 ; i < 10 ; i++) { + var blank = new RecentMotion({ x:0, y:0, z:0 }, { x:0, y:0, z:0 }, DIRECTION_FORWARDS, STANDING ); + this.recentMotions.push(blank); + } + + // recentDirection 'method' args: (direction enum, number of steps back to compare) + this.recentDirection = function () { + + if( arguments[0] && arguments[1] ) { + + var directionCount = 0; + if( arguments[1] > this.recentMotions.length ) + arguments[1] = this.recentMotions.length; + + for(var i = 0 ; i < arguments[1] ; i++ ) { + if( this.recentMotions[i].principleDirection === arguments[0] ) + directionCount++; + } + return directionCount / arguments[1] === 1 ? true : false; + } + return false; + } + + // recentState 'method' args: (state enum, number of steps back to compare) + this.recentState = function () { + + if( arguments[0] && arguments[1] ) { + + var stateCount = 0; + if( arguments[1] > this.recentMotions.length ) + arguments[1] = this.recentMotions.length; + + for(var i = 0 ; i < arguments[1] ; i++ ) { + if( this.recentMotions[i].state === arguments[0] ) + stateCount++; + } + return stateCount / arguments[1] === 1 ? true : false; + } + return false; + } + this.lastWalkStartTime = 0; // short walks and long walks need different handling +} +var framesHistory = new FramesHistory(); + +// constructor for animation Transition class +function Transition(lastAnimation, nextAnimation, reachPoses, transitionDuration, easingLower, easingUpper) { + this.lastAnimation = lastAnimation; // name of last animation + if(lastAnimation === selectedWalk || + nextAnimation === selectedSideStepLeft || + nextAnimation === selectedSideStepRight) + this.walkingAtStart = true; // boolean - is the last animation a walking animation? + else + this.walkingAtStart = false; // boolean - is the last animation a walking animation? + this.nextAnimation = nextAnimation; // name of next animation + if(nextAnimation === selectedWalk || + nextAnimation === selectedSideStepLeft || + nextAnimation === selectedSideStepRight) + this.walkingAtEnd = true; // boolean - is the next animation a walking animation? + else + this.walkingAtEnd = false; // boolean - is the next animation a walking animation? + this.reachPoses = reachPoses; // array of reach poses - am very much looking forward to putting these in! + this.transitionDuration = transitionDuration; // length of transition (seconds) + this.easingLower = easingLower; // Bezier curve handle (normalised) + this.easingUpper = easingUpper; // Bezier curve handle (normalised) + this.startTime = new Date().getTime(); // Starting timestamp (seconds) + this.progress = 0; // how far are we through the transition? + this.walkWheelIncrement = 3; // how much to turn the walkwheel each frame when coming to a halt. Get's set to 0 once the feet are under the avi + this.walkWheelAdvance = 0; // how many degrees the walk wheel has been advanced during the transition + this.walkStopAngle = 0; // what angle should we stop the walk cycle? (calculated on the fly) +} + +// convert a local (to the avi) translation to a global one +function localToGlobal(localTranslation) { + + var aviOrientation = MyAvatar.orientation; + var front = Quat.getFront(aviOrientation); + var right = Quat.getRight(aviOrientation); + var up = Quat.getUp (aviOrientation); + var aviFront = Vec3.multiply(front,localTranslation.z); + var aviRight = Vec3.multiply(right,localTranslation.x); + var aviUp = Vec3.multiply(up ,localTranslation.y); + var globalTranslation = {x:0,y:0,z:0}; // final value + globalTranslation = Vec3.sum(globalTranslation, aviFront); + globalTranslation = Vec3.sum(globalTranslation, aviRight); + globalTranslation = Vec3.sum(globalTranslation, aviUp); + return globalTranslation; +} + +// similar ot above - convert hips translations to global and apply +function translateHips(localHipsTranslation) { + + var aviOrientation = MyAvatar.orientation; + var front = Quat.getFront(aviOrientation); + var right = Quat.getRight(aviOrientation); + var up = Quat.getUp (aviOrientation); + var aviFront = Vec3.multiply(front,localHipsTranslation.y); + var aviRight = Vec3.multiply(right,localHipsTranslation.x); + var aviUp = Vec3.multiply(up ,localHipsTranslation.z); + var AviTranslationOffset = {x:0,y:0,z:0}; // final value + AviTranslationOffset = Vec3.sum(AviTranslationOffset, aviFront); + AviTranslationOffset = Vec3.sum(AviTranslationOffset, aviRight); + AviTranslationOffset = Vec3.sum(AviTranslationOffset, aviUp); + MyAvatar.position = {x: MyAvatar.position.x + AviTranslationOffset.x, + y: MyAvatar.position.y + AviTranslationOffset.y, + z: MyAvatar.position.z + AviTranslationOffset.z }; +} + +// clear all joint data +function resetJoints() { + + var avatarJointNames = MyAvatar.getJointNames(); + for (var i = 0; i < avatarJointNames.length; i++) { + MyAvatar.clearJointData(avatarJointNames[i]); + } +} + +// play footstep sound +function playFootstep(side) { + + var options = new AudioInjectionOptions(); + options.position = Camera.getPosition(); + options.volume = 0.5; + var walkNumber = 2; // 0 to 2 + if(side===DIRECTION_RIGHT && playFootStepSounds) { + Audio.playSound(footsteps[walkNumber+1], options); + } + else if(side===DIRECTION_LEFT && playFootStepSounds) { + Audio.playSound(footsteps[walkNumber], options); + } +} + +// put the fingers into a relaxed pose +function curlFingers() { + + // left hand fingers + for(var i = 18 ; i < 34 ; i++) { + MyAvatar.setJointData(jointList[i], Quat.fromPitchYawRollDegrees(8,0,0)); + } + // left hand thumb + for(var i = 34 ; i < 38 ; i++) { + MyAvatar.setJointData(jointList[i], Quat.fromPitchYawRollDegrees(0,0,0)); + } + // right hand fingers + for(var i = 42 ; i < 58 ; i++) { + MyAvatar.setJointData(jointList[i], Quat.fromPitchYawRollDegrees(8,0,0)); + } + // right hand thumb + for(var i = 58 ; i < 62 ; i++) { + MyAvatar.setJointData(jointList[i], Quat.fromPitchYawRollDegrees(0,0,0)); + } +} + +// additional maths functions +function degToRad( degreesValue ) { return degreesValue * Math.PI / 180; } +function radToDeg( radiansValue ) { return radiansValue * 180 / Math.PI; } + +// animateAvatar - sine wave generators working like clockwork +function animateAvatar( deltaTime, velocity, principleDirection ) { + + // adjusting the walk speed in edit mode causes a nasty flicker. pausing the animation stops this + if(paused) return; + + var cycle = cumulativeTime; + var transitionProgress = 1; + var adjustedFrequency = currentAnimation.settings.baseFrequency; + + // upper legs phase reversal for walking backwards + var forwardModifier = 1; + if(principleDirection===DIRECTION_BACKWARDS) + forwardModifier = -1; + + // don't want to lean forwards if going directly upwards + var leanPitchModifier = 1; + if(principleDirection===DIRECTION_UP) + leanPitchModifier = 0; + + // is there a Transition to include? + if(currentTransition!==null) { + + // if is a new transiton + if(currentTransition.progress===0) { + + if( currentTransition.walkingAtStart ) { + + if( INTERNAL_STATE !== SIDE_STEPPING ) { + + // work out where we want the walk cycle to stop + var leftStop = selectedWalk.settings.stopAngleForwards + 180; + var rightStop = selectedWalk.settings.stopAngleForwards; + if( principleDirection === DIRECTION_BACKWARDS ) { + leftStop = selectedWalk.settings.stopAngleBackwards + 180; + rightStop = selectedWalk.settings.stopAngleBackwards; + } + + // find the closest stop point from the walk wheel's angle + var angleToLeftStop = 180 - Math.abs( Math.abs( walkWheelPosition - leftStop ) - 180); + var angleToRightStop = 180 - Math.abs( Math.abs( walkWheelPosition - rightStop ) - 180); + if( walkWheelPosition > angleToLeftStop ) angleToLeftStop = 360 - angleToLeftStop; + if( walkWheelPosition > angleToRightStop ) angleToRightStop = 360 - angleToRightStop; + + currentTransition.walkWheelIncrement = 6; + + // keep the walkwheel turning by setting the walkWheelIncrement until our feet are tucked nicely underneath us. + if( angleToLeftStop < angleToRightStop ) { + + currentTransition.walkStopAngle = leftStop; + + } else { + + currentTransition.walkStopAngle = rightStop; + } + + } else { + + // freeze wheel for sidestepping transitions + currentTransition.walkWheelIncrement = 0; + } + } + } // end if( currentTransition.walkingAtStart ) + + // calculate the Transition progress + var elapasedTime = (new Date().getTime() - currentTransition.startTime) / 1000; + currentTransition.progress = elapasedTime / currentTransition.transitionDuration; + transitionProgress = getBezier((1-currentTransition.progress), {x:0,y:0}, currentTransition.easingLower, currentTransition.easingUpper, {x:1,y:1}).y; + + if(currentTransition.progress>=1) { + + // time to kill off the transition + delete currentTransition; + currentTransition = null; + + } else { + + if( currentTransition.walkingAtStart ) { + + if( INTERNAL_STATE !== SIDE_STEPPING ) { + + // if at a stop angle, hold the walk wheel position for remainder of transition + var tolerance = 7; // must be greater than the walkWheel increment + if(( walkWheelPosition > (currentTransition.walkStopAngle - tolerance )) && + ( walkWheelPosition < (currentTransition.walkStopAngle + tolerance ))) { + + currentTransition.walkWheelIncrement = 0; + } + // keep turning walk wheel until both feet are below the avi + walkWheelPosition += currentTransition.walkWheelIncrement; + currentTransition.walkWheelAdvance += currentTransition.walkWheelIncrement; + } + } + } + } + + // will we need to use the walk wheel this frame? + if(currentAnimation === selectedWalk || + currentAnimation === selectedSideStepLeft || + currentAnimation === selectedSideStepRight || + currentTransition !== null) { + + // set the stride length + if( INTERNAL_STATE!==SIDE_STEPPING && + INTERNAL_STATE!==CONFIG_SIDESTEP_LEFT && + INTERNAL_STATE!==CONFIG_SIDESTEP_RIGHT && + currentTransition===null ) { + + // if the timing's right, take a snapshot of the stride max and recalibrate + var tolerance = 1.0; // higher the number, the higher the chance of a calibration taking place, but is traded off with lower accuracy + var strideOne = 40; + var strideTwo = 220; + + if( principleDirection === DIRECTION_BACKWARDS ) { + strideOne = 130; + strideTwo = 300; + } + + if(( walkWheelPosition < (strideOne+tolerance) && walkWheelPosition > (strideOne-tolerance) ) || + ( walkWheelPosition < (strideTwo+tolerance) && walkWheelPosition > (strideTwo-tolerance) )) { + + // calculate the feet's offset from each other (in local Z only) + var footRPos = localToGlobal(MyAvatar.getJointPosition("RightFoot")); + var footLPos = localToGlobal(MyAvatar.getJointPosition("LeftFoot")); + var footOffsetZ = Math.abs(footRPos.z - footLPos.z); + if(footOffsetZ>1) strideLength = 2 * footOffsetZ + aviFootSize.z; // sometimes getting very low value here - just ignore for now + if(statsOn) print('Stride length calibrated to '+strideLength.toFixed(4)+' metres at '+walkWheelPosition.toFixed(1)+' degrees'); + if(principleDirection===DIRECTION_FORWARDS) { + currentAnimation.calibration.strideLengthForwards = strideLength; + } else if (principleDirection===DIRECTION_BACKWARDS) { + currentAnimation.calibration.strideLengthBackwards = strideLength; + } + + } else { + + if(principleDirection===DIRECTION_FORWARDS) + strideLength = currentAnimation.calibration.strideLengthForwards; + else if (principleDirection===DIRECTION_BACKWARDS) + strideLength = currentAnimation.calibration.strideLengthBackwards; + } + } + else if(( INTERNAL_STATE===SIDE_STEPPING || + INTERNAL_STATE===CONFIG_SIDESTEP_LEFT || + INTERNAL_STATE===CONFIG_SIDESTEP_RIGHT ) ) { + + // calculate lateral stride - same same, but different + // if the timing's right, take a snapshot of the stride max and recalibrate the stride length + var tolerance = 1.0; // higher the number, the higher the chance of a calibration taking place, but is traded off with lower accuracy + if(principleDirection===DIRECTION_LEFT) { + + if( walkWheelPosition < (3+tolerance) && walkWheelPosition > (3-tolerance) ) { + + // calculate the feet's offset from the hips (in local X only) + var footRPos = localToGlobal(MyAvatar.getJointPosition("RightFoot")); + var footLPos = localToGlobal(MyAvatar.getJointPosition("LeftFoot")); + var footOffsetX = Math.abs(footRPos.x - footLPos.x); + strideLength = footOffsetX; + if(statsOn) print('Stride width calibrated to '+strideLength.toFixed(4)+ ' metres at '+walkWheelPosition.toFixed(1)+' degrees'); + currentAnimation.calibration.strideLengthLeft = strideLength; + + } else { + + strideLength = currentAnimation.calibration.strideLengthLeft; + } + } + else if (principleDirection===DIRECTION_RIGHT) { + + if( walkWheelPosition < (170+tolerance) && walkWheelPosition > (170-tolerance) ) { + + // calculate the feet's offset from the hips (in local X only) + var footRPos = localToGlobal(MyAvatar.getJointPosition("RightFoot")); + var footLPos = localToGlobal(MyAvatar.getJointPosition("LeftFoot")); + var footOffsetX = Math.abs(footRPos.x - footLPos.x); + strideLength = footOffsetX; + if(statsOn) print('Stride width calibrated to '+strideLength.toFixed(4)+ ' metres at '+walkWheelPosition.toFixed(1)+' degrees'); + currentAnimation.calibration.strideLengthRight = strideLength; + + } else { + + strideLength = currentAnimation.calibration.strideLengthRight; + } + } + } // end stride length calculations + + // wrap the stride length around a 'surveyor's wheel' twice and calculate the angular velocity at the given (linear) velocity + // omega = v / r , where r = circumference / 2 PI , where circumference = 2 * stride length + var wheelRadius = strideLength / Math.PI; + var angularVelocity = velocity / wheelRadius; + + // calculate the degrees turned (at this angular velocity) since last frame + var radiansTurnedSinceLastFrame = deltaTime * angularVelocity; + var degreesTurnedSinceLastFrame = radToDeg(radiansTurnedSinceLastFrame); + + // if we are in an edit mode, we will need fake time to turn the wheel + if( INTERNAL_STATE!==WALKING && + INTERNAL_STATE!==SIDE_STEPPING ) + degreesTurnedSinceLastFrame = currentAnimation.settings.baseFrequency / 70; + + if( walkWheelPosition >= 360 ) + walkWheelPosition = walkWheelPosition % 360; + + // advance the walk wheel the appropriate amount + if( currentTransition===null || currentTransition.walkingAtEnd ) + walkWheelPosition += degreesTurnedSinceLastFrame; + + // set the new values for the exact correct walk cycle speed at this velocity + adjustedFrequency = 1; + cycle = walkWheelPosition; + + // show stats and walk wheel? + if(statsOn) { + + var distanceTravelled = velocity * deltaTime; + var deltaTimeMS = deltaTime * 1000; + + if( INTERNAL_STATE===SIDE_STEPPING || + INTERNAL_STATE===CONFIG_SIDESTEP_LEFT || + INTERNAL_STATE===CONFIG_SIDESTEP_RIGHT ) { + + // draw the walk wheel turning around the z axis for sidestepping + var directionSign = 1; + if(principleDirection===DIRECTION_RIGHT) directionSign = -1; + var yOffset = hipsToFeetDistance - (wheelRadius/1.2); // /1.2 is a visual kludge, probably necessary because of either the 'avi feet penetrate floor' issue - TODO - once ground plane following is in Interface, lock this down + var sinWalkWheelPosition = wheelRadius * Math.sin(degToRad( directionSign * walkWheelPosition )); + var cosWalkWheelPosition = wheelRadius * Math.cos(degToRad( directionSign * -walkWheelPosition )); + var wheelXPos = {x: cosWalkWheelPosition, y:-sinWalkWheelPosition - yOffset, z: 0}; + var wheelXEnd = {x: -cosWalkWheelPosition, y:sinWalkWheelPosition - yOffset, z: 0}; + sinWalkWheelPosition = wheelRadius * Math.sin(degToRad( -directionSign * walkWheelPosition+90 )); + cosWalkWheelPosition = wheelRadius * Math.cos(degToRad( -directionSign * walkWheelPosition+90 )); + var wheelYPos = {x:cosWalkWheelPosition, y:sinWalkWheelPosition - yOffset, z: 0}; + var wheelYEnd = {x:-cosWalkWheelPosition, y:-sinWalkWheelPosition - yOffset, z: 0}; + Overlays.editOverlay(walkWheelYLine, {visible: true, position:wheelYPos, end:wheelYEnd}); + Overlays.editOverlay(walkWheelZLine, {visible: true, position:wheelXPos, end:wheelXEnd}); + + } else { + + // draw the walk wheel turning around the x axis for walking forwards or backwards + var yOffset = hipsToFeetDistance - (wheelRadius/1.2); // /1.2 is a visual kludge, probably necessary because of either the 'avi feet penetrate floor' issue - TODO - once ground plane following is in Interface, lock this down + var sinWalkWheelPosition = wheelRadius * Math.sin(degToRad((forwardModifier*-1) * walkWheelPosition)); + var cosWalkWheelPosition = wheelRadius * Math.cos(degToRad((forwardModifier*-1) * -walkWheelPosition)); + var wheelZPos = {x:0, y:-sinWalkWheelPosition - yOffset, z: cosWalkWheelPosition}; + var wheelZEnd = {x:0, y:sinWalkWheelPosition - yOffset, z: -cosWalkWheelPosition}; + sinWalkWheelPosition = wheelRadius * Math.sin(degToRad(forwardModifier * walkWheelPosition+90)); + cosWalkWheelPosition = wheelRadius * Math.cos(degToRad(forwardModifier * walkWheelPosition+90)); + var wheelYPos = {x:0, y:sinWalkWheelPosition - yOffset, z: cosWalkWheelPosition}; + var wheelYEnd = {x:0, y:-sinWalkWheelPosition - yOffset, z: -cosWalkWheelPosition}; + Overlays.editOverlay(walkWheelYLine, { visible: true, position:wheelYPos, end:wheelYEnd }); + Overlays.editOverlay(walkWheelZLine, { visible: true, position:wheelZPos, end:wheelZEnd }); + } + + // populate stats overlay + var walkWheelInfo = + ' Walk Wheel Stats\n--------------------------------------\n \n \n' + + '\nFrame time: '+deltaTimeMS.toFixed(2) + + ' mS\nVelocity: '+velocity.toFixed(2) + + ' m/s\nDistance: '+distanceTravelled.toFixed(3) + + ' m\nOmega: '+angularVelocity.toFixed(3) + + ' rad / s\nDeg to turn: '+degreesTurnedSinceLastFrame.toFixed(2) + + ' deg\nWheel position: '+cycle.toFixed(1) + + ' deg\nWheel radius: '+wheelRadius.toFixed(3) + + ' m\nHips To Feet: '+hipsToFeetDistance.toFixed(3) + + ' m\nStride: '+strideLength.toFixed(3) + + ' m\n'; + Overlays.editOverlay(walkWheelStats, {text: walkWheelInfo}); + } + } // end of walk wheel and stride length calculation + + + // Start applying motion + var pitchOscillation = 0; + var yawOscillation = 0; + var rollOscillation = 0; + var pitchOscillationLast = 0; + var yawOscillationLast = 0; + var rollOscillationLast = 0; + var pitchOffset = 0; + var yawOffset = 0; + var rollOffset = 0; + var pitchOffsetLast = 0; + var yawOffsetLast = 0; + var rollOffsetLast = 0; + + // calcualte any hips translation + // Note: can only apply hips translations whilst in a config (edit) mode at present, not whilst under locomotion + if( INTERNAL_STATE===CONFIG_WALK_STYLES || + INTERNAL_STATE===CONFIG_WALK_TWEAKS || + INTERNAL_STATE===CONFIG_WALK_JOINTS || + INTERNAL_STATE===CONFIG_SIDESTEP_LEFT || + INTERNAL_STATE===CONFIG_SIDESTEP_RIGHT || + INTERNAL_STATE===CONFIG_FLYING || + INTERNAL_STATE===CONFIG_FLYING_UP || + INTERNAL_STATE===CONFIG_FLYING_DOWN ) { + + // calculate hips translation + var motorOscillation = Math.sin(degToRad((cycle * adjustedFrequency * 2) + currentAnimation.joints[0].thrustPhase)) + currentAnimation.joints[0].thrustOffset; + var swayOscillation = Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[0].swayPhase)) + currentAnimation.joints[0].swayOffset; + var bobOscillation = Math.sin(degToRad((cycle * adjustedFrequency * 2) + currentAnimation.joints[0].bobPhase)) + currentAnimation.joints[0].bobOffset; + + // apply hips translation + translateHips({x:swayOscillation*currentAnimation.joints[0].sway, y:motorOscillation*currentAnimation.joints[0].thrust, z:bobOscillation*currentAnimation.joints[0].bob}); + } + + // hips rotation + // apply the current Transition? + if(currentTransition!==null) { + + if( currentTransition.walkingAtStart ) { + + pitchOscillation = currentAnimation.joints[0].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency * 2) + + currentAnimation.joints[0].pitchPhase)) + currentAnimation.joints[0].pitchOffset; + + yawOscillation = currentAnimation.joints[0].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[0].yawPhase)) + currentAnimation.joints[0].yawOffset; + + rollOscillation = (currentAnimation.joints[0].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[0].rollPhase)) + currentAnimation.joints[0].rollOffset); + + pitchOscillationLast = currentTransition.lastAnimation.joints[0].pitch * Math.sin(degToRad(( walkWheelPosition * 2) + + currentTransition.lastAnimation.joints[0].pitchPhase)) + currentTransition.lastAnimation.joints[0].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[0].yaw * Math.sin(degToRad(( walkWheelPosition ) + + currentTransition.lastAnimation.joints[0].yawPhase)) + currentTransition.lastAnimation.joints[0].yawOffset; + + rollOscillationLast = (currentTransition.lastAnimation.joints[0].roll * Math.sin(degToRad(( walkWheelPosition ) + + currentTransition.lastAnimation.joints[0].rollPhase)) + currentTransition.lastAnimation.joints[0].rollOffset); + + } else { + + pitchOscillation = currentAnimation.joints[0].pitch * Math.sin(degToRad((cycle * adjustedFrequency * 2) + + currentAnimation.joints[0].pitchPhase)) + currentAnimation.joints[0].pitchOffset; + + yawOscillation = currentAnimation.joints[0].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + + currentAnimation.joints[0].yawPhase)) + currentAnimation.joints[0].yawOffset; + + rollOscillation = (currentAnimation.joints[0].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + + currentAnimation.joints[0].rollPhase)) + currentAnimation.joints[0].rollOffset); + + pitchOscillationLast = currentTransition.lastAnimation.joints[0].pitch * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency * 2) + + currentTransition.lastAnimation.joints[0].pitchPhase)) + currentTransition.lastAnimation.joints[0].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[0].yaw * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[0].yawPhase)) + currentTransition.lastAnimation.joints[0].yawOffset; + + rollOscillationLast = (currentTransition.lastAnimation.joints[0].roll * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[0].rollPhase)) + currentTransition.lastAnimation.joints[0].rollOffset); + + } + + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + } else { + + pitchOscillation = currentAnimation.joints[0].pitch * Math.sin(degToRad((cycle * adjustedFrequency * 2) + + currentAnimation.joints[0].pitchPhase)) + currentAnimation.joints[0].pitchOffset; + + yawOscillation = currentAnimation.joints[0].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + + currentAnimation.joints[0].yawPhase)) + currentAnimation.joints[0].yawOffset; + + rollOscillation = (currentAnimation.joints[0].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + + currentAnimation.joints[0].rollPhase)) + currentAnimation.joints[0].rollOffset); + } + + // apply hips rotation + MyAvatar.setJointData("Hips", Quat.fromPitchYawRollDegrees(-pitchOscillation + (leanPitchModifier * getLeanPitch(velocity)), // getLeanPitch - lean forwards as velocity increases + yawOscillation, // Yup, that's correct ;-) + rollOscillation + getLeanRoll(deltaTime, velocity))); // getLeanRoll - banking on cornering + + // upper legs + if( INTERNAL_STATE!==SIDE_STEPPING && + INTERNAL_STATE!==CONFIG_SIDESTEP_LEFT && + INTERNAL_STATE!==CONFIG_SIDESTEP_RIGHT ) { + + // apply the current Transition to the upper legs? + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[1].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + (forwardModifier * currentAnimation.joints[1].pitchPhase))); + yawOscillation = currentAnimation.joints[1].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[1].yawPhase)); + rollOscillation = currentAnimation.joints[1].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[1].rollPhase)); + + pitchOffset = currentAnimation.joints[1].pitchOffset; + yawOffset = currentAnimation.joints[1].yawOffset; + rollOffset = currentAnimation.joints[1].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[1].pitch + * Math.sin(degToRad( walkWheelPosition + (forwardModifier * currentTransition.lastAnimation.joints[1].pitchPhase ))); + + yawOscillationLast = currentTransition.lastAnimation.joints[1].yaw + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[1].yawPhase )); + + rollOscillationLast = currentTransition.lastAnimation.joints[1].roll + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[1].rollPhase )); + + pitchOffsetLast = currentTransition.lastAnimation.joints[1].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[1].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[1].rollOffset; + + } else { + + pitchOscillation = currentAnimation.joints[1].pitch * Math.sin(degToRad((cycle * adjustedFrequency ) + (forwardModifier * currentAnimation.joints[1].pitchPhase))); + yawOscillation = currentAnimation.joints[1].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[1].yawPhase)); + rollOscillation = currentAnimation.joints[1].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[1].rollPhase)); + + pitchOffset = currentAnimation.joints[1].pitchOffset; + yawOffset = currentAnimation.joints[1].yawOffset; + rollOffset = currentAnimation.joints[1].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[1].pitch + * Math.sin(degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + (forwardModifier * currentTransition.lastAnimation.joints[1].pitchPhase ))); + + yawOscillationLast = currentTransition.lastAnimation.joints[1].yaw + * Math.sin(degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[1].yawPhase )); + + rollOscillationLast = currentTransition.lastAnimation.joints[1].roll + * Math.sin(degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[1].rollPhase )); + + pitchOffsetLast = currentTransition.lastAnimation.joints[1].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[1].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[1].rollOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation ) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation ) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation ) + ((1-transitionProgress) * rollOscillationLast); + + pitchOffset = (transitionProgress * pitchOffset) + ((1-transitionProgress) * pitchOffsetLast); + yawOffset = (transitionProgress * yawOffset) + ((1-transitionProgress) * yawOffsetLast); + rollOffset = (transitionProgress * rollOffset) + ((1-transitionProgress) * rollOffsetLast); + + } else { + + pitchOscillation = currentAnimation.joints[1].pitch * Math.sin(degToRad((cycle * adjustedFrequency ) + (forwardModifier * currentAnimation.joints[1].pitchPhase))); + yawOscillation = currentAnimation.joints[1].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[1].yawPhase)); + rollOscillation = currentAnimation.joints[1].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[1].rollPhase)); + + pitchOffset = currentAnimation.joints[1].pitchOffset; + yawOffset = currentAnimation.joints[1].yawOffset; + rollOffset = currentAnimation.joints[1].rollOffset; + } + + // apply the upper leg rotations + MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees( pitchOscillation + pitchOffset, yawOscillation + yawOffset, -rollOscillation - rollOffset )); + MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees( -pitchOscillation + pitchOffset, yawOscillation - yawOffset, -rollOscillation + rollOffset )); + + + // lower leg + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[2].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[2].pitchPhase)); + yawOscillation = currentAnimation.joints[2].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[2].yawPhase)); + rollOscillation = currentAnimation.joints[2].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[2].rollPhase)); + + pitchOffset = currentAnimation.joints[2].pitchOffset; + yawOffset = currentAnimation.joints[2].yawOffset; + rollOffset = currentAnimation.joints[2].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[2].pitch * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[2].pitchPhase)); + yawOscillationLast = currentTransition.lastAnimation.joints[2].yaw * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[2].yawPhase)); + rollOscillationLast = currentTransition.lastAnimation.joints[2].roll * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[2].rollPhase)); + + pitchOffsetLast = currentTransition.lastAnimation.joints[2].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[2].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[2].rollOffset; + + } else { + + pitchOscillation = currentAnimation.joints[2].pitch * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[2].pitchPhase)); + yawOscillation = currentAnimation.joints[2].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[2].yawPhase)); + rollOscillation = currentAnimation.joints[2].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[2].rollPhase)); + + pitchOffset = currentAnimation.joints[2].pitchOffset; + yawOffset = currentAnimation.joints[2].yawOffset; + rollOffset = currentAnimation.joints[2].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[2].pitch + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[2].pitchPhase)); + yawOscillationLast = currentTransition.lastAnimation.joints[2].yaw + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[2].yawPhase)); + rollOscillationLast = currentTransition.lastAnimation.joints[2].roll + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[2].rollPhase)); + + pitchOffsetLast = currentTransition.lastAnimation.joints[2].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[2].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[2].rollOffset; + } + + pitchOscillation = ( transitionProgress * pitchOscillation ) + ( (1-transitionProgress) * pitchOscillationLast ); + yawOscillation = ( transitionProgress * yawOscillation ) + ( (1-transitionProgress) * yawOscillationLast ); + rollOscillation = ( transitionProgress * rollOscillation ) + ( (1-transitionProgress) * rollOscillationLast ); + + pitchOffset = ( transitionProgress * pitchOffset ) + ( (1-transitionProgress) * pitchOffsetLast ); + yawOffset = ( transitionProgress * yawOffset ) + ( (1-transitionProgress) * yawOffsetLast ); + rollOffset = ( transitionProgress * rollOffset ) + ( (1-transitionProgress) * rollOffsetLast ); + + } else { + + pitchOscillation = currentAnimation.joints[2].pitch * Math.sin( degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[2].pitchPhase )); + yawOscillation = currentAnimation.joints[2].yaw * Math.sin( degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[2].yawPhase )); + rollOscillation = currentAnimation.joints[2].roll * Math.sin( degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[2].rollPhase )); + + pitchOffset = currentAnimation.joints[2].pitchOffset; + yawOffset = currentAnimation.joints[2].yawOffset; + rollOffset = currentAnimation.joints[2].rollOffset; + } + + // apply lower leg joint rotations + MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees( -pitchOscillation + pitchOffset, yawOscillation - yawOffset, rollOscillation - rollOffset )); + MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees( pitchOscillation + pitchOffset, yawOscillation + yawOffset, rollOscillation + rollOffset )); + + } // end if !SIDE_STEPPING + + else if( INTERNAL_STATE===SIDE_STEPPING || + INTERNAL_STATE===CONFIG_SIDESTEP_LEFT || + INTERNAL_STATE===CONFIG_SIDESTEP_RIGHT ) { + + // sidestepping uses the sinewave generators slightly differently for the legs + pitchOscillation = currentAnimation.joints[1].pitch * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[1].pitchPhase)); + yawOscillation = currentAnimation.joints[1].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[1].yawPhase)); + rollOscillation = currentAnimation.joints[1].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[1].rollPhase)); + + // apply upper leg rotations for sidestepping + MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees( + -pitchOscillation + currentAnimation.joints[1].pitchOffset, + yawOscillation + currentAnimation.joints[1].yawOffset, + rollOscillation + currentAnimation.joints[1].rollOffset )); + MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees( + pitchOscillation + currentAnimation.joints[1].pitchOffset, + yawOscillation - currentAnimation.joints[1].yawOffset, + -rollOscillation - currentAnimation.joints[1].rollOffset )); + + // calculate lower leg joint rotations for sidestepping + pitchOscillation = currentAnimation.joints[2].pitch * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[2].pitchPhase)); + yawOscillation = currentAnimation.joints[2].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[2].yawPhase)); + rollOscillation = currentAnimation.joints[2].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[2].rollPhase)); + + // apply lower leg joint rotations + MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees( + -pitchOscillation + currentAnimation.joints[2].pitchOffset, + yawOscillation - currentAnimation.joints[2].yawOffset, + rollOscillation - currentAnimation.joints[2].rollOffset)); // TODO: needs a kick just before fwd peak + MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees( + pitchOscillation + currentAnimation.joints[2].pitchOffset, + yawOscillation + currentAnimation.joints[2].yawOffset, + rollOscillation + currentAnimation.joints[2].rollOffset)); + } + + // feet + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[3].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[3].pitchPhase)); + yawOscillation = currentAnimation.joints[3].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[3].yawPhase)); + rollOscillation = currentAnimation.joints[3].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[3].rollPhase)); + + pitchOffset = currentAnimation.joints[3].pitchOffset; + yawOffset = currentAnimation.joints[3].yawOffset; + rollOffset = currentAnimation.joints[3].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[3].pitch * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[3].pitchPhase)); + yawOscillationLast = currentTransition.lastAnimation.joints[3].yaw * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[3].yawPhase)); + rollOscillationLast = currentTransition.lastAnimation.joints[3].roll * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[3].rollPhase)); + + pitchOffsetLast = currentTransition.lastAnimation.joints[3].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[3].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[3].rollOffset; + + } else { + + pitchOscillation = currentAnimation.joints[3].pitch * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[3].pitchPhase)); + yawOscillation = currentAnimation.joints[3].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[3].yawPhase)); + rollOscillation = currentAnimation.joints[3].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[3].rollPhase)); + + pitchOffset = currentAnimation.joints[3].pitchOffset; + yawOffset = currentAnimation.joints[3].yawOffset; + rollOffset = currentAnimation.joints[3].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[3].pitch + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[3].pitchPhase)); + + yawOscillationLast = currentTransition.lastAnimation.joints[3].yaw + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[3].yawPhase)); + + rollOscillationLast = currentTransition.lastAnimation.joints[3].roll + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[3].rollPhase)); + + pitchOffsetLast = currentTransition.lastAnimation.joints[3].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[3].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[3].rollOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation ) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation ) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation ) + ((1-transitionProgress) * rollOscillationLast); + + pitchOffset = (transitionProgress * pitchOffset) + ((1-transitionProgress) * pitchOffsetLast); + yawOffset = (transitionProgress * yawOffset) + ((1-transitionProgress) * yawOffsetLast); + rollOffset = (transitionProgress * rollOffset) + ((1-transitionProgress) * rollOffsetLast); + + } else { + + pitchOscillation = currentAnimation.joints[3].pitch * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[3].pitchPhase)); + yawOscillation = currentAnimation.joints[3].yaw * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[3].yawPhase)); + rollOscillation = currentAnimation.joints[3].roll * Math.sin(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[3].rollPhase)); + + pitchOffset = currentAnimation.joints[3].pitchOffset; + yawOffset = currentAnimation.joints[3].yawOffset; + rollOffset = currentAnimation.joints[3].rollOffset; + } + + // apply foot rotations + MyAvatar.setJointData("RightFoot", Quat.fromPitchYawRollDegrees( pitchOscillation + pitchOffset, yawOscillation + yawOffset, rollOscillation + rollOffset )); + MyAvatar.setJointData("LeftFoot", Quat.fromPitchYawRollDegrees( -pitchOscillation + pitchOffset, yawOscillation - yawOffset, rollOscillation - rollOffset )); + + // play footfall sound yet? To determine this, we take the differential of the foot's pitch curve to decide when the foot hits the ground. + if( INTERNAL_STATE===WALKING || + INTERNAL_STATE===SIDE_STEPPING || + INTERNAL_STATE===CONFIG_WALK_STYLES || + INTERNAL_STATE===CONFIG_WALK_TWEAKS || + INTERNAL_STATE===CONFIG_WALK_JOINTS || + INTERNAL_STATE===CONFIG_SIDESTEP_LEFT || + INTERNAL_STATE===CONFIG_SIDESTEP_RIGHT ) { + + // finding dy/dx is as simple as determining the cosine wave for the foot's pitch function. + var feetPitchDifferential = Math.cos(degToRad((cycle * adjustedFrequency ) + currentAnimation.joints[3].pitchPhase)); + var threshHold = 0.9; // sets the audio trigger point. with accuracy. + if(feetPitchDifferential<-threshHold && + nextStep===DIRECTION_LEFT && + principleDirection!==DIRECTION_UP && + principleDirection!==DIRECTION_DOWN) { + + playFootstep(DIRECTION_LEFT); + nextStep = DIRECTION_RIGHT; + } + else if(feetPitchDifferential>threshHold && + nextStep===DIRECTION_RIGHT && + principleDirection!==DIRECTION_UP && + principleDirection!==DIRECTION_DOWN) { + + playFootstep(DIRECTION_RIGHT); + nextStep = DIRECTION_LEFT; + } + } + + // toes + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[4].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[4].pitchPhase)); + yawOscillation = currentAnimation.joints[4].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[4].yawPhase)) + currentAnimation.joints[4].yawOffset; + rollOscillation = currentAnimation.joints[4].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[4].rollPhase)) + currentAnimation.joints[4].rollOffset; + + pitchOffset = currentAnimation.joints[4].pitchOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[4].pitch * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[4].pitchPhase)); + yawOscillationLast = currentTransition.lastAnimation.joints[4].yaw * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[4].yawPhase)) + currentTransition.lastAnimation.joints[4].yawOffset;; + rollOscillationLast = currentTransition.lastAnimation.joints[4].roll * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[4].rollPhase))+ currentTransition.lastAnimation.joints[4].rollOffset; + + pitchOffsetLast = currentTransition.lastAnimation.joints[4].pitchOffset; + + } else { + + pitchOscillation = currentAnimation.joints[4].pitch * Math.sin(degToRad((cycle * adjustedFrequency) + currentAnimation.joints[4].pitchPhase)); + yawOscillation = currentAnimation.joints[4].yaw * Math.sin(degToRad((cycle * adjustedFrequency) + currentAnimation.joints[4].yawPhase)) + currentAnimation.joints[4].yawOffset; + rollOscillation = currentAnimation.joints[4].roll * Math.sin(degToRad((cycle * adjustedFrequency) + currentAnimation.joints[4].rollPhase)) + currentAnimation.joints[4].rollOffset; + + pitchOffset = currentAnimation.joints[4].pitchOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[4].pitch + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[4].pitchPhase)); + + yawOscillationLast = currentTransition.lastAnimation.joints[4].yaw + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[4].yawPhase)) + currentTransition.lastAnimation.joints[4].yawOffset;; + + rollOscillationLast = currentTransition.lastAnimation.joints[4].roll + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[4].rollPhase))+ currentTransition.lastAnimation.joints[4].rollOffset; + + pitchOffsetLast = currentTransition.lastAnimation.joints[4].pitchOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + pitchOffset = (transitionProgress * pitchOffset) + ((1-transitionProgress) * pitchOffsetLast); + + } else { + + pitchOscillation = currentAnimation.joints[4].pitch * Math.sin(degToRad((cycle * adjustedFrequency) + currentAnimation.joints[4].pitchPhase)); + yawOscillation = currentAnimation.joints[4].yaw * Math.sin(degToRad((cycle * adjustedFrequency) + currentAnimation.joints[4].yawPhase)) + currentAnimation.joints[4].yawOffset; + rollOscillation = currentAnimation.joints[4].roll * Math.sin(degToRad((cycle * adjustedFrequency) + currentAnimation.joints[4].rollPhase)) + currentAnimation.joints[4].rollOffset; + + pitchOffset = currentAnimation.joints[4].pitchOffset; + } + + // apply toe rotations + MyAvatar.setJointData("RightToeBase", Quat.fromPitchYawRollDegrees(-pitchOscillation + pitchOffset, yawOscillation, rollOscillation)); + MyAvatar.setJointData("LeftToeBase", Quat.fromPitchYawRollDegrees( pitchOscillation + pitchOffset, yawOscillation, rollOscillation)); + + // spine + if( currentTransition !== null ) { + + if( currentTransition.walkingAtStart ) { + + pitchOscillation = currentAnimation.joints[5].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency * 2 ) + currentAnimation.joints[5].pitchPhase)) + currentAnimation.joints[5].pitchOffset; + yawOscillation = currentAnimation.joints[5].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[5].yawPhase)) + currentAnimation.joints[5].yawOffset; + rollOscillation = currentAnimation.joints[5].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[5].rollPhase)) + currentAnimation.joints[5].rollOffset; + + // calculate where we would have been if we'd continued in the last state + pitchOscillationLast = currentTransition.lastAnimation.joints[5].pitch + * Math.sin(degToRad(( walkWheelPosition * 2 ) + currentTransition.lastAnimation.joints[5].pitchPhase)) + + currentTransition.lastAnimation.joints[5].pitchOffset; + yawOscillationLast = currentTransition.lastAnimation.joints[5].yaw + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[5].yawPhase)) + + currentTransition.lastAnimation.joints[5].yawOffset; + rollOscillationLast = currentTransition.lastAnimation.joints[5].roll + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[5].rollPhase)) + + currentTransition.lastAnimation.joints[5].rollOffset; + } else { + + pitchOscillation = currentAnimation.joints[5].pitch * Math.sin( degToRad(( cycle * adjustedFrequency * 2) + + currentAnimation.joints[5].pitchPhase)) + currentAnimation.joints[5].pitchOffset; + + yawOscillation = currentAnimation.joints[5].yaw * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[5].yawPhase)) + currentAnimation.joints[5].yawOffset; + + rollOscillation = currentAnimation.joints[5].roll * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[5].rollPhase)) + currentAnimation.joints[5].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[5].pitch + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency * 2 ) + + currentTransition.lastAnimation.joints[5].pitchPhase)) + + currentTransition.lastAnimation.joints[5].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[5].yaw + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[5].yawPhase)) + + currentTransition.lastAnimation.joints[5].yawOffset; + + rollOscillationLast = currentTransition.lastAnimation.joints[5].roll + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[5].rollPhase)) + + currentTransition.lastAnimation.joints[5].rollOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation ) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation ) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation ) + ((1-transitionProgress) * rollOscillationLast); + + } else { + + pitchOscillation = currentAnimation.joints[5].pitch * Math.sin(degToRad(( cycle * adjustedFrequency * 2) + currentAnimation.joints[5].pitchPhase)) + currentAnimation.joints[5].pitchOffset; + yawOscillation = currentAnimation.joints[5].yaw * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[5].yawPhase)) + currentAnimation.joints[5].yawOffset; + rollOscillation = currentAnimation.joints[5].roll * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[5].rollPhase)) + currentAnimation.joints[5].rollOffset; + } + + // apply spine joint rotations + MyAvatar.setJointData("Spine", Quat.fromPitchYawRollDegrees( pitchOscillation, yawOscillation, rollOscillation )); + + // spine 1 + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[6].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency * 2) + currentAnimation.joints[6].pitchPhase)) + currentAnimation.joints[6].pitchOffset; + yawOscillation = currentAnimation.joints[6].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[6].yawPhase)) + currentAnimation.joints[6].yawOffset; + rollOscillation = currentAnimation.joints[6].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[6].rollPhase)) + currentAnimation.joints[6].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[6].pitch + * Math.sin(degToRad(( walkWheelPosition * 2 ) + currentTransition.lastAnimation.joints[6].pitchPhase)) + + currentTransition.lastAnimation.joints[6].pitchOffset; + yawOscillationLast = currentTransition.lastAnimation.joints[6].yaw + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[6].yawPhase)) + + currentTransition.lastAnimation.joints[6].yawOffset; + rollOscillationLast = currentTransition.lastAnimation.joints[6].roll + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[6].rollPhase)) + + currentTransition.lastAnimation.joints[6].rollOffset; + + } else { + + pitchOscillation = currentAnimation.joints[6].pitch * Math.sin( degToRad(( cycle * adjustedFrequency * 2) + + currentAnimation.joints[6].pitchPhase)) + currentAnimation.joints[6].pitchOffset; + + yawOscillation = currentAnimation.joints[6].yaw * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[6].yawPhase)) + currentAnimation.joints[6].yawOffset; + + rollOscillation = currentAnimation.joints[6].roll * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[6].rollPhase)) + currentAnimation.joints[6].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[6].pitch + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency * 2 ) + + currentTransition.lastAnimation.joints[6].pitchPhase)) + + currentTransition.lastAnimation.joints[6].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[6].yaw + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[6].yawPhase)) + + currentTransition.lastAnimation.joints[6].yawOffset; + + rollOscillationLast = currentTransition.lastAnimation.joints[6].roll + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[6].rollPhase)) + + currentTransition.lastAnimation.joints[6].rollOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + } else { + + pitchOscillation = currentAnimation.joints[6].pitch * Math.sin(degToRad(( cycle * adjustedFrequency * 2) + currentAnimation.joints[6].pitchPhase)) + currentAnimation.joints[6].pitchOffset; + yawOscillation = currentAnimation.joints[6].yaw * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[6].yawPhase)) + currentAnimation.joints[6].yawOffset; + rollOscillation = currentAnimation.joints[6].roll * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[6].rollPhase)) + currentAnimation.joints[6].rollOffset; + } + // apply spine1 joint rotations + MyAvatar.setJointData("Spine1", Quat.fromPitchYawRollDegrees( pitchOscillation, yawOscillation, rollOscillation )); + + // spine 2 + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + pitchOscillation = currentAnimation.joints[7].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency * 2) + currentAnimation.joints[7].pitchPhase)) + currentAnimation.joints[7].pitchOffset; + yawOscillation = currentAnimation.joints[7].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[7].yawPhase)) + currentAnimation.joints[7].yawOffset; + rollOscillation = currentAnimation.joints[7].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[7].rollPhase)) + currentAnimation.joints[7].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[7].pitch + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[7].pitchPhase)) + + currentTransition.lastAnimation.joints[7].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[7].yaw + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[7].yawPhase)) + + currentTransition.lastAnimation.joints[7].yawOffset; + + rollOscillationLast = currentTransition.lastAnimation.joints[7].roll + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[7].rollPhase)) + + currentTransition.lastAnimation.joints[7].rollOffset; + + } else { + + pitchOscillation = currentAnimation.joints[7].pitch + * Math.sin( degToRad(( cycle * adjustedFrequency * 2) + + currentAnimation.joints[7].pitchPhase)) + + currentAnimation.joints[7].pitchOffset; + + yawOscillation = currentAnimation.joints[7].yaw + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[7].yawPhase)) + + currentAnimation.joints[7].yawOffset; + + rollOscillation = currentAnimation.joints[7].roll + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[7].rollPhase)) + + currentAnimation.joints[7].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[7].pitch + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[7].pitchPhase)) + + currentTransition.lastAnimation.joints[7].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[7].yaw + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[7].yawPhase)) + + currentTransition.lastAnimation.joints[7].yawOffset; + + rollOscillationLast = currentTransition.lastAnimation.joints[7].roll + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[7].rollPhase)) + + currentTransition.lastAnimation.joints[7].rollOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation ) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation ) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation ) + ((1-transitionProgress) * rollOscillationLast); + + } else { + + pitchOscillation = currentAnimation.joints[7].pitch * Math.sin(degToRad(( cycle * adjustedFrequency * 2) + currentAnimation.joints[7].pitchPhase)) + + currentAnimation.joints[7].pitchOffset; + + yawOscillation = currentAnimation.joints[7].yaw * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[7].yawPhase)) + + currentAnimation.joints[7].yawOffset; + + rollOscillation = currentAnimation.joints[7].roll * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[7].rollPhase)) + + currentAnimation.joints[7].rollOffset; + } + // apply spine2 joint rotations + MyAvatar.setJointData("Spine2", Quat.fromPitchYawRollDegrees( pitchOscillation, yawOscillation, rollOscillation )); + + if(!armsFree) { + + // shoulders + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + pitchOscillation = currentAnimation.joints[8].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[8].pitchPhase)) + currentAnimation.joints[8].pitchOffset; + + yawOscillation = currentAnimation.joints[8].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[8].yawPhase)); + + rollOscillation = currentAnimation.joints[8].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency * 2) + + currentAnimation.joints[8].rollPhase)) + currentAnimation.joints[8].rollOffset; + + yawOffset = currentAnimation.joints[8].yawOffset; + + + pitchOscillationLast = currentTransition.lastAnimation.joints[8].pitch + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[8].pitchPhase)) + + currentTransition.lastAnimation.joints[8].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[8].yaw + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[8].yawPhase)) + + rollOscillationLast = currentTransition.lastAnimation.joints[8].roll + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[8].rollPhase)) + + currentTransition.lastAnimation.joints[8].rollOffset; + + yawOffsetLast = currentTransition.lastAnimation.joints[8].yawOffset; + + } else { + + pitchOscillation = currentAnimation.joints[8].pitch * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[8].pitchPhase)) + currentAnimation.joints[8].pitchOffset; + + yawOscillation = currentAnimation.joints[8].yaw * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[8].yawPhase)); + + rollOscillation = currentAnimation.joints[8].roll * Math.sin( degToRad(( cycle * adjustedFrequency * 2) + + currentAnimation.joints[8].rollPhase)) + currentAnimation.joints[8].rollOffset; + + yawOffset = currentAnimation.joints[8].yawOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[8].pitch + * Math.sin( degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[8].pitchPhase)) + + currentTransition.lastAnimation.joints[8].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[8].yaw + * Math.sin( degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[8].yawPhase)) + + rollOscillationLast = currentTransition.lastAnimation.joints[8].roll + * Math.sin( degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[8].rollPhase)) + + currentTransition.lastAnimation.joints[8].rollOffset; + + yawOffsetLast = currentTransition.lastAnimation.joints[8].yawOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + yawOffset = (transitionProgress * yawOffset) + ((1-transitionProgress) * yawOffsetLast); + + } else { + + pitchOscillation = currentAnimation.joints[8].pitch * Math.sin(degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[8].pitchPhase)) + currentAnimation.joints[8].pitchOffset; + + yawOscillation = currentAnimation.joints[8].yaw * Math.sin(degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[8].yawPhase)); + + rollOscillation = currentAnimation.joints[8].roll * Math.sin(degToRad(( cycle * adjustedFrequency * 2) + + currentAnimation.joints[8].rollPhase)) + currentAnimation.joints[8].rollOffset; + + yawOffset = currentAnimation.joints[8].yawOffset; + } + + MyAvatar.setJointData("RightShoulder", Quat.fromPitchYawRollDegrees(pitchOscillation, yawOscillation + yawOffset, rollOscillation )); + MyAvatar.setJointData("LeftShoulder", Quat.fromPitchYawRollDegrees(pitchOscillation, yawOscillation - yawOffset, -rollOscillation )); + + // upper arms + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[9].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[9].pitchPhase)); + yawOscillation = currentAnimation.joints[9].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[9].yawPhase)); + rollOscillation = currentAnimation.joints[9].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency * 2) + currentAnimation.joints[9].rollPhase)) + currentAnimation.joints[9].rollOffset; + + pitchOffset = currentAnimation.joints[9].pitchOffset; + yawOffset = currentAnimation.joints[9].yawOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[9].pitch + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[9].pitchPhase)) + + yawOscillationLast = currentTransition.lastAnimation.joints[9].yaw + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[9].yawPhase)) + + rollOscillationLast = currentTransition.lastAnimation.joints[9].roll + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[9].rollPhase)) + + currentTransition.lastAnimation.joints[9].rollOffset; + + pitchOffsetLast = currentTransition.lastAnimation.joints[9].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[9].yawOffset; + + } else { + + pitchOscillation = currentAnimation.joints[9].pitch + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[9].pitchPhase)); + + yawOscillation = currentAnimation.joints[9].yaw + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[9].yawPhase)); + + rollOscillation = currentAnimation.joints[9].roll + * Math.sin(degToRad(( cycle * adjustedFrequency * 2) + + currentAnimation.joints[9].rollPhase)) + + currentAnimation.joints[9].rollOffset; + + pitchOffset = currentAnimation.joints[9].pitchOffset; + yawOffset = currentAnimation.joints[9].yawOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[9].pitch + * Math.sin( degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[9].pitchPhase)) + + yawOscillationLast = currentTransition.lastAnimation.joints[9].yaw + * Math.sin( degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[9].yawPhase)) + + rollOscillationLast = currentTransition.lastAnimation.joints[9].roll + * Math.sin( degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[9].rollPhase)) + + currentTransition.lastAnimation.joints[9].rollOffset; + + pitchOffsetLast = currentTransition.lastAnimation.joints[9].pitchOffset; + yawOffsetLast = currentTransition.lastAnimation.joints[9].yawOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + pitchOffset = (transitionProgress * pitchOffset) + ((1-transitionProgress) * pitchOffsetLast); + yawOffset = (transitionProgress * yawOffset) + ((1-transitionProgress) * yawOffsetLast); + + } else { + + pitchOscillation = currentAnimation.joints[9].pitch + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[9].pitchPhase)); + + yawOscillation = currentAnimation.joints[9].yaw + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[9].yawPhase)); + + rollOscillation = currentAnimation.joints[9].roll + * Math.sin( degToRad(( cycle * adjustedFrequency * 2) + + currentAnimation.joints[9].rollPhase)) + + currentAnimation.joints[9].rollOffset; + + pitchOffset = currentAnimation.joints[9].pitchOffset; + yawOffset = currentAnimation.joints[9].yawOffset; + + } + MyAvatar.setJointData("RightArm", Quat.fromPitchYawRollDegrees( -pitchOscillation + pitchOffset, yawOscillation - yawOffset, rollOscillation )); + MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees( pitchOscillation + pitchOffset, yawOscillation + yawOffset, -rollOscillation )); + + // forearms + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[10].pitch + * Math.sin( degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[10].pitchPhase )) + + currentAnimation.joints[10].pitchOffset; + + yawOscillation = currentAnimation.joints[10].yaw + * Math.sin( degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[10].yawPhase )); + + rollOscillation = currentAnimation.joints[10].roll + * Math.sin( degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[10].rollPhase )); + + yawOffset = currentAnimation.joints[10].yawOffset; + rollOffset = currentAnimation.joints[10].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[10].pitch + * Math.sin( degToRad(( walkWheelPosition ) + + currentTransition.lastAnimation.joints[10].pitchPhase)) + + currentTransition.lastAnimation.joints[10].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[10].yaw + * Math.sin( degToRad(( walkWheelPosition ) + + currentTransition.lastAnimation.joints[10].yawPhase)); + + rollOscillationLast = currentTransition.lastAnimation.joints[10].roll + * Math.sin( degToRad(( walkWheelPosition ) + + currentTransition.lastAnimation.joints[10].rollPhase)); + + yawOffsetLast = currentTransition.lastAnimation.joints[10].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[10].rollOffset; + + } else { + + pitchOscillation = currentAnimation.joints[10].pitch + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[10].pitchPhase )) + + currentAnimation.joints[10].pitchOffset; + + yawOscillation = currentAnimation.joints[10].yaw + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[10].yawPhase )); + + rollOscillation = currentAnimation.joints[10].roll + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[10].rollPhase )); + + yawOffset = currentAnimation.joints[10].yawOffset; + rollOffset = currentAnimation.joints[10].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[10].pitch + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[10].pitchPhase)) + + currentTransition.lastAnimation.joints[10].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[10].yaw + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[10].yawPhase)); + + rollOscillationLast = currentTransition.lastAnimation.joints[10].roll + * Math.sin( degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[10].rollPhase)); + + yawOffsetLast = currentTransition.lastAnimation.joints[10].yawOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[10].rollOffset; + } + + // blend the previous and next + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = -(transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + yawOffset = (transitionProgress * yawOffset) + ((1-transitionProgress) * yawOffsetLast); + rollOffset = (transitionProgress * rollOffset) + ((1-transitionProgress) * rollOffsetLast); + + } else { + + pitchOscillation = currentAnimation.joints[10].pitch + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[10].pitchPhase )) + + currentAnimation.joints[10].pitchOffset; + + yawOscillation = currentAnimation.joints[10].yaw + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[10].yawPhase )); + + rollOscillation = currentAnimation.joints[10].roll + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[10].rollPhase )); + + yawOffset = currentAnimation.joints[10].yawOffset; + rollOffset = currentAnimation.joints[10].rollOffset; + } + + // apply forearms rotations + MyAvatar.setJointData("RightForeArm", Quat.fromPitchYawRollDegrees( pitchOscillation, yawOscillation + yawOffset, rollOscillation + rollOffset )); + MyAvatar.setJointData("LeftForeArm", Quat.fromPitchYawRollDegrees( pitchOscillation, yawOscillation - yawOffset, rollOscillation - rollOffset )); + + // hands + var sideStepSign = 1; + if(INTERNAL_STATE===SIDE_STEPPING) { + sideStepSign = 1; + } + if(currentTransition!==null) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = currentAnimation.joints[11].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[11].pitchPhase)) + currentAnimation.joints[11].pitchOffset; + yawOscillation = currentAnimation.joints[11].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[11].yawPhase)) + currentAnimation.joints[11].yawOffset; + rollOscillation = currentAnimation.joints[11].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + currentAnimation.joints[11].rollPhase)) ; + + pitchOscillationLast = currentTransition.lastAnimation.joints[11].pitch + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[11].pitchPhase)) + + currentTransition.lastAnimation.joints[11].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[11].yaw + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[11].yawPhase)) + + currentTransition.lastAnimation.joints[11].yawOffset; + + rollOscillationLast = currentTransition.lastAnimation.joints[11].roll + * Math.sin(degToRad( walkWheelPosition + currentTransition.lastAnimation.joints[11].rollPhase)) + + rollOffset = currentAnimation.joints[11].rollOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[11].rollOffset; + + } else { + + pitchOscillation = currentAnimation.joints[11].pitch + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[11].pitchPhase)) + + currentAnimation.joints[11].pitchOffset; + + yawOscillation = currentAnimation.joints[11].yaw + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[11].yawPhase)) + + currentAnimation.joints[11].yawOffset; + + rollOscillation = currentAnimation.joints[11].roll + * Math.sin( degToRad(( cycle * adjustedFrequency ) + + currentAnimation.joints[11].rollPhase)); + + rollOffset = currentAnimation.joints[11].rollOffset; + + pitchOscillationLast = currentTransition.lastAnimation.joints[11].pitch + * Math.sin(degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[11].pitchPhase)) + + currentTransition.lastAnimation.joints[11].pitchOffset; + + yawOscillationLast = currentTransition.lastAnimation.joints[11].yaw + * Math.sin(degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[11].yawPhase)) + + currentTransition.lastAnimation.joints[11].yawOffset; + + rollOscillationLast = currentTransition.lastAnimation.joints[11].roll + * Math.sin(degToRad( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency + + currentTransition.lastAnimation.joints[11].rollPhase)) + + rollOffset = currentAnimation.joints[11].rollOffset; + rollOffsetLast = currentTransition.lastAnimation.joints[11].rollOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + rollOffset = (transitionProgress * rollOffset) + ((1-transitionProgress) * rollOffsetLast); + + } else { + + pitchOscillation = currentAnimation.joints[11].pitch * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[11].pitchPhase)) + currentAnimation.joints[11].pitchOffset; + yawOscillation = currentAnimation.joints[11].yaw * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[11].yawPhase)) + currentAnimation.joints[11].yawOffset; + rollOscillation = currentAnimation.joints[11].roll * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[11].rollPhase)); + + rollOffset = currentAnimation.joints[11].rollOffset; + } + + // set the hand rotations + MyAvatar.setJointData("RightHand", Quat.fromPitchYawRollDegrees( sideStepSign * pitchOscillation, yawOscillation, rollOscillation + rollOffset)); + MyAvatar.setJointData("LeftHand", Quat.fromPitchYawRollDegrees( pitchOscillation, -yawOscillation, rollOscillation - rollOffset)); + + } // end if(!armsFree) + + // head (includes neck joint) - currently zeroed out in STANDING animation files by request + if( currentTransition !== null ) { + + if(currentTransition.walkingAtStart) { + + pitchOscillation = 0.5 * currentAnimation.joints[12].pitch * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency * 2) + + currentAnimation.joints[12].pitchPhase)) + currentAnimation.joints[12].pitchOffset; + + yawOscillation = 0.5 * currentAnimation.joints[12].yaw * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[12].yawPhase)) + currentAnimation.joints[12].yawOffset; + + rollOscillation = 0.5 * currentAnimation.joints[12].roll * Math.sin(degToRad(( cumulativeTime * currentAnimation.settings.baseFrequency ) + + currentAnimation.joints[12].rollPhase)) + currentAnimation.joints[12].rollOffset; + + pitchOscillationLast = 0.5 * currentTransition.lastAnimation.joints[12].pitch + * Math.sin(degToRad(( walkWheelPosition * 2) + currentTransition.lastAnimation.joints[12].pitchPhase)) + + currentTransition.lastAnimation.joints[12].pitchOffset; + + yawOscillationLast = 0.5 * currentTransition.lastAnimation.joints[12].yaw + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[12].yawPhase)) + + currentTransition.lastAnimation.joints[12].yawOffset; + + rollOscillationLast = 0.5 * currentTransition.lastAnimation.joints[12].roll + * Math.sin(degToRad(( walkWheelPosition ) + currentTransition.lastAnimation.joints[12].rollPhase)) + + currentTransition.lastAnimation.joints[12].rollOffset; + + } else { + + pitchOscillation = 0.5 * currentAnimation.joints[12].pitch * Math.sin(degToRad(( cycle * adjustedFrequency * 2) + currentAnimation.joints[12].pitchPhase)) + currentAnimation.joints[12].pitchOffset; + yawOscillation = 0.5 * currentAnimation.joints[12].yaw * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[12].yawPhase)) + currentAnimation.joints[12].yawOffset; + rollOscillation = 0.5 * currentAnimation.joints[12].roll * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[12].rollPhase)) + currentAnimation.joints[12].rollOffset; + + pitchOscillationLast = 0.5 * currentTransition.lastAnimation.joints[12].pitch + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency * 2) + + currentTransition.lastAnimation.joints[12].pitchPhase)) + + currentTransition.lastAnimation.joints[12].pitchOffset; + + yawOscillationLast = 0.5 * currentTransition.lastAnimation.joints[12].yaw + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[12].yawPhase)) + + currentTransition.lastAnimation.joints[12].yawOffset; + + rollOscillationLast = 0.5 * currentTransition.lastAnimation.joints[12].roll + * Math.sin(degToRad(( cumulativeTime * currentTransition.lastAnimation.settings.baseFrequency ) + + currentTransition.lastAnimation.joints[12].rollPhase)) + + currentTransition.lastAnimation.joints[12].rollOffset; + } + + pitchOscillation = (transitionProgress * pitchOscillation) + ((1-transitionProgress) * pitchOscillationLast); + yawOscillation = (transitionProgress * yawOscillation) + ((1-transitionProgress) * yawOscillationLast); + rollOscillation = (transitionProgress * rollOscillation) + ((1-transitionProgress) * rollOscillationLast); + + } else { + + pitchOscillation = 0.5 * currentAnimation.joints[12].pitch * Math.sin(degToRad(( cycle * adjustedFrequency * 2) + currentAnimation.joints[12].pitchPhase)) + currentAnimation.joints[12].pitchOffset; + yawOscillation = 0.5 * currentAnimation.joints[12].yaw * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[12].yawPhase)) + currentAnimation.joints[12].yawOffset; + rollOscillation = 0.5 * currentAnimation.joints[12].roll * Math.sin(degToRad(( cycle * adjustedFrequency ) + currentAnimation.joints[12].rollPhase)) + currentAnimation.joints[12].rollOffset; + } + + MyAvatar.setJointData( "Head", Quat.fromPitchYawRollDegrees( pitchOscillation, yawOscillation, rollOscillation )); + MyAvatar.setJointData( "Neck", Quat.fromPitchYawRollDegrees( pitchOscillation, yawOscillation, rollOscillation )); +} + + + +// Bezier function for applying to transitions - src: Dan Pupius (www.pupius.net) http://13thparallel.com/archive/bezier-curves/ +Coord = function (x,y) { + if(!x) var x=0; + if(!y) var y=0; + return {x: x, y: y}; +} + +function B1(t) { return t*t*t } +function B2(t) { return 3*t*t*(1-t) } +function B3(t) { return 3*t*(1-t)*(1-t) } +function B4(t) { return (1-t)*(1-t)*(1-t) } + +function getBezier(percent,C1,C2,C3,C4) { + var pos = new Coord(); + pos.x = C1.x*B1(percent) + C2.x*B2(percent) + C3.x*B3(percent) + C4.x*B4(percent); + pos.y = C1.y*B1(percent) + C2.y*B2(percent) + C3.y*B3(percent) + C4.y*B4(percent); + return pos; +} + +// Butterworth LP filter - coeffs calculated using: http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html +var NZEROS = 8; +var NPOLES = 8; +var GAIN = 17.40692157; +var xv = [0,0,0,0,0,0,0,0,0]; //xv.length = NZEROS+1; +var yv = [0,0,0,0,0,0,0,0,0]; //yv.length = NPOLES+1; + +function filterButterworth(nextInputValue) +{ + xv[0] = xv[1]; xv[1] = xv[2]; xv[2] = xv[3]; xv[3] = xv[4]; xv[4] = xv[5]; xv[5] = xv[6]; xv[6] = xv[7]; xv[7] = xv[8]; + xv[8] = nextInputValue / GAIN; + yv[0] = yv[1]; yv[1] = yv[2]; yv[2] = yv[3]; yv[3] = yv[4]; yv[4] = yv[5]; yv[5] = yv[6]; yv[6] = yv[7]; yv[7] = yv[8]; + yv[8] = (xv[0] + xv[8]) + 8 * (xv[1] + xv[7]) + 28 * (xv[2] + xv[6]) + + 56 * (xv[3] + xv[5]) + 70 * xv[4] + + ( -0.0033008230 * yv[0]) + ( -0.0440409341 * yv[1]) + + ( -0.2663485333 * yv[2]) + ( -0.9570250765 * yv[3]) + + ( -2.2596729000 * yv[4]) + ( -3.6088345059 * yv[5]) + + ( -3.9148571397 * yv[6]) + ( -2.6527135283 * yv[7]); + return yv[8]; +} + +// the faster we go, the further we lean forward. the angle is calcualted here +var leanAngles = []; // smooth out and add damping with simple averaging filter. +leanAngles.length = 15; + +function getLeanPitch(velocity) { + + if(velocity>TERMINAL_VELOCITY) velocity=TERMINAL_VELOCITY; + var leanProgress = velocity / TERMINAL_VELOCITY; + var responseSharpness = 1.8; + if(principleDirection==DIRECTION_BACKWARDS) responseSharpness = 3.6; // lean back a bit extra when walking backwards + var leanProgressBezier = getBezier((1-leanProgress),{x:0,y:0},{x:0,y:responseSharpness},{x:0,y:1},{x:1,y:1}).y; + + // simple averaging filter seems to give best results + leanAngles.push(leanProgressBezier); + leanAngles.shift(); // FIFO + var totalLeanAngles = 0; + for(ea in leanAngles) totalLeanAngles += leanAngles[ea]; + var leanProgressAverageFiltered = totalLeanAngles / leanAngles.length; + + // calculate final return value + var leanPitchFinal = 0; + if(principleDirection===DIRECTION_BACKWARDS) { + leanPitchFinal = -currentAnimation.settings.flyingHipsPitch * leanProgressAverageFiltered;// + finalAccelerationResponse; + } else { + leanPitchFinal = currentAnimation.settings.flyingHipsPitch * leanProgressAverageFiltered;// + finalAccelerationResponse; + } + return leanPitchFinal; +} + +// calculate the angle at which to bank into corners when turning +var leanRollAngles = []; // smooth out and add damping with simple averaging filter +leanRollAngles.length = 25; + +var angularVelocities = []; // keep a record of the last few so can filter out spurious values +angularVelocities.length = 5; + +function getLeanRoll(deltaTime, velocity) { + + // what's our current anglular velocity? + var angularVelocityMax = 70; // from observation + var currentOrientationVec3 = Quat.safeEulerAngles(MyAvatar.orientation); + var lastOrientationVec3 = Quat.safeEulerAngles(lastOrientation); + var deltaYaw = lastOrientationVec3.y-currentOrientationVec3.y; + lastOrientation = MyAvatar.orientation; + + var angularVelocity = deltaYaw / deltaTime; + if(angularVelocity>angularVelocityMax) angularVelocity = angularVelocityMax; + if(angularVelocity<-angularVelocityMax) angularVelocity = -angularVelocityMax; + + // filter the angular velocity for a nicer response and a bit of wobble (intentional overshoot / ringing) + angularVelocity = filterButterworth(angularVelocity); + + var turnSign = 1; + if(angularVelocity<0) turnSign = -1; + if(principleDirection===DIRECTION_BACKWARDS) + turnSign *= -1; + + // calculate the amount of roll based on both angular and linear velocities + if(velocity>TERMINAL_VELOCITY) velocity = TERMINAL_VELOCITY; + var leanRollProgress = (velocity / TERMINAL_VELOCITY) * (Math.abs(angularVelocity) / angularVelocityMax); + + // apply our response curve + var leanRollProgressBezier = getBezier((1-leanRollProgress),{x:0,y:0},{x:0,y:2.5},{x:0,y:1},{x:1,y:1}).y; + + // simple averaging filter + leanRollAngles.push(turnSign * leanRollProgressBezier); + leanRollAngles.shift(); // FIFO + var totalLeanRollAngles = 0; + for(var ea in leanRollAngles) totalLeanRollAngles += leanRollAngles[ea]; + var leanRollProgressAverageFiltered = totalLeanRollAngles / leanRollAngles.length; + + return currentAnimation.settings.maxBankingAngle * leanRollProgressAverageFiltered; +} + +// set up the interface components, update the internal state and kick off any transitions +function setInternalState(newInternalState) { + + switch(newInternalState) { + + case WALKING: + + if(!minimised) doStandardMenu(); + INTERNAL_STATE = WALKING; + return; + + case FLYING: + + if(!minimised) doStandardMenu(); + INTERNAL_STATE = FLYING; + return; + + case SIDE_STEPPING: + + if(!minimised) doStandardMenu(); + INTERNAL_STATE = SIDE_STEPPING; + return; + + case CONFIG_WALK_STYLES: + + INTERNAL_STATE = CONFIG_WALK_STYLES; + currentAnimation = selectedWalk; + if(!minimised) { + hidebuttonOverlays(); + hideJointControls(); + showFrontPanelButtons(false); + showWalkStyleButtons(true); + setBackground(controlsBackgroundWalkEditStyles); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configWalkStylesButtonSelected); + setButtonOverlayVisible(configWalkTweaksButton); + setButtonOverlayVisible(configWalkJointsButton); + setButtonOverlayVisible(backButton); + setSliderThumbsVisible(false); + } + return; + + case CONFIG_WALK_TWEAKS: + + INTERNAL_STATE = CONFIG_WALK_TWEAKS; + currentAnimation = selectedWalk; + if(!minimised) { + hidebuttonOverlays(); + hideJointControls(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditTweaks); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configWalkStylesButton); + setButtonOverlayVisible(configWalkTweaksButtonSelected); + setButtonOverlayVisible(configWalkJointsButton); + setButtonOverlayVisible(backButton); + initialiseWalkTweaks(); + } + return; + + case CONFIG_WALK_JOINTS: + + INTERNAL_STATE = CONFIG_WALK_JOINTS; + currentAnimation = selectedWalk; + if(!minimised) { + hidebuttonOverlays(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditJoints); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configWalkStylesButton); + setButtonOverlayVisible(configWalkTweaksButton); + setButtonOverlayVisible(configWalkJointsButtonSelected); + setButtonOverlayVisible(backButton); + initialiseJointsEditingPanel(selectedJointIndex); + } + return; + + case CONFIG_STANDING: + + INTERNAL_STATE = CONFIG_STANDING; + currentAnimation = selectedStand; + if(!minimised) { + hidebuttonOverlays(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditJoints); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configSideStepRightButton); + setButtonOverlayVisible(configSideStepLeftButton); + setButtonOverlayVisible(configStandButtonSelected); + setButtonOverlayVisible(backButton); + initialiseJointsEditingPanel(selectedJointIndex); + } + return; + + case CONFIG_SIDESTEP_LEFT: + + INTERNAL_STATE = CONFIG_SIDESTEP_LEFT; + currentAnimation = selectedSideStepLeft; + if(!minimised) { + hidebuttonOverlays(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditJoints); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configSideStepRightButton); + setButtonOverlayVisible(configSideStepLeftButtonSelected); + setButtonOverlayVisible(configStandButton); + setButtonOverlayVisible(backButton); + initialiseJointsEditingPanel(selectedJointIndex); + } + return; + + case CONFIG_SIDESTEP_RIGHT: + + INTERNAL_STATE = CONFIG_SIDESTEP_RIGHT; + currentAnimation = selectedSideStepRight; + if(!minimised) { + hidebuttonOverlays(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditJoints); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configSideStepRightButtonSelected); + setButtonOverlayVisible(configSideStepLeftButton); + setButtonOverlayVisible(configStandButton); + setButtonOverlayVisible(backButton); + initialiseJointsEditingPanel(selectedJointIndex); + } + return; + + case CONFIG_FLYING: + + INTERNAL_STATE = CONFIG_FLYING; + currentAnimation = selectedFly; + if(!minimised) { + hidebuttonOverlays(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditJoints); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configFlyingUpButton); + setButtonOverlayVisible(configFlyingDownButton); + setButtonOverlayVisible(configFlyingButtonSelected); + setButtonOverlayVisible(backButton); + initialiseJointsEditingPanel(selectedJointIndex); + } + return; + + case CONFIG_FLYING_UP: + + INTERNAL_STATE = CONFIG_FLYING_UP; + currentAnimation = selectedFlyUp; + if(!minimised) { + hidebuttonOverlays(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditJoints); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configFlyingUpButtonSelected); + setButtonOverlayVisible(configFlyingDownButton); + setButtonOverlayVisible(configFlyingButton); + setButtonOverlayVisible(backButton); + initialiseJointsEditingPanel(selectedJointIndex); + } + return; + + case CONFIG_FLYING_DOWN: + + INTERNAL_STATE = CONFIG_FLYING_DOWN; + currentAnimation = selectedFlyDown; + if(!minimised) { + hidebuttonOverlays(); + showFrontPanelButtons(false); + showWalkStyleButtons(false); + setBackground(controlsBackgroundWalkEditJoints); + setButtonOverlayVisible(onButton); + setButtonOverlayVisible(configFlyingUpButton); + setButtonOverlayVisible(configFlyingDownButtonSelected); + setButtonOverlayVisible(configFlyingButton); + setButtonOverlayVisible(backButton); + initialiseJointsEditingPanel(selectedJointIndex); + } + return; + + case STANDING: + default: + + INTERNAL_STATE = STANDING; + if(!minimised) doStandardMenu(); + + // initialisation - runs at script startup only + if(strideLength===0) { + + if(principleDirection===DIRECTION_BACKWARDS) + strideLength = selectedWalk.calibration.strideLengthBackwards; + else + strideLength = selectedWalk.calibration.strideLengthForwards; + + curlFingers(); + } + return; + } +} + +// Main loop + +// stabilising vars - most state changes are preceded by a couple of hints that they are about to happen rather than +// momentarilly switching between states (causes flicker), we count the number of hints in a row before actually changing state +var standHints = 0; +var walkHints = 0; +var flyHints = 0; +var requiredHints = 2; // tweakable - debounce state changes - how many times do we get a state change request in a row before we actually change state? (used to be 4 or 5) +var principleDirection = 0; + +// helper function for stats output +function directionAsString(directionEnum) { + + switch(directionEnum) { + case DIRECTION_UP: return 'Up'; + case DIRECTION_DOWN: return 'Down'; + case DIRECTION_LEFT: return 'Left'; + case DIRECTION_RIGHT: return 'Right'; + case DIRECTION_FORWARDS: return 'Forwards'; + case DIRECTION_BACKWARDS: return 'Backwards'; + default: return 'Unknown'; + } +} +// helper function for stats output +function internalStateAsString(internalState) { + + switch(internalState) { + case STANDING: return 'Standing'; + case WALKING: return 'Walking'; + case SIDE_STEPPING: return 'Side Stepping'; + case FLYING: return 'Flying'; + default: return 'Editing'; + } +} + +Script.update.connect(function(deltaTime) { + + if(powerOn) { + + frameStartTime = new Date().getTime(); + cumulativeTime += deltaTime; + nFrames++; + var speed = 0; + + // firstly check for editing modes, as these require no positioning calculations + var editing = false; + switch(INTERNAL_STATE) { + + case CONFIG_WALK_STYLES: + currentAnimation = selectedWalk; + animateAvatar(deltaTime, speed, principleDirection); + editing = true; + break; + case CONFIG_WALK_TWEAKS: + currentAnimation = selectedWalk; + animateAvatar(deltaTime, speed, DIRECTION_FORWARDS); + editing = true; + break; + case CONFIG_WALK_JOINTS: + currentAnimation = selectedWalk; + animateAvatar(deltaTime, speed, DIRECTION_FORWARDS); + editing = true; + break; + case CONFIG_STANDING: + currentAnimation = selectedStand; + animateAvatar(deltaTime, speed, DIRECTION_FORWARDS); + editing = true; + break; + case CONFIG_SIDESTEP_LEFT: + currentAnimation = selectedSideStepLeft; + animateAvatar(deltaTime, speed, DIRECTION_LEFT); + editing = true; + break; + case CONFIG_SIDESTEP_RIGHT: + currentAnimation = selectedSideStepRight; + animateAvatar(deltaTime, speed, DIRECTION_RIGHT); + editing = true; + break; + case CONFIG_FLYING: + currentAnimation = selectedFly; + animateAvatar(deltaTime, speed, DIRECTION_FORWARDS); + editing = true; + break; + case CONFIG_FLYING_UP: + currentAnimation = selectedFlyUp; + animateAvatar(deltaTime, speed, DIRECTION_UP); + editing = true; + break; + case CONFIG_FLYING_DOWN: + currentAnimation = selectedFlyDown; + animateAvatar(deltaTime, speed, DIRECTION_DOWN); + editing = true; + break; + default: + break; + } + + // we have to declare these vars here ( outside 'if(!editing)' ), so they are still in scope + // when we record the frame's data and when we do the stats update at the end + var deltaX = 0; + var deltaY = 0; + var deltaZ = 0; + var acceleration = { x:0, y:0, z:0 }; + var accelerationJS = MyAvatar.getAcceleration(); + + // calculate overriding (local) direction of translation for use later when decide which animation should be played + var inverseRotation = Quat.inverse(MyAvatar.orientation); + var localVelocity = Vec3.multiplyQbyV(inverseRotation, MyAvatar.getVelocity()); + + if(!editing) { + + // the first thing to do is find out how fast we're going, + // what our acceleration is and which direction we're principly moving in + + // calcualte (local) change in velocity + var velocity = MyAvatar.getVelocity(); + speed = Vec3.length(velocity); + + // determine the candidate animation to play + var actionToTake = 0; + if( speed < 0.5) { + actionToTake = STANDING; + standHints++; + } + else if( speed < FLYING_SPEED ) { + actionToTake = WALKING; + walkHints++; + } + else if( speed >= FLYING_SPEED ) { + actionToTake = FLYING; + flyHints++; + } + + deltaX = localVelocity.x; + deltaY = localVelocity.y; + deltaZ = -localVelocity.z; + + // determine the principle direction + if(Math.abs(deltaX)>Math.abs(deltaY) + &&Math.abs(deltaX)>Math.abs(deltaZ)) { + if(deltaX<0) { + principleDirection = DIRECTION_RIGHT; + } else { + principleDirection = DIRECTION_LEFT; + } + } + else if(Math.abs(deltaY)>Math.abs(deltaX) + &&Math.abs(deltaY)>Math.abs(deltaZ)) { + if(deltaY>0) { + principleDirection = DIRECTION_UP; + } + else { + principleDirection = DIRECTION_DOWN; + } + } + else if(Math.abs(deltaZ)>Math.abs(deltaX) + &&Math.abs(deltaZ)>Math.abs(deltaY)) { + if(deltaZ>0) { + principleDirection = DIRECTION_FORWARDS; + } else { + principleDirection = DIRECTION_BACKWARDS; + } + } + + // NB: this section will change significantly once we are ground plane aware + // it will change even more once we have uneven surfaces to deal with + + // maybe at walking speed, but sideways? + if( actionToTake === WALKING && + ( principleDirection === DIRECTION_LEFT || + principleDirection === DIRECTION_RIGHT )) { + + actionToTake = SIDE_STEPPING; + } + + // maybe at walking speed, but flying up? + if( actionToTake === WALKING && + ( principleDirection === DIRECTION_UP )) { + + actionToTake = FLYING; + standHints--; + flyHints++; + } + + // maybe at walking speed, but flying down? + if( actionToTake === WALKING && + ( principleDirection === DIRECTION_DOWN )) { + + actionToTake = FLYING; + standHints--; + flyHints++; + } + + // log this frame's motion for later reference + var accelerationX = ( framesHistory.recentMotions[0].velocity.x - localVelocity.x ) / deltaTime; + var accelerationY = ( localVelocity.y - framesHistory.recentMotions[0].velocity.y ) / deltaTime; + var accelerationZ = ( framesHistory.recentMotions[0].velocity.z - localVelocity.z ) / deltaTime; + acceleration = {x:accelerationX, y:accelerationY, z:accelerationZ}; + + + // select appropriate animation and initiate Transition if required + switch(actionToTake) { + + case STANDING: + + if( standHints > requiredHints || INTERNAL_STATE===STANDING) { // wait for a few consecutive hints (17mS each) + + standHints = 0; + walkHints = 0; + flyHints = 0; + + // do we need to change state? + if( INTERNAL_STATE!==STANDING ) { + + // initiate the transition + if(currentTransition) { + delete currentTransition; + currentTransition = null; + } + + switch(currentAnimation) { + + case selectedWalk: + + // Walking to Standing + var timeWalking = new Date().getTime() - framesHistory.lastWalkStartTime; + + var bezierCoeffsOne = {x:0.0, y:1.0}; + var bezierCoeffsTwo = {x:0.0, y:1.0}; + var transitionTime = 0.4; + + // very different curves for incremental steps + if( timeWalking < 550 ) { + bezierCoeffsOne = {x:0.63, y:0.17}; + bezierCoeffsTwo = {x:0.77, y:0.3}; + transitionTime = 0.75; + } + currentTransition = new Transition( currentAnimation, selectedStand, [], transitionTime, bezierCoeffsOne, bezierCoeffsTwo ); + break; + + + case selectedSideStepLeft: + case selectedSideStepRight: + + break; + + default: + + currentTransition = new Transition(currentAnimation, selectedStand, [], 0.3, {x:0.5,y:0.08}, {x:0.28,y:1}); + break; + } + + setInternalState(STANDING); + currentAnimation = selectedStand; + + } + animateAvatar(1,0,principleDirection); + } + break; + + case WALKING: + case SIDE_STEPPING: + if( walkHints > requiredHints || + INTERNAL_STATE===WALKING || + INTERNAL_STATE===SIDE_STEPPING ) { // wait for few consecutive hints (17mS each) + + standHints = 0; + walkHints = 0; + flyHints = 0; + + if( actionToTake === WALKING && INTERNAL_STATE !== WALKING) { + + // initiate the transition + if(currentTransition) { + delete currentTransition; + currentTransition = null; + } + + // set the appropriate start position for the walk wheel + if( principleDirection === DIRECTION_BACKWARDS ) { + + walkWheelPosition = selectedWalk.settings.startAngleBackwards; + + } else { + + walkWheelPosition = selectedWalk.settings.startAngleForwards; + } + + switch(currentAnimation) { + + case selectedStand: + + // Standing to Walking + currentTransition = new Transition(currentAnimation, selectedWalk, [], 0.25, {x:0.5,y:0.08}, {x:0.05,y:0.75}); + break; + + case selectedSideStepLeft: + case selectedSideStepRight: + + break; + + default: + + currentTransition = new Transition(currentAnimation, selectedWalk, [], 0.3, {x:0.5,y:0.08}, {x:0.05,y:0.75}); + break; + } + framesHistory.lastWalkStartTime = new Date().getTime(); + setInternalState(WALKING); + currentAnimation = selectedWalk; + } + else if(actionToTake===SIDE_STEPPING) { + + var selectedSideStep = selectedSideStepRight; + if( principleDirection === DIRECTION_LEFT ) { + + selectedSideStep = selectedSideStepLeft; + + } else { + + selectedSideStep = selectedSideStepRight; + } + + if( INTERNAL_STATE !== SIDE_STEPPING ) { + + if( principleDirection === DIRECTION_LEFT ) { + + walkWheelPosition = sideStepCycleStartLeft; + + } else { + + walkWheelPosition = sideStepCycleStartRight; + } + switch(currentAnimation) { + + case selectedStand: + + break; + + default: + + break; + } + setInternalState(SIDE_STEPPING); + } + + currentAnimation = selectedSideStep; + } + animateAvatar( deltaTime, speed, principleDirection ); + } + break; + + case FLYING: + + if( flyHints > requiredHints - 1 || INTERNAL_STATE===FLYING ) { // wait for a few consecutive hints (17mS each) + + standHints = 0; + walkHints = 0; + flyHints = 0; + + if(INTERNAL_STATE!==FLYING) setInternalState(FLYING); + + // change animation for flying directly up or down. TODO - check RecentMotions, if is a change then put a transition on it + if(principleDirection===DIRECTION_UP) { + + if(currentAnimation !== selectedFlyUp) { + + // initiate a Transition + if(currentTransition && currentTransition.nextAnimation!==selectedFlyUp) { + delete currentTransition; + currentTransition = null; + } + switch(currentAnimation) { + + case selectedStand: + + currentTransition = new Transition(currentAnimation, selectedFlyUp, [], 0.35, {x:0.5,y:0.08}, {x:0.28,y:1}); + break; + + case selectedSideStepLeft: + case selectedSideStepRight: + + break; + + default: + + currentTransition = new Transition(currentAnimation, selectedFlyUp, [], 0.35, {x:0.5,y:0.08}, {x:0.28,y:1}); + break; + } + currentAnimation = selectedFlyUp; + } + + } else if(principleDirection==DIRECTION_DOWN) { + + if(currentAnimation !== selectedFlyDown) { // TODO: as the locomotion gets cleaner (i.e. less false reports from Interface) this value can be reduced + + // initiate a Transition + if(currentTransition && currentTransition.nextAnimation!==selectedFlyDown) { + delete currentTransition; + currentTransition = null; + } + switch(currentAnimation) { + + case selectedStand: + + currentTransition = new Transition(currentAnimation, selectedFlyDown, [], 0.35, {x:0.5,y:0.08}, {x:0.28,y:1}); + break; + + case selectedSideStepLeft: + case selectedSideStepRight: + + break; + + default: + + currentTransition = new Transition(currentAnimation, selectedFlyDown, [], 0.35, {x:0.5,y:0.08}, {x:0.28,y:1}); + break; + } + currentAnimation = selectedFlyDown; + } + + } else { + + if(currentAnimation !== selectedFly) { // TODO: as the locomotion gets cleaner (i.e. less false reports from Interface) this value can be reduced + + // initiate a Transition + if(currentTransition && currentTransition.nextAnimation!==selectedFly) { + delete currentTransition; + currentTransition = null; + } + switch(currentAnimation) { + + case selectedStand: + + currentTransition = new Transition(currentAnimation, selectedFly, [], 0.35, {x:0.5,y:0.08}, {x:0.28,y:1}); + break; + + case selectedSideStepLeft: + case selectedSideStepRight: + + break; + + default: + + currentTransition = new Transition(currentAnimation, selectedFly, [], 0.35, {x:0.5,y:0.08}, {x:0.28,y:1}); + break; + } + currentAnimation = selectedFly; + } + } + animateAvatar(deltaTime, speed, principleDirection); + } + break; + + } // end switch(actionToTake) + + } // end if(!editing) + + + // record the frame's stats for later reference + var thisMotion = new RecentMotion(localVelocity, acceleration, principleDirection, INTERNAL_STATE); + framesHistory.recentMotions.push(thisMotion); + framesHistory.recentMotions.shift(); + + + // before we go, populate the stats overlay + if( statsOn ) { + + var cumulativeTimeMS = Math.floor(cumulativeTime*1000); + var deltaTimeMS = deltaTime * 1000; + var frameExecutionTime = new Date().getTime() - frameStartTime; + if(frameExecutionTime>frameExecutionTimeMax) frameExecutionTimeMax = frameExecutionTime; + + var angluarVelocity = Vec3.length(MyAvatar.getAngularVelocity()); + + var debugInfo = '\n \n \n \n Stats\n--------------------------------------\n \n \n' + + '\nFrame number: '+nFrames + + '\nFrame time: '+deltaTimeMS.toFixed(2) + + ' mS\nRender time: '+frameExecutionTime.toFixed(0) + + ' mS\nLocalised speed: '+speed.toFixed(3) + + ' m/s\nCumulative Time '+cumulativeTimeMS.toFixed(0) + + ' mS\nState: '+internalStateAsString(INTERNAL_STATE) + + ' \nDirection: '+directionAsString(principleDirection) + + ' \nAngular Velocity: ' + angluarVelocity.toFixed(3) + + ' rad/s'; + Overlays.editOverlay(debugStats, {text: debugInfo}); + + // update these every 250 mS (assuming 60 fps) + if( nFrames % 15 === 0 ) { + var debugInfo = ' Periodic Stats\n--------------------------------------\n \n \n' + + ' \n \nRender time peak hold: '+frameExecutionTimeMax.toFixed(0) + + ' mS\n \n \n(L) MyAvatar.getVelocity()' + + ' \n \nlocalVelocityX: '+deltaX.toFixed(1) + + ' m/s\nlocalVelocityY: '+deltaY.toFixed(1) + + ' m/s\nlocalVelocityZ: '+deltaZ.toFixed(1) + + ' m/s\n \n(G) MyAvatar.getAcceleration()' + + ' \n\nAcceleration X: '+accelerationJS.x.toFixed(1) + + ' m/s/s\nAcceleration Y: '+accelerationJS.y.toFixed(1) + + ' m/s/s\nAcceleration Z: '+accelerationJS.z.toFixed(1) + + ' m/s/s\n \n(L) Acceleration using\nMyAvatar.getVelocity()' + + ' \n \nAcceleration X: '+acceleration.x.toFixed(1) + + ' m/s/s\nAcceleration Y: '+acceleration.y.toFixed(1) + + ' m/s/s\nAcceleration Z: '+acceleration.z.toFixed(1) + + ' m/s/s'; + Overlays.editOverlay(debugStatsPeriodic, {text: debugInfo}); + frameExecutionTimeMax = 0; + } + } + } +}); + + +// overlays start + +// controller dimensions +var backgroundWidth = 350; +var backgroundHeight = 700; +var backgroundX = Window.innerWidth-backgroundWidth-58; +var backgroundY = Window.innerHeight/2 - backgroundHeight/2; +var minSliderX = backgroundX + 30; +var maxSliderX = backgroundX + 295; +var sliderRangeX = 295 - 30; +var jointsControlWidth = 200; +var jointsControlHeight = 300; +var jointsControlX = backgroundX + backgroundWidth/2 - jointsControlWidth/2; +var jointsControlY = backgroundY + 242 - jointsControlHeight/2; +var buttonsY = 20; // distance from top of panel to buttons + +// arrays of overlay names +var sliderThumbOverlays = []; // thumb sliders +var backgroundOverlays = []; +var buttonOverlays = []; +var jointsControlOverlays = []; +var bigButtonOverlays = []; + + +// load UI backgrounds +var controlsBackground = Overlays.addOverlay("image", { + bounds: { x: backgroundX, y: backgroundY, width: backgroundWidth, height: backgroundHeight }, + imageURL: pathToOverlays+"ddao-background.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +backgroundOverlays.push(controlsBackground); + +var controlsBackgroundWalkEditStyles = Overlays.addOverlay("image", { + bounds: { x: backgroundX, y: backgroundY, width: backgroundWidth, height: backgroundHeight }, + imageURL: pathToOverlays+"ddao-background-edit-styles.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +backgroundOverlays.push(controlsBackgroundWalkEditStyles); + +var controlsBackgroundWalkEditTweaks = Overlays.addOverlay("image", { + bounds: { x: backgroundX, y: backgroundY, width: backgroundWidth, height: backgroundHeight }, + imageURL: pathToOverlays+"ddao-background-edit-tweaks.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +backgroundOverlays.push(controlsBackgroundWalkEditTweaks); + +var controlsBackgroundWalkEditJoints = Overlays.addOverlay("image", { + bounds: { x: backgroundX, y: backgroundY, width: backgroundWidth, height: backgroundHeight }, + imageURL: pathToOverlays+"ddao-background-edit-joints.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +backgroundOverlays.push(controlsBackgroundWalkEditJoints); + +var controlsBackgroundFlyingEdit = Overlays.addOverlay("image", { + bounds: { x: backgroundX, y: backgroundY, width: backgroundWidth, height: backgroundHeight }, + imageURL: pathToOverlays+"ddao-background-flying-edit.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +backgroundOverlays.push(controlsBackgroundFlyingEdit); + +// minimised tab - not put in array, as is a one off +var controlsMinimisedTab = Overlays.addOverlay("image", { + x: Window.innerWidth-58, y: Window.innerHeight -145, width: 50, height: 50, + //subImage: { x: 0, y: 50, width: 50, height: 50 }, + imageURL: pathToOverlays + 'ddao-minimise-tab.png', + visible: minimised, + alpha: 0.9 + }); + +// load character joint selection control images +var hipsJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-hips.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(hipsJointControl); + +var upperLegsJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-upper-legs.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(upperLegsJointControl); + +var lowerLegsJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-lower-legs.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(lowerLegsJointControl); + +var feetJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-feet.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(feetJointControl); + +var toesJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-toes.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(toesJointControl); + +var spineJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-spine.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(spineJointControl); + +var spine1JointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-spine1.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(spine1JointControl); + +var spine2JointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-spine2.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(spine2JointControl); + +var shouldersJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-shoulders.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(shouldersJointControl); + +var upperArmsJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-upper-arms.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(upperArmsJointControl); + +var forearmsJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-forearms.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(forearmsJointControl); + +var handsJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-hands.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(handsJointControl); + +var headJointControl = Overlays.addOverlay("image", { + bounds: { x: jointsControlX, y: jointsControlY, width: 200, height: 300}, + imageURL: pathToOverlays+"ddao-background-edit-head.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +jointsControlOverlays.push(headJointControl); + + +// sider thumb overlays +var sliderOne = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderOne); +var sliderTwo = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderTwo); +var sliderThree = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderThree); +var sliderFour = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderFour); +var sliderFive = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderFive); +var sliderSix = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderSix); +var sliderSeven = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderSeven); +var sliderEight = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderEight); +var sliderNine = Overlays.addOverlay("image", { + bounds: { x: 0, y: 0, width: 25, height: 25 }, + imageURL: pathToOverlays+"ddao-slider-handle.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +sliderThumbOverlays.push(sliderNine); + + +// button overlays +var onButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+20, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-on-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(onButton); +var offButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+20, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-off-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(offButton); +var configWalkButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-walk-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkButton); +var configWalkButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-walk-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkButtonSelected); +var configStandButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+146, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-stand-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configStandButton); +var configStandButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+146, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-stand-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configStandButtonSelected); + +var configFlyingButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+209, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-fly-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configFlyingButton); +var configFlyingButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+209, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-fly-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configFlyingButtonSelected); + +var configFlyingUpButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-fly-up-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configFlyingUpButton); +var configFlyingUpButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-fly-up-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configFlyingUpButtonSelected); + +var configFlyingDownButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+146, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-fly-down-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configFlyingDownButton); +var configFlyingDownButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+146, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-fly-down-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configFlyingDownButtonSelected); + +var hideButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+272, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-hide-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(hideButton); +var hideButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+272, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-hide-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(hideButtonSelected); +var configWalkStylesButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-walk-styles-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkStylesButton); +var configWalkStylesButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-walk-styles-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkStylesButtonSelected); +var configWalkTweaksButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+146, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-walk-tweaks-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkTweaksButton); +var configWalkTweaksButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+146, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-walk-tweaks-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkTweaksButtonSelected); + +var configSideStepLeftButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-sidestep-left-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configSideStepLeftButton); +var configSideStepLeftButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+83, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-sidestep-left-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configSideStepLeftButtonSelected); + +var configSideStepRightButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+209, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-sidestep-right-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configSideStepRightButton); +var configSideStepRightButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+209, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-edit-sidestep-right-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configSideStepRightButtonSelected); + +var configWalkJointsButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+209, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-bones-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkJointsButton); +var configWalkJointsButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+209, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-bones-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(configWalkJointsButtonSelected); + +var backButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX+272, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-back-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(backButton); +var backButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX+272, y: backgroundY+buttonsY, width: 60, height: 47 }, + imageURL: pathToOverlays+"ddao-back-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +buttonOverlays.push(backButtonSelected); + +// big button overlays - front panel +var bigButtonYOffset = 408; // distance from top of panel to top of first button + +var femaleBigButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-female-big-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(femaleBigButton); + +var femaleBigButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-female-big-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(femaleBigButtonSelected); + +var maleBigButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset + 60, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-male-big-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(maleBigButton); + +var maleBigButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset + 60, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-male-big-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(maleBigButtonSelected); + +var armsFreeBigButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset + 120, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-arms-free-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(armsFreeBigButton); + +var armsFreeBigButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset + 120, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-arms-free-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(armsFreeBigButtonSelected); + +var footstepsBigButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset + 180, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-footsteps-big-button.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(footstepsBigButton); + +var footstepsBigButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset + 180, width: 230, height: 36}, + imageURL: pathToOverlays+"ddao-footsteps-big-button-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(footstepsBigButtonSelected); + + +// walk styles +bigButtonYOffset = 121; +var strutWalkBigButton = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset, width: 230, height: 36 }, + imageURL: pathToOverlays+"ddao-walk-select-button-strut.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(strutWalkBigButton); + +var strutWalkBigButtonSelected = Overlays.addOverlay("image", { + bounds: { x: backgroundX + backgroundWidth/2 - 115, y: backgroundY + bigButtonYOffset, width: 230, height: 36 }, + imageURL: pathToOverlays+"ddao-walk-select-button-strut-selected.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +bigButtonOverlays.push(strutWalkBigButtonSelected); + +// overlays to show the walk wheel stats +var walkWheelZLine = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z:hipsToFeetDistance }, + end: { x: 0, y: 0, z: -hipsToFeetDistance }, + color: { red: 0, green: 255, blue: 255}, + alpha: 1, + lineWidth: 5, + visible: statsOn, + anchor: "MyAvatar" + }); +var walkWheelYLine = Overlays.addOverlay("line3d", { + position: { x: 0, y: hipsToFeetDistance, z:0 }, + end: { x: 0, y: -hipsToFeetDistance, z:0 }, + color: { red: 255, green: 0, blue: 255}, + alpha: 1, + lineWidth: 5, + visible: statsOn, + anchor: "MyAvatar" + }); +var debugStats = Overlays.addOverlay("text", { + x: backgroundX-199, y: backgroundY, + width: 200, + height: 180, + color: { red: 204, green: 204, blue: 204}, + topMargin: 10, + leftMargin: 15, + visible: statsOn, + backgroundColor: { red: 34, green: 34, blue: 34}, + alpha: 1.0, + text: "Debug Stats\n\n\nNothing to report yet." + }); +var debugStatsPeriodic = Overlays.addOverlay("text", { + x: backgroundX-199, y: backgroundY+179, + width: 200, + height: 392, + color: { red: 204, green: 204, blue: 204}, + topMargin: 5, + leftMargin: 15, + visible: statsOn, + backgroundColor: { red: 34, green: 34, blue: 34}, + alpha: 1.0, + text: "Debug Stats\n\n\nNothing to report yet." + }); +var walkWheelStats = Overlays.addOverlay("text", { + x: backgroundX-199, y: backgroundY+510, + width: 200, + height: 190, + color: { red: 204, green: 204, blue: 204}, + topMargin: 5, + leftMargin: 15, + visible: statsOn, + backgroundColor: { red: 34, green: 34, blue: 34}, + alpha: 1.0, + text: "WalkWheel Stats\n\n\nNothing to report yet.\n\n\nPlease start walking\nto see the walkwheel." + }); + +// various show / hide GUI element functions +function doStandardMenu() { + hidebuttonOverlays(); + hideJointControls(); + setBackground(controlsBackground); + if(powerOn) setButtonOverlayVisible(onButton); + else setButtonOverlayVisible(offButton); + setButtonOverlayVisible(configWalkButton); + setButtonOverlayVisible(configStandButton); + setButtonOverlayVisible(configFlyingButton); + setButtonOverlayVisible(hideButton); + setSliderThumbsVisible(false); + showFrontPanelButtons(true); + showWalkStyleButtons(false); +} +function showFrontPanelButtons(showButtons) { + + if(avatarGender===FEMALE) { + Overlays.editOverlay(femaleBigButtonSelected, { visible: showButtons } ); + Overlays.editOverlay(femaleBigButton, { visible: false } ); + Overlays.editOverlay(maleBigButtonSelected, { visible: false } ); + Overlays.editOverlay(maleBigButton, { visible: showButtons } ); + } else { + Overlays.editOverlay(femaleBigButtonSelected, { visible: false } ); + Overlays.editOverlay(femaleBigButton, { visible: showButtons } ); + Overlays.editOverlay(maleBigButtonSelected, { visible: showButtons } ); + Overlays.editOverlay(maleBigButton, { visible: false } ); + } + if(armsFree) { + Overlays.editOverlay(armsFreeBigButtonSelected, { visible: showButtons } ); + Overlays.editOverlay(armsFreeBigButton, { visible: false } ); + } else { + Overlays.editOverlay(armsFreeBigButtonSelected, { visible: false } ); + Overlays.editOverlay(armsFreeBigButton, { visible: showButtons } ); + } + if(playFootStepSounds) { + Overlays.editOverlay(footstepsBigButtonSelected, { visible: showButtons } ); + Overlays.editOverlay(footstepsBigButton, { visible: false } ); + } else { + Overlays.editOverlay(footstepsBigButtonSelected, { visible: false } ); + Overlays.editOverlay(footstepsBigButton, { visible: showButtons } ); + } +} +function minimiseDialog() { + + if(minimised) { + setBackground(); + hidebuttonOverlays(); + setSliderThumbsVisible(false); + hideJointControls(); + showFrontPanelButtons(false); + Overlays.editOverlay(controlsMinimisedTab, { visible: true } ); + } else { + setInternalState(STANDING); // show all the controls again + Overlays.editOverlay(controlsMinimisedTab, { visible: false } ); + } +} +function setBackground(backgroundName) { + for(var i in backgroundOverlays) { + if(backgroundOverlays[i] === backgroundName) + Overlays.editOverlay(backgroundName, { visible: true } ); + else Overlays.editOverlay(backgroundOverlays[i], { visible: false } ); + } +} +function setButtonOverlayVisible(buttonOverlayName) { + for(var i in buttonOverlays) { + if(buttonOverlays[i] === buttonOverlayName) { + Overlays.editOverlay(buttonOverlayName, { visible: true } ); + } + } +} +// top row menu type buttons (smaller) +function hidebuttonOverlays() { + for(var i in buttonOverlays) { + Overlays.editOverlay(buttonOverlays[i], { visible: false } ); + } +} +function hideJointControls() { + for(var i in jointsControlOverlays) { + Overlays.editOverlay(jointsControlOverlays[i], { visible: false } ); + } +} +function setSliderThumbsVisible(thumbsVisible) { + for(var i = 0 ; i < sliderThumbOverlays.length ; i++) { + Overlays.editOverlay(sliderThumbOverlays[i], { visible: thumbsVisible } ); + } +} +function initialiseJointsEditingPanel(propertyIndex) { + + selectedJointIndex = propertyIndex; + + // set the image for the selected joint on the character control + hideJointControls(); + switch (selectedJointIndex) { + case 0: + Overlays.editOverlay(hipsJointControl, { visible: true }); + break; + case 1: + Overlays.editOverlay(upperLegsJointControl, { visible: true }); + break; + case 2: + Overlays.editOverlay(lowerLegsJointControl, { visible: true }); + break; + case 3: + Overlays.editOverlay(feetJointControl, { visible: true }); + break; + case 4: + Overlays.editOverlay(toesJointControl, { visible: true }); + break; + case 5: + Overlays.editOverlay(spineJointControl, { visible: true }); + break; + case 6: + Overlays.editOverlay(spine1JointControl, { visible: true }); + break; + case 7: + Overlays.editOverlay(spine2JointControl, { visible: true }); + break; + case 8: + Overlays.editOverlay(shouldersJointControl, { visible: true }); + break; + case 9: + Overlays.editOverlay(upperArmsJointControl, { visible: true }); + break; + case 10: + Overlays.editOverlay(forearmsJointControl, { visible: true }); + break; + case 11: + Overlays.editOverlay(handsJointControl, { visible: true }); + break; + case 12: + Overlays.editOverlay(headJointControl, { visible: true }); + break; + } + + // set sliders to adjust individual joint properties + var i = 0; + var yLocation = backgroundY+359; + + // pitch your role + var sliderXPos = currentAnimation.joints[selectedJointIndex].pitch + / sliderRanges.joints[selectedJointIndex].pitchRange * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + sliderXPos = currentAnimation.joints[selectedJointIndex].yaw + / sliderRanges.joints[selectedJointIndex].yawRange * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + sliderXPos = currentAnimation.joints[selectedJointIndex].roll + / sliderRanges.joints[selectedJointIndex].rollRange * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + + // set phases (full range, -180 to 180) + sliderXPos = (90 + currentAnimation.joints[selectedJointIndex].pitchPhase/2)/180 * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + sliderXPos = (90 + currentAnimation.joints[selectedJointIndex].yawPhase/2)/180 * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + sliderXPos = (90 + currentAnimation.joints[selectedJointIndex].rollPhase/2)/180 * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + + // offset ranges are also -ve thr' zero to +ve, so have to offset + sliderXPos = (((sliderRanges.joints[selectedJointIndex].pitchOffsetRange+currentAnimation.joints[selectedJointIndex].pitchOffset)/2) + /sliderRanges.joints[selectedJointIndex].pitchOffsetRange) * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + sliderXPos = (((sliderRanges.joints[selectedJointIndex].yawOffsetRange+currentAnimation.joints[selectedJointIndex].yawOffset)/2) + /sliderRanges.joints[selectedJointIndex].yawOffsetRange) * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); + sliderXPos = (((sliderRanges.joints[selectedJointIndex].rollOffsetRange+currentAnimation.joints[selectedJointIndex].rollOffset)/2) + /sliderRanges.joints[selectedJointIndex].rollOffsetRange) * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=30, visible: true }); +} + +function initialiseWalkTweaks() { + + // set sliders to adjust walk properties + var i = 0; + var yLocation = backgroundY+71; + + var sliderXPos = currentAnimation.settings.baseFrequency / MAX_WALK_SPEED * sliderRangeX; // walk speed + Overlays.editOverlay(sliderThumbOverlays[i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = 0 * sliderRangeX; // start flying speed - depricated + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = currentAnimation.joints[0].sway / sliderRanges.joints[0].swayRange * sliderRangeX; // Hips sway + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = currentAnimation.joints[0].bob / sliderRanges.joints[0].bobRange * sliderRangeX; // Hips bob + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = currentAnimation.joints[0].thrust / sliderRanges.joints[0].thrustRange * sliderRangeX; // Hips thrust + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = (((sliderRanges.joints[1].rollOffsetRange+currentAnimation.joints[1].rollOffset)/2) // legs separation - is upper legs roll offset + / sliderRanges.joints[1].rollOffsetRange) * sliderRangeX; + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = currentAnimation.joints[1].pitch / sliderRanges.joints[1].pitchRange * sliderRangeX; // stride - is upper legs pitch + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = currentAnimation.joints[9].yaw / sliderRanges.joints[9].yawRange * sliderRangeX; // arms swing - is upper arms yaw + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); + sliderXPos = (((sliderRanges.joints[9].pitchOffsetRange-currentAnimation.joints[9].pitchOffset)/2) + / sliderRanges.joints[9].pitchOffsetRange) * sliderRangeX; // arms out - is upper arms pitch offset + Overlays.editOverlay(sliderThumbOverlays[++i], { x: minSliderX + sliderXPos, y: yLocation+=60, visible: true }); +} + +function showWalkStyleButtons(showButtons) { + + // set all big buttons to hidden, but skip the first 8, as are for the front panel + for(var i = 8 ; i < bigButtonOverlays.length ; i++) { + Overlays.editOverlay(bigButtonOverlays[i], { visible: false }); + } + + if(!showButtons) return; + + // set all the non-selected ones to showing + for(var i = 8 ; i < bigButtonOverlays.length ; i+=2) { + Overlays.editOverlay(bigButtonOverlays[i], { visible: showButtons }); + } + + // set the currently selected one + if(selectedWalk === femaleStrutWalk || selectedWalk === maleStrutWalk) { + Overlays.editOverlay(strutWalkBigButtonSelected, { visible: showButtons }); + Overlays.editOverlay(strutWalkBigButton, {visible: false}); + } +} + +// mouse event handlers +var movingSliderOne = false; +var movingSliderTwo = false; +var movingSliderThree = false; +var movingSliderFour = false; +var movingSliderFive = false; +var movingSliderSix = false; +var movingSliderSeven = false; +var movingSliderEight = false; +var movingSliderNine = false; + +function mousePressEvent(event) { + + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + // check for a character joint control click + switch (clickedOverlay) { + + case hideButton: + Overlays.editOverlay(hideButton, { visible: false } ); + Overlays.editOverlay(hideButtonSelected, { visible: true } ); + return; + + case backButton: + Overlays.editOverlay(backButton, { visible: false } ); + Overlays.editOverlay(backButtonSelected, { visible: true } ); + return; + + case controlsMinimisedTab: + // TODO: add visual user feedback for tab click + return; + + case footstepsBigButton: + playFootStepSounds = true; + Overlays.editOverlay(footstepsBigButtonSelected, { visible: true } ); + Overlays.editOverlay(footstepsBigButton, { visible: false } ); + return; + + case footstepsBigButtonSelected: + playFootStepSounds = false; + Overlays.editOverlay(footstepsBigButton, { visible: true } ); + Overlays.editOverlay(footstepsBigButtonSelected, { visible: false } ); + return; + + case femaleBigButton: + case maleBigButtonSelected: + avatarGender = FEMALE; + selectedWalk = femaleStrutWalk; + selectedStand = femaleStandOne; + selectedFlyUp = femaleFlyingUp; + selectedFly = femaleFlying; + selectedFlyDown = femaleFlyingDown; + selectedSideStepLeft = femaleSideStepLeft; + selectedSideStepRight = femaleSideStepRight; + Overlays.editOverlay(femaleBigButtonSelected, { visible: true } ); + Overlays.editOverlay(femaleBigButton, { visible: false } ); + Overlays.editOverlay(maleBigButton, { visible: true } ); + Overlays.editOverlay(maleBigButtonSelected, { visible: false } ); + return; + + case armsFreeBigButton: + armsFree = true; + Overlays.editOverlay(armsFreeBigButtonSelected, { visible: true } ); + Overlays.editOverlay(armsFreeBigButton, { visible: false } ); + return; + + case armsFreeBigButtonSelected: + armsFree = false; + Overlays.editOverlay(armsFreeBigButtonSelected, { visible: false } ); + Overlays.editOverlay(armsFreeBigButton, { visible: true } ); + return; + + case maleBigButton: + case femaleBigButtonSelected: + avatarGender = MALE; + selectedWalk = maleStrutWalk; + selectedStand = maleStandOne; + selectedFlyUp = maleFlyingUp; + selectedFly = maleFlying; + selectedFlyDown = maleFlyingDown; + selectedSideStepLeft = maleSideStepLeft; + selectedSideStepRight = maleSideStepRight; + Overlays.editOverlay(femaleBigButton, { visible: true } ); + Overlays.editOverlay(femaleBigButtonSelected, { visible: false } ); + Overlays.editOverlay(maleBigButtonSelected, { visible: true } ); + Overlays.editOverlay(maleBigButton, { visible: false } ); + return; + + + case strutWalkBigButton: + if(avatarGender===FEMALE) selectedWalk = femaleStrutWalk; + else selectedWalk = maleStrutWalk; + currentAnimation = selectedWalk; + showWalkStyleButtons(true); + return; + + case strutWalkBigButtonSelected: + + // toggle forwards / backwards walk display + if(principleDirection===DIRECTION_FORWARDS) { + principleDirection=DIRECTION_BACKWARDS; + } else principleDirection=DIRECTION_FORWARDS; + return; + + case sliderOne: + movingSliderOne = true; + return; + + case sliderTwo: + movingSliderTwo = true; + return; + + case sliderThree: + movingSliderThree = true; + return; + + case sliderFour: + movingSliderFour = true; + return; + + case sliderFive: + movingSliderFive = true; + return; + + case sliderSix: + movingSliderSix = true; + return; + + case sliderSeven: + movingSliderSeven = true; + return; + + case sliderEight: + movingSliderEight = true; + return; + + case sliderNine: + movingSliderNine = true; + return; + } + + if( INTERNAL_STATE===CONFIG_WALK_JOINTS || + INTERNAL_STATE===CONFIG_STANDING || + INTERNAL_STATE===CONFIG_FLYING || + INTERNAL_STATE===CONFIG_FLYING_UP || + INTERNAL_STATE===CONFIG_FLYING_DOWN || + INTERNAL_STATE===CONFIG_SIDESTEP_LEFT || + INTERNAL_STATE===CONFIG_SIDESTEP_RIGHT ) { + + // check for new joint selection and update display accordingly + var clickX = event.x - backgroundX - 75; + var clickY = event.y - backgroundY - 92; + + if(clickX>60&&clickX<120&&clickY>123&&clickY<155) { + initialiseJointsEditingPanel(0); + return; + } + else if(clickX>63&&clickX<132&&clickY>156&&clickY<202) { + initialiseJointsEditingPanel(1); + return; + } + else if(clickX>58&&clickX<137&&clickY>203&&clickY<250) { + initialiseJointsEditingPanel(2); + return; + } + else if(clickX>58&&clickX<137&&clickY>250&&clickY<265) { + initialiseJointsEditingPanel(3); + return; + } + else if(clickX>58&&clickX<137&&clickY>265&&clickY<280) { + initialiseJointsEditingPanel(4); + return; + } + else if(clickX>78&&clickX<121&&clickY>111&&clickY<128) { + initialiseJointsEditingPanel(5); + return; + } + else if(clickX>78&&clickX<128&&clickY>89&&clickY<111) { + initialiseJointsEditingPanel(6); + return; + } + else if(clickX>85&&clickX<118&&clickY>77&&clickY<94) { + initialiseJointsEditingPanel(7); + return; + } + else if(clickX>64&&clickX<125&&clickY>55&&clickY<77) { + initialiseJointsEditingPanel(8); + return; + } + else if((clickX>44&&clickX<73&&clickY>71&&clickY<94) + ||(clickX>125&&clickX<144&&clickY>71&&clickY<94)) { + initialiseJointsEditingPanel(9); + return; + } + else if((clickX>28&&clickX<57&&clickY>94&&clickY<119) + ||(clickX>137&&clickX<170&&clickY>97&&clickY<114)) { + initialiseJointsEditingPanel(10); + return; + } + else if((clickX>18&&clickX<37&&clickY>115&&clickY<136) + ||(clickX>157&&clickX<182&&clickY>115&&clickY<136)) { + initialiseJointsEditingPanel(11); + return; + } + else if(clickX>81&&clickX<116&&clickY>12&&clickY<53) { + initialiseJointsEditingPanel(12); + return; + } + } +} +function mouseMoveEvent(event) { + + // only need deal with slider changes + if(powerOn) { + + if( INTERNAL_STATE===CONFIG_WALK_JOINTS || + INTERNAL_STATE===CONFIG_STANDING || + INTERNAL_STATE===CONFIG_FLYING || + INTERNAL_STATE===CONFIG_FLYING_UP || + INTERNAL_STATE===CONFIG_FLYING_DOWN || + INTERNAL_STATE===CONFIG_SIDESTEP_LEFT || + INTERNAL_STATE===CONFIG_SIDESTEP_RIGHT ) { + + var thumbClickOffsetX = event.x - minSliderX; + var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX; + if(thumbPositionNormalised<0) thumbPositionNormalised = 0; + if(thumbPositionNormalised>1) thumbPositionNormalised = 1; + var sliderX = thumbPositionNormalised * sliderRangeX ; // sets range + + if(movingSliderOne) { // currently selected joint pitch + Overlays.editOverlay(sliderOne, { x: sliderX + minSliderX} ); + currentAnimation.joints[selectedJointIndex].pitch = thumbPositionNormalised * sliderRanges.joints[selectedJointIndex].pitchRange; + } + else if(movingSliderTwo) { // currently selected joint yaw + Overlays.editOverlay(sliderTwo, { x: sliderX + minSliderX} ); + currentAnimation.joints[selectedJointIndex].yaw = thumbPositionNormalised * sliderRanges.joints[selectedJointIndex].yawRange; + } + else if(movingSliderThree) { // currently selected joint roll + Overlays.editOverlay(sliderThree, { x: sliderX + minSliderX} ); + currentAnimation.joints[selectedJointIndex].roll = thumbPositionNormalised * sliderRanges.joints[selectedJointIndex].rollRange; + } + else if(movingSliderFour) { // currently selected joint pitch phase + Overlays.editOverlay(sliderFour, { x: sliderX + minSliderX} ); + var newPhase = 360 * thumbPositionNormalised - 180; + currentAnimation.joints[selectedJointIndex].pitchPhase = newPhase; + } + else if(movingSliderFive) { // currently selected joint yaw phase; + Overlays.editOverlay(sliderFive, { x: sliderX + minSliderX} ); + var newPhase = 360 * thumbPositionNormalised - 180; + currentAnimation.joints[selectedJointIndex].yawPhase = newPhase; + } + else if(movingSliderSix) { // currently selected joint roll phase + Overlays.editOverlay(sliderSix, { x: sliderX + minSliderX} ); + var newPhase = 360 * thumbPositionNormalised - 180; + currentAnimation.joints[selectedJointIndex].rollPhase = newPhase; + } + else if(movingSliderSeven) { // currently selected joint pitch offset + Overlays.editOverlay(sliderSeven, { x: sliderX + minSliderX} ); // currently selected joint pitch offset + var newOffset = (thumbPositionNormalised-0.5) * 2 * sliderRanges.joints[selectedJointIndex].pitchOffsetRange; + currentAnimation.joints[selectedJointIndex].pitchOffset = newOffset; + } + else if(movingSliderEight) { // currently selected joint yaw offset + Overlays.editOverlay(sliderEight, { x: sliderX + minSliderX} ); // currently selected joint yaw offset + var newOffset = (thumbPositionNormalised-0.5) * 2 * sliderRanges.joints[selectedJointIndex].yawOffsetRange; + currentAnimation.joints[selectedJointIndex].yawOffset = newOffset; + } + else if(movingSliderNine) { // currently selected joint roll offset + Overlays.editOverlay(sliderNine, { x: sliderX + minSliderX} ); // currently selected joint roll offset + var newOffset = (thumbPositionNormalised-0.5) * 2 * sliderRanges.joints[selectedJointIndex].rollOffsetRange; + currentAnimation.joints[selectedJointIndex].rollOffset = newOffset; + } + } + else if(INTERNAL_STATE===CONFIG_WALK_TWEAKS) { + + var thumbClickOffsetX = event.x - minSliderX; + var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX; + if(thumbPositionNormalised<0) thumbPositionNormalised = 0; + if(thumbPositionNormalised>1) thumbPositionNormalised = 1; + var sliderX = thumbPositionNormalised * sliderRangeX ; // sets range + + if(movingSliderOne) { // walk speed + paused = true; // avoid nasty jittering + Overlays.editOverlay(sliderOne, { x: sliderX + minSliderX} ); + currentAnimation.settings.baseFrequency = thumbPositionNormalised * MAX_WALK_SPEED; + } + else if(movingSliderTwo) { // take flight speed + Overlays.editOverlay(sliderTwo, { x: sliderX + minSliderX} ); + //currentAnimation.settings.takeFlightVelocity = thumbPositionNormalised * 300; + } + else if(movingSliderThree) { // hips sway + Overlays.editOverlay(sliderThree, { x: sliderX + minSliderX} ); + currentAnimation.joints[0].sway = thumbPositionNormalised * sliderRanges.joints[0].swayRange; + } + else if(movingSliderFour) { // hips bob + Overlays.editOverlay(sliderFour, { x: sliderX + minSliderX} ); + currentAnimation.joints[0].bob = thumbPositionNormalised * sliderRanges.joints[0].bobRange; + } + else if(movingSliderFive) { // hips thrust + Overlays.editOverlay(sliderFive, { x: sliderX + minSliderX} ); + currentAnimation.joints[0].thrust = thumbPositionNormalised * sliderRanges.joints[0].thrustRange; + } + else if(movingSliderSix) { // legs separation + Overlays.editOverlay(sliderSix, { x: sliderX + minSliderX} ); + currentAnimation.joints[1].rollOffset = (thumbPositionNormalised-0.5) * 2 * sliderRanges.joints[1].rollOffsetRange; + } + else if(movingSliderSeven) { // stride + Overlays.editOverlay(sliderSeven, { x: sliderX + minSliderX} ); + currentAnimation.joints[1].pitch = thumbPositionNormalised * sliderRanges.joints[1].pitchRange; + } + else if(movingSliderEight) { // arms swing = upper arms yaw + Overlays.editOverlay(sliderEight, { x: sliderX + minSliderX} ); + currentAnimation.joints[9].yaw = thumbPositionNormalised * sliderRanges.joints[9].yawRange; + } + else if(movingSliderNine) { // arms out = upper arms pitch offset + Overlays.editOverlay(sliderNine, { x: sliderX + minSliderX} ); + currentAnimation.joints[9].pitchOffset = (thumbPositionNormalised-0.5) * -2 * sliderRanges.joints[9].pitchOffsetRange; + } + } + } +} +function mouseReleaseEvent(event) { + + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + if(paused) paused = false; + + if(clickedOverlay === offButton) { + powerOn = true; + Overlays.editOverlay(offButton, { visible: false } ); + Overlays.editOverlay(onButton, { visible: true } ); + stand(); + } + else if(clickedOverlay === hideButton || clickedOverlay === hideButtonSelected){ + Overlays.editOverlay(hideButton, { visible: true } ); + Overlays.editOverlay(hideButtonSelected, { visible: false } ); + minimised = true; + minimiseDialog(); + } + else if(clickedOverlay === controlsMinimisedTab) { + minimised = false; + minimiseDialog(); + } + else if(powerOn) { + + if(movingSliderOne) movingSliderOne = false; + else if(movingSliderTwo) movingSliderTwo = false; + else if(movingSliderThree) movingSliderThree = false; + else if(movingSliderFour) movingSliderFour = false; + else if(movingSliderFive) movingSliderFive = false; + else if(movingSliderSix) movingSliderSix = false; + else if(movingSliderSeven) movingSliderSeven = false; + else if(movingSliderEight) movingSliderEight = false; + else if(movingSliderNine) movingSliderNine = false; + else { + + switch(clickedOverlay) { + + case configWalkButtonSelected: + case configStandButtonSelected: + case configSideStepLeftButtonSelected: + case configSideStepRightButtonSelected: + case configFlyingButtonSelected: + case configFlyingUpButtonSelected: + case configFlyingDownButtonSelected: + case configWalkStylesButtonSelected: + case configWalkTweaksButtonSelected: + case configWalkJointsButtonSelected: + setInternalState(STANDING); + break; + + case onButton: + powerOn = false; + setInternalState(STANDING); + Overlays.editOverlay(offButton, { visible: true } ); + Overlays.editOverlay(onButton, { visible: false } ); + resetJoints(); + break; + + case backButton: + case backButtonSelected: + Overlays.editOverlay(backButton, { visible: false } ); + Overlays.editOverlay(backButtonSelected, { visible: false } ); + setInternalState(STANDING); + break; + + case configWalkStylesButton: + setInternalState(CONFIG_WALK_STYLES); + break; + + case configWalkTweaksButton: + setInternalState(CONFIG_WALK_TWEAKS); + break; + + case configWalkJointsButton: + setInternalState(CONFIG_WALK_JOINTS); + break; + + case configWalkButton: + setInternalState(CONFIG_WALK_STYLES); // set the default walk adjustment panel here (i.e. first panel shown when Walk button clicked) + break; + + case configStandButton: + setInternalState(CONFIG_STANDING); + break; + + case configSideStepLeftButton: + setInternalState(CONFIG_SIDESTEP_LEFT); + break; + + case configSideStepRightButton: + setInternalState(CONFIG_SIDESTEP_RIGHT); + break; + + case configFlyingButton: + setInternalState(CONFIG_FLYING); + break; + + case configFlyingUpButton: + setInternalState(CONFIG_FLYING_UP); + break; + + case configFlyingDownButton: + setInternalState(CONFIG_FLYING_DOWN); + break; + } + } + } +} +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + +// Script ending +Script.scriptEnding.connect(function() { + + // remove the background overlays + for(var i in backgroundOverlays) { + Overlays.deleteOverlay(backgroundOverlays[i]); + } + // remove the button overlays + for(var i in buttonOverlays) { + Overlays.deleteOverlay(buttonOverlays[i]); + } + // remove the slider thumb overlays + for(var i in sliderThumbOverlays) { + Overlays.deleteOverlay(sliderThumbOverlays[i]); + } + // remove the character joint control overlays + for(var i in bigButtonOverlays) { + Overlays.deleteOverlay(jointsControlOverlays[i]); + } + // remove the big button overlays + for(var i in bigButtonOverlays) { + Overlays.deleteOverlay(bigButtonOverlays[i]); + } + // remove the mimimised tab + Overlays.deleteOverlay(controlsMinimisedTab); + + // remove the walk wheel overlays + Overlays.deleteOverlay(walkWheelYLine); + Overlays.deleteOverlay(walkWheelZLine); + Overlays.deleteOverlay(walkWheelStats); + + // remove the debug stats overlays + Overlays.deleteOverlay(debugStats); + Overlays.deleteOverlay(debugStatsPeriodic); +}); + +var sideStep = 0.002; // i.e. 2mm increments whilst sidestepping - JS movement keys don't work well :-( +function keyPressEvent(event) { + + if (event.text == "q") { + // export currentAnimation as json string when q key is pressed. reformat result at http://www.freeformatter.com/json-formatter.html + print('\n'); + print('walk.js dumping animation: '+currentAnimation.name+'\n'); + print('\n'); + print(JSON.stringify(currentAnimation), null, '\t'); + } + else if (event.text == "t") { + statsOn = !statsOn; + Overlays.editOverlay(debugStats, {visible: statsOn}); + Overlays.editOverlay(debugStatsPeriodic, {visible: statsOn}); + Overlays.editOverlay(walkWheelStats, {visible: statsOn}); + Overlays.editOverlay(walkWheelYLine, {visible: statsOn}); + Overlays.editOverlay(walkWheelZLine, {visible: statsOn}); + } +} +Controller.keyPressEvent.connect(keyPressEvent); + +// get the list of joint names +var jointList = MyAvatar.getJointNames(); + +// clear the joint data so can calculate hips to feet distance +for(var i = 0 ; i < 5 ; i++) { + MyAvatar.setJointData(i, Quat.fromPitchYawRollDegrees(0,0,0)); +} +// used to position the visual representation of the walkwheel only +var hipsToFeetDistance = MyAvatar.getJointPosition("Hips").y - MyAvatar.getJointPosition("RightFoot").y; + +// This script is designed around the Finite State Machine (FSM) model, so to start things up we just select the STANDING state. +setInternalState(STANDING); \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 215848b355..a31b148d1b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -350,7 +350,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkDiskCache* cache = new QNetworkDiskCache(); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); networkAccessManager.setCache(cache); diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index cb3d43925d..4808842036 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -28,7 +28,7 @@ FileLogger::FileLogger(QObject* parent) : setExtraDebugging(false); _fileName = FileUtils::standardPath(LOGS_DIRECTORY); - QHostAddress clientAddress = QHostAddress(getHostOrderLocalAddress()); + QHostAddress clientAddress = getLocalAddress(); QDateTime now = QDateTime::currentDateTime(); _fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT))); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 75eb96adc9..954f04f47d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1238,7 +1238,7 @@ void Menu::toggleLocationList() { if (!_userLocationsDialog) { JavascriptObjectMap locationObjectMap; locationObjectMap.insert("InterfaceLocation", LocationScriptingInterface::getInstance()); - _userLocationsDialog = DataWebDialog::dialogForPath("/locations", locationObjectMap); + _userLocationsDialog = DataWebDialog::dialogForPath("/user/locations", locationObjectMap); } if (!_userLocationsDialog->isVisible()) { @@ -1282,7 +1282,7 @@ void Menu::nameLocation() { if (!_newLocationDialog) { JavascriptObjectMap locationObjectMap; locationObjectMap.insert("InterfaceLocation", LocationScriptingInterface::getInstance()); - _newLocationDialog = DataWebDialog::dialogForPath("/locations/new", locationObjectMap); + _newLocationDialog = DataWebDialog::dialogForPath("/user/locations/new", locationObjectMap); } if (!_newLocationDialog->isVisible()) { diff --git a/interface/src/ScriptsModel.cpp b/interface/src/ScriptsModel.cpp index 8bea122338..b95b6ae735 100644 --- a/interface/src/ScriptsModel.cpp +++ b/interface/src/ScriptsModel.cpp @@ -117,7 +117,7 @@ void ScriptsModel::requestRemoteFiles(QString marker) { } url.setQuery(query); - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); QNetworkReply* reply = networkAccessManager.get(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a31c25b572..087d670760 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -49,8 +49,8 @@ const float PITCH_SPEED = 100.0f; // degrees/sec const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions const float COLLISION_RADIUS_SCALE = 0.125f; -const float MIN_KEYBOARD_CONTROL_SPEED = 1.5f; -const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED; +const float MIN_KEYBOARD_CONTROL_SPEED = 0.50f; +const float MAX_WALKING_SPEED = 4.5f; // TODO: normalize avatar speed for standard avatar size, then scale all motion logic // to properly follow avatar size. @@ -1262,21 +1262,19 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe // Compute the target keyboard velocity (which ramps up slowly, and damps very quickly) // the max magnitude of which depends on what we're doing: - float finalMaxMotorSpeed = hasFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED; float motorLength = glm::length(_keyboardMotorVelocity); + float finalMaxMotorSpeed = hasFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED; + float speedGrowthTimescale = 2.0f; + float speedIncreaseFactor = 1.8f; + motorLength *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) { // an active keyboard motor should never be slower than this - _keyboardMotorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction; + motorLength = _scale * MIN_KEYBOARD_CONTROL_SPEED; motorEfficiency = 1.0f; - } else { - float KEYBOARD_MOTOR_LENGTH_TIMESCALE = 2.0f; - float INCREASE_FACTOR = 1.8f; - motorLength *= 1.0f + glm::clamp(deltaTime / KEYBOARD_MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR; - if (motorLength > finalMaxMotorSpeed) { - motorLength = finalMaxMotorSpeed; - } - _keyboardMotorVelocity = motorLength * direction; + } else if (motorLength > finalMaxMotorSpeed) { + motorLength = finalMaxMotorSpeed; } + _keyboardMotorVelocity = motorLength * direction; _isPushing = true; } } else { diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 26505af0be..d81eaa36b4 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -26,7 +26,7 @@ AddressBarDialog::AddressBarDialog() : void AddressBarDialog::setupUI() { const QString DIALOG_STYLESHEET = "font-family: Helvetica, Arial, sans-serif;"; - const QString ADDRESSBAR_PLACEHOLDER = "Go to: domain, @user, #location"; + const QString ADDRESSBAR_PLACEHOLDER = "Go to: domain, location, @user, /x,y,z"; const QString ADDRESSBAR_STYLESHEET = "padding: 5px 10px; font-size: 20px;"; const int ADDRESSBAR_MIN_WIDTH = 200; diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 9ff839256d..7a76bc2d7d 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -26,7 +26,7 @@ const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "attachments" }; -static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; +static const QString S3_URL = "https://s3.amazonaws.com/hifi-public"; static const QString PUBLIC_URL = "http://public.highfidelity.io"; static const QString MODELS_LOCATION = "models/"; @@ -221,7 +221,7 @@ void ModelHandler::update() { } for (int i = 0; i < _model.rowCount(); ++i) { QUrl url(_model.item(i,0)->data(Qt::UserRole).toString()); - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); QNetworkReply* reply = networkAccessManager.head(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); @@ -272,7 +272,7 @@ void ModelHandler::queryNewFiles(QString marker) { // Download url.setQuery(query); - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request(url); QNetworkReply* reply = networkAccessManager.get(request); connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 1473e4a6a0..b55c753061 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -150,7 +150,7 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) { disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished); } } else { - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url)); qDebug() << "Downloading included script at" << scriptPath; QEventLoop loop; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index cecec29130..6ff256d48e 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -21,8 +21,15 @@ Circle3DOverlay::Circle3DOverlay() : _startAt(0.0f), _endAt(360.0f), _outerRadius(1.0f), - _innerRadius(0.0f) + _innerRadius(0.0f), + _hasTickMarks(false), + _majorTickMarksAngle(0.0f), + _minorTickMarksAngle(0.0f), + _majorTickMarksLength(0.0f), + _minorTickMarksLength(0.0f) { + _majorTickMarksColor.red = _majorTickMarksColor.green = _majorTickMarksColor.blue = (unsigned char)0; + _minorTickMarksColor.red = _minorTickMarksColor.green = _minorTickMarksColor.blue = (unsigned char)0; } Circle3DOverlay::~Circle3DOverlay() { @@ -142,6 +149,66 @@ void Circle3DOverlay::render() { glVertex2f(lastOuterPoint.x, lastOuterPoint.y); glEnd(); } + + // draw our tick marks + // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise + // we just draw a line... + if (getHasTickMarks()) { + glBegin(GL_LINES); + + // draw our major tick marks + if (getMajorTickMarksAngle() > 0.0f && getMajorTickMarksLength() != 0.0f) { + + xColor color = getMajorTickMarksColor(); + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + float angle = startAt; + float angleInRadians = glm::radians(angle); + float tickMarkLength = getMajorTickMarksLength(); + float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; + float endRadius = startRadius + tickMarkLength; + + while (angle <= endAt) { + angleInRadians = glm::radians(angle); + + glm::vec2 thisPointA(cos(angleInRadians) * startRadius, sin(angleInRadians) * startRadius); + glm::vec2 thisPointB(cos(angleInRadians) * endRadius, sin(angleInRadians) * endRadius); + + glVertex2f(thisPointA.x, thisPointA.y); + glVertex2f(thisPointB.x, thisPointB.y); + + angle += getMajorTickMarksAngle(); + } + } + + // draw our minor tick marks + if (getMinorTickMarksAngle() > 0.0f && getMinorTickMarksLength() != 0.0f) { + + xColor color = getMinorTickMarksColor(); + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + float angle = startAt; + float angleInRadians = glm::radians(angle); + float tickMarkLength = getMinorTickMarksLength(); + float startRadius = (tickMarkLength > 0.0f) ? innerRadius : outerRadius; + float endRadius = startRadius + tickMarkLength; + + while (angle <= endAt) { + angleInRadians = glm::radians(angle); + + glm::vec2 thisPointA(cos(angleInRadians) * startRadius, sin(angleInRadians) * startRadius); + glm::vec2 thisPointB(cos(angleInRadians) * endRadius, sin(angleInRadians) * endRadius); + + glVertex2f(thisPointA.x, thisPointA.y); + glVertex2f(thisPointB.x, thisPointB.y); + + angle += getMinorTickMarksAngle(); + } + } + + glEnd(); + } + glPopMatrix(); glPopMatrix(); @@ -173,6 +240,55 @@ void Circle3DOverlay::setProperties(const QScriptValue &properties) { if (innerRadius.isValid()) { setInnerRadius(innerRadius.toVariant().toFloat()); } + + QScriptValue hasTickMarks = properties.property("hasTickMarks"); + if (hasTickMarks.isValid()) { + setHasTickMarks(hasTickMarks.toVariant().toBool()); + } + + QScriptValue majorTickMarksAngle = properties.property("majorTickMarksAngle"); + if (majorTickMarksAngle.isValid()) { + setMajorTickMarksAngle(majorTickMarksAngle.toVariant().toFloat()); + } + + QScriptValue minorTickMarksAngle = properties.property("minorTickMarksAngle"); + if (minorTickMarksAngle.isValid()) { + setMinorTickMarksAngle(minorTickMarksAngle.toVariant().toFloat()); + } + + QScriptValue majorTickMarksLength = properties.property("majorTickMarksLength"); + if (majorTickMarksLength.isValid()) { + setMajorTickMarksLength(majorTickMarksLength.toVariant().toFloat()); + } + + QScriptValue minorTickMarksLength = properties.property("minorTickMarksLength"); + if (minorTickMarksLength.isValid()) { + setMinorTickMarksLength(minorTickMarksLength.toVariant().toFloat()); + } + + QScriptValue majorTickMarksColor = properties.property("majorTickMarksColor"); + if (majorTickMarksColor.isValid()) { + QScriptValue red = majorTickMarksColor.property("red"); + QScriptValue green = majorTickMarksColor.property("green"); + QScriptValue blue = majorTickMarksColor.property("blue"); + if (red.isValid() && green.isValid() && blue.isValid()) { + _majorTickMarksColor.red = red.toVariant().toInt(); + _majorTickMarksColor.green = green.toVariant().toInt(); + _majorTickMarksColor.blue = blue.toVariant().toInt(); + } + } + + QScriptValue minorTickMarksColor = properties.property("minorTickMarksColor"); + if (minorTickMarksColor.isValid()) { + QScriptValue red = minorTickMarksColor.property("red"); + QScriptValue green = minorTickMarksColor.property("green"); + QScriptValue blue = minorTickMarksColor.property("blue"); + if (red.isValid() && green.isValid() && blue.isValid()) { + _minorTickMarksColor.red = red.toVariant().toInt(); + _minorTickMarksColor.green = green.toVariant().toInt(); + _minorTickMarksColor.blue = blue.toVariant().toInt(); + } + } } diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index cea9db0384..791d951105 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -26,17 +26,38 @@ public: float getEndAt() const { return _endAt; } float getOuterRadius() const { return _outerRadius; } float getInnerRadius() const { return _innerRadius; } + bool getHasTickMarks() const { return _hasTickMarks; } + float getMajorTickMarksAngle() const { return _majorTickMarksAngle; } + float getMinorTickMarksAngle() const { return _minorTickMarksAngle; } + float getMajorTickMarksLength() const { return _majorTickMarksLength; } + float getMinorTickMarksLength() const { return _minorTickMarksLength; } + xColor getMajorTickMarksColor() const { return _majorTickMarksColor; } + xColor getMinorTickMarksColor() const { return _minorTickMarksColor; } void setStartAt(float value) { _startAt = value; } void setEndAt(float value) { _endAt = value; } void setOuterRadius(float value) { _outerRadius = value; } void setInnerRadius(float value) { _innerRadius = value; } + void setHasTickMarks(bool value) { _hasTickMarks = value; } + void setMajorTickMarksAngle(float value) { _majorTickMarksAngle = value; } + void setMinorTickMarksAngle(float value) { _minorTickMarksAngle = value; } + void setMajorTickMarksLength(float value) { _majorTickMarksLength = value; } + void setMinorTickMarksLength(float value) { _minorTickMarksLength = value; } + void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; } + void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } protected: float _startAt; float _endAt; float _outerRadius; float _innerRadius; + bool _hasTickMarks; + float _majorTickMarksAngle; + float _minorTickMarksAngle; + float _majorTickMarksLength; + float _minorTickMarksLength; + xColor _majorTickMarksColor; + xColor _minorTickMarksColor; }; diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index 8322b9bea4..2b4e1e2f56 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -37,7 +37,7 @@ ImageOverlay::~ImageOverlay() { // TODO: handle setting image multiple times, how do we manage releasing the bound texture? void ImageOverlay::setImageURL(const QUrl& url) { _isLoaded = false; - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url)); connect(reply, &QNetworkReply::finished, this, &ImageOverlay::replyFinished); } diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 6fa002a664..2266385425 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -78,7 +78,7 @@ Sound::Sound(const QUrl& sampleURL, bool isStereo, QObject* parent) : // assume we have a QApplication or QCoreApplication instance and use the // QNetworkAccess manager to grab the raw audio file at the given URL - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); qDebug() << "Requesting audio file" << sampleURL.toDisplayString(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ef7083e3bf..62cde44909 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1048,7 +1048,7 @@ void AvatarData::setBillboardFromURL(const QString &billboardURL) { QNetworkRequest billboardRequest; billboardRequest.setUrl(QUrl(billboardURL)); - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* networkReply = networkAccessManager.get(billboardRequest); connect(networkReply, SIGNAL(finished()), this, SLOT(setBillboardFromNetworkReply())); } @@ -1113,7 +1113,7 @@ void AvatarData::updateJointMappings() { _jointNames.clear(); if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* networkReply = networkAccessManager.get(QNetworkRequest(_skeletonModelURL)); connect(networkReply, SIGNAL(finished()), this, SLOT(setJointMappingsFromNetworkReply())); } diff --git a/libraries/avatars/src/Recording.cpp b/libraries/avatars/src/Recording.cpp index 7465eb1aac..0d089a2bd2 100644 --- a/libraries/avatars/src/Recording.cpp +++ b/libraries/avatars/src/Recording.cpp @@ -385,7 +385,7 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp") { // Download file if necessary qDebug() << "Downloading recording at" << url; - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url)); QEventLoop loop; QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 7d924d02de..bb471442ea 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -187,7 +187,7 @@ void AccountManager::invokedRequest(const QString& path, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) { - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; @@ -359,7 +359,7 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) void AccountManager::requestAccessToken(const QString& login, const QString& password) { - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request; @@ -431,7 +431,7 @@ void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) } void AccountManager::requestProfile() { - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QUrl profileURL = _authURL; profileURL.setPath("/api/v1/users/profile"); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 5714e6923d..760c9f4c04 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -90,7 +90,7 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos void DomainHandler::setUUID(const QUuid& uuid) { if (uuid != _uuid) { _uuid = uuid; - qDebug() << "Domain uuid changed to" << uuidStringWithoutCurlyBraces(_uuid); + qDebug() << "Domain ID changed to" << uuidStringWithoutCurlyBraces(_uuid); } } diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index d30f7944d7..97e9721356 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -90,32 +90,29 @@ QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr) { return dataStream; } -quint32 getHostOrderLocalAddress() { +QHostAddress getLocalAddress() { - static int localAddress = 0; + QHostAddress localAddress; - if (localAddress == 0) { - foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { - if (networkInterface.flags() & QNetworkInterface::IsUp - && networkInterface.flags() & QNetworkInterface::IsRunning - && networkInterface.flags() & ~QNetworkInterface::IsLoopBack) { - // we've decided that this is the active NIC - // enumerate it's addresses to grab the IPv4 address - foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { - // make sure it's an IPv4 address that isn't the loopback - if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { - qDebug("Node's local address is %s", entry.ip().toString().toLocal8Bit().constData()); - - // set our localAddress and break out - localAddress = entry.ip().toIPv4Address(); - break; - } + foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (networkInterface.flags() & QNetworkInterface::IsUp + && networkInterface.flags() & QNetworkInterface::IsRunning + && networkInterface.flags() & ~QNetworkInterface::IsLoopBack) { + // we've decided that this is the active NIC + // enumerate it's addresses to grab the IPv4 address + foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { + // make sure it's an IPv4 address that isn't the loopback + if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { + + // set our localAddress and break out + localAddress = entry.ip(); + break; } } - - if (localAddress != 0) { - break; - } + } + + if (!localAddress.isNull()) { + break; } } diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index 8a591a60b8..42f815390a 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -58,7 +58,7 @@ private: uint qHash(const HifiSockAddr& key, uint seed); -quint32 getHostOrderLocalAddress(); +QHostAddress getLocalAddress(); Q_DECLARE_METATYPE(HifiSockAddr) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 507788009a..dd33c96d74 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -70,6 +70,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _nodeHashMutex(QMutex::Recursive), _nodeSocket(this), _dtlsSocket(NULL), + _localSockAddr(), _publicSockAddr(), _numCollectedPackets(0), _numCollectedBytes(0), @@ -89,6 +90,15 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short const int LARGER_BUFFER_SIZE = 1048576; changeSocketBufferSizes(LARGER_BUFFER_SIZE); + // check for local socket updates every so often + const int LOCAL_SOCKET_UPDATE_INTERVAL_MSECS = 5 * 1000; + QTimer* localSocketUpdate = new QTimer(this); + connect(localSocketUpdate, &QTimer::timeout, this, &LimitedNodeList::updateLocalSockAddr); + localSocketUpdate->start(LOCAL_SOCKET_UPDATE_INTERVAL_MSECS); + + // check the local socket right now + updateLocalSockAddr(); + _packetStatTimer.start(); } @@ -570,12 +580,19 @@ void LimitedNodeList::sendSTUNRequest() { memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); // lookup the IP for the STUN server - static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), stunSockAddr.getAddress(), stunSockAddr.getPort()); } +void LimitedNodeList::rebindNodeSocket() { + quint16 oldPort = _nodeSocket.localPort(); + + _nodeSocket.close(); + _nodeSocket.bind(QHostAddress::AnyIPv4, oldPort); +} + bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { // check the cookie to make sure this is actually a STUN response // and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS @@ -652,6 +669,23 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { return false; } +void LimitedNodeList::updateLocalSockAddr() { + HifiSockAddr newSockAddr(getLocalAddress(), _nodeSocket.localPort()); + if (newSockAddr != _localSockAddr) { + + if (_localSockAddr.isNull()) { + qDebug() << "Local socket is" << newSockAddr; + } else { + qDebug() << "Local socket has changed from" << _localSockAddr << "to" << newSockAddr; + } + + + _localSockAddr = newSockAddr; + + emit localSockAddrChanged(_localSockAddr); + } +} + void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr, QUuid headerID, const QUuid& connectionRequestID) { @@ -662,7 +696,7 @@ void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSock QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat, headerID); QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append); - iceDataStream << _publicSockAddr << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()); + iceDataStream << _publicSockAddr << _localSockAddr; if (!connectionRequestID.isNull()) { iceDataStream << connectionRequestID; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index a7ffc7ec28..c416773201 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -69,6 +69,8 @@ public: const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); + + void rebindNodeSocket(); QUdpSocket& getNodeSocket() { return _nodeSocket; } QUdpSocket& getDTLSSocket(); @@ -127,11 +129,15 @@ public slots: void removeSilentNodes(); + void updateLocalSockAddr(); + void killNodeWithUUID(const QUuid& nodeUUID); signals: void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void nodeAdded(SharedNodePointer); void nodeKilled(SharedNodePointer); + + void localSockAddrChanged(const HifiSockAddr& localSockAddr); void publicSockAddrChanged(const HifiSockAddr& publicSockAddr); protected: static std::auto_ptr _sharedInstance; @@ -153,6 +159,7 @@ protected: QMutex _nodeHashMutex; QUdpSocket _nodeSocket; QUdpSocket* _dtlsSocket; + HifiSockAddr _localSockAddr; HifiSockAddr _publicSockAddr; int _numCollectedPackets; int _numCollectedBytes; diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp index e92760d303..841b7491c7 100644 --- a/libraries/networking/src/NetworkAccessManager.cpp +++ b/libraries/networking/src/NetworkAccessManager.cpp @@ -13,15 +13,12 @@ #include "NetworkAccessManager.h" -QThreadStorage networkAccessManagers; +QThreadStorage networkAccessManagers; -NetworkAccessManager& NetworkAccessManager::getInstance() { +QNetworkAccessManager& NetworkAccessManager::getInstance() { if (!networkAccessManagers.hasLocalData()) { - networkAccessManagers.setLocalData(new NetworkAccessManager()); + networkAccessManagers.setLocalData(new QNetworkAccessManager()); } return *networkAccessManagers.localData(); } - -NetworkAccessManager::NetworkAccessManager() { -} diff --git a/libraries/networking/src/NetworkAccessManager.h b/libraries/networking/src/NetworkAccessManager.h index 9594170518..d911d935dc 100644 --- a/libraries/networking/src/NetworkAccessManager.h +++ b/libraries/networking/src/NetworkAccessManager.h @@ -15,13 +15,10 @@ #include /// Wrapper around QNetworkAccessManager to restrict at one instance by thread -class NetworkAccessManager : public QNetworkAccessManager { +class NetworkAccessManager : public QObject { Q_OBJECT public: - static NetworkAccessManager& getInstance(); - -private: - NetworkAccessManager(); + static QNetworkAccessManager& getInstance(); }; #endif // hifi_NetworkAccessManager_h \ No newline at end of file diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 905fc06eeb..25531861ca 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -302,9 +302,7 @@ void NodeList::sendDomainServerCheckIn() { QDataStream packetStream(&domainServerPacket, QIODevice::Append); // pack our data to send to the domain-server - packetStream << _ownerType << _publicSockAddr - << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) - << (quint8) _nodeTypesOfInterest.size(); + packetStream << _ownerType << _publicSockAddr << _localSockAddr << (quint8) _nodeTypesOfInterest.size(); // copy over the bytes for node types of interest, if required foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { @@ -420,13 +418,7 @@ void NodeList::sendAssignment(Assignment& assignment) { packetStream << assignment; - static HifiSockAddr DEFAULT_ASSIGNMENT_SOCKET(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, DEFAULT_DOMAIN_SERVER_PORT); - - const HifiSockAddr* assignmentServerSocket = _assignmentServerSocket.isNull() - ? &DEFAULT_ASSIGNMENT_SOCKET - : &_assignmentServerSocket; - - _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); + _nodeSocket.writeDatagram(packet, _assignmentServerSocket.getAddress(), _assignmentServerSocket.getPort()); } void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index cd80c441c1..6d2e366499 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -59,6 +59,9 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy NodeList* nodeList = NodeList::getInstance(); nodeList->setOwnerType(nodeType); + // this is a temp fix for Qt 5.3 - rebinding the node socket gives us readyRead for the socket on this thread + nodeList->rebindNodeSocket(); + QTimer* domainServerTimer = new QTimer(this); connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 47e0ced6bf..51789aec3a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -157,7 +157,7 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, emit errorMessage("ERROR Loading file:" + fileName); } } else { - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url)); qDebug() << "Downloading script at" << url; QEventLoop loop; @@ -681,8 +681,8 @@ void ScriptEngine::include(const QString& includeFile) { QUrl url = resolveInclude(includeFile); QString includeContents; - if (url.scheme() == "http" || url.scheme() == "ftp") { - NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp") { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url)); qDebug() << "Downloading included script at" << includeFile; QEventLoop loop;