From f16eb0fd896879165e8fbc203e1f352d47291ee6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 19 Jul 2014 14:33:48 -0700 Subject: [PATCH 001/206] Encapsulate and lint "new model" toolbar --- examples/editModels.js | 204 +++++++++++++++++++++++------------------ 1 file changed, 114 insertions(+), 90 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 458ddf7b4a..0c6ac8b426 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -9,12 +9,12 @@ // // If using the hydras : // grab grab models with the triggers, you can then move the models around or scale them with both hands. -// You can switch mode using the bumpers so that you can move models roud more easily. +// You can switch mode using the bumpers so that you can move models around more easily. // // If using the mouse : // - left click lets you move the model in the plane facing you. -// If pressing shift, it will move on the horizontale plane it's in. -// - right click lets you rotate the model. z and x give you access to more axix of rotation while shift allows for finer control. +// If pressing shift, it will move on the horizontal plane it's in. +// - right click lets you rotate the model. z and x give access to more axes of rotation while shift provides finer control. // - left + right click lets you scale the model. // - you can press r while holding the model to reset its rotation // @@ -39,27 +39,122 @@ var MAX_ANGULAR_SIZE = 45; var LEFT = 0; var RIGHT = 1; - var SPAWN_DISTANCE = 1; -var radiusDefault = 0.10; +var DEFAULT_RADIUS = 0.10; var modelURLs = [ - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX", - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx", - "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx", - ]; - -var toolBar; + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx", + "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx" + ]; var jointList = MyAvatar.getJointNames(); var mode = 0; + +var toolBar = (function () { + var that = {}, + toolBar, + newModelButton, + browseModelsButton; + + function initialize() { + toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); + newModelButton = toolBar.addTool({ + imageURL: toolIconUrl + "add-model-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + visible: true, + alpha: 0.9 + }); + browseModelsButton = toolBar.addTool({ + imageURL: toolIconUrl + "list-icon.png", + width: toolWidth, + height: toolHeight, + visible: true, + alpha: 0.7 + }); + } + + 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) { + Models.addModel({ + position: position, + radius: DEFAULT_RADIUS, + modelURL: url + }); + } else { + print("Can't create model: Model would be out of bounds."); + } + } + + that.move = function () { + var newViewPort, + toolsX, + toolsY; + + newViewPort = Controller.getViewportDimensions(); + + if (toolBar === undefined) { + initialize(); + + } else if (windowDimensions.x === newViewPort.x && + windowDimensions.y === newViewPort.y) { + return; + } + + windowDimensions = newViewPort; + toolsX = windowDimensions.x - 8 - toolBar.width; + toolsY = (windowDimensions.y - toolBar.height) / 2; + + toolBar.move(toolsX, toolsY); + }; + + that.mousePressEvent = function (event) { + var clickedOverlay, + url, + position; + + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (newModelButton === toolBar.clicked(clickedOverlay)) { + url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); + if (url !== null && url !== "") { + addModel(url); + } + return true; + } + + if (browseModelsButton === toolBar.clicked(clickedOverlay)) { + url = Window.s3Browse(); + if (url !== null && url !== "") { + addModel(url); + } + return true; + } + + return false; + }; + + that.cleanup = function () { + toolBar.cleanup(); + }; + + return that; +}()); + + function isLocked(properties) { // special case to lock the ground plane model in hq. if (location.hostname == "hq.highfidelity.io" && @@ -663,45 +758,7 @@ function checkController(deltaTime) { } } - moveOverlays(); -} -var newModel; -var browser; -function initToolBar() { - toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); - // New Model - newModel = toolBar.addTool({ - imageURL: toolIconUrl + "add-model-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }); - browser = toolBar.addTool({ - imageURL: toolIconUrl + "list-icon.png", - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.7 - }); -} - -function moveOverlays() { - var newViewPort = Controller.getViewportDimensions(); - - if (typeof(toolBar) === 'undefined') { - initToolBar(); - - } else if (windowDimensions.x == newViewPort.x && - windowDimensions.y == newViewPort.y) { - return; - } - - - windowDimensions = newViewPort; - var toolsX = windowDimensions.x - 8 - toolBar.width; - var toolsY = (windowDimensions.y - toolBar.height) / 2; - - toolBar.move(toolsX, toolsY); + toolBar.move(); } @@ -784,42 +841,9 @@ function mousePressEvent(event) { mouseLastPosition = { x: event.x, y: event.y }; modelSelected = false; - var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - if (newModel == toolBar.clicked(clickedOverlay)) { - var url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]); - if (url == null || url == "") { - return; - } - - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - - if (position.x > 0 && position.y > 0 && position.z > 0) { - Models.addModel({ position: position, - radius: radiusDefault, - modelURL: url - }); - } else { - print("Can't create model: Model would be out of bounds."); - } - - } else if (browser == toolBar.clicked(clickedOverlay)) { - var url = Window.s3Browse(); - if (url == null || url == "") { - return; - } - - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - - if (position.x > 0 && position.y > 0 && position.z > 0) { - Models.addModel({ position: position, - radius: radiusDefault, - modelURL: url - }); - } else { - print("Can't create model: Model would be out of bounds."); - } - + if (toolBar.mousePressEvent(event)) { + // Event handled; do nothing. } else { var pickRay = Camera.computePickRay(event.x, event.y); Vec3.print("[Mouse] Looking at: ", pickRay.origin); From c5faeadd818ce6a545cdc61cc760add0befb8f47 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 19 Jul 2014 15:27:41 -0700 Subject: [PATCH 002/206] Add button menu options for loading model from URL or file --- examples/editModels.js | 77 ++++++++++++++++++++++++++++++++++++++---- examples/toolBars.js | 8 +++++ 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 0c6ac8b426..721878c656 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -62,25 +62,72 @@ var toolBar = (function () { var that = {}, toolBar, newModelButton, - browseModelsButton; + browseModelsButton, + loadURLMenuItem, + loadFileMenuItem, + menuItemWidth = 90, + menuItemOffset = 2, + menuItemHeight = Tool.IMAGE_HEIGHT / 2 - menuItemOffset, + menuItemMargin = 5, + menuTextColor = { red: 255, green: 255, blue: 255 }, + menuBackgoundColor = { red: 18, green: 66, blue: 66 }; function initialize() { toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); + newModelButton = toolBar.addTool({ imageURL: toolIconUrl + "add-model-tool.svg", subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }); + alpha: 0.9, + visible: true + }, true); + browseModelsButton = toolBar.addTool({ imageURL: toolIconUrl + "list-icon.png", width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.7 + alpha: 0.7, + visible: true }); + + loadURLMenuItem = Overlays.addOverlay("text", { + x: newModelButton.x - menuItemWidth, + y: newModelButton.y + menuItemOffset, + width: menuItemWidth, + height: menuItemHeight, + backgroundColor: menuBackgoundColor, + topMargin: menuItemMargin, + text: "Model URL", + alpha: 0.9, + visible: false + }); + + loadFileMenuItem = Overlays.addOverlay("text", { + x: newModelButton.x - menuItemWidth, + y: newModelButton.y + menuItemOffset + menuItemHeight, + width: menuItemWidth, + height: menuItemHeight, + backgroundColor: menuBackgoundColor, + topMargin: menuItemMargin, + text: "Model File", + alpha: 0.9, + visible: false + }); + } + + function toggleToolbar(active) { + if (active === undefined) { + print("active === undefine"); + active = toolBar.toolSelected(newModelButton); + } else { + print("active !== undefine"); + toolBar.selectTool(newModelButton, active); + } + + Overlays.editOverlay(loadURLMenuItem, { visible: active }); + Overlays.editOverlay(loadFileMenuItem, { visible: active }); } function addModel(url) { @@ -119,6 +166,9 @@ var toolBar = (function () { toolsY = (windowDimensions.y - toolBar.height) / 2; toolBar.move(toolsX, toolsY); + + Overlays.editOverlay(loadURLMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset }); + Overlays.editOverlay(loadFileMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset + menuItemHeight }); }; that.mousePressEvent = function (event) { @@ -129,6 +179,12 @@ var toolBar = (function () { clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); if (newModelButton === toolBar.clicked(clickedOverlay)) { + toggleToolbar(); + return true; + } + + if (clickedOverlay === loadURLMenuItem) { + toggleToolbar(false); url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); if (url !== null && url !== "") { addModel(url); @@ -136,7 +192,14 @@ var toolBar = (function () { return true; } + if (clickedOverlay === loadFileMenuItem) { + toggleToolbar(false); + print("TODO: Upload model file"); + return true; + } + if (browseModelsButton === toolBar.clicked(clickedOverlay)) { + toggleToolbar(false); url = Window.s3Browse(); if (url !== null && url !== "") { addModel(url); @@ -149,6 +212,8 @@ var toolBar = (function () { that.cleanup = function () { toolBar.cleanup(); + Overlays.deleteOverlay(loadURLMenuItem); + Overlays.deleteOverlay(loadFileMenuItem); }; return that; diff --git a/examples/toolBars.js b/examples/toolBars.js index 1a464b4e4f..ede3b80062 100644 --- a/examples/toolBars.js +++ b/examples/toolBars.js @@ -186,6 +186,14 @@ ToolBar = function(x, y, direction) { return this.tools.length; } + this.selectTool = function (tool, select) { + this.tools[tool].select(select); + } + + this.toolSelected = function (tool) { + return this.tools[tool].selected(); + } + this.cleanup = function() { for(var tool in this.tools) { this.tools[tool].cleanup(); From 6027f4dad1bd0133cce90f8a53a190ade4c40ffd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 19 Jul 2014 15:33:54 -0700 Subject: [PATCH 003/206] Add model file selection dialog box --- examples/editModels.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 721878c656..71a4050176 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -174,6 +174,7 @@ var toolBar = (function () { that.mousePressEvent = function (event) { var clickedOverlay, url, + file, position; clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); @@ -185,7 +186,7 @@ var toolBar = (function () { if (clickedOverlay === loadURLMenuItem) { toggleToolbar(false); - url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); + url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]); if (url !== null && url !== "") { addModel(url); } @@ -194,7 +195,10 @@ var toolBar = (function () { if (clickedOverlay === loadFileMenuItem) { toggleToolbar(false); - print("TODO: Upload model file"); + file = Window.browse("Model File", "", "FST, FBX, or SVO files (*.fst *.fbx *.svo)"); + if (file !== null) { + print("TODO: Upload model file: " + file); + } return true; } From 33ffed71359c74346e45b2254e13a03f4ff0f5d2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 19 Jul 2014 15:47:06 -0700 Subject: [PATCH 004/206] Add GET of local files to JavaScript XMLHttpRequest --- examples/Test.js | 7 + examples/testXMLHttpRequest.js | 95 +++++++++++++ .../script-engine/src/XMLHttpRequestClass.cpp | 125 +++++++++++++++--- .../script-engine/src/XMLHttpRequestClass.h | 2 + 4 files changed, 211 insertions(+), 18 deletions(-) diff --git a/examples/Test.js b/examples/Test.js index 36dee7bd90..612c56d10b 100644 --- a/examples/Test.js +++ b/examples/Test.js @@ -59,6 +59,13 @@ UnitTest.prototype.assertEquals = function(expected, actual, message) { } }; +UnitTest.prototype.assertContains = function (expected, actual, message) { + this.numAssertions++; + if (actual.indexOf(expected) == -1) { + throw new AssertionException(expected, actual, message); + } +}; + UnitTest.prototype.assertHasProperty = function(property, actual, message) { this.numAssertions++; if (actual[property] === undefined) { diff --git a/examples/testXMLHttpRequest.js b/examples/testXMLHttpRequest.js index 421eb458e4..ec5bcf6c4c 100644 --- a/examples/testXMLHttpRequest.js +++ b/examples/testXMLHttpRequest.js @@ -145,3 +145,98 @@ test("Test timeout", function() { this.assertEquals(0, req.status, "status should be `0`"); this.assertEquals(4, req.errorCode, "4 is the timeout error code for QNetworkReply::NetworkError"); }); + + +var localFile = Window.browse("Find defaultScripts.js file ...", "", "defaultScripts.js (defaultScripts.js)"); + +if (localFile !== null) { + + localFile = "file:///" + localFile; + + test("Test GET local file synchronously", function () { + var req = new XMLHttpRequest(); + + var statesVisited = [true, false, false, false, false] + req.onreadystatechange = function () { + statesVisited[req.readyState] = true; + }; + + req.open("GET", localFile, false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(200, req.status, "status should be `200`"); + this.assertEquals("OK", req.statusText, "statusText should be `OK`"); + this.assertEquals(0, req.errorCode); + this.assertEquals("", req.getAllResponseHeaders(), "headers should be null"); + this.assertContains("High Fidelity", req.response.substring(0, 100), "expected text not found in response") + + for (var i = 0; i <= req.DONE; i++) { + this.assertEquals(true, statesVisited[i], i + " should be set"); + } + }); + + test("Test GET nonexistent local file", function () { + var nonexistentFile = localFile.replace(".js", "NoExist.js"); + + var req = new XMLHttpRequest(); + req.open("GET", nonexistentFile, false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(404, req.status, "status should be `404`"); + this.assertEquals("Not Found", req.statusText, "statusText should be `Not Found`"); + this.assertNotEquals(0, req.errorCode); + }); + + test("Test GET local file already open", function () { + // Can't open file exclusively in order to test. + }); + + test("Test GET local file with data not implemented", function () { + var req = new XMLHttpRequest(); + req.open("GET", localFile, true); + req.send("data"); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(501, req.status, "status should be `501`"); + this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`"); + this.assertNotEquals(0, req.errorCode); + }); + + test("Test GET local file asynchronously not implemented", function () { + var req = new XMLHttpRequest(); + req.open("GET", localFile, true); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(501, req.status, "status should be `501`"); + this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`"); + this.assertNotEquals(0, req.errorCode); + }); + + test("Test POST local file not implemented", function () { + var req = new XMLHttpRequest(); + req.open("POST", localFile, false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(501, req.status, "status should be `501`"); + this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`"); + this.assertNotEquals(0, req.errorCode); + }); + + test("Test local file username and password not implemented", function () { + var req = new XMLHttpRequest(); + req.open("GET", localFile, false, "username", "password"); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(501, req.status, "status should be `501`"); + this.assertEquals("Not Implemented", req.statusText, "statusText should be `Not Implemented`"); + this.assertNotEquals(0, req.errorCode); + }); + +} else { + print("Local file operation not tested"); +} diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index d9b7312bf4..6355b689ea 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -13,6 +13,7 @@ // #include +#include #include @@ -33,6 +34,7 @@ XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : _onReadyStateChange(QScriptValue::NullValue), _readyState(XMLHttpRequestClass::UNSENT), _errorCode(QNetworkReply::NoError), + _file(NULL), _timeout(0), _timer(this), _numRedirects(0) { @@ -52,6 +54,19 @@ QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEn QScriptValue XMLHttpRequestClass::getStatus() const { if (_reply) { return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); + } else if(_url.isLocalFile()) { + switch (_errorCode) { + case QNetworkReply::NoError: + return QScriptValue(200); + case QNetworkReply::ContentNotFoundError: + return QScriptValue(404); + case QNetworkReply::ContentAccessDenied: + return QScriptValue(409); + case QNetworkReply::TimeoutError: + return QScriptValue(408); + case QNetworkReply::ContentOperationNotPermittedError: + return QScriptValue(501); + } } return QScriptValue(0); } @@ -59,6 +74,19 @@ QScriptValue XMLHttpRequestClass::getStatus() const { QString XMLHttpRequestClass::getStatusText() const { if (_reply) { return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + } else if (_url.isLocalFile()) { + switch (_errorCode) { + case QNetworkReply::NoError: + return "OK"; + case QNetworkReply::ContentNotFoundError: + return "Not Found"; + case QNetworkReply::ContentAccessDenied: + return "Conflict"; + case QNetworkReply::TimeoutError: + return "Timeout"; + case QNetworkReply::ContentOperationNotPermittedError: + return "Not Implemented"; + } } return ""; } @@ -126,17 +154,42 @@ void XMLHttpRequestClass::setReadyState(ReadyState readyState) { void XMLHttpRequestClass::open(const QString& method, const QString& url, bool async, const QString& username, const QString& password) { if (_readyState == UNSENT) { - _async = async; - _url.setUrl(url); - if (!username.isEmpty()) { - _url.setUserName(username); - } - if (!password.isEmpty()) { - _url.setPassword(password); - } - _request.setUrl(_url); _method = method; - setReadyState(OPENED); + _url.setUrl(url); + _async = async; + + if (_url.isLocalFile()) { + if (_method.toUpper() == "GET" && !_async && username.isEmpty() && password.isEmpty()) { + _file = new QFile(_url.toLocalFile()); + if (!_file->exists()) { + qDebug() << "Can't find file " << _url.fileName(); + abortRequest(); + _errorCode = QNetworkReply::ContentNotFoundError; + setReadyState(DONE); + emit requestComplete(); + } else if (!_file->open(QIODevice::ReadOnly)) { + qDebug() << "Can't open file " << _url.fileName(); + abortRequest(); + //_errorCode = QNetworkReply::ContentConflictError; // TODO: Use this status when update to Qt 5.3 + _errorCode = QNetworkReply::ContentAccessDenied; + setReadyState(DONE); + emit requestComplete(); + } else { + setReadyState(OPENED); + } + } else { + notImplemented(); + } + } else { + if (!username.isEmpty()) { + _url.setUserName(username); + } + if (!password.isEmpty()) { + _url.setPassword(password); + } + _request.setUrl(_url); + setReadyState(OPENED); + } } } @@ -147,13 +200,18 @@ void XMLHttpRequestClass::send() { void XMLHttpRequestClass::send(const QString& data) { if (_readyState == OPENED && !_reply) { if (!data.isNull()) { - _sendData = new QBuffer(this); - _sendData->setData(data.toUtf8()); + if (_url.isLocalFile()) { + notImplemented(); + return; + } else { + _sendData = new QBuffer(this); + _sendData->setData(data.toUtf8()); + } } doSend(); - if (!_async) { + if (!_async && !_url.isLocalFile()) { QEventLoop loop; connect(this, SIGNAL(requestComplete()), &loop, SLOT(quit())); loop.exec(); @@ -162,14 +220,24 @@ void XMLHttpRequestClass::send(const QString& data) { } void XMLHttpRequestClass::doSend() { - _reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData); - - connectToReply(_reply); + + if (!_url.isLocalFile()) { + _reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData); + connectToReply(_reply); + } if (_timeout > 0) { _timer.start(_timeout); connect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); } + + if (_url.isLocalFile()) { + setReadyState(HEADERS_RECEIVED); + setReadyState(LOADING); + _rawResponseData = _file->readAll(); + _file->close(); + requestFinished(); + } } void XMLHttpRequestClass::requestTimeout() { @@ -188,9 +256,16 @@ void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) { void XMLHttpRequestClass::requestFinished() { disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); - _errorCode = _reply->error(); + if (!_url.isLocalFile()) { + _errorCode = _reply->error(); + } else { + _errorCode = QNetworkReply::NoError; + } + if (_errorCode == QNetworkReply::NoError) { - _rawResponseData.append(_reply->readAll()); + if (!_url.isLocalFile()) { + _rawResponseData.append(_reply->readAll()); + } if (_responseType == "json") { _responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")"); @@ -204,6 +279,7 @@ void XMLHttpRequestClass::requestFinished() { _responseData = QScriptValue(QString(_rawResponseData.data())); } } + setReadyState(DONE); emit requestComplete(); } @@ -217,6 +293,19 @@ void XMLHttpRequestClass::abortRequest() { delete _reply; _reply = NULL; } + + if (_file != NULL) { + _file->close(); + _file = NULL; + } +} + +void XMLHttpRequestClass::notImplemented() { + abortRequest(); + //_errorCode = QNetworkReply::OperationNotImplementedError; TODO: Use this status code when update to Qt 5.3 + _errorCode = QNetworkReply::ContentOperationNotPermittedError; + setReadyState(DONE); + emit requestComplete(); } void XMLHttpRequestClass::connectToReply(QNetworkReply* reply) { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index 48f1a596e1..9052a627b7 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -97,6 +97,7 @@ private: void connectToReply(QNetworkReply* reply); void disconnectFromReply(QNetworkReply* reply); void abortRequest(); + void notImplemented(); QScriptEngine* _engine; bool _async; @@ -112,6 +113,7 @@ private: QScriptValue _onReadyStateChange; ReadyState _readyState; QNetworkReply::NetworkError _errorCode; + QFile* _file; int _timeout; QTimer _timer; int _numRedirects; From d05435db91664f7dee5b96c716c562ee1f31bd4d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 19 Jul 2014 23:27:36 -0700 Subject: [PATCH 005/206] Add arraybuffer binary data handling in JavaScript XMLHttpRequest --- libraries/script-engine/src/XMLHttpRequestClass.cpp | 13 +++++++++---- libraries/script-engine/src/XMLHttpRequestClass.h | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 6355b689ea..84e49f2364 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -194,10 +194,10 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a } void XMLHttpRequestClass::send() { - send(QString::Null()); + send(QString()); } -void XMLHttpRequestClass::send(const QString& data) { +void XMLHttpRequestClass::send(const QVariant& data) { if (_readyState == OPENED && !_reply) { if (!data.isNull()) { if (_url.isLocalFile()) { @@ -205,7 +205,12 @@ void XMLHttpRequestClass::send(const QString& data) { return; } else { _sendData = new QBuffer(this); - _sendData->setData(data.toUtf8()); + if (_responseType == "arraybuffer") { + QByteArray ba = qvariant_cast(data); + _sendData->setData(ba); + } else { + _sendData->setData(data.toString().toUtf8()); + } } } @@ -274,7 +279,7 @@ void XMLHttpRequestClass::requestFinished() { _responseData = QScriptValue::NullValue; } } else if (_responseType == "arraybuffer") { - _responseData = QScriptValue(_rawResponseData.data()); + _responseData = _engine->newVariant(QVariant::fromValue(_rawResponseData)); } else { _responseData = QScriptValue(QString(_rawResponseData.data())); } diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index 9052a627b7..e482e57077 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -84,7 +84,7 @@ public slots: void open(const QString& method, const QString& url, bool async = true, const QString& username = "", const QString& password = ""); void send(); - void send(const QString& data); + void send(const QVariant& data); QScriptValue getAllResponseHeaders() const; QScriptValue getResponseHeader(const QString& name) const; From ed1b058cb14fdde8f810ba90ed4cf5aa2ab76657 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 20 Jul 2014 13:55:26 -0700 Subject: [PATCH 006/206] Upload selected model file directly to public folder for starters --- examples/editModels.js | 54 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 71a4050176..9304afbc49 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -58,6 +58,51 @@ var jointList = MyAvatar.getJointNames(); var mode = 0; +var modelUploader = (function () { + var that = {}, + urlBase = "http://public.highfidelity.io/meshes/"; + + that.upload = function (file, callback) { + var url, + reqRead, + reqSend; + + // Read model content ... + url = "file:///" + file; + print("Reading model from " + url); + reqRead = new XMLHttpRequest(); + reqRead.open("GET", url, false); + reqRead.responseType = "arraybuffer"; + reqRead.send(); + if (reqRead.status !== 200) { + print("Error reading file: " + reqRead.status + " " + reqRead.statusText); + Window.alert("Could not read file " + reqRead.status + " " + reqRead.statusText); + return; + } + + // Upload to High Fidelity ... + url = urlBase + file.replace(/^(.*[\/\\])*/, ""); + print("Uploading model to " + url); + reqSend = new XMLHttpRequest(); + reqSend.open("PUT", url, true); + reqSend.responseType = "arraybuffer"; + reqSend.onreadystatechange = function () { + if (reqSend.readyState === reqSend.DONE) { + if (reqSend.status === 200) { + print("Uploaded model"); + callback(url); + } else { + print("Error uploading file: " + reqSend.status + " " + reqSend.statusText); + Window.alert("Could not upload file: " + reqSend.status + " " + reqSend.statusText); + } + } + }; + reqSend.send(reqRead.response); + }; + + return that; +}()); + var toolBar = (function () { var that = {}, toolBar, @@ -119,10 +164,8 @@ var toolBar = (function () { function toggleToolbar(active) { if (active === undefined) { - print("active === undefine"); active = toolBar.toolSelected(newModelButton); } else { - print("active !== undefine"); toolBar.selectTool(newModelButton, active); } @@ -174,8 +217,7 @@ var toolBar = (function () { that.mousePressEvent = function (event) { var clickedOverlay, url, - file, - position; + file; clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); @@ -195,9 +237,9 @@ var toolBar = (function () { if (clickedOverlay === loadFileMenuItem) { toggleToolbar(false); - file = Window.browse("Model File", "", "FST, FBX, or SVO files (*.fst *.fbx *.svo)"); + file = Window.browse("Select your model file ...", "", "Model files (*.fst *.fbx *.svo)"); if (file !== null) { - print("TODO: Upload model file: " + file); + modelUploader.upload(file, addModel); } return true; } From 2be03ada9c86a390243a3cf1e37e9721d1af7442 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 20 Jul 2014 15:14:48 -0700 Subject: [PATCH 007/206] Prepare for reading and processing model file content --- examples/editModels.js | 90 +++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 9304afbc49..a7fe76abdf 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -60,44 +60,80 @@ var mode = 0; var modelUploader = (function () { var that = {}, - urlBase = "http://public.highfidelity.io/meshes/"; + urlBase = "http://public.highfidelity.io/meshes/", + model; - that.upload = function (file, callback) { + function readModel(file) { var url, - reqRead, - reqSend; + req; + + print("Reading model from " + file); - // Read model content ... url = "file:///" + file; - print("Reading model from " + url); - reqRead = new XMLHttpRequest(); - reqRead.open("GET", url, false); - reqRead.responseType = "arraybuffer"; - reqRead.send(); - if (reqRead.status !== 200) { - print("Error reading file: " + reqRead.status + " " + reqRead.statusText); - Window.alert("Could not read file " + reqRead.status + " " + reqRead.statusText); - return; + req = new XMLHttpRequest(); + req.open("GET", url, false); + req.responseType = "arraybuffer"; + req.send(); + if (req.status !== 200) { + print("Error reading file: " + req.status + " " + req.statusText); + Window.alert("Could not read file " + req.status + " " + req.statusText); + return false; } + model = req.response; - // Upload to High Fidelity ... - url = urlBase + file.replace(/^(.*[\/\\])*/, ""); - print("Uploading model to " + url); - reqSend = new XMLHttpRequest(); - reqSend.open("PUT", url, true); - reqSend.responseType = "arraybuffer"; - reqSend.onreadystatechange = function () { - if (reqSend.readyState === reqSend.DONE) { - if (reqSend.status === 200) { - print("Uploaded model"); + return true; + } + + function setProperties() { + print("Setting model properties"); + return true; + } + + function createHttpMessage() { + print("Putting model into HTTP message"); + return true; + } + + function sendToHighFidelity(url, callback) { + var req; + + print("Sending model to High Fidelity"); + + req = new XMLHttpRequest(); + req.open("PUT", url, true); + req.responseType = "arraybuffer"; + req.onreadystatechange = function () { + if (req.readyState === req.DONE) { + if (req.status === 200) { + print("Uploaded model: " + url); callback(url); } else { - print("Error uploading file: " + reqSend.status + " " + reqSend.statusText); - Window.alert("Could not upload file: " + reqSend.status + " " + reqSend.statusText); + print("Error uploading file: " + req.status + " " + req.statusText); + Window.alert("Could not upload file: " + req.status + " " + req.statusText); } } }; - reqSend.send(reqRead.response); + req.send(model); + } + + that.upload = function (file, callback) { + var url = urlBase + file.replace(/^(.*[\/\\])*/, ""), + ok; + + // Read model content ... + ok = readModel(file); + if (!ok) { return; } + + // Set model properties ... + ok = setProperties(); + if (!ok) { return; } + + // Put model in HTTP message ... + ok = createHttpMessage(); + if (!ok) { return; } + + // Send model to High Fidelity ... + sendToHighFidelity(url, callback); }; return that; From 3f24f61180dee38114919a817a1bdc7cca6d00c0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 20 Jul 2014 22:45:19 -0700 Subject: [PATCH 008/206] Provide Content-Type and -Length headers when reading local files --- examples/testXMLHttpRequest.js | 2 +- .../script-engine/src/XMLHttpRequestClass.cpp | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/testXMLHttpRequest.js b/examples/testXMLHttpRequest.js index ec5bcf6c4c..79d2842464 100644 --- a/examples/testXMLHttpRequest.js +++ b/examples/testXMLHttpRequest.js @@ -168,7 +168,7 @@ if (localFile !== null) { this.assertEquals(200, req.status, "status should be `200`"); this.assertEquals("OK", req.statusText, "statusText should be `OK`"); this.assertEquals(0, req.errorCode); - this.assertEquals("", req.getAllResponseHeaders(), "headers should be null"); + this.assertNotEquals("", req.getAllResponseHeaders(), "headers should not be null"); this.assertContains("High Fidelity", req.response.substring(0, 100), "expected text not found in response") for (var i = 0; i <= req.DONE; i++) { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 84e49f2364..8e48682ea0 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -54,7 +54,8 @@ QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEn QScriptValue XMLHttpRequestClass::getStatus() const { if (_reply) { return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); - } else if(_url.isLocalFile()) { + } + if(_url.isLocalFile()) { switch (_errorCode) { case QNetworkReply::NoError: return QScriptValue(200); @@ -74,7 +75,8 @@ QScriptValue XMLHttpRequestClass::getStatus() const { QString XMLHttpRequestClass::getStatusText() const { if (_reply) { return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); - } else if (_url.isLocalFile()) { + } + if (_url.isLocalFile()) { switch (_errorCode) { case QNetworkReply::NoError: return "OK"; @@ -132,6 +134,13 @@ QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const { } return QString(headers.data()); } + if (_url.isLocalFile()) { + QString headers = QString("Content-Type: application/octet-stream\n"); + headers.append("Content-Length: "); + headers.append(QString("%1").arg(_rawResponseData.length())); + headers.append("\n"); + return headers; + } return QScriptValue(""); } @@ -139,6 +148,14 @@ QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { if (_reply && _reply->hasRawHeader(name.toLatin1())) { return QScriptValue(QString(_reply->rawHeader(name.toLatin1()))); } + if (_url.isLocalFile()) { + if (name.toLower() == "content-type") { + return QString("application/octet-stream"); + } + if (name.toLower() == "content-length") { + return QString("%1").arg(_rawResponseData.length()); + } + } return QScriptValue::NullValue; } From ab0ec9f4746be7f047dba960547aa4e8b294362c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 20 Jul 2014 22:55:07 -0700 Subject: [PATCH 009/206] Read raw data of different model file types --- examples/editModels.js | 136 +++++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 20 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index a7fe76abdf..54cf014dfe 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -58,28 +58,115 @@ var jointList = MyAvatar.getJointNames(); var mode = 0; +if (typeof String.prototype.fileName !== 'function') { + String.prototype.fileName = function (str) { + return this.replace(/^(.*[\/\\])*/, ""); + }; +} + +if (typeof String.prototype.path !== 'function') { + String.prototype.path = function (str) { + return this.replace(/[\\\/][^\\\/]*$/, ""); + }; +} + + var modelUploader = (function () { var that = {}, urlBase = "http://public.highfidelity.io/meshes/", - model; + fstBuffer, + fbxBuffer, + svoBuffer, + mapping = {}, + NAME_FIELD = "name", + SCALE_FIELD = "scale", + FILENAME_FIELD = "filename", + TEXDIR_FIELD = "texdir", + fbxDataView; - function readModel(file) { - var url, - req; + function error(message) { + Window.alert(message); + print(message); + } - print("Reading model from " + file); + function readFile(filename, buffer, length) { + var url = "file:///" + filename, + req = new XMLHttpRequest(); - url = "file:///" + file; - req = new XMLHttpRequest(); req.open("GET", url, false); req.responseType = "arraybuffer"; req.send(); if (req.status !== 200) { - print("Error reading file: " + req.status + " " + req.statusText); - Window.alert("Could not read file " + req.status + " " + req.statusText); - return false; + error("Could not read file: " + filename + " : " + req.status + " " + req.statusText); + return null; + } + + return { + buffer: req.response, + length: parseInt(req.getResponseHeader("Content-Length"), 10) + }; + } + + function readMapping(buffer) { + return {}; // DJRTODO + } + + function readGeometry(buffer) { + return {}; // DJRTODO + } + + function readModel(filename) { + var url, + req, + fbxFilename, + geometry; + + if (filename.toLowerCase().slice(-4) === ".svo") { + svoBuffer = readFile(filename); + if (svoBuffer === null) { + return false; + } + + } else { + + if (filename.toLowerCase().slice(-4) === ".fst") { + fstBuffer = readFile(filename); + if (fstBuffer === null) { + return false; + } + mapping = readMapping(fstBuffer); + fbxFilename = filename.path() + "\\" + mapping[FILENAME_FIELD]; + + } else if (filename.toLowerCase().slice(-4) === ".fbx") { + fbxFilename = filename; + mapping[FILENAME_FIELD] = filename.fileName(); + + } else { + error("Unrecognized file type: " + filename); + return false; + } + + fbxBuffer = readFile(fbxFilename); + if (fbxBuffer === null) { + return false; + } + + geometry = readGeometry(fbxBuffer); + if (!mapping.hasOwnProperty(SCALE_FIELD)) { + mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0 : 15.0); + } + } + + // Add any missing basic mappings + if (!mapping.hasOwnProperty(NAME_FIELD)) { + mapping[NAME_FIELD] = filename.fileName().slice(0, -4); + } + if (!mapping.hasOwnProperty(TEXDIR_FIELD)) { + mapping[TEXDIR_FIELD] = "."; + } + if (!mapping.hasOwnProperty(SCALE_FIELD)) { + mapping[SCALE_FIELD] = 0.2; // For SVO models. } - model = req.response; return true; } @@ -99,6 +186,8 @@ var modelUploader = (function () { print("Sending model to High Fidelity"); + // DJRTODO + req = new XMLHttpRequest(); req.open("PUT", url, true); req.responseType = "arraybuffer"; @@ -113,24 +202,31 @@ var modelUploader = (function () { } } }; - req.send(model); + + if (fbxBuffer !== null) { + req.send(fbxBuffer.buffer); + } else { + req.send(svoBuffer.buffer); + } } that.upload = function (file, callback) { - var url = urlBase + file.replace(/^(.*[\/\\])*/, ""), - ok; + var url = urlBase + file.fileName(); // Read model content ... - ok = readModel(file); - if (!ok) { return; } + if (!readModel(file)) { + return; + } // Set model properties ... - ok = setProperties(); - if (!ok) { return; } + if (!setProperties()) { + return; + } // Put model in HTTP message ... - ok = createHttpMessage(); - if (!ok) { return; } + if (!createHttpMessage()) { + return; + } // Send model to High Fidelity ... sendToHighFidelity(url, callback); From f87a83f79e69664556a827dfa44ebb115d4e87ab Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 21 Jul 2014 12:55:25 -0700 Subject: [PATCH 010/206] Read mappings from FST file --- examples/editModels.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/examples/editModels.js b/examples/editModels.js index 54cf014dfe..b8ebcd8a6c 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -108,7 +108,32 @@ var modelUploader = (function () { } function readMapping(buffer) { - return {}; // DJRTODO + var mapping = {}, + lines, + line, + values, + name, + i; + + // Simplified to target values relevant to model uploading. + lines = String(buffer.buffer).split(/\r\n|\r|\n/); + for (i = 0; i < lines.length; i += 1) { + line = lines[i].trim(); + if (line.length > 0 && line[0] !== "#") { + values = line.split(/\s*=\s*/); + name = values[0].toLowerCase(); + if (values.length === 2) { + mapping[name] = values[1]; + } else if (values.length === 3 && name === "lod") { + if (mapping[name] === undefined) { + mapping[name] = {}; + } + mapping[name][values[1]] = values[2]; + } + } + } + + return mapping; } function readGeometry(buffer) { @@ -121,6 +146,8 @@ var modelUploader = (function () { fbxFilename, geometry; + print("Reading model file: " + filename); + if (filename.toLowerCase().slice(-4) === ".svo") { svoBuffer = readFile(filename); if (svoBuffer === null) { From ef8ce8ad7d1848976cbaa8c0ad0d59d26a2696a5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 21 Jul 2014 21:39:52 -0700 Subject: [PATCH 011/206] Remember location of model file last selected --- examples/editModels.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/editModels.js b/examples/editModels.js index b8ebcd8a6c..0363f29b05 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -396,8 +396,11 @@ var toolBar = (function () { if (clickedOverlay === loadFileMenuItem) { toggleToolbar(false); - file = Window.browse("Select your model file ...", "", "Model files (*.fst *.fbx *.svo)"); + file = Window.browse("Select your model file ...", + Settings.getValue("LastModelUploadLocation").path(), + "Model files (*.fst *.fbx *.svo)"); if (file !== null) { + Settings.setValue("LastModelUploadLocation", file); modelUploader.upload(file, addModel); } return true; From 09d52251ef04d1f9d9d1ad4c5868a22c5b4c27cf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Jul 2014 20:38:44 -0700 Subject: [PATCH 012/206] Fix merge --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8b00bface7..02f4842e88 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3628,7 +3628,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->getModelsScriptingInterface()->setModelTree(_models.getTree()); // model has some custom types - Model::registerMetaTypes(scriptEngine->getEngine()); + Model::registerMetaTypes(scriptEngine); // hook our avatar object into this script engine scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features From ed7bd9317e3d1cd4aef678828cc1bec2e7300276 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 22 Jul 2014 21:48:48 -0700 Subject: [PATCH 013/206] Make XMLHttpRequest return an ArrayBuffer object when requested --- libraries/script-engine/src/XMLHttpRequestClass.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 8e48682ea0..563e268222 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -18,6 +18,7 @@ #include #include "XMLHttpRequestClass.h" +#include "ScriptEngine.h" XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : _engine(engine), @@ -296,7 +297,8 @@ void XMLHttpRequestClass::requestFinished() { _responseData = QScriptValue::NullValue; } } else if (_responseType == "arraybuffer") { - _responseData = _engine->newVariant(QVariant::fromValue(_rawResponseData)); + QScriptValue data = _engine->newVariant(QVariant::fromValue(_rawResponseData)); + _responseData = _engine->newObject(reinterpret_cast(_engine)->getArrayBufferClass(), data); } else { _responseData = QScriptValue(QString(_rawResponseData.data())); } From ec8b82bf6e5c9ffd69d864da1ada45741d12f42d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Jul 2014 12:27:52 -0700 Subject: [PATCH 014/206] Read author and texture filenames from binary FBX files --- examples/editModels.js | 95 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 0363f29b05..51757f81bb 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -58,18 +58,62 @@ var jointList = MyAvatar.getJointNames(); var mode = 0; -if (typeof String.prototype.fileName !== 'function') { +if (typeof String.prototype.fileName !== "function") { String.prototype.fileName = function (str) { return this.replace(/^(.*[\/\\])*/, ""); }; } -if (typeof String.prototype.path !== 'function') { +if (typeof String.prototype.path !== "function") { String.prototype.path = function (str) { return this.replace(/[\\\/][^\\\/]*$/, ""); }; } +if (typeof DataView.prototype.indexOf !== "function") { + DataView.prototype.indexOf = function (searchString, position) { + var searchLength = searchString.length, + byteArrayLength = this.byteLength, + maxSearchIndex = byteArrayLength - searchLength, + searchCharCodes = [], + found, + i, + j; + + searchCharCodes[searchLength] = 0; + for (j = 0; j < searchLength; j += 1) { + searchCharCodes[j] = searchString.charCodeAt(j); + } + + i = position; + found = false; + while (i < maxSearchIndex && !found) { + j = 0; + while (j < searchLength && this.getUint8(i + j) === searchCharCodes[j]) { + j += 1; + } + found = (j === searchLength); + i += 1; + } + + return found ? i - 1 : -1; + }; +} + +if (typeof DataView.prototype.string !== "function") { + DataView.prototype.string = function (i, length) { + var charCodes = [], + end = i + length, + j; + + for (j = i; j < end; j += 1) { + charCodes.push(this.getUint8(j)); + } + + return String.fromCharCode.apply(String, charCodes); + }; +} + var modelUploader = (function () { var that = {}, @@ -136,8 +180,51 @@ var modelUploader = (function () { return mapping; } - function readGeometry(buffer) { - return {}; // DJRTODO + function readGeometry(fbxBuffer) { + var dv = new DataView(fbxBuffer.buffer), + geometry = {}, + pathLength, + filename, + author, + i; + + // Simple direct search of FBX file for relevant texture filenames (excl. paths) instead of interpreting FBX format. + // - "RelativeFilename" Record type + // - char Subtype + // - Uint8 Length of path string + // - 00 00 00 3 null chars + // - Path and name of texture file + geometry.textures = []; + i = 0; + while (i !== -1) { + i = dv.indexOf("RelativeFilename", i); + if (i !== -1) { + i += 17; // Record type and subtype + pathLength = dv.getUint8(i); + i += 4; // Path length and null chars + filename = dv.string(i, pathLength).fileName(); + geometry.textures.push(filename); + i += pathLength; + } + } + + // Simple direct search of FBX file for the first author record. + // - "Author" Record type + // - char Subtype + // - Uint8 Length of path string + // - 00 00 00 3 null chars + // - Path and name of texture file + i = dv.indexOf("Author", 0); + if (i !== -1) { + i += 7; + pathLength = dv.getUint8(i); + if (pathLength > 0) { + author = dv.string(i, pathLength); + geometry.author = author; + } + } + + return geometry; } function readModel(filename) { From 6b72274d219a69371d2df1e5792724becdfefa83 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Jul 2014 16:05:34 -0700 Subject: [PATCH 015/206] Read author and texture filenames from text FBX files --- examples/editModels.js | 51 +++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 51757f81bb..bcfa0176cc 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -183,43 +183,68 @@ var modelUploader = (function () { function readGeometry(fbxBuffer) { var dv = new DataView(fbxBuffer.buffer), geometry = {}, - pathLength, + binary, + stringLength, filename, author, i; + binary = (dv.string(0, 18) === "Kaydara FBX Binary"); + // Simple direct search of FBX file for relevant texture filenames (excl. paths) instead of interpreting FBX format. - // - "RelativeFilename" Record type + // Binary format: + // - 'RelativeFilename' Record type // - char Subtype // - Uint8 Length of path string // - 00 00 00 3 null chars // - Path and name of texture file + // Text format: + // - 'RelativeFilename' Record type + // - ': " ' Pre-string colon and quote + // - Path and name of texture file + // - '"' End-of-string quote geometry.textures = []; i = 0; while (i !== -1) { i = dv.indexOf("RelativeFilename", i); if (i !== -1) { - i += 17; // Record type and subtype - pathLength = dv.getUint8(i); - i += 4; // Path length and null chars - filename = dv.string(i, pathLength).fileName(); + if (binary) { + i += 17; + stringLength = dv.getUint8(i); + i += 4; + } else { + i = dv.indexOf("\"", i) + 1; + stringLength = dv.indexOf("\"", i) - i; + } + filename = dv.string(i, stringLength).fileName(); geometry.textures.push(filename); - i += pathLength; + i += stringLength; } } // Simple direct search of FBX file for the first author record. - // - "Author" Record type + // Binary format: + // - 'Author' Record type // - char Subtype // - Uint8 Length of path string // - 00 00 00 3 null chars - // - Path and name of texture file + // - Author name + // Text format: + // - 'Author' Record type + // - ': "' Pre-string colon and quote + // - Author name; may be empty + // - '"' End-of-string quote i = dv.indexOf("Author", 0); if (i !== -1) { - i += 7; - pathLength = dv.getUint8(i); - if (pathLength > 0) { - author = dv.string(i, pathLength); + if (binary) { + i += 7; + stringLength = dv.getUint8(i); + } else { + i = dv.indexOf("\"", i) + 1; + stringLength = dv.indexOf("\"", i) - i; + } + if (stringLength > 0) { + author = dv.string(i, stringLength); geometry.author = author; } } From 401326ddd7c140ce932b6e852695006c83347f64 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Jul 2014 16:59:52 -0700 Subject: [PATCH 016/206] Fix FST file processing after ArrayBuffer change --- examples/editModels.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index bcfa0176cc..917f9ad7fd 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -151,8 +151,9 @@ var modelUploader = (function () { }; } - function readMapping(buffer) { - var mapping = {}, + function readMapping(fstBuffer) { + var dv = new DataView(fstBuffer.buffer), + mapping = {}, lines, line, values, @@ -160,7 +161,7 @@ var modelUploader = (function () { i; // Simplified to target values relevant to model uploading. - lines = String(buffer.buffer).split(/\r\n|\r|\n/); + lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/); for (i = 0; i < lines.length; i += 1) { line = lines[i].trim(); if (line.length > 0 && line[0] !== "#") { From 7c6f1ff414d819e9d3b21b13b0d57d8286865923 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 23 Jul 2014 17:09:40 -0700 Subject: [PATCH 017/206] Handle FBX file name not found in FST file more gracefully --- examples/editModels.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/editModels.js b/examples/editModels.js index 917f9ad7fd..1498cff6d0 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -275,7 +275,12 @@ var modelUploader = (function () { return false; } mapping = readMapping(fstBuffer); - fbxFilename = filename.path() + "\\" + mapping[FILENAME_FIELD]; + if (mapping.hasOwnProperty(FILENAME_FIELD)) { + fbxFilename = filename.path() + "\\" + mapping[FILENAME_FIELD]; + } else { + error("FBX file name not found in FST file!"); + return false; + } } else if (filename.toLowerCase().slice(-4) === ".fbx") { fbxFilename = filename; From 040254a119ad8d651c642401ee31d0459b5f1929 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Jul 2014 17:04:02 -0700 Subject: [PATCH 018/206] Add optional Cancel button to JavaScript Window.form() --- .../scripting/WindowScriptingInterface.cpp | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 827f66c8d5..a36d149a86 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -94,11 +94,14 @@ QScriptValue WindowScriptingInterface::showConfirm(const QString& message) { /// Display a form layout with an edit box /// \param const QString& title title to display /// \param const QScriptValue form to display (array containing labels and values) -/// \return QScriptValue result form (unchanged is dialog canceled) +/// \return QScriptValue `true` if 'OK' was clicked, `false` otherwise QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptValue form) { + if (form.isArray() && form.property("length").toInt32() > 0) { QDialog* editDialog = new QDialog(Application::getInstance()->getWindow()); editDialog->setWindowTitle(title); + + bool cancelButton = false; QVBoxLayout* layout = new QVBoxLayout(); editDialog->setLayout(layout); @@ -120,42 +123,60 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal QVector edits; for (int i = 0; i < form.property("length").toInt32(); ++i) { QScriptValue item = form.property(i); - edits.push_back(new QLineEdit(item.property("value").toString())); - formLayout->addRow(item.property("label").toString(), edits.back()); + + if (item.property("button").toString() != "") { + cancelButton = cancelButton || item.property("button").toString().toLower() == "cancel"; + } else { + edits.push_back(new QLineEdit(item.property("value").toString())); + formLayout->addRow(item.property("label").toString(), edits.back()); + } } - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); + + QDialogButtonBox* buttons = new QDialogButtonBox( + QDialogButtonBox::Ok + | (cancelButton ? QDialogButtonBox::Cancel : QDialogButtonBox::NoButton) + ); connect(buttons, SIGNAL(accepted()), editDialog, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), editDialog, SLOT(reject())); layout->addWidget(buttons); - if (editDialog->exec() == QDialog::Accepted) { + int result = editDialog->exec(); + if (result == QDialog::Accepted) { + int j = -1; for (int i = 0; i < form.property("length").toInt32(); ++i) { QScriptValue item = form.property(i); QScriptValue value = item.property("value"); bool ok = true; - if (value.isNumber()) { - value = edits.at(i)->text().toDouble(&ok); - } else if (value.isString()) { - value = edits.at(i)->text(); - } else if (value.isBool()) { - if (edits.at(i)->text() == "true") { - value = true; - } else if (edits.at(i)->text() == "false") { - value = false; - } else { - ok = false; + qDebug() << "item.property(""button"").toString() = " << item.property("button").toString(); + if (item.property("button").toString() == "") { + j += 1; + if (value.isNumber()) { + value = edits.at(j)->text().toDouble(&ok); + } else if (value.isString()) { + value = edits.at(j)->text(); + } else if (value.isBool()) { + if (edits.at(j)->text() == "true") { + value = true; + } else if (edits.at(j)->text() == "false") { + value = false; + } else { + ok = false; + } + } + if (ok) { + item.setProperty("value", value); + form.setProperty(i, item); } - } - if (ok) { - item.setProperty("value", value); - form.setProperty(i, item); } } } delete editDialog; + + return (result == QDialog::Accepted); } - return form; + return false; } /// Display a prompt with a text box From eecdc2dc7b942df35bfed245e3b513423915d268 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 24 Jul 2014 19:00:41 -0700 Subject: [PATCH 019/206] Increase initial width of the edit fields in JavaScript Window.form() --- interface/src/scripting/WindowScriptingInterface.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index a36d149a86..1889b283f8 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -127,8 +127,10 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal if (item.property("button").toString() != "") { cancelButton = cancelButton || item.property("button").toString().toLower() == "cancel"; } else { - edits.push_back(new QLineEdit(item.property("value").toString())); - formLayout->addRow(item.property("label").toString(), edits.back()); + QLineEdit* edit = new QLineEdit(item.property("value").toString()); + edit->setMinimumWidth(200); + edits.push_back(edit); + formLayout->addRow(new QLabel(item.property("label").toString()), edit); } } From 49e0d07ac8123eba72dae92e41f1fb9cd14fd10a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 25 Jul 2014 20:32:44 -0700 Subject: [PATCH 020/206] Add directory picker button option to JavaScript Window.form() --- .../scripting/WindowScriptingInterface.cpp | 94 +++++++++++++++++-- .../src/scripting/WindowScriptingInterface.h | 3 + 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 1889b283f8..19e2ba8194 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -91,9 +91,44 @@ QScriptValue WindowScriptingInterface::showConfirm(const QString& message) { return QScriptValue(response == QMessageBox::Yes); } +void WindowScriptingInterface::chooseDirectory() { + QPushButton* button = reinterpret_cast(sender()); + + QString title = button->property("title").toString(); + QString path = button->property("path").toString(); + QRegExp displayAs = button->property("displayAs").toRegExp(); + QRegExp validateAs = button->property("validateAs").toRegExp(); + QString errorMessage = button->property("errorMessage").toString(); + + QString directory = QFileDialog::getExistingDirectory(button, title, path); + if (directory.isEmpty()) { + return; + } + + if (!validateAs.exactMatch(directory)) { + QMessageBox::warning(NULL, "Invalid Directory", errorMessage); + return; + } + + button->setProperty("path", directory); + + displayAs.indexIn(directory); + QString buttonText = displayAs.cap(1) != "" ? displayAs.cap(1) : "."; + button->setText(buttonText); +} + +QString WindowScriptingInterface::jsRegExp2QtRegExp(QString string) { + // Converts string representation of RegExp from JavaScript format to Qt format. + return string.mid(1, string.length() - 2) // No enclosing slashes. + .replace("\\/", "/"); // No escaping of forward slash. +} + /// Display a form layout with an edit box /// \param const QString& title title to display -/// \param const QScriptValue form to display (array containing labels and values) +/// \param const QScriptValue form to display as an array of objects: +/// - label, value +/// - label, directory, title, display regexp, validate regexp, error message +/// - button ("Cancel") /// \return QScriptValue `true` if 'OK' was clicked, `false` otherwise QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptValue form) { @@ -121,11 +156,42 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal area->setWidget(container); QVector edits; + QVector directories; for (int i = 0; i < form.property("length").toInt32(); ++i) { QScriptValue item = form.property(i); if (item.property("button").toString() != "") { cancelButton = cancelButton || item.property("button").toString().toLower() == "cancel"; + + } else if (item.property("directory").toString() != "") { + QString path = item.property("directory").toString(); + QString title = item.property("title").toString(); + if (title == "") { + title = "Choose Directory"; + } + QString displayAsString = item.property("displayAs").toString(); + QRegExp displayAs = QRegExp(displayAsString != "" ? jsRegExp2QtRegExp(displayAsString) : "^(.*)$"); + QString validateAsString = item.property("validateAs").toString(); + QRegExp validateAs = QRegExp(validateAsString != "" ? jsRegExp2QtRegExp(validateAsString) : ".*"); + QString errorMessage = item.property("errorMessage").toString(); + if (errorMessage == "") { + errorMessage = "Invalid directory"; + } + + QPushButton* directory = new QPushButton(displayAs.cap(1)); + directory->setProperty("title", title); + directory->setProperty("path", path); + directory->setProperty("displayAs", displayAs); + directory->setProperty("validateAs", validateAs); + directory->setProperty("errorMessage", errorMessage); + directory->setText(displayAs.cap(1) != "" ? displayAs.cap(1) : "."); + + directory->setMinimumWidth(200); + directories.push_back(directory); + + formLayout->addRow(new QLabel(item.property("label").toString()), directory); + connect(directory, SIGNAL(clicked(bool)), SLOT(chooseDirectory())); + } else { QLineEdit* edit = new QLineEdit(item.property("value").toString()); edit->setMinimumWidth(200); @@ -144,22 +210,30 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal int result = editDialog->exec(); if (result == QDialog::Accepted) { - int j = -1; + int e = -1; + int d = -1; for (int i = 0; i < form.property("length").toInt32(); ++i) { QScriptValue item = form.property(i); QScriptValue value = item.property("value"); - bool ok = true; - qDebug() << "item.property(""button"").toString() = " << item.property("button").toString(); - if (item.property("button").toString() == "") { - j += 1; + + if (item.property("button").toString() != "") { + // Nothing to do + } else if (item.property("directory").toString() != "") { + d += 1; + value = directories.at(d)->property("path").toString(); + item.setProperty("directory", value); + form.setProperty(i, item); + } else { + e += 1; + bool ok = true; if (value.isNumber()) { - value = edits.at(j)->text().toDouble(&ok); + value = edits.at(e)->text().toDouble(&ok); } else if (value.isString()) { - value = edits.at(j)->text(); + value = edits.at(e)->text(); } else if (value.isBool()) { - if (edits.at(j)->text() == "true") { + if (edits.at(e)->text() == "true") { value = true; - } else if (edits.at(j)->text() == "false") { + } else if (edits.at(e)->text() == "false") { value = false; } else { ok = false; diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 654b048b24..025ee06ed7 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -40,9 +40,12 @@ private slots: QScriptValue showPrompt(const QString& message, const QString& defaultText); QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter); QScriptValue showS3Browse(const QString& nameFilter); + void chooseDirectory(); private: WindowScriptingInterface(); + + QString jsRegExp2QtRegExp(QString string); }; #endif // hifi_WindowScriptingInterface_h From fcfaf6a9beb366c763bc8a79de83a14dff7000c0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Jul 2014 08:49:42 -0700 Subject: [PATCH 021/206] Add Set Model Properties dialog for model uploading --- examples/editModels.js | 85 +++++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 1498cff6d0..58855f24cd 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -118,14 +118,19 @@ if (typeof DataView.prototype.string !== "function") { var modelUploader = (function () { var that = {}, urlBase = "http://public.highfidelity.io/meshes/", + modelFile, fstBuffer, fbxBuffer, svoBuffer, - mapping = {}, + mapping, NAME_FIELD = "name", SCALE_FIELD = "scale", FILENAME_FIELD = "filename", TEXDIR_FIELD = "texdir", + ANIMATION_URL_FIELD = "animationurl", + PITCH_FIELD = "pitch", + YAW_FIELD = "yaw", + ROLL_FIELD = "roll", fbxDataView; function error(message) { @@ -253,41 +258,41 @@ var modelUploader = (function () { return geometry; } - function readModel(filename) { - var url, - req, - fbxFilename, + function readModel() { + var fbxFilename, geometry; - print("Reading model file: " + filename); + print("Reading model file: " + modelFile); - if (filename.toLowerCase().slice(-4) === ".svo") { - svoBuffer = readFile(filename); + mapping = {}; + + if (modelFile.toLowerCase().slice(-4) === ".svo") { + svoBuffer = readFile(modelFile); if (svoBuffer === null) { return false; } } else { - if (filename.toLowerCase().slice(-4) === ".fst") { - fstBuffer = readFile(filename); + if (modelFile.toLowerCase().slice(-4) === ".fst") { + fstBuffer = readFile(modelFile); if (fstBuffer === null) { return false; } mapping = readMapping(fstBuffer); if (mapping.hasOwnProperty(FILENAME_FIELD)) { - fbxFilename = filename.path() + "\\" + mapping[FILENAME_FIELD]; + fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; } else { error("FBX file name not found in FST file!"); return false; } - } else if (filename.toLowerCase().slice(-4) === ".fbx") { - fbxFilename = filename; - mapping[FILENAME_FIELD] = filename.fileName(); + } else if (modelFile.toLowerCase().slice(-4) === ".fbx") { + fbxFilename = modelFile; + mapping[FILENAME_FIELD] = modelFile.fileName(); } else { - error("Unrecognized file type: " + filename); + error("Unrecognized file type: " + modelFile); return false; } @@ -304,7 +309,7 @@ var modelUploader = (function () { // Add any missing basic mappings if (!mapping.hasOwnProperty(NAME_FIELD)) { - mapping[NAME_FIELD] = filename.fileName().slice(0, -4); + mapping[NAME_FIELD] = modelFile.fileName().slice(0, -4); } if (!mapping.hasOwnProperty(TEXDIR_FIELD)) { mapping[TEXDIR_FIELD] = "."; @@ -317,7 +322,50 @@ var modelUploader = (function () { } function setProperties() { - print("Setting model properties"); + var form = [], + decimals = 3, + directory, + displayAs, + validateAs; + + form.push({ label: "Name:", value: mapping[NAME_FIELD] }); + + directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD]; + displayAs = new RegExp("^" + modelFile.path().replace(/[\\\\\\\/]/, "[\\\\\\\/]") + "[\\\\\\\/](.*)"); + validateAs = new RegExp("^" + modelFile.path().replace(/[\\\\\\\/]/, "[\\\\\\\/]") + "([\\\\\\\/].*)?"); + + form.push({ + label: "Texture directory:", + directory: modelFile.path() + "/" + mapping[TEXDIR_FIELD], + title: "Choose Texture Directory", + displayAs: displayAs, + validateAs: validateAs, + errorMessage: "Texture directory must be subdirectory of model directory." + }); + + form.push({ label: "Animation URL:", value: "" }); + form.push({ label: "Pitch:", value: (0).toFixed(decimals) }); + form.push({ label: "Yaw:", value: (0).toFixed(decimals) }); + form.push({ label: "Roll:", value: (0).toFixed(decimals) }); + form.push({ label: "Scale:", value: mapping[SCALE_FIELD].toFixed(decimals) }); + form.push({ button: "Cancel" }); + + if (!Window.form("Set Model Properties", form)) { + print("User cancelled uploading model"); + return false; + } + + mapping[NAME_FIELD] = form[0].value; + mapping[TEXDIR_FIELD] = form[1].directory.slice(modelFile.path().length + 1); + if (mapping[TEXDIR_FIELD] === "") { + mapping[TEXDIR_FIELD] = "."; + } + mapping[ANIMATION_URL_FIELD] = form[2].value; + mapping[PITCH_FIELD] = form[3].value; + mapping[YAW_FIELD] = form[4].value; + mapping[ROLL_FIELD] = form[5].value; + mapping[SCALE_FIELD] = form[6].value; + return true; } @@ -356,10 +404,11 @@ var modelUploader = (function () { } that.upload = function (file, callback) { + modelFile = file; var url = urlBase + file.fileName(); // Read model content ... - if (!readModel(file)) { + if (!readModel()) { return; } From f602f42189b8a22b3a084c4315f0054e3f79c57d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 26 Jul 2014 09:22:44 -0700 Subject: [PATCH 022/206] Add Cancel button to model editing dialog --- examples/editModels.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 58855f24cd..2b1aa190c6 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1618,23 +1618,25 @@ function handeMenuEvent(menuItem){ array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) }); array.push({ label: "Roll:", value: angles.z.toFixed(decimals) }); array.push({ label: "Scale:", value: 2 * selectedModelProperties.radius.toFixed(decimals) }); - - var propertyName = Window.form("Edit Properties", array); - modelSelected = false; - - selectedModelProperties.modelURL = array[0].value; - selectedModelProperties.animationURL = array[1].value; - selectedModelProperties.position.x = array[2].value; - selectedModelProperties.position.y = array[3].value; - selectedModelProperties.position.z = array[4].value; - angles.x = array[5].value; - angles.y = array[6].value; - angles.z = array[7].value; - selectedModelProperties.modelRotation = Quat.fromVec3Degrees(angles); - selectedModelProperties.radius = array[8].value / 2; - print(selectedModelProperties.radius); + array.push({ button: "Cancel" }); - Models.editModel(selectedModelID, selectedModelProperties); + if (Window.form("Edit Properties", array)) { + selectedModelProperties.modelURL = array[0].value; + selectedModelProperties.animationURL = array[1].value; + selectedModelProperties.position.x = array[2].value; + selectedModelProperties.position.y = array[3].value; + selectedModelProperties.position.z = array[4].value; + angles.x = array[5].value; + angles.y = array[6].value; + angles.z = array[7].value; + selectedModelProperties.modelRotation = Quat.fromVec3Degrees(angles); + selectedModelProperties.radius = array[8].value / 2; + print(selectedModelProperties.radius); + + Models.editModel(selectedModelID, selectedModelProperties); + } + + modelSelected = false; } } tooltip.show(false); From 61bb21cc00477865cde342c6330e3bfd4d227583 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sun, 27 Jul 2014 22:05:18 -0700 Subject: [PATCH 023/206] Prepare multipart/form-date message with model file and attributes --- examples/editModels.js | 242 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 223 insertions(+), 19 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 2b1aa190c6..c5a8c5c1e8 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -70,6 +70,39 @@ if (typeof String.prototype.path !== "function") { }; } +if (typeof String.prototype.toArrayBuffer !== "function") { + String.prototype.toArrayBuffer = function () { + var length, + buffer, + view, + charCode, + charCodes, + i; + + charCodes = []; + + length = this.length; + for (i = 0; i < length; i += 1) { + charCode = this.charCodeAt(i); + if (i <= 255) { + charCodes.push(charCode); + } else { + charCodes.push(charCode / 256); + charCodes.push(charCode % 256); + } + } + + length = charCodes.length; + buffer = new ArrayBuffer(length); + view = new Uint8Array(buffer); + for (i = 0; i < length; i += 1) { + view[i] = charCodes[i]; + } + + return buffer; + }; +} + if (typeof DataView.prototype.indexOf !== "function") { DataView.prototype.indexOf = function (searchString, position) { var searchLength = searchString.length, @@ -101,19 +134,139 @@ if (typeof DataView.prototype.indexOf !== "function") { } if (typeof DataView.prototype.string !== "function") { - DataView.prototype.string = function (i, length) { + DataView.prototype.string = function (start, length) { var charCodes = [], - end = i + length, - j; + end, + i; - for (j = i; j < end; j += 1) { - charCodes.push(this.getUint8(j)); + if (start === undefined) { + start = 0; + } + if (length === undefined) { + length = this.length; + } + + end = start + length; + for (i = start; i < end; i += 1) { + charCodes.push(this.getUint8(i)); } return String.fromCharCode.apply(String, charCodes); }; } +var httpMultiPart = (function () { + var that = {}, + parts, + byteLength, + boundaryString, + crlf; + + function clear() { + boundaryString = "--boundary_" + Uuid.generate() + "="; + parts = []; + byteLength = 0; + crlf = ""; + } + that.clear = clear; + + function boundary() { + return boundaryString.slice(2); + } + that.boundary = boundary; + + function length() { + return byteLength; + } + that.length = length; + + function add(object) { + // - name, string + // - name, buffer + var buffer, + stringBuffer, + string, + length; + + if (object.name === undefined) { + + throw new Error("Item to add to HttpMultiPart must have a name"); + + } else if (object.string !== undefined) { + //--= + //Content-Disposition: form-data; name="model_name" + // + // + + string = crlf + boundaryString + "\r\n" + + "Content-Disposition: form-data; name=\"" + object.name + "\"\r\n" + + "\r\n" + + object.string; + buffer = string.toArrayBuffer(); + + } else if (object.buffer !== undefined) { + //--= + //Content-Disposition: form-data; name="fbx"; filename="" + //Content-Type: application/octet-stream + // + // + + string = crlf + boundaryString + "\r\n" + + "Content-Disposition: form-data; name=\"" + object.name + + "\"; filename=\"" + object.buffer.filename + "\"\r\n" + + "Content-Type: application/octet-stream\r\n" + + "\r\n"; + stringBuffer = string.toArrayBuffer(); + + buffer = new Uint8Array(stringBuffer.byteLength + object.buffer.buffer.byteLength); + buffer.set(new Uint8Array(stringBuffer)); + buffer.set(new Uint8Array(object.buffer.buffer), stringBuffer.byteLength); + + } else { + + throw new Error("Item to add to HttpMultiPart not recognized"); + } + + byteLength += buffer.byteLength; + parts.push(buffer); + + crlf = "\r\n"; + + return true; + } + that.add = add; + + function response() { + var buffer, + view, + charCodes, + str, + i, + j; + + str = crlf + boundaryString + "--\r\n"; + buffer = str.toArrayBuffer(); + byteLength += buffer.byteLength; + parts.push(buffer); + + charCodes = []; + for (i = 0; i < parts.length; i += 1) { + view = new Uint8Array(parts[i]); + for (j = 0; j < view.length; j += 1) { + charCodes.push(view[j]); + } + } + str = String.fromCharCode.apply(String, charCodes); + + return str; + } + that.response = response; + + clear(); + + return that; +}()); + var modelUploader = (function () { var that = {}, @@ -151,8 +304,8 @@ var modelUploader = (function () { } return { + filename: filename.fileName(), buffer: req.response, - length: parseInt(req.getResponseHeader("Content-Length"), 10) }; } @@ -259,8 +412,8 @@ var modelUploader = (function () { } function readModel() { - var fbxFilename, - geometry; + var geometry, + fbxFilename; print("Reading model file: " + modelFile); @@ -328,6 +481,8 @@ var modelUploader = (function () { displayAs, validateAs; + print("Setting model properties"); + form.push({ label: "Name:", value: mapping[NAME_FIELD] }); directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD]; @@ -370,7 +525,60 @@ var modelUploader = (function () { } function createHttpMessage() { - print("Putting model into HTTP message"); + var i; + + print("Preparing to send model"); + + httpMultiPart.clear(); + + // Model name + if (mapping.hasOwnProperty(NAME_FIELD)) { + httpMultiPart.add({ + name : "model_name", + string : mapping[NAME_FIELD] + }); + } else { + error("Model name is missing"); + httpMultiPart.clear(); + return false; + } + + // FST file + if (fstBuffer) { + httpMultiPart.add({ + name : "fst", + buffer: fstBuffer + }); + } + + // FBX file + if (fbxBuffer) { + httpMultiPart.add({ + name : "fbx", + buffer: fbxBuffer + }); + } + + // SVO file + if (svoBuffer) { + httpMultiPart.add({ + name : "svo", + buffer: svoBuffer + }); + } + + // LOD files + // DJRTODO + + // Textures + // DJRTODO + + // Model category + httpMultiPart.add({ + name : "model_category", + string : "item" // DJRTODO: What model category to use? + }); + return true; } @@ -379,11 +587,10 @@ var modelUploader = (function () { print("Sending model to High Fidelity"); - // DJRTODO - req = new XMLHttpRequest(); - req.open("PUT", url, true); - req.responseType = "arraybuffer"; + req.open("POST", url, true); + req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\""); + req.onreadystatechange = function () { if (req.readyState === req.DONE) { if (req.status === 200) { @@ -396,17 +603,14 @@ var modelUploader = (function () { } }; - if (fbxBuffer !== null) { - req.send(fbxBuffer.buffer); - } else { - req.send(svoBuffer.buffer); - } + req.send(httpMultiPart.response()); } that.upload = function (file, callback) { - modelFile = file; var url = urlBase + file.fileName(); + modelFile = file; + // Read model content ... if (!readModel()) { return; From 4825457f4d0895799b4ea5d20e4fbbc7f2cb8d17 Mon Sep 17 00:00:00 2001 From: wangyix Date: Mon, 28 Jul 2014 16:49:53 -0700 Subject: [PATCH 024/206] silent audio packet type generalized --- .../src/audio/AvatarAudioStream.cpp | 25 ++------- .../src/audio/AvatarAudioStream.h | 1 - interface/src/Audio.cpp | 54 +++++++++++-------- libraries/audio/src/InboundAudioStream.cpp | 45 ++++++++++++---- libraries/audio/src/InboundAudioStream.h | 2 +- libraries/audio/src/InjectedAudioStream.cpp | 4 -- libraries/audio/src/InjectedAudioStream.h | 1 - libraries/audio/src/PositionalAudioStream.h | 7 --- libraries/networking/src/PacketHeaders.cpp | 3 +- libraries/script-engine/src/ScriptEngine.cpp | 26 ++++----- 10 files changed, 87 insertions(+), 81 deletions(-) diff --git a/assignment-client/src/audio/AvatarAudioStream.cpp b/assignment-client/src/audio/AvatarAudioStream.cpp index c6a7d31468..509f2889f2 100644 --- a/assignment-client/src/audio/AvatarAudioStream.cpp +++ b/assignment-client/src/audio/AvatarAudioStream.cpp @@ -38,26 +38,9 @@ int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray& // read the positional data readBytes += parsePositionalData(packetAfterSeqNum.mid(readBytes)); - if (type == PacketTypeSilentAudioFrame) { - int16_t numSilentSamples; - memcpy(&numSilentSamples, packetAfterSeqNum.data() + readBytes, sizeof(int16_t)); - readBytes += sizeof(int16_t); - - numAudioSamples = numSilentSamples; - } else { - int numAudioBytes = packetAfterSeqNum.size() - readBytes; - numAudioSamples = numAudioBytes / sizeof(int16_t); - } - return readBytes; -} - -int AvatarAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { - int readBytes = 0; - if (type == PacketTypeSilentAudioFrame) { - writeDroppableSilentSamples(numAudioSamples); - } else { - // there is audio data to read - readBytes += _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); - } + // calculate how many samples are in this packet + int numAudioBytes = packetAfterSeqNum.size() - readBytes; + numAudioSamples = numAudioBytes / sizeof(int16_t); + return readBytes; } diff --git a/assignment-client/src/audio/AvatarAudioStream.h b/assignment-client/src/audio/AvatarAudioStream.h index de7920c278..e6735d4975 100644 --- a/assignment-client/src/audio/AvatarAudioStream.h +++ b/assignment-client/src/audio/AvatarAudioStream.h @@ -26,7 +26,6 @@ private: AvatarAudioStream& operator= (const AvatarAudioStream&); int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); - int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples); }; #endif // hifi_AvatarAudioStream_h diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index e830e5f6d4..6b0ce30de4 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -453,9 +453,12 @@ void Audio::handleAudioInput() { static char audioDataPacket[MAX_PACKET_SIZE]; static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho); - static int leadingBytes = numBytesPacketHeader + sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8); - static int16_t* networkAudioSamples = (int16_t*) (audioDataPacket + leadingBytes); + // NOTE: we assume PacketTypeMicrophoneAudioWithEcho has same size headers as + // PacketTypeMicrophoneAudioNoEcho. If not, then networkAudioSamples will be pointing to the wrong place for writing + // audio samples with echo. + static int leadingBytes = numBytesPacketHeader + sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8); + static int16_t* networkAudioSamples = (int16_t*)(audioDataPacket + leadingBytes); float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio(_numInputCallbackBytes); @@ -666,19 +669,13 @@ void Audio::handleAudioInput() { glm::vec3 headPosition = interfaceAvatar->getHead()->getPosition(); glm::quat headOrientation = interfaceAvatar->getHead()->getFinalOrientationInWorldFrame(); quint8 isStereo = _isStereoInput ? 1 : 0; - - int numAudioBytes = 0; - + + int numPacketBytes = 0; + PacketType packetType; if (_lastInputLoudness == 0) { packetType = PacketTypeSilentAudioFrame; - - // we need to indicate how many silent samples this is to the audio mixer - networkAudioSamples[0] = numNetworkSamples; - numAudioBytes = sizeof(int16_t); } else { - numAudioBytes = numNetworkBytes; - if (Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)) { packetType = PacketTypeMicrophoneAudioWithEcho; } else { @@ -687,27 +684,38 @@ void Audio::handleAudioInput() { } char* currentPacketPtr = audioDataPacket + populatePacketHeader(audioDataPacket, packetType); - + // pack sequence number memcpy(currentPacketPtr, &_outgoingAvatarAudioSequenceNumber, sizeof(quint16)); currentPacketPtr += sizeof(quint16); - // set the mono/stereo byte - *currentPacketPtr++ = isStereo; + if (packetType == PacketTypeSilentAudioFrame) { + // pack num silent samples + quint16 numSilentSamples = numNetworkSamples; + memcpy(currentPacketPtr, &numSilentSamples, sizeof(quint16)); + currentPacketPtr += sizeof(quint16); + } else { + // set the mono/stereo byte + *currentPacketPtr++ = isStereo; - // memcpy the three float positions - memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); - currentPacketPtr += (sizeof(headPosition)); + // memcpy the three float positions + memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); + currentPacketPtr += (sizeof(headPosition)); - // memcpy our orientation - memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); - currentPacketPtr += sizeof(headOrientation); - - nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer); + // memcpy our orientation + memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); + currentPacketPtr += sizeof(headOrientation); + + // audio samples have already been packed (written to networkAudioSamples) + currentPacketPtr += numNetworkBytes; + } + + int packetBytes = currentPacketPtr - audioDataPacket; + nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO) - .updateValue(numAudioBytes + leadingBytes); + .updateValue(packetBytes); } delete[] inputAudioSamples; } diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index bfaa4c6d63..85ad10081c 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -67,34 +67,52 @@ int InboundAudioStream::parseData(const QByteArray& packet) { // parse header int numBytesHeader = numBytesForPacketHeader(packet); - const char* sequenceAt = packet.constData() + numBytesHeader; + const char* dataAt = packet.constData() + numBytesHeader; int readBytes = numBytesHeader; // parse sequence number and track it - quint16 sequence = *(reinterpret_cast(sequenceAt)); + quint16 sequence = *(reinterpret_cast(dataAt)); + dataAt += sizeof(quint16); readBytes += sizeof(quint16); SequenceNumberStats::ArrivalInfo arrivalInfo = frameReceivedUpdateNetworkStats(sequence, senderUUID); - // TODO: handle generalized silent packet here????? - - // parse the info after the seq number and before the audio data.(the stream properties) int numAudioSamples; - readBytes += parseStreamProperties(packetType, packet.mid(readBytes), numAudioSamples); + + if (packetType == PacketTypeSilentAudioFrame) { + // this is a general silent packet; parse the number of silent samples + quint16 numSilentSamples = *(reinterpret_cast(dataAt)); + dataAt += sizeof(quint16); + readBytes += sizeof(quint16); + + numAudioSamples = numSilentSamples; + } else { + // parse the info after the seq number and before the audio data (the stream properties) + readBytes += parseStreamProperties(packetType, packet.mid(readBytes), numAudioSamples); + } // handle this packet based on its arrival status. - // For now, late packets are ignored. It may be good in the future to insert the late audio frame - // into the ring buffer to fill in the missing frame if it hasn't been mixed yet. switch (arrivalInfo._status) { case SequenceNumberStats::Early: { + // Packet is early; write droppable silent samples for each of the skipped packets. + // NOTE: we assume that each dropped packet contains the same number of samples + // as the packet we just received. int packetsDropped = arrivalInfo._seqDiffFromExpected; writeSamplesForDroppedPackets(packetsDropped * numAudioSamples); + // fall through to OnTime case } case SequenceNumberStats::OnTime: { - readBytes += parseAudioData(packetType, packet.mid(readBytes), numAudioSamples); + // Packet is on time; parse its data to the ringbuffer + if (packetType == PacketTypeSilentAudioFrame) { + writeDroppableSilentSamples(numAudioSamples); + } else { + readBytes += parseAudioData(packetType, packet.mid(readBytes), numAudioSamples); + } break; } default: { + // For now, late packets are ignored. It may be good in the future to insert the late audio packet data + // into the ring buffer to fill in the missing frame if it hasn't been mixed yet. break; } } @@ -108,6 +126,10 @@ int InboundAudioStream::parseData(const QByteArray& packet) { return readBytes; } +int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { + return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); +} + bool InboundAudioStream::popFrames(int numFrames, bool starveOnFail) { int numSamplesRequested = numFrames * _ringBuffer.getNumFrameSamples(); if (_isStarved) { @@ -119,6 +141,8 @@ bool InboundAudioStream::popFrames(int numFrames, bool starveOnFail) { // we have enough samples to pop, so we're good to mix _lastPopOutput = _ringBuffer.nextOutput(); _ringBuffer.shiftReadPosition(numSamplesRequested); + + _framesAvailableStats.update(_ringBuffer.framesAvailable()); _hasStarted = true; _lastPopSucceeded = true; @@ -132,6 +156,7 @@ bool InboundAudioStream::popFrames(int numFrames, bool starveOnFail) { _lastPopSucceeded = false; } } + return _lastPopSucceeded; } @@ -145,6 +170,8 @@ void InboundAudioStream::starved() { _isStarved = true; _consecutiveNotMixedCount = 0; _starveCount++; + + _framesAvailableStats.reset(); } void InboundAudioStream::overrideDesiredJitterBufferFramesTo(int desired) { diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 958491bca1..c679bdeedd 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -113,7 +113,7 @@ protected: virtual int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) = 0; /// parses the audio data in the network packet - virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) = 0; + virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples); int writeDroppableSilentSamples(int numSilentSamples); diff --git a/libraries/audio/src/InjectedAudioStream.cpp b/libraries/audio/src/InjectedAudioStream.cpp index 4c23fbd823..3afcc50fdb 100644 --- a/libraries/audio/src/InjectedAudioStream.cpp +++ b/libraries/audio/src/InjectedAudioStream.cpp @@ -58,10 +58,6 @@ int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray return packetStream.device()->pos(); } -int InjectedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { - return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); -} - AudioStreamStats InjectedAudioStream::getAudioStreamStats() const { AudioStreamStats streamStats = PositionalAudioStream::getAudioStreamStats(); streamStats._streamIdentifier = _streamIdentifier; diff --git a/libraries/audio/src/InjectedAudioStream.h b/libraries/audio/src/InjectedAudioStream.h index b92736b0ba..cf750c6440 100644 --- a/libraries/audio/src/InjectedAudioStream.h +++ b/libraries/audio/src/InjectedAudioStream.h @@ -32,7 +32,6 @@ private: AudioStreamStats getAudioStreamStats() const; int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); - int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples); const QUuid _streamIdentifier; float _radius; diff --git a/libraries/audio/src/PositionalAudioStream.h b/libraries/audio/src/PositionalAudioStream.h index 06835b93a8..66db79c0db 100644 --- a/libraries/audio/src/PositionalAudioStream.h +++ b/libraries/audio/src/PositionalAudioStream.h @@ -50,13 +50,6 @@ protected: PositionalAudioStream(const PositionalAudioStream&); PositionalAudioStream& operator= (const PositionalAudioStream&); - /// parses the info between the seq num and the audio data in the network packet and calculates - /// how many audio samples this packet contains - virtual int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) = 0; - - /// parses the audio data in the network packet - virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) = 0; - int parsePositionalData(const QByteArray& positionalByteArray); protected: diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index f17715ddfe..a074c45095 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -49,8 +49,9 @@ PacketVersion versionForPacketType(PacketType type) { switch (type) { case PacketTypeMicrophoneAudioNoEcho: case PacketTypeMicrophoneAudioWithEcho: - case PacketTypeSilentAudioFrame: return 2; + case PacketTypeSilentAudioFrame: + return 3; case PacketTypeMixedAudio: return 1; case PacketTypeAvatarData: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index fab21ea928..c67a499dcb 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -490,14 +490,6 @@ void ScriptEngine::run() { // pack a placeholder value for sequence number for now, will be packed when destination node is known int numPreSequenceNumberBytes = audioPacket.size(); packetStream << (quint16) 0; - - // assume scripted avatar audio is mono and set channel flag to zero - packetStream << (quint8) 0; - - // use the orientation and position of this avatar for the source of this audio - packetStream.writeRawData(reinterpret_cast(&_avatarData->getPosition()), sizeof(glm::vec3)); - glm::quat headOrientation = _avatarData->getHeadOrientation(); - packetStream.writeRawData(reinterpret_cast(&headOrientation), sizeof(glm::quat)); if (silentFrame) { if (!_isListeningToAudioStream) { @@ -507,12 +499,20 @@ void ScriptEngine::run() { // write the number of silent samples so the audio-mixer can uphold timing packetStream.writeRawData(reinterpret_cast(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t)); - } else if (nextSoundOutput) { - // write the raw audio data - packetStream.writeRawData(reinterpret_cast(nextSoundOutput), - numAvailableSamples * sizeof(int16_t)); - } + } else if (nextSoundOutput) { + // assume scripted avatar audio is mono and set channel flag to zero + packetStream << (quint8)0; + + // use the orientation and position of this avatar for the source of this audio + packetStream.writeRawData(reinterpret_cast(&_avatarData->getPosition()), sizeof(glm::vec3)); + glm::quat headOrientation = _avatarData->getHeadOrientation(); + packetStream.writeRawData(reinterpret_cast(&headOrientation), sizeof(glm::quat)); + + // write the raw audio data + packetStream.writeRawData(reinterpret_cast(nextSoundOutput), numAvailableSamples * sizeof(int16_t)); + } + // write audio packet to AudioMixer nodes NodeList* nodeList = NodeList::getInstance(); foreach(const SharedNodePointer& node, nodeList->getNodeHash()) { From f46c064e888ddf817fcba7e62b210b338fd7c3ad Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Jul 2014 19:58:53 -0700 Subject: [PATCH 025/206] Speed up model reading --- examples/editModels.js | 150 +++++++++++++++++++++++++---------------- 1 file changed, 93 insertions(+), 57 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index c5a8c5c1e8..f4170b5655 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -340,72 +340,108 @@ var modelUploader = (function () { } function readGeometry(fbxBuffer) { - var dv = new DataView(fbxBuffer.buffer), - geometry = {}, - binary, - stringLength, - filename, - author, - i; + var geometry, + view, + index, + EOF; - binary = (dv.string(0, 18) === "Kaydara FBX Binary"); + // Reference: + // http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ - // Simple direct search of FBX file for relevant texture filenames (excl. paths) instead of interpreting FBX format. - // Binary format: - // - 'RelativeFilename' Record type - // - char Subtype - // - Uint8 Length of path string - // - 00 00 00 3 null chars - // - Path and name of texture file - // Text format: - // - 'RelativeFilename' Record type - // - ': " ' Pre-string colon and quote - // - Path and name of texture file - // - '"' End-of-string quote + geometry = {}; geometry.textures = []; - i = 0; - while (i !== -1) { - i = dv.indexOf("RelativeFilename", i); - if (i !== -1) { - if (binary) { - i += 17; - stringLength = dv.getUint8(i); - i += 4; - } else { - i = dv.indexOf("\"", i) + 1; - stringLength = dv.indexOf("\"", i) - i; - } - filename = dv.string(i, stringLength).fileName(); + view = new DataView(fbxBuffer.buffer); + EOF = false; + + function parseBinaryFBX() { + var endOffset, + numProperties, + propertyListLength, + nameLength, + name, + filename, + author; + + endOffset = view.getUint32(index, true); + numProperties = view.getUint32(index + 4, true); + propertyListLength = view.getUint32(index + 8, true); + nameLength = view.getUint8(index + 12); + index += 13; + + if (endOffset === 0) { + return; + } + if (endOffset < index || endOffset > view.byteLength) { + EOF = true; + return; + } + + name = view.string(index, nameLength).toLowerCase(); + index += nameLength; + + if (name === "relativefilename") { + filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName(); geometry.textures.push(filename); - i += stringLength; + + } else if (name === "author") { + author = view.string(index + 5, view.getUint32(index + 1, true)); + geometry.author = author; + + } + + index += (propertyListLength); + + while (index < endOffset && !EOF) { + parseBinaryFBX(); } } - // Simple direct search of FBX file for the first author record. - // Binary format: - // - 'Author' Record type - // - char Subtype - // - Uint8 Length of path string - // - 00 00 00 3 null chars - // - Author name - // Text format: - // - 'Author' Record type - // - ': "' Pre-string colon and quote - // - Author name; may be empty - // - '"' End-of-string quote - i = dv.indexOf("Author", 0); - if (i !== -1) { - if (binary) { - i += 7; - stringLength = dv.getUint8(i); - } else { - i = dv.indexOf("\"", i) + 1; - stringLength = dv.indexOf("\"", i) - i; + function readTextFBX() { + var line, + view, + viewLength, + charCode, + charCodes, + author, + filename; + + view = new Uint8Array(fbxBuffer.buffer); + viewLength = view.byteLength; + charCodes = []; + + for (index = 0; index < viewLength; index += 1) { + charCode = view[index]; + if (charCode === 10) { // Can ignore EOF + line = String.fromCharCode.apply(String, charCodes).trim(); + + if (line.slice(0, 7).toLowerCase() === "author:") { + author = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length); + geometry.author = author; + + } + if (line.slice(0, 17).toLowerCase() === "relativefilename:") { + filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); + geometry.textures.push(filename); + } + + charCodes = []; + } else { + charCodes.push(charCode); + } } - if (stringLength > 0) { - author = dv.string(i, stringLength); - geometry.author = author; + } + + if (view.string(0, 18) === "Kaydara FBX Binary") { + + index = 27; + while (index < view.byteLength - 39 && !EOF) { + parseBinaryFBX(); } + + } else { + + readTextFBX(); + } return geometry; From 63d7ff0bdea225e61c67d71b2c35d00b8c2739f8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Jul 2014 20:00:23 -0700 Subject: [PATCH 026/206] Tidying --- examples/editModels.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index f4170b5655..6bf0d54e1d 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -59,13 +59,13 @@ var mode = 0; if (typeof String.prototype.fileName !== "function") { - String.prototype.fileName = function (str) { + String.prototype.fileName = function () { return this.replace(/^(.*[\/\\])*/, ""); }; } if (typeof String.prototype.path !== "function") { - String.prototype.path = function (str) { + String.prototype.path = function () { return this.replace(/[\\\/][^\\\/]*$/, ""); }; } @@ -185,8 +185,7 @@ var httpMultiPart = (function () { // - name, buffer var buffer, stringBuffer, - string, - length; + string; if (object.name === undefined) { @@ -267,7 +266,6 @@ var httpMultiPart = (function () { return that; }()); - var modelUploader = (function () { var that = {}, urlBase = "http://public.highfidelity.io/meshes/", @@ -283,15 +281,14 @@ var modelUploader = (function () { ANIMATION_URL_FIELD = "animationurl", PITCH_FIELD = "pitch", YAW_FIELD = "yaw", - ROLL_FIELD = "roll", - fbxDataView; + ROLL_FIELD = "roll"; function error(message) { Window.alert(message); print(message); } - function readFile(filename, buffer, length) { + function readFile(filename) { var url = "file:///" + filename, req = new XMLHttpRequest(); @@ -305,7 +302,7 @@ var modelUploader = (function () { return { filename: filename.fileName(), - buffer: req.response, + buffer: req.response }; } @@ -561,7 +558,6 @@ var modelUploader = (function () { } function createHttpMessage() { - var i; print("Preparing to send model"); From ccf37c6c178eb297a6224a78a6f5fd5feb76e09a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Jul 2014 21:53:00 -0700 Subject: [PATCH 027/206] Fix text displayed on Window.form() directory button --- interface/src/scripting/WindowScriptingInterface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 19e2ba8194..c1be4f8a02 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -184,6 +184,7 @@ QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptVal directory->setProperty("displayAs", displayAs); directory->setProperty("validateAs", validateAs); directory->setProperty("errorMessage", errorMessage); + displayAs.indexIn(path); directory->setText(displayAs.cap(1) != "" ? displayAs.cap(1) : "."); directory->setMinimumWidth(200); From 0bb42ba5f26aee60560470d3d3ed72eda12cd14e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Jul 2014 21:55:05 -0700 Subject: [PATCH 028/206] Fix call stack overflow when preparing large multipart/form-data --- examples/editModels.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 6bf0d54e1d..ce9b52c3a0 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -248,15 +248,15 @@ var httpMultiPart = (function () { byteLength += buffer.byteLength; parts.push(buffer); - charCodes = []; + str = ""; for (i = 0; i < parts.length; i += 1) { + charCodes = []; view = new Uint8Array(parts[i]); - for (j = 0; j < view.length; j += 1) { + for(j = 0; j < view.length; j += 1) { charCodes.push(view[j]); } + str = str + String.fromCharCode.apply(String, charCodes); } - str = String.fromCharCode.apply(String, charCodes); - return str; } that.response = response; From c7c2f3119253e4cfc928933313c82b059823f05b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Jul 2014 21:59:44 -0700 Subject: [PATCH 029/206] Fix display of scale read from model file --- examples/editModels.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index ce9b52c3a0..b3e90d7995 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -488,8 +488,11 @@ var modelUploader = (function () { } geometry = readGeometry(fbxBuffer); - if (!mapping.hasOwnProperty(SCALE_FIELD)) { - mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0 : 15.0); + + if (mapping.hasOwnProperty(SCALE_FIELD)) { + mapping[SCALE_FIELD] = parseFloat(mapping[SCALE_FIELD]); + } else { + mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0: 15.0); } } From 00abf43faee07c8118571fe07b53cf892b8700b9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Jul 2014 22:10:25 -0700 Subject: [PATCH 030/206] Add LOD files to multipart/form-data --- examples/editModels.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/editModels.js b/examples/editModels.js index b3e90d7995..24b0745503 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -561,6 +561,8 @@ var modelUploader = (function () { } function createHttpMessage() { + var lodCount, + lodBuffer; print("Preparing to send model"); @@ -603,7 +605,15 @@ var modelUploader = (function () { } // LOD files - // DJRTODO + lodCount = 0; + for (var n in mapping["lod"]) { + lodBuffer = readFile(modelFile.path() + "\\" + n); + httpMultiPart.add({ + name: "lod" + lodCount, + buffer: lodBuffer + }) + lodCount += 1; + } // Textures // DJRTODO From edf96b749e3465d1d49c4560d450cbeb2743eab9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 28 Jul 2014 23:01:12 -0700 Subject: [PATCH 031/206] Add texture files to multipart/form-data --- examples/editModels.js | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 24b0745503..01871b8a60 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -281,7 +281,8 @@ var modelUploader = (function () { ANIMATION_URL_FIELD = "animationurl", PITCH_FIELD = "pitch", YAW_FIELD = "yaw", - ROLL_FIELD = "roll"; + ROLL_FIELD = "roll", + geometry; function error(message) { Window.alert(message); @@ -338,6 +339,7 @@ var modelUploader = (function () { function readGeometry(fbxBuffer) { var geometry, + textures, view, index, EOF; @@ -347,6 +349,7 @@ var modelUploader = (function () { geometry = {}; geometry.textures = []; + textures = {}; view = new DataView(fbxBuffer.buffer); EOF = false; @@ -378,7 +381,10 @@ var modelUploader = (function () { if (name === "relativefilename") { filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName(); - geometry.textures.push(filename); + if (!textures.hasOwnProperty(filename)) { + textures[filename] = ""; + geometry.textures.push(filename); + } } else if (name === "author") { author = view.string(index + 5, view.getUint32(index + 1, true)); @@ -418,7 +424,10 @@ var modelUploader = (function () { } if (line.slice(0, 17).toLowerCase() === "relativefilename:") { filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); - geometry.textures.push(filename); + if (!textures.hasOwnProperty(filename)) { + textures[filename] = ""; + geometry.textures.push(filename); + } } charCodes = []; @@ -445,8 +454,7 @@ var modelUploader = (function () { } function readModel() { - var geometry, - fbxFilename; + var fbxFilename; print("Reading model file: " + modelFile); @@ -562,7 +570,10 @@ var modelUploader = (function () { function createHttpMessage() { var lodCount, - lodBuffer; + lodBuffer, + textureCount, + textureBuffer, + i; print("Preparing to send model"); @@ -607,7 +618,7 @@ var modelUploader = (function () { // LOD files lodCount = 0; for (var n in mapping["lod"]) { - lodBuffer = readFile(modelFile.path() + "\\" + n); + lodBuffer = readFile(modelFile.path() + "\/" + n); httpMultiPart.add({ name: "lod" + lodCount, buffer: lodBuffer @@ -616,7 +627,15 @@ var modelUploader = (function () { } // Textures - // DJRTODO + textureCount = 0; + for (i = 0; i < geometry.textures.length; i += 1) { + textureBuffer = readFile(modelFile.path() + "\/" + mapping[TEXDIR_FIELD] + "\/" + geometry.textures[i]); + httpMultiPart.add({ + name: "texture" + textureCount, + buffer: textureBuffer + }); + textureCount += 1; + } // Model category httpMultiPart.add({ From 5a5bbfd612ef0721aab2dab50b76f8cb131b1434 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 29 Jul 2014 09:46:54 -0700 Subject: [PATCH 032/206] Tidying --- examples/editModels.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 01871b8a60..31a55bf821 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -252,7 +252,7 @@ var httpMultiPart = (function () { for (i = 0; i < parts.length; i += 1) { charCodes = []; view = new Uint8Array(parts[i]); - for(j = 0; j < view.length; j += 1) { + for (j = 0; j < view.length; j += 1) { charCodes.push(view[j]); } str = str + String.fromCharCode.apply(String, charCodes); @@ -458,6 +458,9 @@ var modelUploader = (function () { print("Reading model file: " + modelFile); + fstBuffer = null; + fbxBuffer = null; + svoBuffer = null; mapping = {}; if (modelFile.toLowerCase().slice(-4) === ".svo") { @@ -500,7 +503,7 @@ var modelUploader = (function () { if (mapping.hasOwnProperty(SCALE_FIELD)) { mapping[SCALE_FIELD] = parseFloat(mapping[SCALE_FIELD]); } else { - mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0: 15.0); + mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0 : 15.0); } } @@ -570,8 +573,8 @@ var modelUploader = (function () { function createHttpMessage() { var lodCount, + lodFile, lodBuffer, - textureCount, textureBuffer, i; @@ -617,24 +620,26 @@ var modelUploader = (function () { // LOD files lodCount = 0; - for (var n in mapping["lod"]) { - lodBuffer = readFile(modelFile.path() + "\/" + n); - httpMultiPart.add({ - name: "lod" + lodCount, - buffer: lodBuffer - }) - lodCount += 1; + for (lodFile in mapping.lod) { + if (mapping.lod.hasOwnProperty(lodFile)) { + lodBuffer = readFile(modelFile.path() + "\/" + lodFile); + httpMultiPart.add({ + name: "lod" + lodCount, + buffer: lodBuffer + }); + lodCount += 1; + } } // Textures - textureCount = 0; for (i = 0; i < geometry.textures.length; i += 1) { - textureBuffer = readFile(modelFile.path() + "\/" + mapping[TEXDIR_FIELD] + "\/" + geometry.textures[i]); + textureBuffer = readFile(modelFile.path() + "\/" + + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") + + geometry.textures[i]); httpMultiPart.add({ - name: "texture" + textureCount, + name: "texture" + i, buffer: textureBuffer }); - textureCount += 1; } // Model category From a3b44a6a7342f3d42ed31654fa693971ae345c77 Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 29 Jul 2014 10:08:55 -0700 Subject: [PATCH 033/206] downstream silent packets seem to be working --- assignment-client/src/Agent.cpp | 2 +- assignment-client/src/audio/AudioMixer.cpp | 65 ++++++++++++------- assignment-client/src/audio/AudioMixer.h | 4 +- .../src/audio/AudioMixerClientData.cpp | 1 + interface/src/DatagramProcessor.cpp | 1 + libraries/audio/src/AudioRingBuffer.cpp | 27 +++++--- libraries/audio/src/AudioRingBuffer.h | 7 +- libraries/audio/src/InboundAudioStream.cpp | 4 ++ libraries/audio/src/InboundAudioStream.h | 2 + libraries/audio/src/PositionalAudioStream.cpp | 6 -- libraries/audio/src/PositionalAudioStream.h | 2 - 11 files changed, 76 insertions(+), 45 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d4da989198..9150e24303 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -146,7 +146,7 @@ void Agent::readPendingDatagrams() { _voxelViewer.processDatagram(mutablePacket, sourceNode); } - } else if (datagramPacketType == PacketTypeMixedAudio) { + } else if (datagramPacketType == PacketTypeMixedAudio || datagramPacketType == PacketTypeSilentAudioFrame) { _receivedAudioStream.parseData(receivedPacket); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index d3ec39ace1..9c7f51ef15 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -93,7 +93,7 @@ const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f; const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f; const float ATTENUATION_EPSILON_DISTANCE = 0.1f; -void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd, +int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd, AvatarAudioStream* listeningNodeStream) { float bearingRelativeAngleToSource = 0.0f; float attenuationCoefficient = 1.0f; @@ -116,7 +116,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* if (streamToAdd->getNextOutputTrailingLoudness() / distanceBetween <= _minAudibilityThreshold) { // according to mixer performance we have decided this does not get to be mixed in // bail out - return; + return 0; } ++_sumMixes; @@ -261,36 +261,39 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); } } + + return 1; } -void AudioMixer::prepareMixForListeningNode(Node* node) { +int AudioMixer::prepareMixForListeningNode(Node* node) { AvatarAudioStream* nodeAudioStream = ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioStream(); - + // zero out the client mix for this node memset(_clientSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_STEREO); // loop through all other nodes that have sufficient audio to mix + int streamsMixed = 0; foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) { if (otherNode->getLinkedData()) { - AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); // enumerate the ARBs attached to the otherNode and add all that should be added to mix const QHash& otherNodeAudioStreams = otherNodeClientData->getAudioStreams(); QHash::ConstIterator i; - for (i = otherNodeAudioStreams.begin(); i != otherNodeAudioStreams.constEnd(); i++) { + for (i = otherNodeAudioStreams.constBegin(); i != otherNodeAudioStreams.constEnd(); i++) { PositionalAudioStream* otherNodeStream = i.value(); - + if ((*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) && otherNodeStream->lastPopSucceeded() - && otherNodeStream->getNextOutputTrailingLoudness() > 0.0f) { + && otherNodeStream->getLastPopOutputFrameLoudness() > 0.0f) { - addStreamToMixForListeningNodeWithStream(otherNodeStream, nodeAudioStream); + streamsMixed += addStreamToMixForListeningNodeWithStream(otherNodeStream, nodeAudioStream); } } } } + return streamsMixed; } void AudioMixer::readPendingDatagrams() { @@ -474,9 +477,8 @@ void AudioMixer::run() { int nextFrame = 0; QElapsedTimer timer; timer.start(); - - char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + sizeof(quint16) - + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)]; + + char clientMixBuffer[MAX_PACKET_SIZE]; int usecToSleep = BUFFER_SEND_INTERVAL_USECS; @@ -555,20 +557,37 @@ void AudioMixer::run() { if (node->getType() == NodeType::Agent && ((AudioMixerClientData*)node->getLinkedData())->getAvatarAudioStream()) { - prepareMixForListeningNode(node.data()); + int streamsMixed = prepareMixForListeningNode(node.data()); - // pack header - int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio); - char* dataAt = clientMixBuffer + numBytesPacketHeader; + char* dataAt; + if (streamsMixed > 0) { + // pack header + int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio); + dataAt = clientMixBuffer + numBytesPacketHeader; - // pack sequence number - quint16 sequence = nodeData->getOutgoingSequenceNumber(); - memcpy(dataAt, &sequence, sizeof(quint16)); - dataAt += sizeof(quint16); + // pack sequence number + quint16 sequence = nodeData->getOutgoingSequenceNumber(); + memcpy(dataAt, &sequence, sizeof(quint16)); + dataAt += sizeof(quint16); - // pack mixed audio samples - memcpy(dataAt, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO); - dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO; + // pack mixed audio samples + memcpy(dataAt, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO); + dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO; + } else { + // pack header + int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeSilentAudioFrame); + dataAt = clientMixBuffer + numBytesPacketHeader; + + // pack sequence number + quint16 sequence = nodeData->getOutgoingSequenceNumber(); + memcpy(dataAt, &sequence, sizeof(quint16)); + dataAt += sizeof(quint16); + + // pack number of silent audio samples + quint16 numSilentSamples = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; + memcpy(dataAt, &numSilentSamples, sizeof(quint16)); + dataAt += sizeof(quint16); + } // send mixed audio packet nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node); diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index bfdb49f393..639b01b78a 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -41,11 +41,11 @@ public slots: private: /// adds one stream to the mix for a listening node - void addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd, + int addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd, AvatarAudioStream* listeningNodeStream); /// prepares and sends a mix to one Node - void prepareMixForListeningNode(Node* node); + int prepareMixForListeningNode(Node* node); // client samples capacity is larger than what will be sent to optimize mixing // we are MMX adding 4 samples at a time so we need client samples to have an extra 4 diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 17e46f3692..39a789580b 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -101,6 +101,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { void AudioMixerClientData::audioStreamsPopFrameForMixing() { QHash::ConstIterator i; for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) { + i.value()->updateNextOutputTrailingLoudness(); i.value()->popFrames(1); } } diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 8fda094f42..9a31306a51 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -48,6 +48,7 @@ void DatagramProcessor::processDatagrams() { // only process this packet if we have a match on the packet version switch (packetTypeForPacket(incomingPacket)) { case PacketTypeMixedAudio: + case PacketTypeSilentAudioFrame: QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToStream", Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); break; diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 8dbc90883b..5042cc7388 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -218,17 +218,24 @@ int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int } } -float AudioRingBuffer::getNextOutputFrameLoudness() const { +float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const { float loudness = 0.0f; - int16_t* sampleAt = _nextOutput; - int16_t* _bufferLastAt = _buffer + _sampleCapacity - 1; - if (samplesAvailable() >= _numFrameSamples) { - for (int i = 0; i < _numFrameSamples; ++i) { - loudness += fabsf(*sampleAt); - sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1; - } - loudness /= _numFrameSamples; - loudness /= MAX_SAMPLE_VALUE; + const int16_t* sampleAt = frameStart; + const int16_t* _bufferLastAt = _buffer + _sampleCapacity - 1; + for (int i = 0; i < _numFrameSamples; ++i) { + loudness += fabsf(*sampleAt); + sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1; } + loudness /= _numFrameSamples; + loudness /= MAX_SAMPLE_VALUE; + return loudness; } + +float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const { + return getFrameLoudness(&(*frameStart)); +} + +float AudioRingBuffer::getNextOutputFrameLoudness() const { + return getFrameLoudness(_nextOutput); +} diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 824b197c93..c8be4cc420 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -75,6 +75,10 @@ public: int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data int addSilentFrame(int numSilentSamples); + +private: + float getFrameLoudness(const int16_t* frameStart) const; + protected: // disallow copying of AudioRingBuffer objects AudioRingBuffer(const AudioRingBuffer&); @@ -110,7 +114,7 @@ public: bool operator==(const ConstIterator& rhs) { return _at == rhs._at; } bool operator!=(const ConstIterator& rhs) { return _at != rhs._at; } - int16_t operator*() { return *_at; } + const int16_t& operator*() { return *_at; } ConstIterator& operator=(const ConstIterator& rhs) { _capacity = rhs._capacity; @@ -179,6 +183,7 @@ public: }; ConstIterator nextOutput() const { return ConstIterator(_buffer, _sampleCapacity, _nextOutput); } + float getFrameLoudness(ConstIterator frameStart) const; }; #endif // hifi_AudioRingBuffer_h diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 85ad10081c..6dd16fb88f 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -174,6 +174,10 @@ void InboundAudioStream::starved() { _framesAvailableStats.reset(); } +float InboundAudioStream::getLastPopOutputFrameLoudness() const { + return _ringBuffer.getFrameLoudness(_lastPopOutput); +} + void InboundAudioStream::overrideDesiredJitterBufferFramesTo(int desired) { _dynamicJitterBuffersOverride = true; _desiredJitterBufferFrames = clampDesiredJitterBufferFramesValue(desired); diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index c679bdeedd..a9204a344c 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -80,6 +80,8 @@ public: /// returns the desired number of jitter buffer frames using Freddy's method int getCalculatedJitterBufferFramesUsingMaxGap() const { return _calculatedJitterBufferFramesUsingMaxGap; } + + float getLastPopOutputFrameLoudness() const; int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; } int getNumFrameSamples() const { return _ringBuffer.getNumFrameSamples(); } diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index b50e339185..bd21e844af 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -34,12 +34,6 @@ PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, b { } -int PositionalAudioStream::parseData(const QByteArray& packet) { - int bytesRead = InboundAudioStream::parseData(packet); - updateNextOutputTrailingLoudness(); - return bytesRead; -} - void PositionalAudioStream::updateNextOutputTrailingLoudness() { float nextLoudness = _ringBuffer.getNextOutputFrameLoudness(); diff --git a/libraries/audio/src/PositionalAudioStream.h b/libraries/audio/src/PositionalAudioStream.h index 66db79c0db..9d56ff3e8d 100644 --- a/libraries/audio/src/PositionalAudioStream.h +++ b/libraries/audio/src/PositionalAudioStream.h @@ -29,8 +29,6 @@ public: PositionalAudioStream(PositionalAudioStream::Type type, bool isStereo = false, bool dynamicJitterBuffers = false); - int parseData(const QByteArray& packet); - virtual AudioStreamStats getAudioStreamStats() const; void updateNextOutputTrailingLoudness(); From 287e3d6800c1ac04831213777a1906a414abf3ee Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 29 Jul 2014 13:52:52 -0700 Subject: [PATCH 034/206] Compress model and texture file data in multipart/form-data A compress() method is added to the JavaScript ArrayBuffer object. --- examples/editModels.js | 8 +++++--- .../script-engine/src/ArrayBufferPrototype.cpp | 15 +++++++++++++++ .../script-engine/src/ArrayBufferPrototype.h | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 31a55bf821..1a242d16d2 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -184,8 +184,9 @@ var httpMultiPart = (function () { // - name, string // - name, buffer var buffer, + string, stringBuffer, - string; + compressedBuffer; if (object.name === undefined) { @@ -217,9 +218,10 @@ var httpMultiPart = (function () { + "\r\n"; stringBuffer = string.toArrayBuffer(); - buffer = new Uint8Array(stringBuffer.byteLength + object.buffer.buffer.byteLength); + compressedBuffer = object.buffer.buffer.compress(); + buffer = new Uint8Array(stringBuffer.byteLength + compressedBuffer.byteLength); buffer.set(new Uint8Array(stringBuffer)); - buffer.set(new Uint8Array(object.buffer.buffer), stringBuffer.byteLength); + buffer.set(new Uint8Array(compressedBuffer), stringBuffer.byteLength); } else { diff --git a/libraries/script-engine/src/ArrayBufferPrototype.cpp b/libraries/script-engine/src/ArrayBufferPrototype.cpp index 53ebebc740..6f78caad2d 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.cpp +++ b/libraries/script-engine/src/ArrayBufferPrototype.cpp @@ -14,6 +14,9 @@ #include "ArrayBufferClass.h" #include "ArrayBufferPrototype.h" +static const int QCOMPRESS_HEADER_POSITION = 0; +static const int QCOMPRESS_HEADER_SIZE = 4; + Q_DECLARE_METATYPE(QByteArray*) ArrayBufferPrototype::ArrayBufferPrototype(QObject* parent) : QObject(parent) { @@ -43,6 +46,18 @@ QByteArray ArrayBufferPrototype::slice(qint32 begin) const { return ba->mid(begin, -1); } +QByteArray ArrayBufferPrototype::compress() const { + QByteArray* ba = thisArrayBuffer(); + + QByteArray buffer = qCompress(*ba); + + // Qt's qCompress() default compression level (-1) is the standard zLib compression. + // Here remove Qt's custom header that prevents the data server from uncompressing the files with zLib. + buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE); + + return buffer; +} + QByteArray* ArrayBufferPrototype::thisArrayBuffer() const { return qscriptvalue_cast(thisObject().data()); } diff --git a/libraries/script-engine/src/ArrayBufferPrototype.h b/libraries/script-engine/src/ArrayBufferPrototype.h index 09d4596f28..2ad9843571 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.h +++ b/libraries/script-engine/src/ArrayBufferPrototype.h @@ -23,6 +23,7 @@ public: public slots: QByteArray slice(qint32 begin, qint32 end) const; QByteArray slice(qint32 begin) const; + QByteArray compress() const; private: QByteArray* thisArrayBuffer() const; From f39aed37b674198a024cfeb8d492487c11293103 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 29 Jul 2014 17:26:42 -0700 Subject: [PATCH 035/206] Recode and rescale texture file data before uploading A recodeImage() method is added to the JavaScript ArrayBuffer object. --- examples/editModels.js | 15 ++++++++ .../src/ArrayBufferPrototype.cpp | 34 ++++++++++++++++--- .../script-engine/src/ArrayBufferPrototype.h | 1 + 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 1a242d16d2..589f4e4bfb 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -64,6 +64,12 @@ if (typeof String.prototype.fileName !== "function") { }; } +if (typeof String.prototype.fileType !== "function") { + String.prototype.fileType = function () { + return this.slice(this.lastIndexOf(".") + 1); + }; +} + if (typeof String.prototype.path !== "function") { String.prototype.path = function () { return this.replace(/[\\\/][^\\\/]*$/, ""); @@ -284,6 +290,7 @@ var modelUploader = (function () { PITCH_FIELD = "pitch", YAW_FIELD = "yaw", ROLL_FIELD = "roll", + MAX_TEXTURE_SIZE = 1024, geometry; function error(message) { @@ -578,6 +585,8 @@ var modelUploader = (function () { lodFile, lodBuffer, textureBuffer, + textureSourceFormat, + textureTargetFormat, i; print("Preparing to send model"); @@ -638,6 +647,12 @@ var modelUploader = (function () { textureBuffer = readFile(modelFile.path() + "\/" + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") + geometry.textures[i]); + + textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); + textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png"); + textureBuffer.buffer = textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE); + textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat; + httpMultiPart.add({ name: "texture" + i, buffer: textureBuffer diff --git a/libraries/script-engine/src/ArrayBufferPrototype.cpp b/libraries/script-engine/src/ArrayBufferPrototype.cpp index 6f78caad2d..9739f67381 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.cpp +++ b/libraries/script-engine/src/ArrayBufferPrototype.cpp @@ -11,6 +11,9 @@ #include +#include +#include + #include "ArrayBufferClass.h" #include "ArrayBufferPrototype.h" @@ -47,17 +50,40 @@ QByteArray ArrayBufferPrototype::slice(qint32 begin) const { } QByteArray ArrayBufferPrototype::compress() const { + // Compresses the ArrayBuffer data in Zlib format. QByteArray* ba = thisArrayBuffer(); QByteArray buffer = qCompress(*ba); - - // Qt's qCompress() default compression level (-1) is the standard zLib compression. - // Here remove Qt's custom header that prevents the data server from uncompressing the files with zLib. - buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE); + buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE); // Remove Qt's custom header to make it proper Zlib. return buffer; } +QByteArray ArrayBufferPrototype::recodeImage(const QString& sourceFormat, const QString& targetFormat, qint32 maxSize) const { + // Recodes image data if sourceFormat and targetFormat are different. + // Rescales image data if either dimension is greater than the specified maximum. + QByteArray* ba = thisArrayBuffer(); + + bool mustRecode = sourceFormat.toLower() != targetFormat.toLower(); + + QImage image = QImage::fromData(*ba); + if (image.width() > maxSize || image.height() > maxSize) { + image = image.scaled(maxSize, maxSize, Qt::KeepAspectRatio); + mustRecode = true; + } + + if (mustRecode) { + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + std::string str = targetFormat.toUpper().toStdString(); + const char* format = str.c_str(); + image.save(&buffer, format); + return buffer.data(); + } + + return *ba; +} + QByteArray* ArrayBufferPrototype::thisArrayBuffer() const { return qscriptvalue_cast(thisObject().data()); } diff --git a/libraries/script-engine/src/ArrayBufferPrototype.h b/libraries/script-engine/src/ArrayBufferPrototype.h index 2ad9843571..f9dd667dc4 100644 --- a/libraries/script-engine/src/ArrayBufferPrototype.h +++ b/libraries/script-engine/src/ArrayBufferPrototype.h @@ -24,6 +24,7 @@ public slots: QByteArray slice(qint32 begin, qint32 end) const; QByteArray slice(qint32 begin) const; QByteArray compress() const; + QByteArray recodeImage(const QString& sourceFormat, const QString& targetFormat, qint32 maxSize) const; private: QByteArray* thisArrayBuffer() const; From 0c589b73c473000dd7f59d2838d93d8ef40c5378 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 30 Jul 2014 22:12:11 -0700 Subject: [PATCH 036/206] Tidy model data handling --- examples/editModels.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 589f4e4bfb..f5183b16d6 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -282,6 +282,7 @@ var modelUploader = (function () { fbxBuffer, svoBuffer, mapping, + geometry, NAME_FIELD = "name", SCALE_FIELD = "scale", FILENAME_FIELD = "filename", @@ -290,14 +291,22 @@ var modelUploader = (function () { PITCH_FIELD = "pitch", YAW_FIELD = "yaw", ROLL_FIELD = "roll", - MAX_TEXTURE_SIZE = 1024, - geometry; + MAX_TEXTURE_SIZE = 1024; function error(message) { Window.alert(message); print(message); } + function resetDataObjects() { + fstBuffer = null; + fbxBuffer = null; + svoBuffer = null; + mapping = {}; + geometry = {}; + geometry.textures = []; + } + function readFile(filename) { var url = "file:///" + filename, req = new XMLHttpRequest(); @@ -318,7 +327,6 @@ var modelUploader = (function () { function readMapping(fstBuffer) { var dv = new DataView(fstBuffer.buffer), - mapping = {}, lines, line, values, @@ -342,13 +350,10 @@ var modelUploader = (function () { } } } - - return mapping; } function readGeometry(fbxBuffer) { - var geometry, - textures, + var textures, view, index, EOF; @@ -356,8 +361,6 @@ var modelUploader = (function () { // Reference: // http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ - geometry = {}; - geometry.textures = []; textures = {}; view = new DataView(fbxBuffer.buffer); EOF = false; @@ -458,8 +461,6 @@ var modelUploader = (function () { readTextFBX(); } - - return geometry; } function readModel() { @@ -467,11 +468,6 @@ var modelUploader = (function () { print("Reading model file: " + modelFile); - fstBuffer = null; - fbxBuffer = null; - svoBuffer = null; - mapping = {}; - if (modelFile.toLowerCase().slice(-4) === ".svo") { svoBuffer = readFile(modelFile); if (svoBuffer === null) { @@ -485,7 +481,7 @@ var modelUploader = (function () { if (fstBuffer === null) { return false; } - mapping = readMapping(fstBuffer); + readMapping(fstBuffer); if (mapping.hasOwnProperty(FILENAME_FIELD)) { fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; } else { @@ -507,7 +503,7 @@ var modelUploader = (function () { return false; } - geometry = readGeometry(fbxBuffer); + readGeometry(fbxBuffer); if (mapping.hasOwnProperty(SCALE_FIELD)) { mapping[SCALE_FIELD] = parseFloat(mapping[SCALE_FIELD]); @@ -697,23 +693,29 @@ var modelUploader = (function () { modelFile = file; + resetDataObjects(); + // Read model content ... if (!readModel()) { + resetDataObjects(); return; } // Set model properties ... if (!setProperties()) { + resetDataObjects(); return; } // Put model in HTTP message ... if (!createHttpMessage()) { + resetDataObjects(); return; } // Send model to High Fidelity ... sendToHighFidelity(url, callback); + resetDataObjects(); }; return that; From 573ce7261b6a6291c03b6c0c23041a8762513224 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 31 Jul 2014 19:24:46 -0700 Subject: [PATCH 037/206] Add proper sending of ArrayBuffers via JavaScript XMLHttpRequest --- examples/editModels.js | 22 ++++++++----------- .../script-engine/src/XMLHttpRequestClass.cpp | 10 +++++---- .../script-engine/src/XMLHttpRequestClass.h | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index f5183b16d6..b6d783700d 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -245,27 +245,23 @@ var httpMultiPart = (function () { function response() { var buffer, - view, - charCodes, + index, str, - i, - j; + i; str = crlf + boundaryString + "--\r\n"; buffer = str.toArrayBuffer(); byteLength += buffer.byteLength; parts.push(buffer); - str = ""; + buffer = new Uint8Array(byteLength); + index = 0; for (i = 0; i < parts.length; i += 1) { - charCodes = []; - view = new Uint8Array(parts[i]); - for (j = 0; j < view.length; j += 1) { - charCodes.push(view[j]); - } - str = str + String.fromCharCode.apply(String, charCodes); + buffer.set(new Uint8Array(parts[i]), index); + index += parts[i].byteLength; } - return str; + + return buffer; } that.response = response; @@ -685,7 +681,7 @@ var modelUploader = (function () { } }; - req.send(httpMultiPart.response()); + req.send(httpMultiPart.response().buffer); } that.upload = function (file, callback) { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 563e268222..9d8988c43d 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -20,6 +20,8 @@ #include "XMLHttpRequestClass.h" #include "ScriptEngine.h" +Q_DECLARE_METATYPE(QByteArray*) + XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : _engine(engine), _async(true), @@ -212,10 +214,10 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a } void XMLHttpRequestClass::send() { - send(QString()); + send(QScriptValue::NullValue); } -void XMLHttpRequestClass::send(const QVariant& data) { +void XMLHttpRequestClass::send(const QScriptValue& data) { if (_readyState == OPENED && !_reply) { if (!data.isNull()) { if (_url.isLocalFile()) { @@ -223,8 +225,8 @@ void XMLHttpRequestClass::send(const QVariant& data) { return; } else { _sendData = new QBuffer(this); - if (_responseType == "arraybuffer") { - QByteArray ba = qvariant_cast(data); + if (data.isObject()) { + QByteArray ba = qscriptvalue_cast(data); _sendData->setData(ba); } else { _sendData->setData(data.toString().toUtf8()); diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index e482e57077..55bf646476 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -84,7 +84,7 @@ public slots: void open(const QString& method, const QString& url, bool async = true, const QString& username = "", const QString& password = ""); void send(); - void send(const QVariant& data); + void send(const QScriptValue& data); QScriptValue getAllResponseHeaders() const; QScriptValue getResponseHeader(const QString& name) const; From ffb178cb43f2e4ed1f41c3e9df9d6bf050f67199 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 1 Aug 2014 13:44:06 -0700 Subject: [PATCH 038/206] Add SpeechRecognizer --- interface/CMakeLists.txt | 3 +- interface/src/Application.cpp | 5 ++ interface/src/Application.h | 5 ++ interface/src/Menu.cpp | 4 ++ interface/src/Menu.h | 1 + interface/src/SpeechRecognizer.h | 41 ++++++++++++++ interface/src/SpeechRecognizer.mm | 94 +++++++++++++++++++++++++++++++ 7 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 interface/src/SpeechRecognizer.h create mode 100644 interface/src/SpeechRecognizer.mm diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 7336b55852..86d818af6d 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -46,6 +46,7 @@ configure_file(InterfaceConfig.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceCon configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVersion.h") # grab the implementation and header files from src dirs +file(GLOB INTERFACE_OBJCPP_SRCS src/*.mm) file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles models) file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) @@ -95,7 +96,7 @@ if (APPLE) endif() # create the executable, make it a bundle on OS X -add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) +add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${INTERFACE_OBJCPP_SRCS} ${QM}) # link in the hifi shared library include(${MACRO_DIR}/LinkHifiLibrary.cmake) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e0f18922d0..b7c58a15f9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,6 +170,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _runningScriptsWidget(NULL), _runningScriptsWidgetWasVisible(false), _trayIcon(new QSystemTrayIcon(_window)), + _speechRecognizer(), _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()) { @@ -1443,6 +1444,10 @@ void Application::setLowVelocityFilter(bool lowVelocityFilter) { getSixenseManager()->setLowVelocityFilter(lowVelocityFilter); } +void Application::setSpeechRecognitionEnabled(bool enabled) { + _speechRecognizer.setEnabled(enabled); +} + void Application::doKillLocalVoxels() { _wantToKillLocalVoxels = true; } diff --git a/interface/src/Application.h b/interface/src/Application.h index a356b26725..2fc6822b81 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -53,6 +53,7 @@ #include "Menu.h" #include "MetavoxelSystem.h" #include "PacketHeaders.h" +#include "SpeechRecognizer.h" #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/AvatarManager.h" @@ -324,6 +325,8 @@ public slots: void setRenderVoxels(bool renderVoxels); void setLowVelocityFilter(bool lowVelocityFilter); + bool getSpeechRecognitionEnabled() { return _speechRecognizer.getEnabled(); } + void setSpeechRecognitionEnabled(bool enabled); void doKillLocalVoxels(); void loadDialog(); void loadScriptURLDialog(); @@ -593,6 +596,8 @@ private: QSystemTrayIcon* _trayIcon; + SpeechRecognizer _speechRecognizer; + quint64 _lastNackTime; quint64 _lastSendDownstreamAudioStats; }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8fa5183d02..4350aef288 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -233,6 +233,8 @@ Menu::Menu() : QMenu* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, this, SLOT(showScriptEditor())); + addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::ControlWithSpeech, Qt::CTRL | Qt::SHIFT | Qt::Key_C, true, + Application::getInstance(), SLOT(setSpeechRecognitionEnabled(bool))); #ifdef HAVE_QXMPP _chatAction = addActionToQMenuAndActionHash(toolsMenu, @@ -651,6 +653,7 @@ void Menu::loadSettings(QSettings* settings) { _snapshotsLocation = settings->value("snapshotsLocation", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString(); setScriptsLocation(settings->value("scriptsLocation", QString()).toString()); + Application::getInstance()->setSpeechRecognitionEnabled(settings->value("speechRecognitionEnabled", false).toBool()); settings->beginGroup("View Frustum Offset Camera"); // in case settings is corrupt or missing loadSetting() will check for NaN @@ -699,6 +702,7 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust); settings->setValue("snapshotsLocation", _snapshotsLocation); settings->setValue("scriptsLocation", _scriptsLocation); + settings->setValue("speechRecognitionEnabled", Application::getInstance()->getSpeechRecognitionEnabled()); settings->beginGroup("View Frustum Offset Camera"); settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw); settings->setValue("viewFrustumOffsetPitch", _viewFrustumOffset.pitch); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 35e3e75d6a..ca58c72a88 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -348,6 +348,7 @@ namespace MenuOption { const QString CollideWithVoxels = "Collide With Voxels"; const QString Collisions = "Collisions"; const QString Console = "Console..."; + const QString ControlWithSpeech = "Control With Speech"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseVoxelSize = "Decrease Voxel Size"; const QString DisableActivityLogger = "Disable Activity Logger"; diff --git a/interface/src/SpeechRecognizer.h b/interface/src/SpeechRecognizer.h new file mode 100644 index 0000000000..f2c78045aa --- /dev/null +++ b/interface/src/SpeechRecognizer.h @@ -0,0 +1,41 @@ +// +// SpeechRecognizer.h +// interface/src +// +// Created by Ryan Huffman on 07/31/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SpeechRecognizer_h +#define hifi_SpeechRecognizer_h + +#include + +class SpeechRecognizer : public QObject { + Q_OBJECT +public: + SpeechRecognizer(); + ~SpeechRecognizer(); + + void init(); + void handleCommandRecognized(const char* command); + bool getEnabled() { return _enabled; } + void setEnabled(bool enabled); + +public slots: + void addCommand(const QString& command); + void removeCommand(const QString& command); + +signals: + void commandRecognized(const QString& command); + +private: + bool _enabled; + void* _speechRecognizerDelegate; + void* _speechRecognizer; +}; + +#endif // hifi_SpeechRecognizer_h diff --git a/interface/src/SpeechRecognizer.mm b/interface/src/SpeechRecognizer.mm new file mode 100644 index 0000000000..4ec6fadfca --- /dev/null +++ b/interface/src/SpeechRecognizer.mm @@ -0,0 +1,94 @@ +// +// SpeechRecognizer.mm +// interface/src +// +// Created by Ryan Huffman on 07/31/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#import +#import + +#include + +#include "SpeechRecognizer.h" + +@interface SpeechRecognizerDelegate : NSObject { + SpeechRecognizer* _listener; +} + +- (void)setListener:(SpeechRecognizer*)listener; +- (void)speechRecognizer:(NSSpeechRecognizer*)sender didRecognizeCommand:(id)command; + +@end + +@implementation SpeechRecognizerDelegate + +- (void)setListener:(SpeechRecognizer*)listener { + _listener = listener; +} + +- (void)speechRecognizer:(NSSpeechRecognizer*)sender didRecognizeCommand:(id)command { + _listener->handleCommandRecognized(((NSString*)command).UTF8String); +} + +@end + +SpeechRecognizer::SpeechRecognizer() : + QObject(), + _enabled(false), + _speechRecognizerDelegate(NULL), + _speechRecognizer(NULL) { + + init(); +} + +SpeechRecognizer::~SpeechRecognizer() { + if (_speechRecognizer) { + [(id)_speechRecognizer dealloc]; + } + if (_speechRecognizerDelegate) { + [(id)_speechRecognizerDelegate dealloc]; + } +} + +void SpeechRecognizer::init() { + _speechRecognizerDelegate = [[SpeechRecognizerDelegate alloc] init]; + [(id)_speechRecognizerDelegate setListener:this]; + + _speechRecognizer = [[NSSpeechRecognizer alloc] init]; + + [(id)_speechRecognizer setCommands:[NSArray array]]; + [(id)_speechRecognizer setDelegate:(id)_speechRecognizerDelegate]; + + setEnabled(_enabled); +} + +void SpeechRecognizer::handleCommandRecognized(const char* command) { + qDebug() << "Got command: " << command; + emit commandRecognized(QString(command)); +} + +void SpeechRecognizer::setEnabled(bool enabled) { + _enabled = enabled; + if (enabled) { + [(id)_speechRecognizer startListening]; + } else { + [(id)_speechRecognizer stopListening]; + } +} + +void SpeechRecognizer::addCommand(const QString& command) { + NSArray *cmds = [(id)_speechRecognizer commands]; + NSString *cmd = [NSString stringWithUTF8String:command.toLocal8Bit().data()]; + [(id)_speechRecognizer setCommands:[cmds arrayByAddingObject:cmd]]; +} + +void SpeechRecognizer::removeCommand(const QString& command) { + NSMutableArray* cmds = [NSMutableArray arrayWithArray:[(id)_speechRecognizer commands]]; + [cmds removeObject:[NSString stringWithUTF8String:command.toLocal8Bit().data()]]; + [(id)_speechRecognizer setCommands:cmds]; +} From 3e572cdf76b72b3f22294f3333af5e4e506346a8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 1 Aug 2014 13:44:40 -0700 Subject: [PATCH 039/206] Add SpeechRecognizer to js and speechControl.js --- examples/speechControl.js | 98 +++++++++++++++++++++++++++++++++++ interface/src/Application.cpp | 2 + 2 files changed, 100 insertions(+) create mode 100644 examples/speechControl.js diff --git a/examples/speechControl.js b/examples/speechControl.js new file mode 100644 index 0000000000..aee2f31fd0 --- /dev/null +++ b/examples/speechControl.js @@ -0,0 +1,98 @@ +// +// speechControl.js +// examples +// +// Created by Ryan Huffman on 07/31/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var CMD_MOVE_FORWARD = "Move forward"; +var CMD_MOVE_BACKWARD = "Move backward"; +var CMD_MOVE_UP = "Move up"; +var CMD_MOVE_DOWN = "Move down"; +var CMD_STRAFE_LEFT = "Strafe left"; +var CMD_STRAFE_RIGHT = "Strafe right"; +var CMD_STOP = "Stop"; + +var CMD_SHOW_COMMANDS = "Show commands"; + +var commands = [ + CMD_MOVE_FORWARD, + CMD_MOVE_BACKWARD, + CMD_MOVE_UP, + CMD_MOVE_DOWN, + CMD_STRAFE_LEFT, + CMD_STRAFE_RIGHT, + CMD_STOP, + CMD_SHOW_COMMANDS, +]; + +var moveForward = 0; +var moveRight = 0; +var turnRight = 0; +var moveUp = 0; + +function commandRecognized(command) { + if (command === CMD_MOVE_FORWARD) { + accel = { x: 0, y: 0, z: 1 }; + } else if (command == CMD_MOVE_BACKWARD) { + accel = { x: 0, y: 0, z: -1 }; + } else if (command === CMD_MOVE_UP) { + accel = { x: 0, y: 1, z: 0 }; + } else if (command == CMD_MOVE_DOWN) { + accel = { x: 0, y: -1, z: 0 }; + } else if (command == CMD_STRAFE_LEFT) { + accel = { x: -1, y: 0, z: 0 }; + } else if (command == CMD_STRAFE_RIGHT) { + accel = { x: 1, y: 0, z: 0 }; + } else if (command == CMD_STOP) { + accel = { x: 0, y: 0, z: 0 }; + } else if (command == CMD_SHOW_COMMANDS) { + var msg = ""; + for (var i = 0; i < commands.length; i++) { + msg += commands[i] + "\n"; + } + Window.alert(msg); + } +} + +var accel = { x: 0, y: 0, z: 0 }; + +var ACCELERATION = 80; +var initialized = false; + +function update(dt) { + var headOrientation = MyAvatar.headOrientation; + var front = Quat.getFront(headOrientation); + var right = Quat.getRight(headOrientation); + var up = Quat.getUp(headOrientation); + + var thrust = Vec3.multiply(front, accel.z * ACCELERATION); + thrust = Vec3.sum(thrust, Vec3.multiply(right, accel.x * ACCELERATION)); + thrust = Vec3.sum(thrust, Vec3.multiply(up, accel.y * ACCELERATION)); + // print(thrust.x + ", " + thrust.y + ", " + thrust.z); + MyAvatar.addThrust(thrust); +} + +function setup() { + for (var i = 0; i < commands.length; i++) { + SpeechRecognizer.addCommand(commands[i]); + } + print("SETTING UP"); +} + +function scriptEnding() { + print("ENDING"); + for (var i = 0; i < commands.length; i++) { + SpeechRecognizer.removeCommand(commands[i]); + } +} + +Script.scriptEnding.connect(scriptEnding); +Script.update.connect(update); +SpeechRecognizer.commandRecognized.connect(commandRecognized); + +setup(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b7c58a15f9..21732763f0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3664,6 +3664,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->registerGlobalObject("Camera", cameraScriptable); connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater())); + scriptEngine->registerGlobalObject("SpeechRecognizer", &_speechRecognizer); + ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface(); scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater())); From 357ba921815a9176952eac9745ac4b92bcc4164f Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 7 Aug 2014 12:41:09 -0700 Subject: [PATCH 040/206] working towards more dials for InboundAdioStream --- .../src/audio/AudioMixerClientData.cpp | 4 +- interface/src/Audio.cpp | 4 +- libraries/audio/src/InboundAudioStream.cpp | 63 ++++--- libraries/audio/src/InboundAudioStream.h | 82 ++++++--- libraries/shared/src/MovingEvent.h | 36 ++++ libraries/shared/src/MovingMinMaxAvg.h | 173 ++++++++++-------- .../shared/src/MovingPercentile - Copy.cpp | 61 ++++++ libraries/shared/src/RingBufferHistory.h | 92 +++++++++- tests/shared/src/main.cpp | 1 + 9 files changed, 383 insertions(+), 133 deletions(-) create mode 100644 libraries/shared/src/MovingEvent.h create mode 100644 libraries/shared/src/MovingPercentile - Copy.cpp diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index fb805e11d8..ecb4d29171 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -184,7 +184,9 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& // pack the calculated number of stream stats for (int i = 0; i < numStreamStatsToPack; i++) { - AudioStreamStats streamStats = audioStreamsIterator.value()->updateSeqHistoryAndGetAudioStreamStats(); + PositionalAudioStream* stream = audioStreamsIterator.value(); + stream->perSecondCallbackForUpdatingStats(); + AudioStreamStats streamStats = stream->getAudioStreamStats(); memcpy(dataAt, &streamStats, sizeof(AudioStreamStats)); dataAt += sizeof(AudioStreamStats); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 2cba22b630..6ff053e5db 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -846,6 +846,8 @@ void Audio::sendDownstreamAudioStatsPacket() { _audioOutputMsecsUnplayedStats.update(getAudioOutputMsecsUnplayed()); + _receivedAudioStream.perSecondCallbackForUpdatingStats(); + char packet[MAX_PACKET_SIZE]; // pack header @@ -863,7 +865,7 @@ void Audio::sendDownstreamAudioStatsPacket() { dataAt += sizeof(quint16); // pack downstream audio stream stats - AudioStreamStats stats = _receivedAudioStream.updateSeqHistoryAndGetAudioStreamStats(); + AudioStreamStats stats = _receivedAudioStream.getAudioStreamStats(); memcpy(dataAt, &stats, sizeof(AudioStreamStats)); dataAt += sizeof(AudioStreamStats); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 0cd9be4a18..f59070ee83 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -12,30 +12,35 @@ #include "InboundAudioStream.h" #include "PacketHeaders.h" -InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacity, - bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired, bool useStDevForJitterCalc) : +const int STARVE_HISTORY_CAPACITY = 50; + +InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings) : _ringBuffer(numFrameSamples, false, numFramesCapacity), _lastPopSucceeded(false), _lastPopOutput(), - _dynamicJitterBuffers(dynamicJitterBuffers), - _staticDesiredJitterBufferFrames(staticDesiredJitterBufferFrames), - _useStDevForJitterCalc(useStDevForJitterCalc), + _dynamicJitterBuffers(settings._dynamicJitterBuffers), + _staticDesiredJitterBufferFrames(settings._staticDesiredJitterBufferFrames), + _useStDevForJitterCalc(settings._useStDevForJitterCalc), _calculatedJitterBufferFramesUsingMaxGap(0), _calculatedJitterBufferFramesUsingStDev(0), - _desiredJitterBufferFrames(dynamicJitterBuffers ? 1 : staticDesiredJitterBufferFrames), - _maxFramesOverDesired(maxFramesOverDesired), + _desiredJitterBufferFrames(settings._dynamicJitterBuffers ? 1 : settings._staticDesiredJitterBufferFrames), + _maxFramesOverDesired(settings._maxFramesOverDesired), _isStarved(true), _hasStarted(false), _consecutiveNotMixedCount(0), _starveCount(0), _silentFramesDropped(0), _oldFramesDropped(0), - _incomingSequenceNumberStats(INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS), + _incomingSequenceNumberStats(STATS_FOR_STATS_PACKET_WINDOW_SECONDS), _lastFrameReceivedTime(0), - _interframeTimeGapStatsForJitterCalc(TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES, TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS), - _interframeTimeGapStatsForStatsPacket(TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES, TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS), + _timeGapStatsForDesiredCalcOnTooManyStarves(0, settings._windowSecondsForDesiredCalcOnTooManyStarves), + _stdevStatsForDesiredCalcOnTooManyStarves(), + _timeGapStatsForDesiredReduction(0, settings._windowSecondsForDesiredReduction), + _starveHistoryWindowSeconds(settings._windowSecondsForDesiredCalcOnTooManyStarves), + _starveHistory(STARVE_HISTORY_CAPACITY), _framesAvailableStat(), - _currentJitterBufferFrames(0) + _currentJitterBufferFrames(0), + _timeGapStatsForStatsPacket(0, STATS_FOR_STATS_PACKET_WINDOW_SECONDS) { } @@ -58,10 +63,13 @@ void InboundAudioStream::resetStats() { _oldFramesDropped = 0; _incomingSequenceNumberStats.reset(); _lastFrameReceivedTime = 0; - _interframeTimeGapStatsForJitterCalc.reset(); - _interframeTimeGapStatsForStatsPacket.reset(); + _timeGapStatsForDesiredCalcOnTooManyStarves.reset(); + _stdevStatsForDesiredCalcOnTooManyStarves = StDev(); + _timeGapStatsForDesiredReduction.reset(); + _starveHistory.clear(); _framesAvailableStat.reset(); _currentJitterBufferFrames = 0; + _timeGapStatsForStatsPacket.reset(); } void InboundAudioStream::clearBuffer() { @@ -70,6 +78,13 @@ void InboundAudioStream::clearBuffer() { _currentJitterBufferFrames = 0; } +void InboundAudioStream::perSecondCallbackForUpdatingStats() { + _incomingSequenceNumberStats.pushStatsToHistory(); + _timeGapStatsForDesiredCalcOnTooManyStarves.currentIntervalComplete(); + _timeGapStatsForDesiredReduction.currentIntervalComplete(); + _timeGapStatsForStatsPacket.currentIntervalComplete(); +} + int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); } @@ -247,14 +262,14 @@ int InboundAudioStream::clampDesiredJitterBufferFramesValue(int desired) const { } void InboundAudioStream::frameReceivedUpdateTimingStats() { - + /* // update our timegap stats and desired jitter buffer frames if necessary // discard the first few packets we receive since they usually have gaps that aren't represensative of normal jitter const int NUM_INITIAL_PACKETS_DISCARD = 3; quint64 now = usecTimestampNow(); if (_incomingSequenceNumberStats.getReceived() > NUM_INITIAL_PACKETS_DISCARD) { quint64 gap = now - _lastFrameReceivedTime; - _interframeTimeGapStatsForStatsPacket.update(gap); + _timeGapStatsForStatsPacket.update(gap); const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; @@ -282,7 +297,7 @@ void InboundAudioStream::frameReceivedUpdateTimingStats() { } } } - _lastFrameReceivedTime = now; + _lastFrameReceivedTime = now;*/ } int InboundAudioStream::writeDroppableSilentSamples(int numSilentSamples) { @@ -318,12 +333,12 @@ int InboundAudioStream::writeSamplesForDroppedPackets(int numSamples) { AudioStreamStats InboundAudioStream::getAudioStreamStats() const { AudioStreamStats streamStats; - streamStats._timeGapMin = _interframeTimeGapStatsForStatsPacket.getMin(); - streamStats._timeGapMax = _interframeTimeGapStatsForStatsPacket.getMax(); - streamStats._timeGapAverage = _interframeTimeGapStatsForStatsPacket.getAverage(); - streamStats._timeGapWindowMin = _interframeTimeGapStatsForStatsPacket.getWindowMin(); - streamStats._timeGapWindowMax = _interframeTimeGapStatsForStatsPacket.getWindowMax(); - streamStats._timeGapWindowAverage = _interframeTimeGapStatsForStatsPacket.getWindowAverage(); + streamStats._timeGapMin = _timeGapStatsForStatsPacket.getMin(); + streamStats._timeGapMax = _timeGapStatsForStatsPacket.getMax(); + streamStats._timeGapAverage = _timeGapStatsForStatsPacket.getAverage(); + streamStats._timeGapWindowMin = _timeGapStatsForStatsPacket.getWindowMin(); + streamStats._timeGapWindowMax = _timeGapStatsForStatsPacket.getWindowMax(); + streamStats._timeGapWindowAverage = _timeGapStatsForStatsPacket.getWindowAverage(); streamStats._framesAvailable = _ringBuffer.framesAvailable(); streamStats._framesAvailableAverage = _framesAvailableStat.getAverage(); @@ -339,7 +354,3 @@ AudioStreamStats InboundAudioStream::getAudioStreamStats() const { return streamStats; } -AudioStreamStats InboundAudioStream::updateSeqHistoryAndGetAudioStreamStats() { - _incomingSequenceNumberStats.pushStatsToHistory(); - return getAudioStreamStats(); -} diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index b65d5c5de0..88760a35cb 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -22,40 +22,63 @@ #include "TimeWeightedAvg.h" // This adds some number of frames to the desired jitter buffer frames target we use when we're dropping frames. -// The larger this value is, the less aggressive we are about reducing the jitter buffer length. -// Setting this to 0 will try to get the jitter buffer to be exactly _desiredJitterBufferFrames long when dropping frames, +// The larger this value is, the less frames we drop when attempting to reduce the jitter buffer length. +// Setting this to 0 will try to get the jitter buffer to be exactly _desiredJitterBufferFrames when dropping frames, // which could lead to a starve soon after. const int DESIRED_JITTER_BUFFER_FRAMES_PADDING = 1; -// the time gaps stats for _desiredJitterBufferFrames calculation -// will recalculate the max for the past 5000 samples every 500 samples -const int TIME_GAPS_FOR_JITTER_CALC_INTERVAL_SAMPLES = 500; -const int TIME_GAPS_FOR_JITTER_CALC_WINDOW_INTERVALS = 10; +// this controls the length of the window for stats used in the stats packet (not the stats used in +// _desiredJitterBufferFrames calculation) +const int STATS_FOR_STATS_PACKET_WINDOW_SECONDS = 30; -// the time gap stats for constructing AudioStreamStats will -// recalculate min/max/avg every ~1 second for the past ~30 seconds of time gap data -const int TIME_GAPS_FOR_STATS_PACKET_INTERVAL_SAMPLES = USECS_PER_SECOND / BUFFER_SEND_INTERVAL_USECS; -const int TIME_GAPS_FOR_STATS_PACKET_WINDOW_INTERVALS = 30; // this controls the window size of the time-weighted avg of frames available. Every time the window fills up, // _currentJitterBufferFrames is updated with the time-weighted avg and the running time-weighted avg is reset. const int FRAMES_AVAILABLE_STAT_WINDOW_USECS = 2 * USECS_PER_SECOND; -// the internal history buffer of the incoming seq stats will cover 30s to calculate -// packet loss % over last 30s -const int INCOMING_SEQ_STATS_HISTORY_LENGTH_SECONDS = 30; - const int INBOUND_RING_BUFFER_FRAME_CAPACITY = 100; const int DEFAULT_MAX_FRAMES_OVER_DESIRED = 10; const int DEFAULT_DESIRED_JITTER_BUFFER_FRAMES = 1; +const int DEFAULT_WINDOW_STARVE_THRESHOLD = 3; +const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES = 50; +const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION = 10; + class InboundAudioStream : public NodeData { Q_OBJECT public: - InboundAudioStream(int numFrameSamples, int numFramesCapacity, - bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired, - bool useStDevForJitterCalc = false); + class Settings { + public: + Settings() + : _maxFramesOverDesired(DEFAULT_MAX_FRAMES_OVER_DESIRED), + _dynamicJitterBuffers(true), + _staticDesiredJitterBufferFrames(DEFAULT_DESIRED_JITTER_BUFFER_FRAMES), + _useStDevForJitterCalc(false), + _windowStarveThreshold(DEFAULT_WINDOW_STARVE_THRESHOLD), + _windowSecondsForDesiredCalcOnTooManyStarves(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES), + _windowSecondsForDesiredReduction(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION) + {} + + // max number of frames over desired in the ringbuffer. + int _maxFramesOverDesired; + + // if false, _desiredJitterBufferFrames will always be _staticDesiredJitterBufferFrames. Otherwise, + // either fred or philip's method will be used to calculate _desiredJitterBufferFrames based on packet timegaps. + bool _dynamicJitterBuffers; + + // settings for static jitter buffer mode + int _staticDesiredJitterBufferFrames; + + // settings for dynamic jitter buffer mode + bool _useStDevForJitterCalc; // if true, philip's method is used. otherwise, fred's method is used. + int _windowStarveThreshold; + int _windowSecondsForDesiredCalcOnTooManyStarves; + int _windowSecondsForDesiredReduction; + }; + +public: + InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings); void reset(); void resetStats(); @@ -77,7 +100,8 @@ public: void setStaticDesiredJitterBufferFrames(int staticDesiredJitterBufferFrames); /// this function should be called once per second to ensure the seq num stats history spans ~30 seconds - AudioStreamStats updateSeqHistoryAndGetAudioStreamStats(); + //AudioStreamStats updateSeqHistoryAndGetAudioStreamStats(); + void setMaxFramesOverDesired(int maxFramesOverDesired) { _maxFramesOverDesired = maxFramesOverDesired; } @@ -110,6 +134,12 @@ public: int getPacketsReceived() const { return _incomingSequenceNumberStats.getReceived(); } +public slots: + /// This function should be called every second for all the stats to function properly. If dynamic jitter buffers + /// is enabled, those stats are used to calculate _desiredJitterBufferFrames. + /// If the stats are not used and dynamic jitter buffers is disabled, it's not necessary to call this function. + void perSecondCallbackForUpdatingStats(); + private: void frameReceivedUpdateTimingStats(); int clampDesiredJitterBufferFramesValue(int desired) const; @@ -169,15 +199,21 @@ protected: SequenceNumberStats _incomingSequenceNumberStats; quint64 _lastFrameReceivedTime; - MovingMinMaxAvg _interframeTimeGapStatsForJitterCalc; - StDev _stdev; - MovingMinMaxAvg _interframeTimeGapStatsForStatsPacket; - + MovingMinMaxAvg _timeGapStatsForDesiredCalcOnTooManyStarves; + StDev _stdevStatsForDesiredCalcOnTooManyStarves; + MovingMinMaxAvg _timeGapStatsForDesiredReduction; + + int _starveHistoryWindowSeconds; + RingBufferHistory _starveHistory; + TimeWeightedAvg _framesAvailableStat; - // this value is based on the time-weighted avg from _framesAvailableStat. it is only used for + // this value is periodically updated with the time-weighted avg from _framesAvailableStat. it is only used for // dropping silent frames right now. int _currentJitterBufferFrames; + + + MovingMinMaxAvg _timeGapStatsForStatsPacket; }; #endif // hifi_InboundAudioStream_h diff --git a/libraries/shared/src/MovingEvent.h b/libraries/shared/src/MovingEvent.h new file mode 100644 index 0000000000..284ed9d890 --- /dev/null +++ b/libraries/shared/src/MovingEvent.h @@ -0,0 +1,36 @@ +// +// MovingPercentile.h +// libraries/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MovingPercentile_h +#define hifi_MovingPercentile_h + +#include + +class MovingPercentile { + +public: + MovingPercentile(int numSamples, float percentile = 0.5f); + + void updatePercentile(float sample); + float getValueAtPercentile() const { return _valueAtPercentile; } + +private: + const int _numSamples; + const float _percentile; + + QList _samplesSorted; + QList _sampleIds; // incrementally assigned, is cyclic + int _newSampleId; + + int _indexOfPercentile; + float _valueAtPercentile; +}; + +#endif diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 734018b469..9ac9ec9006 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -17,45 +17,62 @@ #include "RingBufferHistory.h" template -class MovingMinMaxAvg { +class MinMaxAvg { +public: + MinMaxAvg() + : _min(std::numeric_limits::max()), + _max(std::numeric_limits::min()), + _average(0.0), + _samples(0) + {} + + void reset() { + _min = std::numeric_limits::max(); + _max = std::numeric_limits::min(); + _average = 0.0; + _samples = 0; + } + + void update(T sample) { + if (sample < _min) { + _min = sample; + } + if (sample > _max) { + _max = sample; + } + double totalSamples = _samples + 1; + _average = _average * ((double)_samples / totalSamples) + + (double)sample / totalSamples; + _samples++; + } + + void update(const MinMaxAvg& other) { + if (other._min < _min) { + _min = other._min; + } + if (other._max > _max) { + _max = other._max; + } + double totalSamples = _samples + other._samples; + _average = _average * ((double)_samples / totalSamples) + + other._average * ((double)other._samples / totalSamples); + _samples += other._samples; + } + + T getMin() const { return _min; } + T getMax() const { return _max; } + double getAverage() const { return _average; } + int getSamples() const { return _samples; } private: - class Stats { - public: - Stats() - : _min(std::numeric_limits::max()), - _max(std::numeric_limits::min()), - _average(0.0) {} - - void updateWithSample(T sample, int& numSamplesInAverage) { - if (sample < _min) { - _min = sample; - } - if (sample > _max) { - _max = sample; - } - _average = _average * ((double)numSamplesInAverage / (numSamplesInAverage + 1)) - + (double)sample / (numSamplesInAverage + 1); - numSamplesInAverage++; - } - - void updateWithOtherStats(const Stats& other, int& numStatsInAverage) { - if (other._min < _min) { - _min = other._min; - } - if (other._max > _max) { - _max = other._max; - } - _average = _average * ((double)numStatsInAverage / (numStatsInAverage + 1)) - + other._average / (numStatsInAverage + 1); - numStatsInAverage++; - } - - T _min; - T _max; - double _average; - }; + T _min; + T _max; + double _average; + int _samples; +}; +template +class MovingMinMaxAvg { public: // This class collects 3 stats (min, max, avg) over a moving window of samples. // The moving window contains _windowIntervals * _intervalLength samples. @@ -65,66 +82,72 @@ public: // this class with MovingMinMaxAvg(100, 50). If you want a moving min of the past 100 samples updated on every // new sample, instantiate this class with MovingMinMaxAvg(1, 100). + + /// use intervalLength = 0 to use in manual mode, where the currentIntervalComplete() function must + /// be called to complete an interval MovingMinMaxAvg(int intervalLength, int windowIntervals) : _intervalLength(intervalLength), _windowIntervals(windowIntervals), _overallStats(), - _samplesCollected(0), _windowStats(), - _existingSamplesInCurrentInterval(0), _currentIntervalStats(), _intervalStats(windowIntervals), _newStatsAvailable(false) {} void reset() { - _overallStats = Stats(); - _samplesCollected = 0; - _windowStats = Stats(); - _existingSamplesInCurrentInterval = 0; - _currentIntervalStats = Stats(); + _overallStats.reset(); + _windowStats.reset(); + _currentIntervalStats.reset(); _intervalStats.clear(); _newStatsAvailable = false; } void update(T newSample) { // update overall stats - _overallStats.updateWithSample(newSample, _samplesCollected); + _overallStats.update(newSample); // update the current interval stats - _currentIntervalStats.updateWithSample(newSample, _existingSamplesInCurrentInterval); + _currentIntervalStats.update(newSample); // if the current interval of samples is now full, record its stats into our past intervals' stats - if (_existingSamplesInCurrentInterval == _intervalLength) { - - // record current interval's stats, then reset them - _intervalStats.insert(_currentIntervalStats); - _currentIntervalStats = Stats(); - _existingSamplesInCurrentInterval = 0; - - // update the window's stats by combining the intervals' stats - typename RingBufferHistory::Iterator i = _intervalStats.begin(); - typename RingBufferHistory::Iterator end = _intervalStats.end(); - _windowStats = Stats(); - int intervalsIncludedInWindowStats = 0; - while (i != end) { - _windowStats.updateWithOtherStats(*i, intervalsIncludedInWindowStats); - i++; - } - - _newStatsAvailable = true; + // NOTE: if _intervalLength is 0 (manual mode), currentIntervalComplete() will not be called here. + if (_currentIntervalStats.getSamples() == _intervalLength) { + currentIntervalComplete(); } } + /// This function can be called to manually control when each interval ends. For example, if each interval + /// needs to last T seconds as opposed to N samples, this function should be called every T seconds. + void currentIntervalComplete() { + // record current interval's stats, then reset them + _intervalStats.insert(_currentIntervalStats); + _currentIntervalStats.reset(); + + // update the window's stats by combining the intervals' stats + typename RingBufferHistory< MinMaxAvg >::Iterator i = _intervalStats.begin(); + typename RingBufferHistory< MinMaxAvg >::Iterator end = _intervalStats.end(); + _windowStats.reset(); + while (i != end) { + _windowStats.update(*i); + ++i; + } + + _newStatsAvailable = true; + } + bool getNewStatsAvailableFlag() const { return _newStatsAvailable; } void clearNewStatsAvailableFlag() { _newStatsAvailable = false; } - T getMin() const { return _overallStats._min; } - T getMax() const { return _overallStats._max; } - double getAverage() const { return _overallStats._average; } - T getWindowMin() const { return _windowStats._min; } - T getWindowMax() const { return _windowStats._max; } - double getWindowAverage() const { return _windowStats._average; } + T getMin() const { return _overallStats.getMin(); } + T getMax() const { return _overallStats.getMax(); } + double getAverage() const { return _overallStats.getAverage(); } + T getWindowMin() const { return _windowStats.getMin(); } + T getWindowMax() const { return _windowStats.getMax(); } + double getWindowAverage() const { return _windowStats.getAverage(); } + + const MinMaxAvg& getOverallStats() const{ return _overallStats; } + const MinMaxAvg& getWindowStats() const{ return _windowStats; } bool isWindowFilled() const { return _intervalStats.isFilled(); } @@ -133,18 +156,16 @@ private: int _windowIntervals; // these are min/max/avg stats for all samples collected. - Stats _overallStats; - int _samplesCollected; + MinMaxAvg _overallStats; // these are the min/max/avg stats for the samples in the moving window - Stats _windowStats; - int _existingSamplesInCurrentInterval; + MinMaxAvg _windowStats; - // these are the min/max/avg stats for the current interval - Stats _currentIntervalStats; + // these are the min/max/avg stats for the samples in the current interval + MinMaxAvg _currentIntervalStats; // these are stored stats for the past intervals in the window - RingBufferHistory _intervalStats; + RingBufferHistory< MinMaxAvg > _intervalStats; bool _newStatsAvailable; }; diff --git a/libraries/shared/src/MovingPercentile - Copy.cpp b/libraries/shared/src/MovingPercentile - Copy.cpp new file mode 100644 index 0000000000..ec007b5c22 --- /dev/null +++ b/libraries/shared/src/MovingPercentile - Copy.cpp @@ -0,0 +1,61 @@ +// +// MovingPercentile.cpp +// libraries/shared/src +// +// Created by Yixin Wang on 6/4/2014 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MovingPercentile.h" + +MovingPercentile::MovingPercentile(int numSamples, float percentile) + : _numSamples(numSamples), + _percentile(percentile), + _samplesSorted(), + _sampleIds(), + _newSampleId(0), + _indexOfPercentile(0), + _valueAtPercentile(0.0f) +{ +} + +void MovingPercentile::updatePercentile(float sample) { + + // insert the new sample into _samplesSorted + int newSampleIndex; + if (_samplesSorted.size() < _numSamples) { + // if not all samples have been filled yet, simply append it + newSampleIndex = _samplesSorted.size(); + _samplesSorted.append(sample); + _sampleIds.append(_newSampleId); + + // update _indexOfPercentile + float index = _percentile * (float)(_samplesSorted.size() - 1); + _indexOfPercentile = (int)(index + 0.5f); // round to int + } else { + // find index of sample with id = _newSampleId and replace it with new sample + newSampleIndex = _sampleIds.indexOf(_newSampleId); + _samplesSorted[newSampleIndex] = sample; + } + + // increment _newSampleId. cycles from 0 thru N-1 + _newSampleId = (_newSampleId == _numSamples - 1) ? 0 : _newSampleId + 1; + + // swap new sample with neighbors in _samplesSorted until it's in sorted order + // try swapping up first, then down. element will only be swapped one direction. + while (newSampleIndex < _samplesSorted.size() - 1 && sample > _samplesSorted[newSampleIndex + 1]) { + _samplesSorted.swap(newSampleIndex, newSampleIndex + 1); + _sampleIds.swap(newSampleIndex, newSampleIndex + 1); + newSampleIndex++; + } + while (newSampleIndex > 0 && sample < _samplesSorted[newSampleIndex - 1]) { + _samplesSorted.swap(newSampleIndex, newSampleIndex - 1); + _sampleIds.swap(newSampleIndex, newSampleIndex - 1); + newSampleIndex--; + } + + // find new value at percentile + _valueAtPercentile = _samplesSorted[_indexOfPercentile]; +} diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 27a78c0055..339f390cd5 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -83,9 +83,14 @@ private: QVector _buffer; public: - class Iterator : public std::iterator < std::forward_iterator_tag, T > { + class Iterator : public std::iterator < std::random_access_iterator_tag, T > { public: - Iterator(T* bufferFirst, T* bufferLast, T* at) : _bufferFirst(bufferFirst), _bufferLast(bufferLast), _at(at) {} + Iterator(T* bufferFirst, T* bufferLast, T* newestAt, T* at) + : _bufferFirst(bufferFirst), + _bufferLast(bufferLast), + _bufferLength(bufferLast - bufferFirst + 1), + _newestAt(newestAt), + _at(at) {} bool operator==(const Iterator& rhs) { return _at == rhs._at; } bool operator!=(const Iterator& rhs) { return _at != rhs._at; } @@ -103,20 +108,95 @@ public: return tmp; } + Iterator& operator--() { + _at = (_at == _bufferLast) ? _bufferFirst : _at + 1; + return *this; + } + + Iterator operator--(int) { + Iterator tmp(*this); + --(*this); + return tmp; + } + + Iterator operator+(int add) { + Iterator sum(*this); + sum._at = atShiftedBy(add); + return sum; + } + + Iterator operator-(int sub) { + Iterator sum(*this); + sum._at = atShiftedBy(-sub); + return sum; + } + + Iterator& operator+=(int add) { + _at = atShiftedBy(add); + return *this; + } + + Iterator& operator-=(int sub) { + _at = atShiftedBy(-sub); + return *this; + } + + T& operator[](int i) { + return *(atShiftedBy(i)); + } + + bool operator<(const Iterator& rhs) { + return age() < rhs.age(); + } + + bool operator>(const Iterator& rhs) { + return age() > rhs.age(); + } + + bool operator<=(const Iterator& rhs) { + return age() < rhs.age(); + } + + bool operator>=(const Iterator& rhs) { + return age() >= rhs.age(); + } + + int operator-(const Iterator& rhs) { + return age() - rhs.age(); + } + private: - T* const _bufferFirst; - T* const _bufferLast; + T* atShiftedBy(int i) { // shifts i places towards _bufferFirst (towards older entries) + i = (_at - _bufferFirst - i) % _bufferLength; + if (i < 0) { + i += _bufferLength; + } + return _bufferFirst + i; + } + + int age() { + int age = _newestAt - _at; + if (age < 0) { + age += _bufferLength; + } + return age; + } + + T* _bufferFirst; + T* _bufferLast; + int _bufferLength; + T* _newestAt; T* _at; }; - Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex]); } + Iterator begin() { return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex], &_buffer[_newestEntryAtIndex]); } Iterator end() { int endAtIndex = _newestEntryAtIndex - _numEntries; if (endAtIndex < 0) { endAtIndex += _size; } - return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[endAtIndex]); + return Iterator(&_buffer.first(), &_buffer.last(), &_buffer[_newestEntryAtIndex], &_buffer[endAtIndex]); } }; diff --git a/tests/shared/src/main.cpp b/tests/shared/src/main.cpp index d4251eef7a..34ba515062 100644 --- a/tests/shared/src/main.cpp +++ b/tests/shared/src/main.cpp @@ -16,6 +16,7 @@ int main(int argc, char** argv) { MovingMinMaxAvgTests::runAllTests(); MovingPercentileTests::runAllTests(); AngularConstraintTests::runAllTests(); + printf("tests complete, press enter to exit\n"); getchar(); return 0; } From 83a868d7418534bd0ee6a51cb78fe298b98b5470 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Aug 2014 15:20:30 -0700 Subject: [PATCH 041/206] Make XMLHttpRequest automatically authorize API calls --- libraries/script-engine/src/XMLHttpRequestClass.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 9d8988c43d..cb891c2ab1 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -17,6 +17,7 @@ #include +#include #include "XMLHttpRequestClass.h" #include "ScriptEngine.h" @@ -201,6 +202,9 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a notImplemented(); } } else { + if (url.toLower().left(33) == "https://data.highfidelity.io/api/") { + _url.setQuery("access_token=" + AccountManager::getInstance().getAccountInfo().getAccessToken().token); + } if (!username.isEmpty()) { _url.setUserName(username); } From 70548976dbec7f11f3d4e2c8f71a8fd2b6be29b0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Aug 2014 15:27:30 -0700 Subject: [PATCH 042/206] Fix boundary string format to not include curly braces --- examples/editModels.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/editModels.js b/examples/editModels.js index b6d783700d..1f1c349738 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -169,7 +169,7 @@ var httpMultiPart = (function () { crlf; function clear() { - boundaryString = "--boundary_" + Uuid.generate() + "="; + boundaryString = "--boundary_" + String(Uuid.generate()).slice(1, 36) + "="; parts = []; byteLength = 0; crlf = ""; From eaf0b366d009807314de697d934846f3c1ebcbdc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Aug 2014 15:30:27 -0700 Subject: [PATCH 043/206] Update model reading in preparation for API upload --- examples/editModels.js | 96 +++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 1f1c349738..0042fe29ab 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -64,6 +64,13 @@ if (typeof String.prototype.fileName !== "function") { }; } +if (typeof String.prototype.fileBase !== "function") { + String.prototype.fileBase = function () { + var filename = this.fileName(); + return filename.slice(0, filename.indexOf(".")); + }; +} + if (typeof String.prototype.fileType !== "function") { String.prototype.fileType = function () { return this.slice(this.lastIndexOf(".") + 1); @@ -272,13 +279,14 @@ var httpMultiPart = (function () { var modelUploader = (function () { var that = {}, - urlBase = "http://public.highfidelity.io/meshes/", modelFile, fstBuffer, fbxBuffer, svoBuffer, mapping, geometry, + API_URL = "https://data.highfidelity.io/api/v1/models", + MODEL_URL = "http://public.highfidelity.io/models/content", NAME_FIELD = "name", SCALE_FIELD = "scale", FILENAME_FIELD = "filename", @@ -290,8 +298,8 @@ var modelUploader = (function () { MAX_TEXTURE_SIZE = 1024; function error(message) { - Window.alert(message); print(message); + Window.alert(message); } function resetDataObjects() { @@ -460,57 +468,71 @@ var modelUploader = (function () { } function readModel() { - var fbxFilename; + var fbxFilename, + svoFilename, + fileType; print("Reading model file: " + modelFile); - if (modelFile.toLowerCase().slice(-4) === ".svo") { - svoBuffer = readFile(modelFile); - if (svoBuffer === null) { + if (modelFile.toLowerCase().slice(-4) === ".fst") { + fstBuffer = readFile(modelFile); + if (fstBuffer === null) { return false; } + readMapping(fstBuffer); + fileType = mapping[FILENAME_FIELD].toLowerCase().fileType(); + if (mapping.hasOwnProperty(FILENAME_FIELD)) { + if (fileType === "fbx") { + fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; + } else if (fileType === "svo") { + svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; + } else { + error("Unrecognized model type in FST file!"); + return false; + } + } else { + error("Model file name not found in FST file!"); + return false; + } + + } else if (modelFile.toLowerCase().slice(-4) === ".fbx") { + fbxFilename = modelFile; + mapping[FILENAME_FIELD] = modelFile.fileName(); + + } else if (modelFile.toLowerCase().slice(-4) === ".svo") { + svoFilename = modelFile; + mapping[FILENAME_FIELD] = modelFile.fileName(); } else { + error("Unrecognized file type: " + modelFile); + return false; + } - if (modelFile.toLowerCase().slice(-4) === ".fst") { - fstBuffer = readFile(modelFile); - if (fstBuffer === null) { - return false; - } - readMapping(fstBuffer); - if (mapping.hasOwnProperty(FILENAME_FIELD)) { - fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; - } else { - error("FBX file name not found in FST file!"); - return false; - } - - } else if (modelFile.toLowerCase().slice(-4) === ".fbx") { - fbxFilename = modelFile; - mapping[FILENAME_FIELD] = modelFile.fileName(); - - } else { - error("Unrecognized file type: " + modelFile); - return false; - } - + if (fbxFilename) { fbxBuffer = readFile(fbxFilename); if (fbxBuffer === null) { return false; } readGeometry(fbxBuffer); + } - if (mapping.hasOwnProperty(SCALE_FIELD)) { - mapping[SCALE_FIELD] = parseFloat(mapping[SCALE_FIELD]); - } else { - mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0 : 15.0); + if (svoFilename) { + svoBuffer = readFile(svoFilename); + if (svoBuffer === null) { + return false; } } + if (mapping.hasOwnProperty(SCALE_FIELD)) { + mapping[SCALE_FIELD] = parseFloat(mapping[SCALE_FIELD]); + } else { + mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0 : 15.0); + } + // Add any missing basic mappings if (!mapping.hasOwnProperty(NAME_FIELD)) { - mapping[NAME_FIELD] = modelFile.fileName().slice(0, -4); + mapping[NAME_FIELD] = modelFile.fileName().fileBase(); } if (!mapping.hasOwnProperty(TEXDIR_FIELD)) { mapping[TEXDIR_FIELD] = "."; @@ -626,6 +648,9 @@ var modelUploader = (function () { for (lodFile in mapping.lod) { if (mapping.lod.hasOwnProperty(lodFile)) { lodBuffer = readFile(modelFile.path() + "\/" + lodFile); + if (lodBuffer === null) { + return false; + } httpMultiPart.add({ name: "lod" + lodCount, buffer: lodBuffer @@ -639,6 +664,9 @@ var modelUploader = (function () { textureBuffer = readFile(modelFile.path() + "\/" + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") + geometry.textures[i]); + if (textureBuffer === null) { + return false; + } textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png"); @@ -654,7 +682,7 @@ var modelUploader = (function () { // Model category httpMultiPart.add({ name : "model_category", - string : "item" // DJRTODO: What model category to use? + string : "content" }); return true; From 7f2d33c4e43f7ad1e37e2b122e5d3f49eeaca1d9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 7 Aug 2014 15:31:44 -0700 Subject: [PATCH 044/206] Add in model uploading via API --- examples/editModels.js | 147 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 15 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 0042fe29ab..67760acdb0 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -688,32 +688,148 @@ var modelUploader = (function () { return true; } - function sendToHighFidelity(url, callback) { - var req; + function sendToHighFidelity(addModelCallback) { + var req, + modelName, + modelURL, + uploadedChecks, + HTTP_GET_TIMEOUT = 60, // 1 minute + HTTP_SEND_TIMEOUT = 900, // 15 minutes + UPLOADED_CHECKS = 30, + CHECK_UPLOADED_TIMEOUT = 1, // 1 second + handleCheckUploadedResponses, + handleUploadModelResponses, + handleRequestUploadResponses; - print("Sending model to High Fidelity"); + function uploadTimedOut() { + error("Model upload failed: Internet request timed out!"); + } - req = new XMLHttpRequest(); - req.open("POST", url, true); - req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\""); + function debugResponse() { + print("req.errorCode = " + req.errorCode); + print("req.readyState = " + req.readyState); + print("req.status = " + req.status); + print("req.statusText = " + req.statusText); + print("req.responseType = " + req.responseType); + print("req.responseText = " + req.responseText); + print("req.response = " + req.response); + print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders()); + } - req.onreadystatechange = function () { + function checkUploaded() { + print("Checking uploaded model"); + + req = new XMLHttpRequest(); + req.open("HEAD", modelURL, true); + req.timeout = HTTP_GET_TIMEOUT * 1000; + req.onreadystatechange = handleCheckUploadedResponses; + req.ontimeout = uploadTimedOut; + req.send(); + } + + handleCheckUploadedResponses = function () { + //debugResponse(); if (req.readyState === req.DONE) { if (req.status === 200) { - print("Uploaded model: " + url); - callback(url); + // Note: Unlike avatar models, for content models we don't need to refresh texture cache. + addModelCallback(modelURL); // Add model to the world + print("Model uploaded: " + modelURL); + Window.alert("Your model has been uploaded as: " + modelURL); + } else if (req.status === 404) { + if (uploadedChecks > 0) { + uploadedChecks -= 1; + Script.setTimeout(checkUploaded, CHECK_UPLOADED_TIMEOUT * 1000); + } else { + print("Error: " + req.status + " " + req.statusText); + error("We could not verify that your model was successfully uploaded but it may have been at: " + + modelURL); + } } else { - print("Error uploading file: " + req.status + " " + req.statusText); - Window.alert("Could not upload file: " + req.status + " " + req.statusText); + print("Error: " + req.status + " " + req.statusText); + error("There was a problem with your upload, please try again later."); } } }; - req.send(httpMultiPart.response().buffer); + function uploadModel(method) { + var url; + + req = new XMLHttpRequest(); + if (method === "PUT") { + url = API_URL + "\/" + modelName; + req.open("PUT", url, true); //print("PUT " + url); + } else { + url = API_URL; + req.open("POST", url, true); //print("POST " + url); + } + req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\""); + req.timeout = HTTP_SEND_TIMEOUT * 1000; + req.onreadystatechange = handleUploadModelResponses; + req.ontimeout = uploadTimedOut; + req.send(httpMultiPart.response().buffer); + } + + handleUploadModelResponses = function () { + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + uploadedChecks = 30; + checkUploaded(); + } else { + print("Error: " + req.status + " " + req.statusText); + error("There was a problem with your upload, please try again later."); + } + } + }; + + function requestUpload() { + var url; + + url = API_URL + "\/" + modelName; // XMLHttpRequest automatically handles authorization of API requests. + req = new XMLHttpRequest(); + req.open("GET", url, true); //print("GET " + url); + req.responseType = "json"; + req.timeout = HTTP_GET_TIMEOUT * 1000; + req.onreadystatechange = handleRequestUploadResponses; + req.ontimeout = uploadTimedOut; + req.send(); + } + + handleRequestUploadResponses = function () { + var response; + + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + if (req.responseType === "json") { + response = JSON.parse(req.responseText); + if (response.status === "success") { + if (response.exists === false) { + uploadModel("POST"); + } else if (response.can_update === true) { + uploadModel("PUT"); + } else { + error("This model file already exists and is owned by someone else!"); + } + return; + } + } + } else { + print("Error: " + req.status + " " + req.statusText); + } + error("Model upload failed! Something went wrong at the data server."); + } + }; + + print("Sending model to High Fidelity"); + + modelName = mapping[NAME_FIELD]; + modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // DJRTODO: Do all models get a FST? + + requestUpload(); } - that.upload = function (file, callback) { - var url = urlBase + file.fileName(); + that.upload = function (file, addModelCallback) { modelFile = file; @@ -738,7 +854,8 @@ var modelUploader = (function () { } // Send model to High Fidelity ... - sendToHighFidelity(url, callback); + sendToHighFidelity(addModelCallback); + resetDataObjects(); }; From 1153a76ab1005686716b048113be0fb4d582b80d Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 7 Aug 2014 16:37:36 -0700 Subject: [PATCH 045/206] new option knobs added, no new behavior yet --- assignment-client/src/Agent.cpp | 7 +- assignment-client/src/audio/AudioMixer.cpp | 74 ++- assignment-client/src/audio/AudioMixer.h | 8 +- .../src/audio/AudioMixerClientData.cpp | 9 +- .../src/audio/AvatarAudioStream.cpp | 4 +- .../src/audio/AvatarAudioStream.h | 2 +- .../resources/web/settings/describe.json | 26 +- interface/src/Application.cpp | 9 +- interface/src/Audio.cpp | 7 +- interface/src/Audio.h | 5 +- interface/src/Menu.cpp | 23 +- interface/src/Menu.h | 9 +- interface/src/ui/PreferencesDialog.cpp | 35 +- interface/ui/preferencesDialog.ui | 471 +++++++++++++++++- libraries/audio/src/InboundAudioStream.cpp | 20 + libraries/audio/src/InboundAudioStream.h | 37 +- libraries/audio/src/InjectedAudioStream.cpp | 4 +- libraries/audio/src/InjectedAudioStream.h | 2 +- libraries/audio/src/MixedAudioStream.cpp | 4 +- libraries/audio/src/MixedAudioStream.h | 2 +- .../audio/src/MixedProcessedAudioStream.cpp | 4 +- .../audio/src/MixedProcessedAudioStream.h | 2 +- libraries/audio/src/PositionalAudioStream.cpp | 5 +- libraries/audio/src/PositionalAudioStream.h | 3 +- libraries/shared/src/MovingMinMaxAvg.h | 9 + libraries/shared/src/RingBufferHistory.h | 8 + 26 files changed, 680 insertions(+), 109 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index dbb1620252..dd48c9daa7 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -33,12 +33,17 @@ #include "Agent.h" +static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; + Agent::Agent(const QByteArray& packet) : ThreadedAssignment(packet), _voxelEditSender(), _particleEditSender(), _modelEditSender(), - _receivedAudioStream(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, 1, false, 1, 0, false), + _receivedAudioStream(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, + InboundAudioStream::Settings(0, false, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, false, + DEFAULT_WINDOW_STARVE_THRESHOLD, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES, + DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION)), _avatarHashMap() { // be the parent of the script engine so it gets moved when we do diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 5900e1f151..fc223ab850 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -67,9 +67,7 @@ void attachNewNodeDataToNode(Node *newNode) { } } -bool AudioMixer::_useDynamicJitterBuffers = false; -int AudioMixer::_staticDesiredJitterBufferFrames = 0; -int AudioMixer::_maxFramesOverDesired = 0; +InboundAudioStream::Settings AudioMixer::_streamSettings; AudioMixer::AudioMixer(const QByteArray& packet) : ThreadedAssignment(packet), @@ -332,7 +330,7 @@ void AudioMixer::readPendingDatagrams() { void AudioMixer::sendStatsPacket() { static QJsonObject statsObject; - statsObject["useDynamicJitterBuffers"] = _useDynamicJitterBuffers; + statsObject["useDynamicJitterBuffers"] = _streamSettings._dynamicJitterBuffers; statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100.0f; statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; @@ -421,36 +419,62 @@ void AudioMixer::run() { if (settingsObject.contains(AUDIO_GROUP_KEY)) { QJsonObject audioGroupObject = settingsObject[AUDIO_GROUP_KEY].toObject(); - // check the payload to see if we have asked for dynamicJitterBuffer support - const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "A-dynamic-jitter-buffer"; - bool shouldUseDynamicJitterBuffers = audioGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool(); - if (shouldUseDynamicJitterBuffers) { - qDebug() << "Enable dynamic jitter buffers."; - _useDynamicJitterBuffers = true; - } else { - qDebug() << "Dynamic jitter buffers disabled."; - _useDynamicJitterBuffers = false; - } - bool ok; - const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "B-desired-jitter-buffer-frames"; - _staticDesiredJitterBufferFrames = audioGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok); - if (!ok) { - _staticDesiredJitterBufferFrames = DEFAULT_DESIRED_JITTER_BUFFER_FRAMES; + // check the payload to see if we have asked for dynamicJitterBuffer support + const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "A-dynamic-jitter-buffer"; + _streamSettings._dynamicJitterBuffers = audioGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool(); + if (_streamSettings._dynamicJitterBuffers) { + qDebug() << "Enable dynamic jitter buffers."; + } else { + qDebug() << "Dynamic jitter buffers disabled."; } - qDebug() << "Static desired jitter buffer frames:" << _staticDesiredJitterBufferFrames; + + const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "B-desired-jitter-buffer-frames"; + _streamSettings._staticDesiredJitterBufferFrames = audioGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok); + if (!ok) { + _streamSettings._staticDesiredJitterBufferFrames = DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES; + } + qDebug() << "Static desired jitter buffer frames:" << _streamSettings._staticDesiredJitterBufferFrames; const QString MAX_FRAMES_OVER_DESIRED_JSON_KEY = "C-max-frames-over-desired"; - _maxFramesOverDesired = audioGroupObject[MAX_FRAMES_OVER_DESIRED_JSON_KEY].toString().toInt(&ok); + _streamSettings._maxFramesOverDesired = audioGroupObject[MAX_FRAMES_OVER_DESIRED_JSON_KEY].toString().toInt(&ok); if (!ok) { - _maxFramesOverDesired = DEFAULT_MAX_FRAMES_OVER_DESIRED; + _streamSettings._maxFramesOverDesired = DEFAULT_MAX_FRAMES_OVER_DESIRED; } - qDebug() << "Max frames over desired:" << _maxFramesOverDesired; + qDebug() << "Max frames over desired:" << _streamSettings._maxFramesOverDesired; + + const QString USE_STDEV_FOR_DESIRED_CALC_JSON_KEY = "D-use-stdev-for-desired-calc"; + _streamSettings._useStDevForJitterCalc = audioGroupObject[USE_STDEV_FOR_DESIRED_CALC_JSON_KEY].toBool(); + if (_streamSettings._useStDevForJitterCalc) { + qDebug() << "Using Philip's stdev method for jitter calc if dynamic jitter buffers enabled"; + } else { + qDebug() << "Using Fred's max-gap method for jitter calc if dynamic jitter buffers enabled"; + } + + const QString WINDOW_STARVE_THRESHOLD_JSON_KEY = "E-window-starve-threshold"; + _streamSettings._windowStarveThreshold = audioGroupObject[WINDOW_STARVE_THRESHOLD_JSON_KEY].toString().toInt(&ok); + if (!ok) { + _streamSettings._windowStarveThreshold = DEFAULT_WINDOW_STARVE_THRESHOLD; + } + qDebug() << "Window A starve threshold:" << _streamSettings._windowStarveThreshold; + + const QString WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY = "F-window-seconds-for-desired-calc-on-too-many-starves"; + _streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY].toString().toInt(&ok); + if (!ok) { + _streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES; + } + qDebug() << "Window A length:" << _streamSettings._windowSecondsForDesiredCalcOnTooManyStarves << "seconds"; + + const QString WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY = "G-window-seconds-for-desired-reduction"; + _streamSettings._windowSecondsForDesiredReduction = audioGroupObject[WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY].toString().toInt(&ok); + if (!ok) { + _streamSettings._windowSecondsForDesiredReduction = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION; + } + qDebug() << "Window B length:" << _streamSettings._windowSecondsForDesiredReduction << "seconds"; - - const QString UNATTENUATED_ZONE_KEY = "D-unattenuated-zone"; + const QString UNATTENUATED_ZONE_KEY = "Z-unattenuated-zone"; QString unattenuatedZoneString = audioGroupObject[UNATTENUATED_ZONE_KEY].toString(); if (!unattenuatedZoneString.isEmpty()) { diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 83769a4209..3df2bce682 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -37,9 +37,7 @@ public slots: void sendStatsPacket(); - static bool getUseDynamicJitterBuffers() { return _useDynamicJitterBuffers; } - static int getStaticDesiredJitterBufferFrames() { return _staticDesiredJitterBufferFrames; } - static int getMaxFramesOverDesired() { return _maxFramesOverDesired; } + static const InboundAudioStream::Settings& getStreamSettings() { return _streamSettings; } private: /// adds one stream to the mix for a listening node @@ -62,9 +60,7 @@ private: AABox* _sourceUnattenuatedZone; AABox* _listenerUnattenuatedZone; - static bool _useDynamicJitterBuffers; - static int _staticDesiredJitterBufferFrames; - static int _maxFramesOverDesired; + static InboundAudioStream::Settings _streamSettings; quint64 _lastSendAudioStreamStatsTime; }; diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index ecb4d29171..333357fbf4 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -73,9 +73,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { quint8 channelFlag = *(reinterpret_cast(channelFlagAt)); bool isStereo = channelFlag == 1; - _audioStreams.insert(nullUUID, - matchingStream = new AvatarAudioStream(isStereo, AudioMixer::getUseDynamicJitterBuffers(), - AudioMixer::getStaticDesiredJitterBufferFrames(), AudioMixer::getMaxFramesOverDesired())); + _audioStreams.insert(nullUUID, matchingStream = new AvatarAudioStream(isStereo, AudioMixer::getStreamSettings())); } else { matchingStream = _audioStreams.value(nullUUID); } @@ -87,9 +85,8 @@ int AudioMixerClientData::parseData(const QByteArray& packet) { QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(bytesBeforeStreamIdentifier, NUM_BYTES_RFC4122_UUID)); if (!_audioStreams.contains(streamIdentifier)) { - _audioStreams.insert(streamIdentifier, - matchingStream = new InjectedAudioStream(streamIdentifier, AudioMixer::getUseDynamicJitterBuffers(), - AudioMixer::getStaticDesiredJitterBufferFrames(), AudioMixer::getMaxFramesOverDesired())); + // we don't have this injected stream yet, so add it + _audioStreams.insert(streamIdentifier, matchingStream = new InjectedAudioStream(streamIdentifier, AudioMixer::getStreamSettings())); } else { matchingStream = _audioStreams.value(streamIdentifier); } diff --git a/assignment-client/src/audio/AvatarAudioStream.cpp b/assignment-client/src/audio/AvatarAudioStream.cpp index fcb78d7a6c..877d1a37c7 100644 --- a/assignment-client/src/audio/AvatarAudioStream.cpp +++ b/assignment-client/src/audio/AvatarAudioStream.cpp @@ -13,8 +13,8 @@ #include "AvatarAudioStream.h" -AvatarAudioStream::AvatarAudioStream(bool isStereo, bool dynamicJitterBuffer, int staticDesiredJitterBufferFrames, int maxFramesOverDesired) : - PositionalAudioStream(PositionalAudioStream::Microphone, isStereo, dynamicJitterBuffer, staticDesiredJitterBufferFrames, maxFramesOverDesired) +AvatarAudioStream::AvatarAudioStream(bool isStereo, const InboundAudioStream::Settings& settings) : + PositionalAudioStream(PositionalAudioStream::Microphone, isStereo, settings) { } diff --git a/assignment-client/src/audio/AvatarAudioStream.h b/assignment-client/src/audio/AvatarAudioStream.h index ebad4585e0..2eb0e674b0 100644 --- a/assignment-client/src/audio/AvatarAudioStream.h +++ b/assignment-client/src/audio/AvatarAudioStream.h @@ -18,7 +18,7 @@ class AvatarAudioStream : public PositionalAudioStream { public: - AvatarAudioStream(bool isStereo, bool dynamicJitterBuffer, int staticDesiredJitterBufferFrames, int maxFramesOverDesired); + AvatarAudioStream(bool isStereo, const InboundAudioStream::Settings& settings); private: // disallow copying of AvatarAudioStream objects diff --git a/domain-server/resources/web/settings/describe.json b/domain-server/resources/web/settings/describe.json index f4920a7b50..59d99eab11 100644 --- a/domain-server/resources/web/settings/describe.json +++ b/domain-server/resources/web/settings/describe.json @@ -21,7 +21,31 @@ "placeholder": "10", "default": "10" }, - "D-unattenuated-zone": { + "D-use-stdev": { + "type": "checkbox", + "label": "Use Stdev for Desired Jitter Frames Calc:", + "help": "If checked, Philip's method (stdev of timegaps) is used to calculate desired jitter frames. Otherwise, Fred's method (max timegap) is used", + "default": false + } + "E-window-starve-threshold": { + "label": "Window Starve Threshold", + "help": "If this many starves occur in an N-second window (N is the number in the next field), then the desired jitter frames will be re-evaluated using Window A.", + "placeholder": "3", + "default": "3" + }, + "F-window-seconds-for-desired-calc-on-too-many-starves": { + "label": "Timegaps Window (A) Seconds:", + "help": "Window A contains a history of timegaps. Its max timegap is used to re-evaluate the desired jitter frames when too many starves occur within it.", + "placeholder": "50", + "default": "50" + }, + "G-window-seconds-for-desired-reduction": { + "label": "Timegaps Window (B) Seconds:", + "help": "Window B contains a history of timegaps. Its max timegap is used as a ceiling for the desired jitter frames value.", + "placeholder": "10", + "default": "10" + }, + "Z-unattenuated-zone": { "label": "Unattenuated Zone", "help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)", "placeholder": "no zone", diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7eb087c531..a7d6cd9f3b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1763,14 +1763,7 @@ void Application::init() { _lastTimeUpdated.start(); Menu::getInstance()->loadSettings(); - if (Menu::getInstance()->getAudioJitterBufferFrames() != 0) { - _audio.setDynamicJitterBuffers(false); - _audio.setStaticDesiredJitterBufferFrames(Menu::getInstance()->getAudioJitterBufferFrames()); - } else { - _audio.setDynamicJitterBuffers(true); - } - - _audio.setMaxFramesOverDesired(Menu::getInstance()->getMaxFramesOverDesired()); + _audio.setReceivedAudioStreamSettings(Menu::getInstance()->getReceivedAudioStreamSettings()); qDebug("Loaded settings"); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 6ff053e5db..9244788de8 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -73,7 +73,7 @@ Audio::Audio(QObject* parent) : _proceduralAudioOutput(NULL), _proceduralOutputDevice(NULL), _inputRingBuffer(0), - _receivedAudioStream(0, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, true, 0, 0, true), + _receivedAudioStream(0, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, InboundAudioStream::Settings()), _isStereoInput(false), _averagedLatency(0.0), _lastInputLoudness(0), @@ -840,12 +840,11 @@ void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { void Audio::sendDownstreamAudioStatsPacket() { - // since this function is called every second, we'll sample some of our stats here - + // since this function is called every second, we'll sample for some of our stats here _inputRingBufferMsecsAvailableStats.update(getInputRingBufferMsecsAvailable()); - _audioOutputMsecsUnplayedStats.update(getAudioOutputMsecsUnplayed()); + // also, call _receivedAudioStream's per-second callback _receivedAudioStream.perSecondCallbackForUpdatingStats(); char packet[MAX_PACKET_SIZE]; diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 8fae6f3bdd..a93b8c5be7 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -71,10 +71,7 @@ public: virtual void startCollisionSound(float magnitude, float frequency, float noise, float duration, bool flashScreen); virtual void startDrumSound(float volume, float frequency, float duration, float decay); - void setDynamicJitterBuffers(bool dynamicJitterBuffers) { _receivedAudioStream.setDynamicJitterBuffers(dynamicJitterBuffers); } - void setStaticDesiredJitterBufferFrames(int staticDesiredJitterBufferFrames) { _receivedAudioStream.setStaticDesiredJitterBufferFrames(staticDesiredJitterBufferFrames); } - - void setMaxFramesOverDesired(int maxFramesOverDesired) { _receivedAudioStream.setMaxFramesOverDesired(maxFramesOverDesired); } + void setReceivedAudioStreamSettings(const InboundAudioStream::Settings& settings) { _receivedAudioStream.setSettings(settings); } int getDesiredJitterBufferFrames() const { return _receivedAudioStream.getDesiredJitterBufferFrames(); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 43d9fde01a..ecf28bcb17 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -82,8 +82,7 @@ const int CONSOLE_HEIGHT = 200; Menu::Menu() : _actionHash(), - _audioJitterBufferFrames(0), - _maxFramesOverDesired(0), + _receivedAudioStreamSettings(), _bandwidthDialog(NULL), _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), _realWorldFieldOfView(DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), @@ -632,8 +631,14 @@ void Menu::loadSettings(QSettings* settings) { lockedSettings = true; } - _audioJitterBufferFrames = loadSetting(settings, "audioJitterBufferFrames", 0); - _maxFramesOverDesired = loadSetting(settings, "maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED); + _receivedAudioStreamSettings._dynamicJitterBuffers = settings->value("dynamicJitterBuffers", DEFAULT_DYNAMIC_JITTER_BUFFERS).toBool(); + _receivedAudioStreamSettings._maxFramesOverDesired = settings->value("maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED).toInt(); + _receivedAudioStreamSettings._staticDesiredJitterBufferFrames = settings->value("staticDesiredJitterBufferFrames", DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES).toInt(); + _receivedAudioStreamSettings._useStDevForJitterCalc = settings->value("useStDevForJitterCalc", DEFAULT_USE_STDEV_FOR_JITTER_CALC).toBool(); + _receivedAudioStreamSettings._windowStarveThreshold = settings->value("windowStarveThreshold", DEFAULT_WINDOW_STARVE_THRESHOLD).toInt(); + _receivedAudioStreamSettings._windowSecondsForDesiredCalcOnTooManyStarves = settings->value("windowSecondsForDesiredCalcOnTooManyStarves", DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES).toInt(); + _receivedAudioStreamSettings._windowSecondsForDesiredReduction = settings->value("windowSecondsForDesiredReduction", DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION).toInt(); + _fieldOfView = loadSetting(settings, "fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES); _realWorldFieldOfView = loadSetting(settings, "realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES); _faceshiftEyeDeflection = loadSetting(settings, "faceshiftEyeDeflection", DEFAULT_FACESHIFT_EYE_DEFLECTION); @@ -683,8 +688,14 @@ void Menu::saveSettings(QSettings* settings) { lockedSettings = true; } - settings->setValue("audioJitterBufferFrames", _audioJitterBufferFrames); - settings->setValue("maxFramesOverDesired", _maxFramesOverDesired); + settings->setValue("dynamicJitterBuffers", _receivedAudioStreamSettings._dynamicJitterBuffers); + settings->setValue("maxFramesOverDesired", _receivedAudioStreamSettings._maxFramesOverDesired); + settings->setValue("staticDesiredJitterBufferFrames", _receivedAudioStreamSettings._staticDesiredJitterBufferFrames); + settings->setValue("useStDevForJitterCalc", _receivedAudioStreamSettings._useStDevForJitterCalc); + settings->setValue("windowStarveThreshold", _receivedAudioStreamSettings._windowStarveThreshold); + settings->setValue("windowSecondsForDesiredCalcOnTooManyStarves", _receivedAudioStreamSettings._windowSecondsForDesiredCalcOnTooManyStarves); + settings->setValue("windowSecondsForDesiredReduction", _receivedAudioStreamSettings._windowSecondsForDesiredReduction); + settings->setValue("fieldOfView", _fieldOfView); settings->setValue("faceshiftEyeDeflection", _faceshiftEyeDeflection); settings->setValue("maxVoxels", _maxVoxels); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 3bef306bef..20097989df 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -85,10 +85,8 @@ public: void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); - float getAudioJitterBufferFrames() const { return _audioJitterBufferFrames; } - void setAudioJitterBufferFrames(float audioJitterBufferSamples) { _audioJitterBufferFrames = audioJitterBufferSamples; } - int getMaxFramesOverDesired() const { return _maxFramesOverDesired; } - void setMaxFramesOverDesired(int maxFramesOverDesired) { _maxFramesOverDesired = maxFramesOverDesired; } + const InboundAudioStream::Settings& getReceivedAudioStreamSettings() const { return _receivedAudioStreamSettings; } + void getReceivedAudioStreamSettings(const InboundAudioStream::Settings& receivedAudioStreamSettings) { _receivedAudioStreamSettings = receivedAudioStreamSettings; } float getFieldOfView() const { return _fieldOfView; } void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; } float getRealWorldFieldOfView() const { return _realWorldFieldOfView; } @@ -259,8 +257,7 @@ private: QHash _actionHash; - int _audioJitterBufferFrames; /// number of extra samples to wait before starting audio playback - int _maxFramesOverDesired; + InboundAudioStream::Settings _receivedAudioStreamSettings; BandwidthDialog* _bandwidthDialog; float _fieldOfView; /// in Degrees, doesn't apply to HMD like Oculus float _realWorldFieldOfView; // The actual FOV set by the user's monitor size and view distance diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 4ebd5f4c1a..7189a74579 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -149,9 +149,21 @@ void PreferencesDialog::loadPreferences() { ui.faceshiftEyeDeflectionSider->setValue(menuInstance->getFaceshiftEyeDeflection() * ui.faceshiftEyeDeflectionSider->maximum()); - ui.audioJitterSpin->setValue(menuInstance->getAudioJitterBufferFrames()); + const InboundAudioStream::Settings& streamSettings = menuInstance->getReceivedAudioStreamSettings(); - ui.maxFramesOverDesiredSpin->setValue(menuInstance->getMaxFramesOverDesired()); + ui.dynamicJitterBuffersCheckBox->setChecked(streamSettings._dynamicJitterBuffers); + + ui.staticDesiredJitterBufferFramesSpin->setValue(streamSettings._staticDesiredJitterBufferFrames); + + ui.maxFramesOverDesiredSpin->setValue(streamSettings._maxFramesOverDesired); + + ui.useStdevForJitterCalcCheckBox->setChecked(streamSettings._useStDevForJitterCalc); + + ui.windowStarveThresholdSpin->setValue(streamSettings._windowStarveThreshold); + + ui.windowSecondsForDesiredCalcOnTooManyStarvesSpin->setValue(streamSettings._windowSecondsForDesiredCalcOnTooManyStarves); + + ui.windowSecondsForDesiredReductionSpin->setValue(streamSettings._windowSecondsForDesiredReduction); ui.realWorldFieldOfViewSpin->setValue(menuInstance->getRealWorldFieldOfView()); @@ -241,16 +253,17 @@ void PreferencesDialog::savePreferences() { Menu::getInstance()->setInvertSixenseButtons(ui.invertSixenseButtonsCheckBox->isChecked()); - Menu::getInstance()->setAudioJitterBufferFrames(ui.audioJitterSpin->value()); - if (Menu::getInstance()->getAudioJitterBufferFrames() != 0) { - Application::getInstance()->getAudio()->setDynamicJitterBuffers(false); - Application::getInstance()->getAudio()->setStaticDesiredJitterBufferFrames(Menu::getInstance()->getAudioJitterBufferFrames()); - } else { - Application::getInstance()->getAudio()->setDynamicJitterBuffers(true); - } + InboundAudioStream::Settings streamSettings; + streamSettings._dynamicJitterBuffers = ui.dynamicJitterBuffersCheckBox->isChecked(); + streamSettings._staticDesiredJitterBufferFrames = ui.staticDesiredJitterBufferFramesSpin->value(); + streamSettings._maxFramesOverDesired = ui.maxFramesOverDesiredSpin->value(); + streamSettings._useStDevForJitterCalc = ui.useStdevForJitterCalcCheckBox->isChecked(); + streamSettings._windowStarveThreshold = ui.windowStarveThresholdSpin->value(); + streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = ui.windowSecondsForDesiredCalcOnTooManyStarvesSpin->value(); + streamSettings._windowSecondsForDesiredReduction = ui.windowSecondsForDesiredReductionSpin->value(); - Menu::getInstance()->setMaxFramesOverDesired(ui.maxFramesOverDesiredSpin->value()); - Application::getInstance()->getAudio()->setMaxFramesOverDesired(Menu::getInstance()->getMaxFramesOverDesired()); + Menu::getInstance()->getReceivedAudioStreamSettings(streamSettings); + Application::getInstance()->getAudio()->setReceivedAudioStreamSettings(streamSettings); Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(), Application::getInstance()->getGLWidget()->height()); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index 566c24e4e3..cddc0f1299 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -1464,6 +1464,97 @@ padding: 10px;margin-top:10px + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Enable Dynamic Jitter Buffers + + + 15 + + + dynamicJitterBuffersCheckBox + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 32 + 0 + + + + + 0 + 0 + + + + + + + + 32 + 32 + + + + + + + + + @@ -1489,13 +1580,13 @@ padding: 10px;margin-top:10px color: rgb(51, 51, 51) - Audio Jitter Buffer Frames (0 for automatic) + Static Jitter Buffer Frames 15 - audioJitterSpin + staticDesiredJitterBufferFramesSpin @@ -1518,7 +1609,7 @@ padding: 10px;margin-top:10px - + 0 @@ -1555,6 +1646,7 @@ padding: 10px;margin-top:10px + @@ -1646,7 +1738,378 @@ padding: 10px;margin-top:10px - + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Use Stdev for Dynamic Jitter Calc + + + 15 + + + useStdevForJitterCalcCheckBox + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 32 + 0 + + + + + 0 + 0 + + + + + + + + 32 + 32 + + + + + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Window A Starve Threshold + + + 15 + + + windowStarveThresholdSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + 0 + + + 10000 + + + 1 + + + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Window A (raise desired on N starves) Seconds + + + 15 + + + windowSecondsForDesiredCalcOnTooManyStarvesSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + 0 + + + 10000 + + + 1 + + + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Window B (desired ceiling) Seconds + + + 15 + + + windowSecondsForDesiredReductionSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + 0 + + + 10000 + + + 1 + + + + + diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index f59070ee83..55322d6f32 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -38,6 +38,7 @@ InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacit _timeGapStatsForDesiredReduction(0, settings._windowSecondsForDesiredReduction), _starveHistoryWindowSeconds(settings._windowSecondsForDesiredCalcOnTooManyStarves), _starveHistory(STARVE_HISTORY_CAPACITY), + _starveThreshold(settings._windowStarveThreshold), _framesAvailableStat(), _currentJitterBufferFrames(0), _timeGapStatsForStatsPacket(0, STATS_FOR_STATS_PACKET_WINDOW_SECONDS) @@ -237,6 +238,16 @@ void InboundAudioStream::setToStarved() { _isStarved = (_ringBuffer.framesAvailable() < _desiredJitterBufferFrames); } +void InboundAudioStream::setSettings(const Settings& settings) { + setMaxFramesOverDesired(settings._maxFramesOverDesired); + setDynamicJitterBuffers(settings._dynamicJitterBuffers); + setStaticDesiredJitterBufferFrames(settings._staticDesiredJitterBufferFrames); + setUseStDevForJitterCalc(settings._useStDevForJitterCalc); + setWindowStarveThreshold(settings._windowStarveThreshold); + setWindowSecondsForDesiredCalcOnTooManyStarves(settings._windowSecondsForDesiredCalcOnTooManyStarves); + setWindowSecondsForDesiredReduction(settings._windowSecondsForDesiredReduction); +} + void InboundAudioStream::setDynamicJitterBuffers(bool dynamicJitterBuffers) { if (!dynamicJitterBuffers) { _desiredJitterBufferFrames = _staticDesiredJitterBufferFrames; @@ -255,6 +266,15 @@ void InboundAudioStream::setStaticDesiredJitterBufferFrames(int staticDesiredJit } } +void InboundAudioStream::setWindowSecondsForDesiredCalcOnTooManyStarves(int windowSecondsForDesiredCalcOnTooManyStarves) { + _timeGapStatsForDesiredCalcOnTooManyStarves.setWindowIntervals(windowSecondsForDesiredCalcOnTooManyStarves); +} + +void InboundAudioStream::setWindowSecondsForDesiredReduction(int windowSecondsForDesiredReduction) { + _timeGapStatsForDesiredReduction.setWindowIntervals(windowSecondsForDesiredReduction); +} + + int InboundAudioStream::clampDesiredJitterBufferFramesValue(int desired) const { const int MIN_FRAMES_DESIRED = 0; const int MAX_FRAMES_DESIRED = _ringBuffer.getFrameCapacity(); diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 88760a35cb..791886ab5e 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -39,7 +39,9 @@ const int FRAMES_AVAILABLE_STAT_WINDOW_USECS = 2 * USECS_PER_SECOND; const int INBOUND_RING_BUFFER_FRAME_CAPACITY = 100; const int DEFAULT_MAX_FRAMES_OVER_DESIRED = 10; -const int DEFAULT_DESIRED_JITTER_BUFFER_FRAMES = 1; +const bool DEFAULT_DYNAMIC_JITTER_BUFFERS = true; +const int DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES = 1; +const bool DEFAULT_USE_STDEV_FOR_JITTER_CALC = false; const int DEFAULT_WINDOW_STARVE_THRESHOLD = 3; const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES = 50; @@ -52,14 +54,26 @@ public: public: Settings() : _maxFramesOverDesired(DEFAULT_MAX_FRAMES_OVER_DESIRED), - _dynamicJitterBuffers(true), - _staticDesiredJitterBufferFrames(DEFAULT_DESIRED_JITTER_BUFFER_FRAMES), - _useStDevForJitterCalc(false), + _dynamicJitterBuffers(DEFAULT_DYNAMIC_JITTER_BUFFERS), + _staticDesiredJitterBufferFrames(DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES), + _useStDevForJitterCalc(DEFAULT_USE_STDEV_FOR_JITTER_CALC), _windowStarveThreshold(DEFAULT_WINDOW_STARVE_THRESHOLD), _windowSecondsForDesiredCalcOnTooManyStarves(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES), _windowSecondsForDesiredReduction(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION) {} + Settings(int maxFramesOverDesired, bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, + bool useStDevForJitterCalc, int windowStarveThreshold, int windowSecondsForDesiredCalcOnTooManyStarves, + int _windowSecondsForDesiredReduction) + : _maxFramesOverDesired(maxFramesOverDesired), + _dynamicJitterBuffers(dynamicJitterBuffers), + _staticDesiredJitterBufferFrames(staticDesiredJitterBufferFrames), + _useStDevForJitterCalc(useStDevForJitterCalc), + _windowStarveThreshold(windowStarveThreshold), + _windowSecondsForDesiredCalcOnTooManyStarves(windowSecondsForDesiredCalcOnTooManyStarves), + _windowSecondsForDesiredReduction(windowSecondsForDesiredCalcOnTooManyStarves) + {} + // max number of frames over desired in the ringbuffer. int _maxFramesOverDesired; @@ -95,15 +109,17 @@ public: void setToStarved(); - - void setDynamicJitterBuffers(bool dynamicJitterBuffers); - void setStaticDesiredJitterBufferFrames(int staticDesiredJitterBufferFrames); - - /// this function should be called once per second to ensure the seq num stats history spans ~30 seconds - //AudioStreamStats updateSeqHistoryAndGetAudioStreamStats(); + void setSettings(const Settings& settings); void setMaxFramesOverDesired(int maxFramesOverDesired) { _maxFramesOverDesired = maxFramesOverDesired; } + void setDynamicJitterBuffers(bool setDynamicJitterBuffers); + void setStaticDesiredJitterBufferFrames(int staticDesiredJitterBufferFrames); + void setUseStDevForJitterCalc(bool useStDevForJitterCalc) { _useStDevForJitterCalc = useStDevForJitterCalc; } + void setWindowStarveThreshold(int windowStarveThreshold) { _starveThreshold = windowStarveThreshold; } + void setWindowSecondsForDesiredCalcOnTooManyStarves(int windowSecondsForDesiredCalcOnTooManyStarves); + void setWindowSecondsForDesiredReduction(int windowSecondsForDesiredReduction); + virtual AudioStreamStats getAudioStreamStats() const; @@ -205,6 +221,7 @@ protected: int _starveHistoryWindowSeconds; RingBufferHistory _starveHistory; + int _starveThreshold; TimeWeightedAvg _framesAvailableStat; diff --git a/libraries/audio/src/InjectedAudioStream.cpp b/libraries/audio/src/InjectedAudioStream.cpp index 5c1c2ed269..9a757b774e 100644 --- a/libraries/audio/src/InjectedAudioStream.cpp +++ b/libraries/audio/src/InjectedAudioStream.cpp @@ -19,8 +19,8 @@ #include "InjectedAudioStream.h" -InjectedAudioStream::InjectedAudioStream(const QUuid& streamIdentifier, bool dynamicJitterBuffer, int staticDesiredJitterBufferFrames, int maxFramesOverDesired) : - PositionalAudioStream(PositionalAudioStream::Injector, false, dynamicJitterBuffer, staticDesiredJitterBufferFrames, maxFramesOverDesired), +InjectedAudioStream::InjectedAudioStream(const QUuid& streamIdentifier, const InboundAudioStream::Settings& settings) : + PositionalAudioStream(PositionalAudioStream::Injector, false, settings), _streamIdentifier(streamIdentifier), _radius(0.0f), _attenuationRatio(0) diff --git a/libraries/audio/src/InjectedAudioStream.h b/libraries/audio/src/InjectedAudioStream.h index d8d9a54c6e..f3840b1029 100644 --- a/libraries/audio/src/InjectedAudioStream.h +++ b/libraries/audio/src/InjectedAudioStream.h @@ -18,7 +18,7 @@ class InjectedAudioStream : public PositionalAudioStream { public: - InjectedAudioStream(const QUuid& streamIdentifier, bool dynamicJitterBuffer, int staticDesiredJitterBufferFrames, int maxFramesOverDesired); + InjectedAudioStream(const QUuid& streamIdentifier, const InboundAudioStream::Settings& settings); float getRadius() const { return _radius; } float getAttenuationRatio() const { return _attenuationRatio; } diff --git a/libraries/audio/src/MixedAudioStream.cpp b/libraries/audio/src/MixedAudioStream.cpp index 38c4ae641d..0041348d26 100644 --- a/libraries/audio/src/MixedAudioStream.cpp +++ b/libraries/audio/src/MixedAudioStream.cpp @@ -11,8 +11,8 @@ #include "MixedAudioStream.h" -MixedAudioStream::MixedAudioStream(int numFrameSamples, int numFramesCapacity, bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired, bool useStDevForJitterCalc) - : InboundAudioStream(numFrameSamples, numFramesCapacity, dynamicJitterBuffers, staticDesiredJitterBufferFrames, maxFramesOverDesired, useStDevForJitterCalc) +MixedAudioStream::MixedAudioStream(int numFrameSamples, int numFramesCapacity, const InboundAudioStream::Settings& settings) + : InboundAudioStream(numFrameSamples, numFramesCapacity, settings) { } diff --git a/libraries/audio/src/MixedAudioStream.h b/libraries/audio/src/MixedAudioStream.h index d19f19af07..0b1979003d 100644 --- a/libraries/audio/src/MixedAudioStream.h +++ b/libraries/audio/src/MixedAudioStream.h @@ -17,7 +17,7 @@ class MixedAudioStream : public InboundAudioStream { public: - MixedAudioStream(int numFrameSamples, int numFramesCapacity, bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired, bool useStDevForJitterCalc); + MixedAudioStream(int numFrameSamples, int numFramesCapacity, const InboundAudioStream::Settings& settings); float getNextOutputFrameLoudness() const { return _ringBuffer.getNextOutputFrameLoudness(); } diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index 49990dcd22..52581bd096 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -11,8 +11,8 @@ #include "MixedProcessedAudioStream.h" -MixedProcessedAudioStream ::MixedProcessedAudioStream (int numFrameSamples, int numFramesCapacity, bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired, bool useStDevForJitterCalc) - : InboundAudioStream(numFrameSamples, numFramesCapacity, dynamicJitterBuffers, staticDesiredJitterBufferFrames, maxFramesOverDesired, useStDevForJitterCalc) +MixedProcessedAudioStream::MixedProcessedAudioStream(int numFrameSamples, int numFramesCapacity, const InboundAudioStream::Settings& settings) + : InboundAudioStream(numFrameSamples, numFramesCapacity, settings) { } diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index 5a5b73115d..e033297362 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -17,7 +17,7 @@ class MixedProcessedAudioStream : public InboundAudioStream { Q_OBJECT public: - MixedProcessedAudioStream (int numFrameSamples, int numFramesCapacity, bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, int maxFramesOverDesired, bool useStDevForJitterCalc); + MixedProcessedAudioStream(int numFrameSamples, int numFramesCapacity, const InboundAudioStream::Settings& settings); signals: diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index 7b407ba62c..d2c1ade85c 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -21,10 +21,9 @@ #include #include -PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, bool isStereo, bool dynamicJitterBuffers, - int staticDesiredJitterBufferFrames, int maxFramesOverDesired) : +PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, bool isStereo, const InboundAudioStream::Settings& settings) : InboundAudioStream(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL, - AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY, dynamicJitterBuffers, staticDesiredJitterBufferFrames, maxFramesOverDesired), + AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY, settings), _type(type), _position(0.0f, 0.0f, 0.0f), _orientation(0.0f, 0.0f, 0.0f, 0.0f), diff --git a/libraries/audio/src/PositionalAudioStream.h b/libraries/audio/src/PositionalAudioStream.h index f99dc3a464..d1d5e013e7 100644 --- a/libraries/audio/src/PositionalAudioStream.h +++ b/libraries/audio/src/PositionalAudioStream.h @@ -27,8 +27,7 @@ public: Injector }; - PositionalAudioStream(PositionalAudioStream::Type type, bool isStereo, bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, - int maxFramesOverDesired); + PositionalAudioStream(PositionalAudioStream::Type type, bool isStereo, const InboundAudioStream::Settings& settings); virtual AudioStreamStats getAudioStreamStats() const; diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 9ac9ec9006..469ec1b3ef 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -103,6 +103,15 @@ public: _newStatsAvailable = false; } + void setWindowIntervals(int windowIntervals) { + _windowIntervals = windowIntervals; + _overallStats.reset(); + _windowStats.reset(); + _currentIntervalStats.reset(); + _intervalStats.setCapacity(_windowIntervals); + _newStatsAvailable = false; + } + void update(T newSample) { // update overall stats _overallStats.update(newSample); diff --git a/libraries/shared/src/RingBufferHistory.h b/libraries/shared/src/RingBufferHistory.h index 339f390cd5..9534b2f1db 100644 --- a/libraries/shared/src/RingBufferHistory.h +++ b/libraries/shared/src/RingBufferHistory.h @@ -35,6 +35,14 @@ public: _numEntries = 0; } + void setCapacity(int capacity) { + _size = capacity + 1; + _capacity = capacity; + _newestEntryAtIndex = 0; + _numEntries = 0; + _buffer.resize(_size); + } + void insert(const T& entry) { // increment newest entry index cyclically _newestEntryAtIndex = (_newestEntryAtIndex == _size - 1) ? 0 : _newestEntryAtIndex + 1; From fd3425dfd1b53030db1eb49b65344877b65b4dc7 Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 7 Aug 2014 17:05:23 -0700 Subject: [PATCH 046/206] knobs confirmed working --- assignment-client/src/audio/AudioMixer.cpp | 2 +- domain-server/resources/web/settings/describe.json | 10 +++++----- libraries/audio/src/InboundAudioStream.cpp | 9 +++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index fc223ab850..e9bedc5433 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -430,7 +430,7 @@ void AudioMixer::run() { qDebug() << "Dynamic jitter buffers disabled."; } - const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "B-desired-jitter-buffer-frames"; + const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "B-static-desired-jitter-buffer-frames"; _streamSettings._staticDesiredJitterBufferFrames = audioGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok); if (!ok) { _streamSettings._staticDesiredJitterBufferFrames = DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES; diff --git a/domain-server/resources/web/settings/describe.json b/domain-server/resources/web/settings/describe.json index 59d99eab11..bb63c5f0a0 100644 --- a/domain-server/resources/web/settings/describe.json +++ b/domain-server/resources/web/settings/describe.json @@ -7,10 +7,10 @@ "type": "checkbox", "label": "Dynamic Jitter Buffers", "help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing", - "default": false + "default": true }, - "B-desired-jitter-buffer-frames": { - "label": "Desired Jitter Buffer Frames", + "B-static-desired-jitter-buffer-frames": { + "label": "Static Desired Jitter Buffer Frames", "help": "If dynamic jitter buffers is disabled, this determines the target number of frames maintained by the AudioMixer's jitter buffers", "placeholder": "1", "default": "1" @@ -21,12 +21,12 @@ "placeholder": "10", "default": "10" }, - "D-use-stdev": { + "D-use-stdev-for-desired-calc": { "type": "checkbox", "label": "Use Stdev for Desired Jitter Frames Calc:", "help": "If checked, Philip's method (stdev of timegaps) is used to calculate desired jitter frames. Otherwise, Fred's method (max timegap) is used", "default": false - } + }, "E-window-starve-threshold": { "label": "Window Starve Threshold", "help": "If this many starves occur in an N-second window (N is the number in the next field), then the desired jitter frames will be re-evaluated using Window A.", diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 55322d6f32..c04f9d5df1 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -246,6 +246,15 @@ void InboundAudioStream::setSettings(const Settings& settings) { setWindowStarveThreshold(settings._windowStarveThreshold); setWindowSecondsForDesiredCalcOnTooManyStarves(settings._windowSecondsForDesiredCalcOnTooManyStarves); setWindowSecondsForDesiredReduction(settings._windowSecondsForDesiredReduction); + + + printf("\n\nmax frames over desired: %d\n", settings._maxFramesOverDesired); + printf("dynamic jitter buffers: %d\n", settings._dynamicJitterBuffers); + printf("static desired jbuffer frames: %d\n", settings._staticDesiredJitterBufferFrames); + printf("use stdev: %d\n", settings._useStDevForJitterCalc); + printf("starve threshold: %d\n", settings._windowStarveThreshold); + printf("window A seconds: %d\n", settings._windowSecondsForDesiredCalcOnTooManyStarves); + printf("window B seconds: %d\n", settings._windowSecondsForDesiredReduction); } void InboundAudioStream::setDynamicJitterBuffers(bool dynamicJitterBuffers) { From 3e456b0ec9965a6b624421b302923c2a6d7afd0c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 7 Aug 2014 17:45:01 -0700 Subject: [PATCH 047/206] Update speech recognizer to fully turn off when disabling --- interface/src/SpeechRecognizer.h | 12 +++++-- interface/src/SpeechRecognizer.mm | 59 +++++++++++++++++++------------ 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/interface/src/SpeechRecognizer.h b/interface/src/SpeechRecognizer.h index f2c78045aa..edd4abe1d6 100644 --- a/interface/src/SpeechRecognizer.h +++ b/interface/src/SpeechRecognizer.h @@ -13,6 +13,8 @@ #define hifi_SpeechRecognizer_h #include +#include +#include class SpeechRecognizer : public QObject { Q_OBJECT @@ -20,20 +22,24 @@ public: SpeechRecognizer(); ~SpeechRecognizer(); - void init(); void handleCommandRecognized(const char* command); - bool getEnabled() { return _enabled; } - void setEnabled(bool enabled); + bool getEnabled() const { return _enabled; } public slots: + void setEnabled(bool enabled); void addCommand(const QString& command); void removeCommand(const QString& command); signals: void commandRecognized(const QString& command); + void enabledUpdated(bool enabled); + +protected: + void reloadCommands(); private: bool _enabled; + QSet _commands; void* _speechRecognizerDelegate; void* _speechRecognizer; }; diff --git a/interface/src/SpeechRecognizer.mm b/interface/src/SpeechRecognizer.mm index 4ec6fadfca..038bcce3e4 100644 --- a/interface/src/SpeechRecognizer.mm +++ b/interface/src/SpeechRecognizer.mm @@ -9,8 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#ifdef Q_OS_MAC + #import #import +#import #include @@ -40,10 +44,11 @@ SpeechRecognizer::SpeechRecognizer() : QObject(), _enabled(false), - _speechRecognizerDelegate(NULL), + _commands(), + _speechRecognizerDelegate([[SpeechRecognizerDelegate alloc] init]), _speechRecognizer(NULL) { - init(); + [(id)_speechRecognizerDelegate setListener:this]; } SpeechRecognizer::~SpeechRecognizer() { @@ -55,40 +60,50 @@ SpeechRecognizer::~SpeechRecognizer() { } } -void SpeechRecognizer::init() { - _speechRecognizerDelegate = [[SpeechRecognizerDelegate alloc] init]; - [(id)_speechRecognizerDelegate setListener:this]; - - _speechRecognizer = [[NSSpeechRecognizer alloc] init]; - - [(id)_speechRecognizer setCommands:[NSArray array]]; - [(id)_speechRecognizer setDelegate:(id)_speechRecognizerDelegate]; - - setEnabled(_enabled); -} - void SpeechRecognizer::handleCommandRecognized(const char* command) { - qDebug() << "Got command: " << command; emit commandRecognized(QString(command)); } void SpeechRecognizer::setEnabled(bool enabled) { + if (enabled == _enabled) { + return; + } + _enabled = enabled; - if (enabled) { + if (_enabled) { + _speechRecognizer = [[NSSpeechRecognizer alloc] init]; + + reloadCommands(); + + [(id)_speechRecognizer setDelegate:(id)_speechRecognizerDelegate]; [(id)_speechRecognizer startListening]; } else { [(id)_speechRecognizer stopListening]; + [(id)_speechRecognizer dealloc]; + _speechRecognizer = NULL; + } + + emit enabledUpdated(_enabled); +} + +void SpeechRecognizer::reloadCommands() { + if (_speechRecognizer) { + NSMutableArray* cmds = [NSMutableArray array]; + for (QSet::const_iterator iter = _commands.constBegin(); iter != _commands.constEnd(); iter++) { + [cmds addObject:[NSString stringWithUTF8String:(*iter).toLocal8Bit().data()]]; + } + [(id)_speechRecognizer setCommands:cmds]; } } void SpeechRecognizer::addCommand(const QString& command) { - NSArray *cmds = [(id)_speechRecognizer commands]; - NSString *cmd = [NSString stringWithUTF8String:command.toLocal8Bit().data()]; - [(id)_speechRecognizer setCommands:[cmds arrayByAddingObject:cmd]]; + _commands.insert(command); + reloadCommands(); } void SpeechRecognizer::removeCommand(const QString& command) { - NSMutableArray* cmds = [NSMutableArray arrayWithArray:[(id)_speechRecognizer commands]]; - [cmds removeObject:[NSString stringWithUTF8String:command.toLocal8Bit().data()]]; - [(id)_speechRecognizer setCommands:cmds]; + _commands.remove(command); + reloadCommands(); } + +#endif // Q_OS_MAC From 9127ac24a040122f75a0f10fadbfa41f5329702a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 7 Aug 2014 17:45:27 -0700 Subject: [PATCH 048/206] Move speechRecognizer to Menu --- interface/src/Application.cpp | 7 +------ interface/src/Application.h | 5 ----- interface/src/Menu.cpp | 20 ++++++++++++++++---- interface/src/Menu.h | 8 ++++++++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 21732763f0..87f63c4b78 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,7 +170,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _runningScriptsWidget(NULL), _runningScriptsWidgetWasVisible(false), _trayIcon(new QSystemTrayIcon(_window)), - _speechRecognizer(), _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()) { @@ -1444,10 +1443,6 @@ void Application::setLowVelocityFilter(bool lowVelocityFilter) { getSixenseManager()->setLowVelocityFilter(lowVelocityFilter); } -void Application::setSpeechRecognitionEnabled(bool enabled) { - _speechRecognizer.setEnabled(enabled); -} - void Application::doKillLocalVoxels() { _wantToKillLocalVoxels = true; } @@ -3664,7 +3659,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->registerGlobalObject("Camera", cameraScriptable); connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater())); - scriptEngine->registerGlobalObject("SpeechRecognizer", &_speechRecognizer); + scriptEngine->registerGlobalObject("SpeechRecognizer", Menu::getInstance()->getSpeechRecognizer()); ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface(); scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); diff --git a/interface/src/Application.h b/interface/src/Application.h index 2fc6822b81..a356b26725 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -53,7 +53,6 @@ #include "Menu.h" #include "MetavoxelSystem.h" #include "PacketHeaders.h" -#include "SpeechRecognizer.h" #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/AvatarManager.h" @@ -325,8 +324,6 @@ public slots: void setRenderVoxels(bool renderVoxels); void setLowVelocityFilter(bool lowVelocityFilter); - bool getSpeechRecognitionEnabled() { return _speechRecognizer.getEnabled(); } - void setSpeechRecognitionEnabled(bool enabled); void doKillLocalVoxels(); void loadDialog(); void loadScriptURLDialog(); @@ -596,8 +593,6 @@ private: QSystemTrayIcon* _trayIcon; - SpeechRecognizer _speechRecognizer; - quint64 _lastNackTime; quint64 _lastSendDownstreamAudioStats; }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4350aef288..bb9ed9a566 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -94,6 +94,9 @@ Menu::Menu() : _octreeStatsDialog(NULL), _lodToolsDialog(NULL), _userLocationsDialog(NULL), +#ifdef Q_OS_MAC + _speechRecognizer(), +#endif _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), _oculusUIAngularSize(DEFAULT_OCULUS_UI_ANGULAR_SIZE), @@ -233,8 +236,12 @@ Menu::Menu() : QMenu* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, this, SLOT(showScriptEditor())); - addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::ControlWithSpeech, Qt::CTRL | Qt::SHIFT | Qt::Key_C, true, - Application::getInstance(), SLOT(setSpeechRecognitionEnabled(bool))); + +#ifdef Q_OS_MAC + QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::ControlWithSpeech, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, _speechRecognizer.getEnabled(), &_speechRecognizer, SLOT(setEnabled(bool))); + connect(&_speechRecognizer, SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); +#endif #ifdef HAVE_QXMPP _chatAction = addActionToQMenuAndActionHash(toolsMenu, @@ -653,7 +660,10 @@ void Menu::loadSettings(QSettings* settings) { _snapshotsLocation = settings->value("snapshotsLocation", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString(); setScriptsLocation(settings->value("scriptsLocation", QString()).toString()); - Application::getInstance()->setSpeechRecognitionEnabled(settings->value("speechRecognitionEnabled", false).toBool()); + +#ifdef Q_OS_MAC + _speechRecognizer.setEnabled(settings->value("speechRecognitionEnabled", false).toBool()); +#endif settings->beginGroup("View Frustum Offset Camera"); // in case settings is corrupt or missing loadSetting() will check for NaN @@ -702,7 +712,9 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust); settings->setValue("snapshotsLocation", _snapshotsLocation); settings->setValue("scriptsLocation", _scriptsLocation); - settings->setValue("speechRecognitionEnabled", Application::getInstance()->getSpeechRecognitionEnabled()); +#ifdef Q_OS_MAC + settings->setValue("speechRecognitionEnabled", _speechRecognizer.getEnabled()); +#endif settings->beginGroup("View Frustum Offset Camera"); settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw); settings->setValue("viewFrustumOffsetPitch", _viewFrustumOffset.pitch); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index ca58c72a88..9f43898214 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -22,6 +22,7 @@ #include #include #include +#include "SpeechRecognizer.h" #include "location/LocationManager.h" #include "ui/PreferencesDialog.h" @@ -137,6 +138,10 @@ public: void setBoundaryLevelAdjust(int boundaryLevelAdjust); int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } +#ifdef Q_OS_MAC + SpeechRecognizer* getSpeechRecognizer() { return &_speechRecognizer; } +#endif + // User Tweakable PPS from Voxel Server int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; } void setMaxVoxelPacketsPerSecond(int maxVoxelPacketsPerSecond) { _maxVoxelPacketsPerSecond = maxVoxelPacketsPerSecond; } @@ -274,6 +279,9 @@ private: OctreeStatsDialog* _octreeStatsDialog; LodToolsDialog* _lodToolsDialog; UserLocationsDialog* _userLocationsDialog; +#ifdef Q_OS_MAC + SpeechRecognizer _speechRecognizer; +#endif int _maxVoxels; float _voxelSizeScale; float _oculusUIAngularSize; From 850a2d8d9463caa1a9db3fc0ecb70ade557e1979 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 09:59:33 -0700 Subject: [PATCH 049/206] inboundaudiostream behavior updated; needs testing --- libraries/audio/src/InboundAudioStream.cpp | 106 +++++++++++++++------ libraries/audio/src/InboundAudioStream.h | 9 +- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index c04f9d5df1..6ab8e30c2a 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -21,8 +21,6 @@ InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacit _dynamicJitterBuffers(settings._dynamicJitterBuffers), _staticDesiredJitterBufferFrames(settings._staticDesiredJitterBufferFrames), _useStDevForJitterCalc(settings._useStDevForJitterCalc), - _calculatedJitterBufferFramesUsingMaxGap(0), - _calculatedJitterBufferFramesUsingStDev(0), _desiredJitterBufferFrames(settings._dynamicJitterBuffers ? 1 : settings._staticDesiredJitterBufferFrames), _maxFramesOverDesired(settings._maxFramesOverDesired), _isStarved(true), @@ -32,9 +30,10 @@ InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacit _silentFramesDropped(0), _oldFramesDropped(0), _incomingSequenceNumberStats(STATS_FOR_STATS_PACKET_WINDOW_SECONDS), - _lastFrameReceivedTime(0), + _lastPacketReceivedTime(0), _timeGapStatsForDesiredCalcOnTooManyStarves(0, settings._windowSecondsForDesiredCalcOnTooManyStarves), _stdevStatsForDesiredCalcOnTooManyStarves(), + _calculatedJitterBufferFramesUsingStDev(0), _timeGapStatsForDesiredReduction(0, settings._windowSecondsForDesiredReduction), _starveHistoryWindowSeconds(settings._windowSecondsForDesiredCalcOnTooManyStarves), _starveHistory(STARVE_HISTORY_CAPACITY), @@ -63,7 +62,7 @@ void InboundAudioStream::resetStats() { _silentFramesDropped = 0; _oldFramesDropped = 0; _incomingSequenceNumberStats.reset(); - _lastFrameReceivedTime = 0; + _lastPacketReceivedTime = 0; _timeGapStatsForDesiredCalcOnTooManyStarves.reset(); _stdevStatsForDesiredCalcOnTooManyStarves = StDev(); _timeGapStatsForDesiredReduction.reset(); @@ -230,12 +229,51 @@ void InboundAudioStream::framesAvailableChanged() { } void InboundAudioStream::setToStarved() { - _isStarved = true; _consecutiveNotMixedCount = 0; _starveCount++; // if we have more than the desired frames when setToStarved() is called, then we'll immediately // be considered refilled. in that case, there's no need to set _isStarved to true. _isStarved = (_ringBuffer.framesAvailable() < _desiredJitterBufferFrames); + + // if dynamic jitter buffers are enabled, we should check if this starve put us over the window + // starve threshold + if (_dynamicJitterBuffers) { + quint64 now = usecTimestampNow(); + _starveHistory.insert(now); + + quint64 windowEnd = now - _starveHistoryWindowSeconds * USECS_PER_SECOND; + RingBufferHistory::Iterator starvesIterator = _starveHistory.begin(); + RingBufferHistory::Iterator end = _starveHistory.end(); + int starvesInWindow = 1; + do { + ++starvesIterator; + if (*starvesIterator < windowEnd) { + break; + } + starvesInWindow++; + } while (starvesIterator != end); + + // this starve put us over the starve threshold. update _desiredJitterBufferFrames to + // value determined by window A. + if (starvesInWindow >= _starveThreshold) { + int calculatedJitterBufferFrames; + if (_useStDevForJitterCalc) { + calculatedJitterBufferFrames = _calculatedJitterBufferFramesUsingStDev; + } else { + // we don't know when the next packet will arrive, so it's possible the gap between the last packet and the + // next packet will exceed the max time gap in the window. If the time since the last packet has already exceeded + // the window max gap, then we should use that value to calculate desired frames. + if (now - _lastPacketReceivedTime) + + calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredCalcOnTooManyStarves.getWindowMax() + / (float)BUFFER_SEND_INTERVAL_USECS); + } + // make sure _desiredJitterBufferFrames does not become lower here + if (calculatedJitterBufferFrames >= _desiredJitterBufferFrames) { + _desiredJitterBufferFrames = calculatedJitterBufferFrames; + } + } + } } void InboundAudioStream::setSettings(const Settings& settings) { @@ -259,9 +297,18 @@ void InboundAudioStream::setSettings(const Settings& settings) { void InboundAudioStream::setDynamicJitterBuffers(bool dynamicJitterBuffers) { if (!dynamicJitterBuffers) { - _desiredJitterBufferFrames = _staticDesiredJitterBufferFrames; + if (_dynamicJitterBuffers) { + // if we're disabling dynamic jitter buffers, set desired frames to its static value + // and clear all the stats for calculating desired frames in dynamic mode. + _desiredJitterBufferFrames = _staticDesiredJitterBufferFrames; + _timeGapStatsForDesiredCalcOnTooManyStarves.reset(); + _stdevStatsForDesiredCalcOnTooManyStarves.reset(); + _timeGapStatsForDesiredReduction.reset(); + _starveHistory.clear(); + } } else { if (!_dynamicJitterBuffers) { + // if we're enabling dynamic jitter buffer frames, start desired frames at 1 _desiredJitterBufferFrames = 1; } } @@ -291,42 +338,45 @@ int InboundAudioStream::clampDesiredJitterBufferFramesValue(int desired) const { } void InboundAudioStream::frameReceivedUpdateTimingStats() { - /* + // update our timegap stats and desired jitter buffer frames if necessary // discard the first few packets we receive since they usually have gaps that aren't represensative of normal jitter const int NUM_INITIAL_PACKETS_DISCARD = 3; quint64 now = usecTimestampNow(); if (_incomingSequenceNumberStats.getReceived() > NUM_INITIAL_PACKETS_DISCARD) { - quint64 gap = now - _lastFrameReceivedTime; + quint64 gap = now - _lastPacketReceivedTime; _timeGapStatsForStatsPacket.update(gap); - const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; + if (_dynamicJitterBuffers) { - // update stats for Freddy's method of jitter calc - _interframeTimeGapStatsForJitterCalc.update(gap); - if (_interframeTimeGapStatsForJitterCalc.getNewStatsAvailableFlag()) { - _calculatedJitterBufferFramesUsingMaxGap = ceilf((float)_interframeTimeGapStatsForJitterCalc.getWindowMax() / USECS_PER_FRAME); - _interframeTimeGapStatsForJitterCalc.clearNewStatsAvailableFlag(); + const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; - if (_dynamicJitterBuffers && !_useStDevForJitterCalc) { - _desiredJitterBufferFrames = clampDesiredJitterBufferFramesValue(_calculatedJitterBufferFramesUsingMaxGap); + // update all stats used for desired frames calculations under dynamic jitter buffer mode + _timeGapStatsForDesiredCalcOnTooManyStarves.update(gap); + _stdevStatsForDesiredCalcOnTooManyStarves.addValue(gap); + _timeGapStatsForDesiredReduction.update(gap); + + const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; + if (_stdevStatsForDesiredCalcOnTooManyStarves.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) { + const float NUM_STANDARD_DEVIATIONS = 3.0f; + _calculatedJitterBufferFramesUsingStDev = (int)ceilf(NUM_STANDARD_DEVIATIONS * _stdevStatsForDesiredCalcOnTooManyStarves.getStDev() + / USECS_PER_FRAME); + _stdevStatsForDesiredCalcOnTooManyStarves.reset(); } - } - // update stats for Philip's method of jitter calc - _stdev.addValue(gap); - const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; - if (_stdev.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) { - const float NUM_STANDARD_DEVIATIONS = 3.0f; - _calculatedJitterBufferFramesUsingStDev = (int)ceilf(NUM_STANDARD_DEVIATIONS * _stdev.getStDev() / USECS_PER_FRAME); - _stdev.reset(); - - if (_dynamicJitterBuffers && _useStDevForJitterCalc) { - _desiredJitterBufferFrames = clampDesiredJitterBufferFramesValue(_calculatedJitterBufferFramesUsingStDev); + // if the max gap in window B (_timeGapStatsForDesiredReduction) corresponds to a smaller number of frames than _desiredJitterBufferFrames, + // then reduce _desiredJitterBufferFrames to that number of frames. + if (_timeGapStatsForDesiredReduction.getNewStatsAvailableFlag() && _timeGapStatsForDesiredReduction.isWindowFilled()) { + int calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredReduction.getWindowMax() / USECS_PER_FRAME); + if (calculatedJitterBufferFrames < _desiredJitterBufferFrames) { + _desiredJitterBufferFrames = calculatedJitterBufferFrames; + } + _timeGapStatsForDesiredReduction.clearNewStatsAvailableFlag(); } } } - _lastFrameReceivedTime = now;*/ + + _lastPacketReceivedTime = now; } int InboundAudioStream::writeDroppableSilentSamples(int numSilentSamples) { diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 791886ab5e..1e58eaa509 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -193,8 +193,6 @@ protected: // if jitter buffer is dynamic, this determines what method of calculating _desiredJitterBufferFrames // if true, Philip's timegap std dev calculation is used. Otherwise, Freddy's max timegap calculation is used bool _useStDevForJitterCalc; - int _calculatedJitterBufferFramesUsingMaxGap; - int _calculatedJitterBufferFramesUsingStDev; int _desiredJitterBufferFrames; @@ -214,9 +212,10 @@ protected: SequenceNumberStats _incomingSequenceNumberStats; - quint64 _lastFrameReceivedTime; - MovingMinMaxAvg _timeGapStatsForDesiredCalcOnTooManyStarves; - StDev _stdevStatsForDesiredCalcOnTooManyStarves; + quint64 _lastPacketReceivedTime; + MovingMinMaxAvg _timeGapStatsForDesiredCalcOnTooManyStarves; // for Freddy's method + StDev _stdevStatsForDesiredCalcOnTooManyStarves; // for Philip's method + int _calculatedJitterBufferFramesUsingStDev; // the most recent desired frames calculated by Philip's method MovingMinMaxAvg _timeGapStatsForDesiredReduction; int _starveHistoryWindowSeconds; From 3338102513fa21060d1b1b6afbc9307d022397bd Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 10:29:45 -0700 Subject: [PATCH 050/206] reverted some behavior to make getCalculated() to work again --- libraries/audio/src/InboundAudioStream.cpp | 68 ++++++++++------------ libraries/audio/src/InboundAudioStream.h | 3 +- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 6ab8e30c2a..64993af2b5 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -34,6 +34,7 @@ InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacit _timeGapStatsForDesiredCalcOnTooManyStarves(0, settings._windowSecondsForDesiredCalcOnTooManyStarves), _stdevStatsForDesiredCalcOnTooManyStarves(), _calculatedJitterBufferFramesUsingStDev(0), + _calculatedJitterBufferFramesUsingMaxGap(0), _timeGapStatsForDesiredReduction(0, settings._windowSecondsForDesiredReduction), _starveHistoryWindowSeconds(settings._windowSecondsForDesiredCalcOnTooManyStarves), _starveHistory(STARVE_HISTORY_CAPACITY), @@ -104,7 +105,7 @@ int InboundAudioStream::parseData(const QByteArray& packet) { readBytes += sizeof(quint16); SequenceNumberStats::ArrivalInfo arrivalInfo = _incomingSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID); - frameReceivedUpdateTimingStats(); + packetReceivedUpdateTimingStats(); // TODO: handle generalized silent packet here????? @@ -235,12 +236,13 @@ void InboundAudioStream::setToStarved() { // be considered refilled. in that case, there's no need to set _isStarved to true. _isStarved = (_ringBuffer.framesAvailable() < _desiredJitterBufferFrames); - // if dynamic jitter buffers are enabled, we should check if this starve put us over the window - // starve threshold - if (_dynamicJitterBuffers) { - quint64 now = usecTimestampNow(); - _starveHistory.insert(now); + // record the time of this starve in the starve history + quint64 now = usecTimestampNow(); + _starveHistory.insert(now); + if (_dynamicJitterBuffers) { + // dynamic jitter buffers are enabled. check if this starve put us over the window + // starve threshold quint64 windowEnd = now - _starveHistoryWindowSeconds * USECS_PER_SECOND; RingBufferHistory::Iterator starvesIterator = _starveHistory.begin(); RingBufferHistory::Iterator end = _starveHistory.end(); @@ -263,10 +265,8 @@ void InboundAudioStream::setToStarved() { // we don't know when the next packet will arrive, so it's possible the gap between the last packet and the // next packet will exceed the max time gap in the window. If the time since the last packet has already exceeded // the window max gap, then we should use that value to calculate desired frames. - if (now - _lastPacketReceivedTime) - - calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredCalcOnTooManyStarves.getWindowMax() - / (float)BUFFER_SEND_INTERVAL_USECS); + int framesSinceLastPacket = ceilf((float)(now - _lastPacketReceivedTime) / (float)BUFFER_SEND_INTERVAL_USECS); + calculatedJitterBufferFrames = std::max(_calculatedJitterBufferFramesUsingMaxGap, framesSinceLastPacket); } // make sure _desiredJitterBufferFrames does not become lower here if (calculatedJitterBufferFrames >= _desiredJitterBufferFrames) { @@ -297,15 +297,7 @@ void InboundAudioStream::setSettings(const Settings& settings) { void InboundAudioStream::setDynamicJitterBuffers(bool dynamicJitterBuffers) { if (!dynamicJitterBuffers) { - if (_dynamicJitterBuffers) { - // if we're disabling dynamic jitter buffers, set desired frames to its static value - // and clear all the stats for calculating desired frames in dynamic mode. - _desiredJitterBufferFrames = _staticDesiredJitterBufferFrames; - _timeGapStatsForDesiredCalcOnTooManyStarves.reset(); - _stdevStatsForDesiredCalcOnTooManyStarves.reset(); - _timeGapStatsForDesiredReduction.reset(); - _starveHistory.clear(); - } + _desiredJitterBufferFrames = _staticDesiredJitterBufferFrames; } else { if (!_dynamicJitterBuffers) { // if we're enabling dynamic jitter buffer frames, start desired frames at 1 @@ -337,7 +329,7 @@ int InboundAudioStream::clampDesiredJitterBufferFramesValue(int desired) const { return glm::clamp(desired, MIN_FRAMES_DESIRED, MAX_FRAMES_DESIRED); } -void InboundAudioStream::frameReceivedUpdateTimingStats() { +void InboundAudioStream::packetReceivedUpdateTimingStats() { // update our timegap stats and desired jitter buffer frames if necessary // discard the first few packets we receive since they usually have gaps that aren't represensative of normal jitter @@ -347,27 +339,31 @@ void InboundAudioStream::frameReceivedUpdateTimingStats() { quint64 gap = now - _lastPacketReceivedTime; _timeGapStatsForStatsPacket.update(gap); + // update all stats used for desired frames calculations under dynamic jitter buffer mode + _timeGapStatsForDesiredCalcOnTooManyStarves.update(gap); + _stdevStatsForDesiredCalcOnTooManyStarves.addValue(gap); + _timeGapStatsForDesiredReduction.update(gap); + + if (_timeGapStatsForDesiredCalcOnTooManyStarves.getNewStatsAvailableFlag()) { + _calculatedJitterBufferFramesUsingMaxGap = ceilf((float)_timeGapStatsForDesiredCalcOnTooManyStarves.getWindowMax() + / (float)BUFFER_SEND_INTERVAL_USECS); + _timeGapStatsForDesiredCalcOnTooManyStarves.clearNewStatsAvailableFlag(); + } + + const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; + if (_stdevStatsForDesiredCalcOnTooManyStarves.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) { + const float NUM_STANDARD_DEVIATIONS = 3.0f; + _calculatedJitterBufferFramesUsingStDev = ceilf(NUM_STANDARD_DEVIATIONS * _stdevStatsForDesiredCalcOnTooManyStarves.getStDev() + / (float)BUFFER_SEND_INTERVAL_USECS); + _stdevStatsForDesiredCalcOnTooManyStarves.reset(); + } + if (_dynamicJitterBuffers) { - const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE; - - // update all stats used for desired frames calculations under dynamic jitter buffer mode - _timeGapStatsForDesiredCalcOnTooManyStarves.update(gap); - _stdevStatsForDesiredCalcOnTooManyStarves.addValue(gap); - _timeGapStatsForDesiredReduction.update(gap); - - const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; - if (_stdevStatsForDesiredCalcOnTooManyStarves.getSamples() > STANDARD_DEVIATION_SAMPLE_COUNT) { - const float NUM_STANDARD_DEVIATIONS = 3.0f; - _calculatedJitterBufferFramesUsingStDev = (int)ceilf(NUM_STANDARD_DEVIATIONS * _stdevStatsForDesiredCalcOnTooManyStarves.getStDev() - / USECS_PER_FRAME); - _stdevStatsForDesiredCalcOnTooManyStarves.reset(); - } - // if the max gap in window B (_timeGapStatsForDesiredReduction) corresponds to a smaller number of frames than _desiredJitterBufferFrames, // then reduce _desiredJitterBufferFrames to that number of frames. if (_timeGapStatsForDesiredReduction.getNewStatsAvailableFlag() && _timeGapStatsForDesiredReduction.isWindowFilled()) { - int calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredReduction.getWindowMax() / USECS_PER_FRAME); + int calculatedJitterBufferFrames = ceilf((float)_timeGapStatsForDesiredReduction.getWindowMax() / (float)BUFFER_SEND_INTERVAL_USECS); if (calculatedJitterBufferFrames < _desiredJitterBufferFrames) { _desiredJitterBufferFrames = calculatedJitterBufferFrames; } diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 1e58eaa509..c62cf957d7 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -157,7 +157,7 @@ public slots: void perSecondCallbackForUpdatingStats(); private: - void frameReceivedUpdateTimingStats(); + void packetReceivedUpdateTimingStats(); int clampDesiredJitterBufferFramesValue(int desired) const; int writeSamplesForDroppedPackets(int numSamples); @@ -214,6 +214,7 @@ protected: quint64 _lastPacketReceivedTime; MovingMinMaxAvg _timeGapStatsForDesiredCalcOnTooManyStarves; // for Freddy's method + int _calculatedJitterBufferFramesUsingMaxGap; StDev _stdevStatsForDesiredCalcOnTooManyStarves; // for Philip's method int _calculatedJitterBufferFramesUsingStDev; // the most recent desired frames calculated by Philip's method MovingMinMaxAvg _timeGapStatsForDesiredReduction; From c82a2003a21d63b38ba66bf65b1f28d351892660 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 11:14:36 -0700 Subject: [PATCH 051/206] Add prompt asking user whether they want to rez their uploaded model A user updating an existing model may not want to rez a new copy. --- examples/editModels.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 67760acdb0..39dcea7cad 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -732,9 +732,10 @@ var modelUploader = (function () { if (req.readyState === req.DONE) { if (req.status === 200) { // Note: Unlike avatar models, for content models we don't need to refresh texture cache. - addModelCallback(modelURL); // Add model to the world print("Model uploaded: " + modelURL); - Window.alert("Your model has been uploaded as: " + modelURL); + if (Window.confirm("Your model has been uploaded as: " + modelURL + "\nDo you want to rez it?")) { + addModelCallback(modelURL); + } } else if (req.status === 404) { if (uploadedChecks > 0) { uploadedChecks -= 1; @@ -943,8 +944,9 @@ var toolBar = (function () { radius: DEFAULT_RADIUS, modelURL: url }); + print("Model added: " + url); } else { - print("Can't create model: Model would be out of bounds."); + print("Can't add model: Model would be out of bounds."); } } From 8183fd31995985eb0ff82e11100c404dad728ce3 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 11:17:32 -0700 Subject: [PATCH 052/206] forgot to update starveHistoryWindowSeconds when window A changes --- libraries/audio/src/InboundAudioStream.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 64993af2b5..e7713f9a22 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -316,6 +316,7 @@ void InboundAudioStream::setStaticDesiredJitterBufferFrames(int staticDesiredJit void InboundAudioStream::setWindowSecondsForDesiredCalcOnTooManyStarves(int windowSecondsForDesiredCalcOnTooManyStarves) { _timeGapStatsForDesiredCalcOnTooManyStarves.setWindowIntervals(windowSecondsForDesiredCalcOnTooManyStarves); + _starveHistoryWindowSeconds = windowSecondsForDesiredCalcOnTooManyStarves; } void InboundAudioStream::setWindowSecondsForDesiredReduction(int windowSecondsForDesiredReduction) { From b670226ee390a0c4cdb2b9e88e9852fe0c58cd60 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 11:22:04 -0700 Subject: [PATCH 053/206] removed printf, removed random copy of some file --- libraries/audio/src/InboundAudioStream.cpp | 9 --- .../shared/src/MovingPercentile - Copy.cpp | 61 ------------------- 2 files changed, 70 deletions(-) delete mode 100644 libraries/shared/src/MovingPercentile - Copy.cpp diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index e7713f9a22..54c9de6f2f 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -284,15 +284,6 @@ void InboundAudioStream::setSettings(const Settings& settings) { setWindowStarveThreshold(settings._windowStarveThreshold); setWindowSecondsForDesiredCalcOnTooManyStarves(settings._windowSecondsForDesiredCalcOnTooManyStarves); setWindowSecondsForDesiredReduction(settings._windowSecondsForDesiredReduction); - - - printf("\n\nmax frames over desired: %d\n", settings._maxFramesOverDesired); - printf("dynamic jitter buffers: %d\n", settings._dynamicJitterBuffers); - printf("static desired jbuffer frames: %d\n", settings._staticDesiredJitterBufferFrames); - printf("use stdev: %d\n", settings._useStDevForJitterCalc); - printf("starve threshold: %d\n", settings._windowStarveThreshold); - printf("window A seconds: %d\n", settings._windowSecondsForDesiredCalcOnTooManyStarves); - printf("window B seconds: %d\n", settings._windowSecondsForDesiredReduction); } void InboundAudioStream::setDynamicJitterBuffers(bool dynamicJitterBuffers) { diff --git a/libraries/shared/src/MovingPercentile - Copy.cpp b/libraries/shared/src/MovingPercentile - Copy.cpp deleted file mode 100644 index ec007b5c22..0000000000 --- a/libraries/shared/src/MovingPercentile - Copy.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// -// MovingPercentile.cpp -// libraries/shared/src -// -// Created by Yixin Wang on 6/4/2014 -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "MovingPercentile.h" - -MovingPercentile::MovingPercentile(int numSamples, float percentile) - : _numSamples(numSamples), - _percentile(percentile), - _samplesSorted(), - _sampleIds(), - _newSampleId(0), - _indexOfPercentile(0), - _valueAtPercentile(0.0f) -{ -} - -void MovingPercentile::updatePercentile(float sample) { - - // insert the new sample into _samplesSorted - int newSampleIndex; - if (_samplesSorted.size() < _numSamples) { - // if not all samples have been filled yet, simply append it - newSampleIndex = _samplesSorted.size(); - _samplesSorted.append(sample); - _sampleIds.append(_newSampleId); - - // update _indexOfPercentile - float index = _percentile * (float)(_samplesSorted.size() - 1); - _indexOfPercentile = (int)(index + 0.5f); // round to int - } else { - // find index of sample with id = _newSampleId and replace it with new sample - newSampleIndex = _sampleIds.indexOf(_newSampleId); - _samplesSorted[newSampleIndex] = sample; - } - - // increment _newSampleId. cycles from 0 thru N-1 - _newSampleId = (_newSampleId == _numSamples - 1) ? 0 : _newSampleId + 1; - - // swap new sample with neighbors in _samplesSorted until it's in sorted order - // try swapping up first, then down. element will only be swapped one direction. - while (newSampleIndex < _samplesSorted.size() - 1 && sample > _samplesSorted[newSampleIndex + 1]) { - _samplesSorted.swap(newSampleIndex, newSampleIndex + 1); - _sampleIds.swap(newSampleIndex, newSampleIndex + 1); - newSampleIndex++; - } - while (newSampleIndex > 0 && sample < _samplesSorted[newSampleIndex - 1]) { - _samplesSorted.swap(newSampleIndex, newSampleIndex - 1); - _sampleIds.swap(newSampleIndex, newSampleIndex - 1); - newSampleIndex--; - } - - // find new value at percentile - _valueAtPercentile = _samplesSorted[_indexOfPercentile]; -} From 63624fae7df4c37808d3510c4b6a400e532de79a Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 11:40:48 -0700 Subject: [PATCH 054/206] cleaned up code, fixed typos --- interface/src/Menu.h | 2 +- interface/src/ui/PreferencesDialog.cpp | 2 +- libraries/audio/src/InboundAudioStream.cpp | 1 - libraries/audio/src/InboundAudioStream.h | 6 +--- libraries/shared/src/MovingEvent.h | 36 ---------------------- 5 files changed, 3 insertions(+), 44 deletions(-) delete mode 100644 libraries/shared/src/MovingEvent.h diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 20097989df..153b402089 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -86,7 +86,7 @@ public: QAction* getActionForOption(const QString& menuOption); const InboundAudioStream::Settings& getReceivedAudioStreamSettings() const { return _receivedAudioStreamSettings; } - void getReceivedAudioStreamSettings(const InboundAudioStream::Settings& receivedAudioStreamSettings) { _receivedAudioStreamSettings = receivedAudioStreamSettings; } + void setReceivedAudioStreamSettings(const InboundAudioStream::Settings& receivedAudioStreamSettings) { _receivedAudioStreamSettings = receivedAudioStreamSettings; } float getFieldOfView() const { return _fieldOfView; } void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; } float getRealWorldFieldOfView() const { return _realWorldFieldOfView; } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 7189a74579..bde3cfd65a 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -262,7 +262,7 @@ void PreferencesDialog::savePreferences() { streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = ui.windowSecondsForDesiredCalcOnTooManyStarvesSpin->value(); streamSettings._windowSecondsForDesiredReduction = ui.windowSecondsForDesiredReductionSpin->value(); - Menu::getInstance()->getReceivedAudioStreamSettings(streamSettings); + Menu::getInstance()->setReceivedAudioStreamSettings(streamSettings); Application::getInstance()->getAudio()->setReceivedAudioStreamSettings(streamSettings); Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(), diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 54c9de6f2f..c6e18b054e 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -351,7 +351,6 @@ void InboundAudioStream::packetReceivedUpdateTimingStats() { } if (_dynamicJitterBuffers) { - // if the max gap in window B (_timeGapStatsForDesiredReduction) corresponds to a smaller number of frames than _desiredJitterBufferFrames, // then reduce _desiredJitterBufferFrames to that number of frames. if (_timeGapStatsForDesiredReduction.getNewStatsAvailableFlag() && _timeGapStatsForDesiredReduction.isWindowFilled()) { diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index c62cf957d7..3275729850 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -31,18 +31,15 @@ const int DESIRED_JITTER_BUFFER_FRAMES_PADDING = 1; // _desiredJitterBufferFrames calculation) const int STATS_FOR_STATS_PACKET_WINDOW_SECONDS = 30; - // this controls the window size of the time-weighted avg of frames available. Every time the window fills up, // _currentJitterBufferFrames is updated with the time-weighted avg and the running time-weighted avg is reset. const int FRAMES_AVAILABLE_STAT_WINDOW_USECS = 2 * USECS_PER_SECOND; -const int INBOUND_RING_BUFFER_FRAME_CAPACITY = 100; - +// default values for members of the Settings struct const int DEFAULT_MAX_FRAMES_OVER_DESIRED = 10; const bool DEFAULT_DYNAMIC_JITTER_BUFFERS = true; const int DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES = 1; const bool DEFAULT_USE_STDEV_FOR_JITTER_CALC = false; - const int DEFAULT_WINDOW_STARVE_THRESHOLD = 3; const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES = 50; const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION = 10; @@ -229,7 +226,6 @@ protected: // dropping silent frames right now. int _currentJitterBufferFrames; - MovingMinMaxAvg _timeGapStatsForStatsPacket; }; diff --git a/libraries/shared/src/MovingEvent.h b/libraries/shared/src/MovingEvent.h deleted file mode 100644 index 284ed9d890..0000000000 --- a/libraries/shared/src/MovingEvent.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// MovingPercentile.h -// libraries/shared/src -// -// Created by Yixin Wang on 6/4/2014 -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_MovingPercentile_h -#define hifi_MovingPercentile_h - -#include - -class MovingPercentile { - -public: - MovingPercentile(int numSamples, float percentile = 0.5f); - - void updatePercentile(float sample); - float getValueAtPercentile() const { return _valueAtPercentile; } - -private: - const int _numSamples; - const float _percentile; - - QList _samplesSorted; - QList _sampleIds; // incrementally assigned, is cyclic - int _newSampleId; - - int _indexOfPercentile; - float _valueAtPercentile; -}; - -#endif From d1701c12cab3182a1a26db5b14621559f0440a63 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 13:36:01 -0700 Subject: [PATCH 055/206] Rewrite FST content so that it includes changed and new properties --- examples/editModels.js | 64 +++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 39dcea7cad..02e7dbf8fb 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -329,33 +329,69 @@ var modelUploader = (function () { }; } - function readMapping(fstBuffer) { - var dv = new DataView(fstBuffer.buffer), + function readMapping(buffer) { + var dv = new DataView(buffer.buffer), lines, line, - values, + tokens, + i, name, - i; + value, + remainder; - // Simplified to target values relevant to model uploading. + mapping = {}; // { name : value | name : { value : remainder } } lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/); for (i = 0; i < lines.length; i += 1) { line = lines[i].trim(); if (line.length > 0 && line[0] !== "#") { - values = line.split(/\s*=\s*/); - name = values[0].toLowerCase(); - if (values.length === 2) { - mapping[name] = values[1]; - } else if (values.length === 3 && name === "lod") { - if (mapping[name] === undefined) { - mapping[name] = {}; + tokens = line.split(/\s*=\s*/); + if (tokens.length > 1) { + name = tokens[0]; + value = tokens[1]; + if (tokens.length === 2) { + mapping[name] = value; + } else { + // We're only interested in the first two fields so put the rest in the remainder + remainder = tokens.slice(2, tokens.length).join(" = "); + if (mapping[name] === undefined) { + mapping[name] = {}; + } + mapping[name][value] = remainder; } - mapping[name][values[1]] = values[2]; } } } } + function writeMapping(buffer) { + var name, + value, + remainder, + string = ""; + + for (name in mapping) { + if (mapping.hasOwnProperty(name)) { + if (typeof mapping[name] === "string") { + string += (name + " = " + mapping[name] + "\n"); + } else { + for (value in mapping[name]) { + if (mapping[name].hasOwnProperty(value)) { + remainder = mapping[name][value]; + if (remainder === null) { + remainder = ""; + } else { + remainder = " = " + remainder; + } + string += (name + " = " + value + remainder + "\n"); + } + } + } + } + } + + buffer.buffer = string.toArrayBuffer(); + } + function readGeometry(fbxBuffer) { var textures, view, @@ -591,6 +627,8 @@ var modelUploader = (function () { mapping[ROLL_FIELD] = form[5].value; mapping[SCALE_FIELD] = form[6].value; + writeMapping(fstBuffer); + return true; } From bf8188c6ffe02279b13a3a486bd2e5edbd0028e9 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 13:38:28 -0700 Subject: [PATCH 056/206] removed duplicate definition --- libraries/audio/src/InboundAudioStream.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index b0a087e9bb..dadc15392b 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -86,10 +86,6 @@ void InboundAudioStream::perSecondCallbackForUpdatingStats() { _timeGapStatsForStatsPacket.currentIntervalComplete(); } -int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { - return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); -} - int InboundAudioStream::parseData(const QByteArray& packet) { PacketType packetType = packetTypeForPacket(packet); From 5b772749696c1c5b558ef4fe6738f69ed64093b9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 13:39:11 -0700 Subject: [PATCH 057/206] Remove animation, pitch, yaw, and roll from Set Model Properties dialog --- examples/editModels.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 02e7dbf8fb..1704368c6a 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -291,10 +291,6 @@ var modelUploader = (function () { SCALE_FIELD = "scale", FILENAME_FIELD = "filename", TEXDIR_FIELD = "texdir", - ANIMATION_URL_FIELD = "animationurl", - PITCH_FIELD = "pitch", - YAW_FIELD = "yaw", - ROLL_FIELD = "roll", MAX_TEXTURE_SIZE = 1024; function error(message) { @@ -604,10 +600,6 @@ var modelUploader = (function () { errorMessage: "Texture directory must be subdirectory of model directory." }); - form.push({ label: "Animation URL:", value: "" }); - form.push({ label: "Pitch:", value: (0).toFixed(decimals) }); - form.push({ label: "Yaw:", value: (0).toFixed(decimals) }); - form.push({ label: "Roll:", value: (0).toFixed(decimals) }); form.push({ label: "Scale:", value: mapping[SCALE_FIELD].toFixed(decimals) }); form.push({ button: "Cancel" }); @@ -621,10 +613,6 @@ var modelUploader = (function () { if (mapping[TEXDIR_FIELD] === "") { mapping[TEXDIR_FIELD] = "."; } - mapping[ANIMATION_URL_FIELD] = form[2].value; - mapping[PITCH_FIELD] = form[3].value; - mapping[YAW_FIELD] = form[4].value; - mapping[ROLL_FIELD] = form[5].value; mapping[SCALE_FIELD] = form[6].value; writeMapping(fstBuffer); From c50be5a872ecd19ac739b8515d43c7438828ac41 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 13:43:25 -0700 Subject: [PATCH 058/206] Miscellaneous fixes --- examples/editModels.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 1704368c6a..8aaeedd7f8 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -613,7 +613,7 @@ var modelUploader = (function () { if (mapping[TEXDIR_FIELD] === "") { mapping[TEXDIR_FIELD] = "."; } - mapping[SCALE_FIELD] = form[6].value; + mapping[SCALE_FIELD] = form[2].value; writeMapping(fstBuffer); @@ -800,7 +800,7 @@ var modelUploader = (function () { //debugResponse(); if (req.readyState === req.DONE) { if (req.status === 200) { - uploadedChecks = 30; + uploadedChecks = UPLOADED_CHECKS; checkUploaded(); } else { print("Error: " + req.status + " " + req.statusText); From 9abfb0f503d25550c2d15e5f0d1e8dfacb7c53d2 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 8 Aug 2014 14:25:55 -0700 Subject: [PATCH 059/206] Added Recorder suite --- interface/src/Recorder.cpp | 12 ++++++ interface/src/Recorder.h | 85 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 interface/src/Recorder.cpp create mode 100644 interface/src/Recorder.h diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp new file mode 100644 index 0000000000..49ef833309 --- /dev/null +++ b/interface/src/Recorder.cpp @@ -0,0 +1,12 @@ +// +// Recorder.cpp +// +// +// Created by Clement on 8/7/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Recorder.h" diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h new file mode 100644 index 0000000000..ffb013723c --- /dev/null +++ b/interface/src/Recorder.h @@ -0,0 +1,85 @@ +// +// Recorder.h +// +// +// Created by Clement on 8/7/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Recorder_h +#define hifi_Recorder_h + +#include +#include +#include +#include + +#include + +/// Stores the different values associated to one recording frame +class RecordingFrame { +public: + QVector getBlendshapeCoefficients() const { return _blendshapeCoefficients; } + QVector getJointRotations() const { return _jointRotations; } + glm::vec3 getTranslation() const { return _translation; } + +private: + + QVector _blendshapeCoefficients; + QVector _jointRotations; + glm::vec3 _translation; +}; + +/// Stores a recording +class Recording { +public: + bool isEmpty() const { return _timestamps.isEmpty(); } + int getLength() const { return _timestamps.last(); } // in ms + + qint32 getFrameTimestamp(int i) const { return _timestamps[i]; } + const RecordingFrame& getFrame(int i) const { return _frames[i]; } + +protected: + void addFrame(int timestamp, RecordingFrame& frame); + void clear(); + +private: + QVector _timestamps; + QVector _frames; + + friend class Recorder; + friend void writeRecordingToFile(Recording& recording, QString file); + friend Recording& readRecordingFromFile(QString file); +}; + + +/// Records a recording +class Recorder { +public: + Recorder(); + + bool isRecording() const; + qint64 elapsed() const; + +public slots: + void startRecording(); + void stopRecording(); + void saveToFile(QString file); + +private: + QElapsedTimer _timer; + Recording _recording; +}; + +/// Plays back a recording +class Player { +public: + +private: + Recording _recording; +}; + +#endif // hifi_Recorder_h \ No newline at end of file From 5b4a3bd5f5eaed8bbfcb3a31a2c18ac8848799c5 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 8 Aug 2014 14:26:16 -0700 Subject: [PATCH 060/206] Recording ground work --- interface/src/Recorder.cpp | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 49ef833309..85e2535141 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -10,3 +10,52 @@ // #include "Recorder.h" + +void Recording::addFrame(int timestamp, RecordingFrame &frame) { + _timestamps << timestamp; + _frames << frame; +} + +void Recording::clear() { + _timestamps.clear(); + _frames.clear(); +} + +void writeRecordingToFile(Recording& recording, QString file) { + qDebug() << "Writing recording to " << file; +} + +Recording& readRecordingFromFile(QString file) { + qDebug() << "Reading recording from " << file; + return *(new Recording()); +} + + +bool Recorder::isRecording() const { + return _timer.isValid(); +} + +qint64 Recorder::elapsed() const { + if (isRecording()) { + return _timer.elapsed(); + } else { + return 0; + } +} + +void Recorder::startRecording() { + _recording.clear(); + _timer.start(); +} + +void Recorder::stopRecording() { + _timer.invalidate(); +} + +void Recorder::saveToFile(QString file) { + if (_recording.isEmpty()) { + qDebug() << "Cannot save recording to file, recording is empty."; + } + + writeRecordingToFile(_recording, file); +} \ No newline at end of file From a2d66b9a8ffa802158146d787585e4a5e507407e Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 14:55:38 -0700 Subject: [PATCH 061/206] fixed audio scope; added parseSilentPacketStreamProperties() --- interface/src/Audio.cpp | 10 +++++----- libraries/audio/src/InboundAudioStream.cpp | 13 +++++++------ libraries/audio/src/InboundAudioStream.h | 4 ++++ .../audio/src/MixedProcessedAudioStream.cpp | 19 +++++++++++++++++-- .../audio/src/MixedProcessedAudioStream.h | 3 +++ 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 29d6b8473e..c09d733cb5 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1481,17 +1481,17 @@ void Audio::renderScope(int width, int height) { return; static const float backgroundColor[4] = { 0.4f, 0.4f, 0.4f, 0.6f }; - static const float gridColor[4] = { 0.3f, 0.3f, 0.3f, 0.6f }; + static const float gridColor[4] = { 0.7f, 0.7f, 0.7f, 1.0f }; static const float inputColor[4] = { 0.3f, 1.0f, 0.3f, 1.0f }; static const float outputLeftColor[4] = { 1.0f, 0.3f, 0.3f, 1.0f }; static const float outputRightColor[4] = { 0.3f, 0.3f, 1.0f, 1.0f }; static const int gridRows = 2; int gridCols = _framesPerScope; - int x = (width - SCOPE_WIDTH) / 2; - int y = (height - SCOPE_HEIGHT) / 2; - int w = SCOPE_WIDTH; - int h = SCOPE_HEIGHT; + int x = (width - (int)SCOPE_WIDTH) / 2; + int y = (height - (int)SCOPE_HEIGHT) / 2; + int w = (int)SCOPE_WIDTH; + int h = (int)SCOPE_HEIGHT; renderBackground(backgroundColor, x, y, w, h); renderGrid(gridColor, x, y, w, h, gridRows, gridCols); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index dadc15392b..2686bd53c5 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -107,12 +107,7 @@ int InboundAudioStream::parseData(const QByteArray& packet) { int numAudioSamples; if (packetType == PacketTypeSilentAudioFrame) { - // this is a general silent packet; parse the number of silent samples - quint16 numSilentSamples = *(reinterpret_cast(dataAt)); - dataAt += sizeof(quint16); - readBytes += sizeof(quint16); - - numAudioSamples = numSilentSamples; + readBytes += parseSilentPacketStreamProperties(packet.mid(readBytes), numAudioSamples); } else { // parse the info after the seq number and before the audio data (the stream properties) readBytes += parseStreamProperties(packetType, packet.mid(readBytes), numAudioSamples); @@ -171,6 +166,12 @@ int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packet return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); } +int InboundAudioStream::parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples) { + // this is a general silent packet; parse the number of silent samples + quint16 numSilentSamples = *(reinterpret_cast(packetAfterSeqNum.data())); + numAudioSamples = numSilentSamples; + return sizeof(quint16); +} int InboundAudioStream::popSamples(int maxSamples, bool allOrNothing, bool starveIfNoSamplesPopped) { int samplesPopped = 0; diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index dee759042b..a3d8729120 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -172,6 +172,10 @@ protected: /// parses the info between the seq num and the audio data in the network packet and calculates /// how many audio samples this packet contains (used when filling in samples for dropped packets). virtual int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) = 0; + + /// parses a silent packet after the seq. default implementation assumes the number of silent samples + /// is the only thing in packetAfterSeqNum and should work in most cases + virtual int parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples); /// parses the audio data in the network packet. /// default implementation assumes packet contains raw audio samples after stream properties diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index 52581bd096..fd3ecb3bc9 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -28,12 +28,22 @@ int MixedProcessedAudioStream::parseStreamProperties(PacketType type, const QByt // since numAudioSamples is used to know how many samples to add for each dropped packet before this one, // we want to set it to the number of device audio samples since this stream contains device audio samples, not network samples. - const int STEREO_DIVIDER = 2; - numAudioSamples = numNetworkSamples * _outputFormatChannelsTimesSampleRate / (STEREO_DIVIDER * SAMPLE_RATE); + numAudioSamples = networkToDeviceSamples(numNetworkSamples); return 0; } +int MixedProcessedAudioStream::parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples) { + int numNetworkSamples; + int bytesRead = InboundAudioStream::parseSilentPacketStreamProperties(packetAfterSeqNum, numNetworkSamples); + + // since numAudioSamples is used to know how many samples to add for each dropped packet before this one, + // we want to set it to the number of device audio samples since this stream contains device audio samples, not network samples. + numAudioSamples = networkToDeviceSamples(numNetworkSamples); + + return bytesRead; +} + int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { QByteArray outputBuffer; @@ -43,3 +53,8 @@ int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& return packetAfterStreamProperties.size(); } + +int MixedProcessedAudioStream::networkToDeviceSamples(int networkSamples) { + const int STEREO_DIVIDER = 2; + return networkSamples * _outputFormatChannelsTimesSampleRate / (STEREO_DIVIDER * SAMPLE_RATE); +} \ No newline at end of file diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index e033297362..7c89a5106d 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -28,8 +28,11 @@ public: protected: int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); + int parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples); int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples); +private: + int networkToDeviceSamples(int networkSamples); private: int _outputFormatChannelsTimesSampleRate; }; From 198fa47409642241a7ce06ace84c0965ecedbc72 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 16:38:27 -0700 Subject: [PATCH 062/206] Get "naked" FBX model content uploading working Include an automatically created FST file in the upload --- examples/editModels.js | 44 +++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 8aaeedd7f8..5525f0b128 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -298,6 +298,18 @@ var modelUploader = (function () { Window.alert(message); } + function randomChar(length) { + var characters = "0123457689abcdefghijklmnopqrstuvwxyz", + string = "", + i; + + for (i = 0; i < length; i += 1) { + string += characters[Math.floor(Math.random() * 36)]; + } + + return string; + } + function resetDataObjects() { fstBuffer = null; fbxBuffer = null; @@ -506,7 +518,7 @@ var modelUploader = (function () { print("Reading model file: " + modelFile); - if (modelFile.toLowerCase().slice(-4) === ".fst") { + if (modelFile.toLowerCase().fileType() === "fst") { fstBuffer = readFile(modelFile); if (fstBuffer === null) { return false; @@ -526,18 +538,24 @@ var modelUploader = (function () { error("Model file name not found in FST file!"); return false; } - - } else if (modelFile.toLowerCase().slice(-4) === ".fbx") { - fbxFilename = modelFile; - mapping[FILENAME_FIELD] = modelFile.fileName(); - - } else if (modelFile.toLowerCase().slice(-4) === ".svo") { - svoFilename = modelFile; - mapping[FILENAME_FIELD] = modelFile.fileName(); - } else { - error("Unrecognized file type: " + modelFile); - return false; + fstBuffer = { + filename: "Interface." + randomChar(6), // Simulate avatar model uploading behaviour + buffer: null + }; + + if (modelFile.toLowerCase().fileType() === "fbx") { + fbxFilename = modelFile; + mapping[FILENAME_FIELD] = modelFile.fileName(); + + } else if (modelFile.toLowerCase().fileType() === "svo") { + svoFilename = modelFile; + mapping[FILENAME_FIELD] = modelFile.fileName(); + + } else { + error("Unrecognized file type: " + modelFile); + return false; + } } if (fbxFilename) { @@ -851,7 +869,7 @@ var modelUploader = (function () { print("Sending model to High Fidelity"); modelName = mapping[NAME_FIELD]; - modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // DJRTODO: Do all models get a FST? + modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST requestUpload(); } From f71e1edd30ad29e5744f0312d29e00e81f9e9dde Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 16:46:12 -0700 Subject: [PATCH 063/206] removed parseSilentPacketStreamProperties() --- libraries/audio/src/InboundAudioStream.cpp | 79 ++++++++++--------- libraries/audio/src/InboundAudioStream.h | 14 ++-- libraries/audio/src/MixedAudioStream.cpp | 6 -- libraries/audio/src/MixedAudioStream.h | 3 - .../audio/src/MixedProcessedAudioStream.cpp | 30 ++----- .../audio/src/MixedProcessedAudioStream.h | 5 +- 6 files changed, 54 insertions(+), 83 deletions(-) diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 2686bd53c5..67259c5d99 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -104,13 +104,15 @@ int InboundAudioStream::parseData(const QByteArray& packet) { packetReceivedUpdateTimingStats(); - int numAudioSamples; + int networkSamples; if (packetType == PacketTypeSilentAudioFrame) { - readBytes += parseSilentPacketStreamProperties(packet.mid(readBytes), numAudioSamples); + quint16 numSilentSamples = *(reinterpret_cast(dataAt)); + readBytes += sizeof(quint16); + networkSamples = (int)numSilentSamples; } else { // parse the info after the seq number and before the audio data (the stream properties) - readBytes += parseStreamProperties(packetType, packet.mid(readBytes), numAudioSamples); + readBytes += parseStreamProperties(packetType, packet.mid(readBytes), networkSamples); } // handle this packet based on its arrival status. @@ -120,16 +122,16 @@ int InboundAudioStream::parseData(const QByteArray& packet) { // NOTE: we assume that each dropped packet contains the same number of samples // as the packet we just received. int packetsDropped = arrivalInfo._seqDiffFromExpected; - writeSamplesForDroppedPackets(packetsDropped * numAudioSamples); + writeSamplesForDroppedPackets(packetsDropped * networkSamples); // fall through to OnTime case } case SequenceNumberStats::OnTime: { // Packet is on time; parse its data to the ringbuffer if (packetType == PacketTypeSilentAudioFrame) { - writeDroppableSilentSamples(numAudioSamples); + writeDroppableSilentSamples(networkSamples); } else { - readBytes += parseAudioData(packetType, packet.mid(readBytes), numAudioSamples); + readBytes += parseAudioData(packetType, packet.mid(readBytes), networkSamples); } break; } @@ -162,15 +164,40 @@ int InboundAudioStream::parseData(const QByteArray& packet) { return readBytes; } +int InboundAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { + // mixed audio packets do not have any info between the seq num and the audio data. + numAudioSamples = packetAfterSeqNum.size() / sizeof(int16_t); + return 0; +} + int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); } -int InboundAudioStream::parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples) { - // this is a general silent packet; parse the number of silent samples - quint16 numSilentSamples = *(reinterpret_cast(packetAfterSeqNum.data())); - numAudioSamples = numSilentSamples; - return sizeof(quint16); +int InboundAudioStream::writeDroppableSilentSamples(int silentSamples) { + + // calculate how many silent frames we should drop. + int samplesPerFrame = _ringBuffer.getNumFrameSamples(); + int desiredJitterBufferFramesPlusPadding = _desiredJitterBufferFrames + DESIRED_JITTER_BUFFER_FRAMES_PADDING; + int numSilentFramesToDrop = 0; + + if (silentSamples >= samplesPerFrame && _currentJitterBufferFrames > desiredJitterBufferFramesPlusPadding) { + + // our avg jitter buffer size exceeds its desired value, so ignore some silent + // frames to get that size as close to desired as possible + int numSilentFramesToDropDesired = _currentJitterBufferFrames - desiredJitterBufferFramesPlusPadding; + int numSilentFramesReceived = silentSamples / samplesPerFrame; + numSilentFramesToDrop = std::min(numSilentFramesToDropDesired, numSilentFramesReceived); + + // dont reset _currentJitterBufferFrames here; we want to be able to drop further silent frames + // without waiting for _framesAvailableStat to fill up to 10s of samples. + _currentJitterBufferFrames -= numSilentFramesToDrop; + _silentFramesDropped += numSilentFramesToDrop; + + _framesAvailableStat.reset(); + } + + return _ringBuffer.addSilentFrame(silentSamples - numSilentFramesToDrop * samplesPerFrame); } int InboundAudioStream::popSamples(int maxSamples, bool allOrNothing, bool starveIfNoSamplesPopped) { @@ -386,34 +413,8 @@ void InboundAudioStream::packetReceivedUpdateTimingStats() { _lastPacketReceivedTime = now; } -int InboundAudioStream::writeDroppableSilentSamples(int numSilentSamples) { - - // calculate how many silent frames we should drop. - int samplesPerFrame = _ringBuffer.getNumFrameSamples(); - int desiredJitterBufferFramesPlusPadding = _desiredJitterBufferFrames + DESIRED_JITTER_BUFFER_FRAMES_PADDING; - int numSilentFramesToDrop = 0; - - if (numSilentSamples >= samplesPerFrame && _currentJitterBufferFrames > desiredJitterBufferFramesPlusPadding) { - - // our avg jitter buffer size exceeds its desired value, so ignore some silent - // frames to get that size as close to desired as possible - int numSilentFramesToDropDesired = _currentJitterBufferFrames - desiredJitterBufferFramesPlusPadding; - int numSilentFramesReceived = numSilentSamples / samplesPerFrame; - numSilentFramesToDrop = std::min(numSilentFramesToDropDesired, numSilentFramesReceived); - - // dont reset _currentJitterBufferFrames here; we want to be able to drop further silent frames - // without waiting for _framesAvailableStat to fill up to 10s of samples. - _currentJitterBufferFrames -= numSilentFramesToDrop; - _silentFramesDropped += numSilentFramesToDrop; - - _framesAvailableStat.reset(); - } - - return _ringBuffer.addSilentFrame(numSilentSamples - numSilentFramesToDrop * samplesPerFrame); -} - -int InboundAudioStream::writeSamplesForDroppedPackets(int numSamples) { - return writeDroppableSilentSamples(numSamples); +int InboundAudioStream::writeSamplesForDroppedPackets(int networkSamples) { + return writeDroppableSilentSamples(networkSamples); } float InboundAudioStream::getLastPopOutputFrameLoudness() const { diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index a3d8729120..62a22b61ab 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -159,7 +159,7 @@ private: void packetReceivedUpdateTimingStats(); int clampDesiredJitterBufferFramesValue(int desired) const; - int writeSamplesForDroppedPackets(int numSamples); + int writeSamplesForDroppedPackets(int networkSamples); void popSamplesNoCheck(int samples); void framesAvailableChanged(); @@ -171,17 +171,15 @@ protected: /// parses the info between the seq num and the audio data in the network packet and calculates /// how many audio samples this packet contains (used when filling in samples for dropped packets). - virtual int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) = 0; + /// default implementation assumes no stream properties and raw audio samples after stream propertiess + virtual int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& networkSamples); - /// parses a silent packet after the seq. default implementation assumes the number of silent samples - /// is the only thing in packetAfterSeqNum and should work in most cases - virtual int parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples); - /// parses the audio data in the network packet. /// default implementation assumes packet contains raw audio samples after stream properties - virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples); + virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); - int writeDroppableSilentSamples(int numSilentSamples); + /// writes silent samples to the buffer that may be dropped to reduce latency caused by the buffer + virtual int writeDroppableSilentSamples(int silentSamples); protected: diff --git a/libraries/audio/src/MixedAudioStream.cpp b/libraries/audio/src/MixedAudioStream.cpp index 0041348d26..85bf71747a 100644 --- a/libraries/audio/src/MixedAudioStream.cpp +++ b/libraries/audio/src/MixedAudioStream.cpp @@ -15,9 +15,3 @@ MixedAudioStream::MixedAudioStream(int numFrameSamples, int numFramesCapacity, c : InboundAudioStream(numFrameSamples, numFramesCapacity, settings) { } - -int MixedAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { - // mixed audio packets do not have any info between the seq num and the audio data. - numAudioSamples = packetAfterSeqNum.size() / sizeof(int16_t); - return 0; -} diff --git a/libraries/audio/src/MixedAudioStream.h b/libraries/audio/src/MixedAudioStream.h index 0b1979003d..edb26c486f 100644 --- a/libraries/audio/src/MixedAudioStream.h +++ b/libraries/audio/src/MixedAudioStream.h @@ -20,9 +20,6 @@ public: MixedAudioStream(int numFrameSamples, int numFramesCapacity, const InboundAudioStream::Settings& settings); float getNextOutputFrameLoudness() const { return _ringBuffer.getNextOutputFrameLoudness(); } - -protected: - int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); }; #endif // hifi_MixedAudioStream_h diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index fd3ecb3bc9..2922459140 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -22,29 +22,7 @@ void MixedProcessedAudioStream::outputFormatChanged(int outputFormatChannelCount _ringBuffer.resizeForFrameSize(deviceOutputFrameSize); } -int MixedProcessedAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { - // mixed audio packets do not have any info between the seq num and the audio data. - int numNetworkSamples = packetAfterSeqNum.size() / sizeof(int16_t); - - // since numAudioSamples is used to know how many samples to add for each dropped packet before this one, - // we want to set it to the number of device audio samples since this stream contains device audio samples, not network samples. - numAudioSamples = networkToDeviceSamples(numNetworkSamples); - - return 0; -} - -int MixedProcessedAudioStream::parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples) { - int numNetworkSamples; - int bytesRead = InboundAudioStream::parseSilentPacketStreamProperties(packetAfterSeqNum, numNetworkSamples); - - // since numAudioSamples is used to know how many samples to add for each dropped packet before this one, - // we want to set it to the number of device audio samples since this stream contains device audio samples, not network samples. - numAudioSamples = networkToDeviceSamples(numNetworkSamples); - - return bytesRead; -} - -int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { +int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples) { QByteArray outputBuffer; emit processSamples(packetAfterStreamProperties, outputBuffer); @@ -54,7 +32,11 @@ int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& return packetAfterStreamProperties.size(); } +int MixedProcessedAudioStream::writeDroppableSilentSamples(int silentSamples) { + return InboundAudioStream::writeDroppableSilentSamples(networkToDeviceSamples(silentSamples)); +} + int MixedProcessedAudioStream::networkToDeviceSamples(int networkSamples) { const int STEREO_DIVIDER = 2; return networkSamples * _outputFormatChannelsTimesSampleRate / (STEREO_DIVIDER * SAMPLE_RATE); -} \ No newline at end of file +} diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index 7c89a5106d..ec65c8f712 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -27,9 +27,8 @@ public: void outputFormatChanged(int outputFormatChannelCountTimesSampleRate); protected: - int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); - int parseSilentPacketStreamProperties(const QByteArray& packetAfterSeqNum, int& numAudioSamples); - int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples); + int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); + int writeDroppableSilentSamples(int silentSamples); private: int networkToDeviceSamples(int networkSamples); From 4ec84b32b076499ade294e297ef653b8aab12dc6 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 17:00:39 -0700 Subject: [PATCH 064/206] updated AudioRingBuffer bytes vs samples returns --- libraries/audio/src/AudioRingBuffer.cpp | 104 ++++++++++------------- libraries/audio/src/AudioRingBuffer.h | 60 +++++++------ tests/audio/src/AudioRingBufferTests.cpp | 37 ++++---- 3 files changed, 97 insertions(+), 104 deletions(-) diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 2ad51e4a36..d9cb34ac1b 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -20,18 +20,16 @@ AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) : - _frameCapacity(numFramesCapacity), - _sampleCapacity(numFrameSamples * numFramesCapacity), - _isFull(false), - _numFrameSamples(numFrameSamples), - _randomAccessMode(randomAccessMode), - _overflowCount(0) +_frameCapacity(numFramesCapacity), +_sampleCapacity(numFrameSamples * numFramesCapacity), +_bufferLength(numFrameSamples * (numFramesCapacity + 1)), +_numFrameSamples(numFrameSamples), +_randomAccessMode(randomAccessMode), +_overflowCount(0) { if (numFrameSamples) { - _buffer = new int16_t[_sampleCapacity]; - if (_randomAccessMode) { - memset(_buffer, 0, _sampleCapacity * sizeof(int16_t)); - } + _buffer = new int16_t[_bufferLength]; + memset(_buffer, 0, _bufferLength * sizeof(int16_t)); _nextOutput = _buffer; _endOfLastWrite = _buffer; } else { @@ -53,28 +51,29 @@ void AudioRingBuffer::reset() { void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) { delete[] _buffer; _sampleCapacity = numFrameSamples * _frameCapacity; + _bufferLength = numFrameSamples * (_frameCapacity + 1); _numFrameSamples = numFrameSamples; - _buffer = new int16_t[_sampleCapacity]; + _buffer = new int16_t[_bufferLength]; + memset(_buffer, 0, _bufferLength * sizeof(int16_t)); if (_randomAccessMode) { - memset(_buffer, 0, _sampleCapacity * sizeof(int16_t)); + memset(_buffer, 0, _bufferLength * sizeof(int16_t)); } reset(); } void AudioRingBuffer::clear() { - _isFull = false; _endOfLastWrite = _buffer; _nextOutput = _buffer; } int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) { - return readData((char*) destination, maxSamples * sizeof(int16_t)); + return readData((char*)destination, maxSamples * sizeof(int16_t)) / sizeof(int16_t); } int AudioRingBuffer::readData(char *data, int maxSize) { // only copy up to the number of samples we have available - int numReadSamples = std::min((int) (maxSize / sizeof(int16_t)), samplesAvailable()); + int numReadSamples = std::min((int)(maxSize / sizeof(int16_t)), samplesAvailable()); // If we're in random access mode, then we consider our number of available read samples slightly // differently. Namely, if anything has been written, we say we have as many samples as they ask for @@ -83,16 +82,16 @@ int AudioRingBuffer::readData(char *data, int maxSize) { numReadSamples = _endOfLastWrite ? (maxSize / sizeof(int16_t)) : 0; } - if (_nextOutput + numReadSamples > _buffer + _sampleCapacity) { + if (_nextOutput + numReadSamples > _buffer + _bufferLength) { // we're going to need to do two reads to get this data, it wraps around the edge // read to the end of the buffer - int numSamplesToEnd = (_buffer + _sampleCapacity) - _nextOutput; + int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput; memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t)); if (_randomAccessMode) { memset(_nextOutput, 0, numSamplesToEnd * sizeof(int16_t)); // clear it } - + // read the rest from the beginning of the buffer memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); if (_randomAccessMode) { @@ -108,22 +107,19 @@ int AudioRingBuffer::readData(char *data, int maxSize) { // push the position of _nextOutput by the number of samples read _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples); - if (numReadSamples > 0) { - _isFull = false; - } return numReadSamples * sizeof(int16_t); } -int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { - return writeData((const char*) source, maxSamples * sizeof(int16_t)); +int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { + return writeData((const char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t); } int AudioRingBuffer::writeData(const char* data, int maxSize) { // make sure we have enough bytes left for this to be the right amount of audio // otherwise we should not copy that data, and leave the buffer pointers where they are int samplesToCopy = std::min((int)(maxSize / sizeof(int16_t)), _sampleCapacity); - + int samplesRoomFor = _sampleCapacity - samplesAvailable(); if (samplesToCopy > samplesRoomFor) { // there's not enough room for this write. erase old data to make room for this new data @@ -132,19 +128,16 @@ int AudioRingBuffer::writeData(const char* data, int maxSize) { _overflowCount++; qDebug() << "Overflowed ring buffer! Overwriting old data"; } - - if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) { + + if (_endOfLastWrite + samplesToCopy <= _buffer + _bufferLength) { memcpy(_endOfLastWrite, data, samplesToCopy * sizeof(int16_t)); } else { - int numSamplesToEnd = (_buffer + _sampleCapacity) - _endOfLastWrite; + int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; memcpy(_endOfLastWrite, data, numSamplesToEnd * sizeof(int16_t)); memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (samplesToCopy - numSamplesToEnd) * sizeof(int16_t)); } _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy); - if (samplesToCopy > 0 && _endOfLastWrite == _nextOutput) { - _isFull = true; - } return samplesToCopy * sizeof(int16_t); } @@ -158,61 +151,52 @@ const int16_t& AudioRingBuffer::operator[] (const int index) const { } void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) { - if (numSamples > 0) { - _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); - _isFull = false; - } + _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); } int AudioRingBuffer::samplesAvailable() const { if (!_endOfLastWrite) { return 0; } - if (_isFull) { - return _sampleCapacity; - } int sampleDifference = _endOfLastWrite - _nextOutput; if (sampleDifference < 0) { - sampleDifference += _sampleCapacity; + sampleDifference += _bufferLength; } return sampleDifference; } -int AudioRingBuffer::addSilentFrame(int numSilentSamples) { +int AudioRingBuffer::addSilentSamples(int silentSamples) { int samplesRoomFor = _sampleCapacity - samplesAvailable(); - if (numSilentSamples > samplesRoomFor) { + if (silentSamples > samplesRoomFor) { // there's not enough room for this write. write as many silent samples as we have room for - numSilentSamples = samplesRoomFor; + silentSamples = samplesRoomFor; qDebug() << "Dropping some silent samples to prevent ring buffer overflow"; } // memset zeroes into the buffer, accomodate a wrap around the end // push the _endOfLastWrite to the correct spot - if (_endOfLastWrite + numSilentSamples <= _buffer + _sampleCapacity) { - memset(_endOfLastWrite, 0, numSilentSamples * sizeof(int16_t)); + if (_endOfLastWrite + silentSamples <= _buffer + _bufferLength) { + memset(_endOfLastWrite, 0, silentSamples * sizeof(int16_t)); } else { - int numSamplesToEnd = (_buffer + _sampleCapacity) - _endOfLastWrite; + int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t)); - memset(_buffer, 0, (numSilentSamples - numSamplesToEnd) * sizeof(int16_t)); - } - _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numSilentSamples); - if (numSilentSamples > 0 && _nextOutput == _endOfLastWrite) { - _isFull = true; + memset(_buffer, 0, (silentSamples - numSamplesToEnd) * sizeof(int16_t)); } + _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, silentSamples); - return numSilentSamples * sizeof(int16_t); + return silentSamples; } int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const { - if (numSamplesShift > 0 && position + numSamplesShift >= _buffer + _sampleCapacity) { + if (numSamplesShift > 0 && position + numSamplesShift >= _buffer + _bufferLength) { // this shift will wrap the position around to the beginning of the ring - return position + numSamplesShift - _sampleCapacity; + return position + numSamplesShift - _bufferLength; } else if (numSamplesShift < 0 && position + numSamplesShift < _buffer) { // this shift will go around to the end of the ring - return position + numSamplesShift + _sampleCapacity; + return position + numSamplesShift + _bufferLength; } else { return position + numSamplesShift; } @@ -221,22 +205,22 @@ int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const { float loudness = 0.0f; const int16_t* sampleAt = frameStart; - const int16_t* _bufferLastAt = _buffer + _sampleCapacity - 1; - + const int16_t* _bufferLastAt = _buffer + _bufferLength - 1; + for (int i = 0; i < _numFrameSamples; ++i) { loudness += fabsf(*sampleAt); sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1; } loudness /= _numFrameSamples; loudness /= MAX_SAMPLE_VALUE; - - return loudness; -} -float AudioRingBuffer::getNextOutputFrameLoudness() const { - return getFrameLoudness(_nextOutput); + return loudness; } float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const { return getFrameLoudness(&(*frameStart)); } + +float AudioRingBuffer::getNextOutputFrameLoudness() const { + return getFrameLoudness(_nextOutput); +} diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 4dd258b2c5..be4dcaf545 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -30,7 +30,7 @@ const int NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL = 512; const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL / sizeof(int16_t); const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL - / (float) SAMPLE_RATE) * USECS_PER_SECOND); + / (float)SAMPLE_RATE) * USECS_PER_SECOND); const int MAX_SAMPLE_VALUE = std::numeric_limits::max(); const int MIN_SAMPLE_VALUE = std::numeric_limits::min(); @@ -44,33 +44,33 @@ public: void reset(); void resizeForFrameSize(int numFrameSamples); - + void clear(); int getSampleCapacity() const { return _sampleCapacity; } int getFrameCapacity() const { return _frameCapacity; } - + int readSamples(int16_t* destination, int maxSamples); int writeSamples(const int16_t* source, int maxSamples); - + int readData(char* data, int maxSize); int writeData(const char* data, int maxSize); - + int16_t& operator[](const int index); const int16_t& operator[] (const int index) const; - + void shiftReadPosition(unsigned int numSamples); float getNextOutputFrameLoudness() const; - + int samplesAvailable() const; int framesAvailable() const { return samplesAvailable() / _numFrameSamples; } int getNumFrameSamples() const { return _numFrameSamples; } - + int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data - - int addSilentFrame(int numSilentSamples); + + int addSilentSamples(int samples); private: float getFrameLoudness(const int16_t* frameStart) const; @@ -79,12 +79,12 @@ protected: // disallow copying of AudioRingBuffer objects AudioRingBuffer(const AudioRingBuffer&); AudioRingBuffer& operator= (const AudioRingBuffer&); - + int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const; int _frameCapacity; int _sampleCapacity; - bool _isFull; + int _bufferLength; // actual length of _buffer: will be one frame larger than _sampleCapacity int _numFrameSamples; int16_t* _nextOutput; int16_t* _endOfLastWrite; @@ -97,13 +97,13 @@ public: class ConstIterator { //public std::iterator < std::forward_iterator_tag, int16_t > { public: ConstIterator() - : _capacity(0), + : _bufferLength(0), _bufferFirst(NULL), _bufferLast(NULL), _at(NULL) {} ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) - : _capacity(capacity), + : _bufferLength(capacity), _bufferFirst(bufferFirst), _bufferLast(bufferFirst + capacity - 1), _at(at) {} @@ -113,7 +113,7 @@ public: const int16_t& operator*() { return *_at; } ConstIterator& operator=(const ConstIterator& rhs) { - _capacity = rhs._capacity; + _bufferLength = rhs._bufferLength; _bufferFirst = rhs._bufferFirst; _bufferLast = rhs._bufferLast; _at = rhs._at; @@ -147,40 +147,50 @@ public: } ConstIterator operator+(int i) { - return ConstIterator(_bufferFirst, _capacity, atShiftedBy(i)); + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i)); } ConstIterator operator-(int i) { - return ConstIterator(_bufferFirst, _capacity, atShiftedBy(-i)); + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i)); } void readSamples(int16_t* dest, int numSamples) { + int16_t* at = _at; for (int i = 0; i < numSamples; i++) { - *dest = *(*this); + *dest = *at; ++dest; - ++(*this); + at = (at == _bufferLast) ? _bufferFirst : at + 1; } } - + + void readSamplesWithFade(int16_t* dest, int numSamples, float fade) { + int16_t* at = _at; + for (int i = 0; i < numSamples; i++) { + *dest = (float)*at * fade; + ++dest; + at = (at == _bufferLast) ? _bufferFirst : at + 1; + } + } + private: int16_t* atShiftedBy(int i) { - i = (_at - _bufferFirst + i) % _capacity; + i = (_at - _bufferFirst + i) % _bufferLength; if (i < 0) { - i += _capacity; + i += _bufferLength; } return _bufferFirst + i; } private: - int _capacity; + int _bufferLength; int16_t* _bufferFirst; int16_t* _bufferLast; int16_t* _at; }; - ConstIterator nextOutput() const { return ConstIterator(_buffer, _sampleCapacity, _nextOutput); } + ConstIterator nextOutput() const { return ConstIterator(_buffer, _bufferLength, _nextOutput); } float getFrameLoudness(ConstIterator frameStart) const; }; -#endif // hifi_AudioRingBuffer_h +#endif // hifi_AudioRingBuffer_h \ No newline at end of file diff --git a/tests/audio/src/AudioRingBufferTests.cpp b/tests/audio/src/AudioRingBufferTests.cpp index b9ed596e52..f31f9988d6 100644 --- a/tests/audio/src/AudioRingBufferTests.cpp +++ b/tests/audio/src/AudioRingBufferTests.cpp @@ -27,28 +27,28 @@ void AudioRingBufferTests::runAllTests() { int16_t readData[10000]; int readIndexAt; - + AudioRingBuffer ringBuffer(10, false, 10); // makes buffer of 100 int16_t samples for (int T = 0; T < 300; T++) { - + writeIndexAt = 0; readIndexAt = 0; // write 73 samples, 73 samples in buffer - writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 73) / sizeof(int16_t); + writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 73); assertBufferSize(ringBuffer, 73); // read 43 samples, 30 samples in buffer - readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 43) / sizeof(int16_t); + readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 43); assertBufferSize(ringBuffer, 30); // write 70 samples, 100 samples in buffer (full) - writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 70) / sizeof(int16_t); + writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 70); assertBufferSize(ringBuffer, 100); // read 100 samples, 0 samples in buffer (empty) - readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t); + readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100); assertBufferSize(ringBuffer, 0); @@ -65,15 +65,15 @@ void AudioRingBufferTests::runAllTests() { readIndexAt = 0; // write 59 samples, 59 samples in buffer - writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 59) / sizeof(int16_t); + writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 59); assertBufferSize(ringBuffer, 59); // write 99 samples, 100 samples in buffer - writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 99) / sizeof(int16_t); + writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 99); assertBufferSize(ringBuffer, 100); // read 100 samples, 0 samples in buffer - readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t); + readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100); assertBufferSize(ringBuffer, 0); // verify 100 samples of read data @@ -88,23 +88,23 @@ void AudioRingBufferTests::runAllTests() { readIndexAt = 0; // write 77 samples, 77 samples in buffer - writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 77) / sizeof(int16_t); + writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 77); assertBufferSize(ringBuffer, 77); // write 24 samples, 100 samples in buffer (overwrote one sample: "0") - writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 24) / sizeof(int16_t); + writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 24); assertBufferSize(ringBuffer, 100); // write 29 silent samples, 100 samples in buffer, make sure non were added int samplesWritten; - if ((samplesWritten = ringBuffer.addSilentFrame(29)) != 0) { - qDebug("addSilentFrame(29) incorrect! Expected: 0 Actual: %d", samplesWritten); + if ((samplesWritten = ringBuffer.addSilentSamples(29)) != 0) { + qDebug("addSilentSamples(29) incorrect! Expected: 0 Actual: %d", samplesWritten); return; } assertBufferSize(ringBuffer, 100); // read 3 samples, 97 samples in buffer (expect to read "1", "2", "3") - readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t); + readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3); for (int i = 0; i < 3; i++) { if (readData[i] != i + 1) { qDebug("Second readData[%d] incorrect! Expcted: %d Actual: %d", i, i + 1, readData[i]); @@ -114,14 +114,14 @@ void AudioRingBufferTests::runAllTests() { assertBufferSize(ringBuffer, 97); // write 4 silent samples, 100 samples in buffer - if ((samplesWritten = ringBuffer.addSilentFrame(4) / sizeof(int16_t)) != 3) { - qDebug("addSilentFrame(4) incorrect! Exptected: 3 Actual: %d", samplesWritten); + if ((samplesWritten = ringBuffer.addSilentSamples(4)) != 3) { + qDebug("addSilentSamples(4) incorrect! Exptected: 3 Actual: %d", samplesWritten); return; } assertBufferSize(ringBuffer, 100); // read back 97 samples (the non-silent samples), 3 samples in buffer (expect to read "4" thru "100") - readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 97) / sizeof(int16_t); + readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 97); for (int i = 3; i < 100; i++) { if (readData[i] != i + 1) { qDebug("third readData[%d] incorrect! Expcted: %d Actual: %d", i, i + 1, readData[i]); @@ -131,7 +131,7 @@ void AudioRingBufferTests::runAllTests() { assertBufferSize(ringBuffer, 3); // read back 3 silent samples, 0 samples in buffer - readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t); + readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3); for (int i = 100; i < 103; i++) { if (readData[i] != 0) { qDebug("Fourth readData[%d] incorrect! Expcted: %d Actual: %d", i, 0, readData[i]); @@ -143,4 +143,3 @@ void AudioRingBufferTests::runAllTests() { qDebug() << "PASSED"; } - From 7aa5a1f830138c9b5dd0f5d5f970a11298f229a5 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 17:02:13 -0700 Subject: [PATCH 065/206] fixed function name --- libraries/audio/src/InboundAudioStream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 67259c5d99..15e940ac49 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -197,7 +197,7 @@ int InboundAudioStream::writeDroppableSilentSamples(int silentSamples) { _framesAvailableStat.reset(); } - return _ringBuffer.addSilentFrame(silentSamples - numSilentFramesToDrop * samplesPerFrame); + return _ringBuffer.addSilentSamples(silentSamples - numSilentFramesToDrop * samplesPerFrame); } int InboundAudioStream::popSamples(int maxSamples, bool allOrNothing, bool starveIfNoSamplesPopped) { From fea97f8fe8b5ea7da42ea6ccfc1ead7eae135d65 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 17:17:30 -0700 Subject: [PATCH 066/206] added networkSamples buffer to MixedProcessedAudioStream --- interface/src/Audio.cpp | 2 +- .../audio/src/MixedProcessedAudioStream.cpp | 23 +++++++++++++++---- .../audio/src/MixedProcessedAudioStream.h | 4 ++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index c09d733cb5..4b05ed9ac9 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -905,7 +905,7 @@ void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& s unsigned int delayCount = delay * _desiredOutputFormat.channelCount(); unsigned int silentCount = (remaining < delayCount) ? remaining : delayCount; if (silentCount) { - _spatialAudioRingBuffer.addSilentFrame(silentCount); + _spatialAudioRingBuffer.addSilentSamples(silentCount); } // Recalculate the number of remaining samples diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index 2922459140..5693af7c6e 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -12,7 +12,8 @@ #include "MixedProcessedAudioStream.h" MixedProcessedAudioStream::MixedProcessedAudioStream(int numFrameSamples, int numFramesCapacity, const InboundAudioStream::Settings& settings) - : InboundAudioStream(numFrameSamples, numFramesCapacity, settings) + : InboundAudioStream(numFrameSamples, numFramesCapacity, settings), + _networkSamplesWritten(0) { } @@ -24,6 +25,9 @@ void MixedProcessedAudioStream::outputFormatChanged(int outputFormatChannelCount int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples) { + memcpy(&_networkSamples[_networkSamplesWritten], packetAfterStreamProperties.data(), packetAfterStreamProperties.size()); + _networkSamplesWritten += packetAfterStreamProperties.size() / sizeof(int16_t); + QByteArray outputBuffer; emit processSamples(packetAfterStreamProperties, outputBuffer); @@ -33,10 +37,21 @@ int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& } int MixedProcessedAudioStream::writeDroppableSilentSamples(int silentSamples) { - return InboundAudioStream::writeDroppableSilentSamples(networkToDeviceSamples(silentSamples)); + int deviceSilentSamplesWritten = InboundAudioStream::writeDroppableSilentSamples(networkToDeviceSamples(silentSamples)); + + int networkSilentSamplesWritten = deviceToNetworkSamples(deviceSilentSamplesWritten); + memset(&_networkSamples[_networkSamplesWritten], 0, networkSilentSamplesWritten * sizeof(int16_t)); + _networkSamplesWritten += networkSilentSamplesWritten; + + return deviceSilentSamplesWritten; } +static const int STEREO_FACTOR = 2; + int MixedProcessedAudioStream::networkToDeviceSamples(int networkSamples) { - const int STEREO_DIVIDER = 2; - return networkSamples * _outputFormatChannelsTimesSampleRate / (STEREO_DIVIDER * SAMPLE_RATE); + return networkSamples * _outputFormatChannelsTimesSampleRate / (STEREO_FACTOR * SAMPLE_RATE); +} + +int MixedProcessedAudioStream::deviceToNetworkSamples(int deviceSamples) { + return deviceSamples * (STEREO_FACTOR * SAMPLE_RATE) / _outputFormatChannelsTimesSampleRate; } diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index ec65c8f712..1ba8b7a29d 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -32,8 +32,12 @@ protected: private: int networkToDeviceSamples(int networkSamples); + int deviceToNetworkSamples(int deviceSamples); private: int _outputFormatChannelsTimesSampleRate; + + int16_t _networkSamples[10 * NETWORK_BUFFER_LENGTH_SAMPLES_STEREO]; + int _networkSamplesWritten; }; #endif // hifi_MixedProcessedAudioStream_h From 0f9da8e13fdd5b753fe5c11fcb330b59b45851c6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 17:34:08 -0700 Subject: [PATCH 067/206] Make mapping writing more robust --- examples/editModels.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 5525f0b128..a7eac56773 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -379,9 +379,7 @@ var modelUploader = (function () { for (name in mapping) { if (mapping.hasOwnProperty(name)) { - if (typeof mapping[name] === "string") { - string += (name + " = " + mapping[name] + "\n"); - } else { + if (typeof mapping[name] === "object") { for (value in mapping[name]) { if (mapping[name].hasOwnProperty(value)) { remainder = mapping[name][value]; @@ -393,6 +391,8 @@ var modelUploader = (function () { string += (name + " = " + value + remainder + "\n"); } } + } else { + string += (name + " = " + mapping[name] + "\n"); } } } From 35bc1a03af0506a7b612bbbad025cd104e52397d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 17:35:36 -0700 Subject: [PATCH 068/206] Simplify model scale to be that specified in FST or 1.0 No UI --- examples/editModels.js | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index a7eac56773..593c6d20ce 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -445,11 +445,6 @@ var modelUploader = (function () { textures[filename] = ""; geometry.textures.push(filename); } - - } else if (name === "author") { - author = view.string(index + 5, view.getUint32(index + 1, true)); - geometry.author = author; - } index += (propertyListLength); @@ -476,12 +471,6 @@ var modelUploader = (function () { charCode = view[index]; if (charCode === 10) { // Can ignore EOF line = String.fromCharCode.apply(String, charCodes).trim(); - - if (line.slice(0, 7).toLowerCase() === "author:") { - author = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length); - geometry.author = author; - - } if (line.slice(0, 17).toLowerCase() === "relativefilename:") { filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); if (!textures.hasOwnProperty(filename)) { @@ -489,7 +478,6 @@ var modelUploader = (function () { geometry.textures.push(filename); } } - charCodes = []; } else { charCodes.push(charCode); @@ -574,12 +562,6 @@ var modelUploader = (function () { } } - if (mapping.hasOwnProperty(SCALE_FIELD)) { - mapping[SCALE_FIELD] = parseFloat(mapping[SCALE_FIELD]); - } else { - mapping[SCALE_FIELD] = (geometry.author === "www.makehuman.org" ? 150.0 : 15.0); - } - // Add any missing basic mappings if (!mapping.hasOwnProperty(NAME_FIELD)) { mapping[NAME_FIELD] = modelFile.fileName().fileBase(); @@ -588,7 +570,7 @@ var modelUploader = (function () { mapping[TEXDIR_FIELD] = "."; } if (!mapping.hasOwnProperty(SCALE_FIELD)) { - mapping[SCALE_FIELD] = 0.2; // For SVO models. + mapping[SCALE_FIELD] = 1.0; } return true; @@ -618,7 +600,6 @@ var modelUploader = (function () { errorMessage: "Texture directory must be subdirectory of model directory." }); - form.push({ label: "Scale:", value: mapping[SCALE_FIELD].toFixed(decimals) }); form.push({ button: "Cancel" }); if (!Window.form("Set Model Properties", form)) { @@ -631,7 +612,6 @@ var modelUploader = (function () { if (mapping[TEXDIR_FIELD] === "") { mapping[TEXDIR_FIELD] = "."; } - mapping[SCALE_FIELD] = form[2].value; writeMapping(fstBuffer); From 7a063b8bc827d409e87e8ce78b50674a6bcf5920 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 8 Aug 2014 18:16:25 -0700 Subject: [PATCH 069/206] scope seems to be working now using networkSamples --- interface/src/Audio.cpp | 9 +++++++-- interface/src/Audio.h | 1 + libraries/audio/src/InboundAudioStream.cpp | 2 ++ libraries/audio/src/InboundAudioStream.h | 3 +++ libraries/audio/src/MixedProcessedAudioStream.h | 5 +++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 4b05ed9ac9..8aac32849e 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -121,6 +121,7 @@ Audio::Audio(QObject* parent) : _noiseSampleFrames = new float[NUMBER_OF_NOISE_SAMPLE_FRAMES]; connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &Audio::processReceivedAudioStreamSamples, Qt::DirectConnection); + connect(&_receivedAudioStream, &MixedProcessedAudioStream::dataParsed, this, &Audio::updateScopeBuffers, Qt::DirectConnection); } void Audio::init(QGLWidget *parent) { @@ -777,11 +778,13 @@ void Audio::processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QBy numNetworkOutputSamples, numDeviceOutputSamples, _desiredOutputFormat, _outputFormat); +} - +void Audio::updateScopeBuffers() { if (_scopeEnabled && !_scopeEnabledPause) { unsigned int numAudioChannels = _desiredOutputFormat.channelCount(); - const int16_t* samples = receivedSamples; + const int16_t* samples = _receivedAudioStream.getNetworkSamples(); + int numNetworkOutputSamples = _receivedAudioStream.getNetworkSamplesWritten(); for (int numSamples = numNetworkOutputSamples / numAudioChannels; numSamples > 0; numSamples -= NETWORK_SAMPLES_PER_FRAME) { unsigned int audioChannel = 0; @@ -801,6 +804,8 @@ void Audio::processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QBy samples += NETWORK_SAMPLES_PER_FRAME * numAudioChannels; } } + + _receivedAudioStream.clearNetworkSamples(); } void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) { diff --git a/interface/src/Audio.h b/interface/src/Audio.h index a93b8c5be7..c080557576 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -106,6 +106,7 @@ public slots: void parseAudioStreamStatsPacket(const QByteArray& packet); void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples); void processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); + void updateScopeBuffers(); void handleAudioInput(); void reset(); void resetStats(); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 15e940ac49..f393a4af44 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -161,6 +161,8 @@ int InboundAudioStream::parseData(const QByteArray& packet) { framesAvailableChanged(); + emit dataParsed(); + return readBytes; } diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 62a22b61ab..dac1bb138c 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -149,6 +149,9 @@ public: int getPacketsReceived() const { return _incomingSequenceNumberStats.getReceived(); } +signals: + void dataParsed(); + public slots: /// This function should be called every second for all the stats to function properly. If dynamic jitter buffers /// is enabled, those stats are used to calculate _desiredJitterBufferFrames. diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index 1ba8b7a29d..e1a54e4449 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -26,6 +26,11 @@ signals: public: void outputFormatChanged(int outputFormatChannelCountTimesSampleRate); + const int16_t* getNetworkSamples() const { return _networkSamples; } + int getNetworkSamplesWritten() const { return _networkSamplesWritten; } + + void clearNetworkSamples() { _networkSamplesWritten = 0; } + protected: int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); int writeDroppableSilentSamples(int silentSamples); From 54ea86d6be7bec8a9d0dad7d86588336683b8cbf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 20:40:15 -0700 Subject: [PATCH 070/206] Fix texture directory display and validation --- examples/editModels.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 593c6d20ce..ffd3940814 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -83,6 +83,12 @@ if (typeof String.prototype.path !== "function") { }; } +if (typeof String.prototype.regExpEscape !== "function") { + String.prototype.regExpEscape = function () { + return this.replace(/([$^.+*?|\\\/{}()\[\]])/g, '\\$1'); + } +} + if (typeof String.prototype.toArrayBuffer !== "function") { String.prototype.toArrayBuffer = function () { var length, @@ -588,8 +594,8 @@ var modelUploader = (function () { form.push({ label: "Name:", value: mapping[NAME_FIELD] }); directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD]; - displayAs = new RegExp("^" + modelFile.path().replace(/[\\\\\\\/]/, "[\\\\\\\/]") + "[\\\\\\\/](.*)"); - validateAs = new RegExp("^" + modelFile.path().replace(/[\\\\\\\/]/, "[\\\\\\\/]") + "([\\\\\\\/].*)?"); + displayAs = new RegExp("^" + modelFile.path().regExpEscape() + "[\\\\\\\/](.*)"); + validateAs = new RegExp("^" + modelFile.path().regExpEscape() + "([\\\\\\\/].*)?"); form.push({ label: "Texture directory:", @@ -597,7 +603,7 @@ var modelUploader = (function () { title: "Choose Texture Directory", displayAs: displayAs, validateAs: validateAs, - errorMessage: "Texture directory must be subdirectory of model directory." + errorMessage: "Texture directory must be subdirectory of the model directory." }); form.push({ button: "Cancel" }); From 9a21f1c355581ae607a548ef72ac120e335dd7ba Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Aug 2014 21:51:14 -0700 Subject: [PATCH 071/206] Fix FST file reading and writing --- examples/editModels.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index ffd3940814..5fda380d55 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -103,7 +103,7 @@ if (typeof String.prototype.toArrayBuffer !== "function") { length = this.length; for (i = 0; i < length; i += 1) { charCode = this.charCodeAt(i); - if (i <= 255) { + if (charCode <= 255) { charCodes.push(charCode); } else { charCodes.push(charCode / 256); @@ -351,7 +351,8 @@ var modelUploader = (function () { i, name, value, - remainder; + remainder, + existing; mapping = {}; // { name : value | name : { value : remainder } } lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/); @@ -362,13 +363,19 @@ var modelUploader = (function () { if (tokens.length > 1) { name = tokens[0]; value = tokens[1]; - if (tokens.length === 2) { + if (tokens.length > 2) { + remainder = tokens.slice(2, tokens.length).join(" = "); + } else { + remainder = null; + } + if (tokens.length === 2 && mapping[name] === undefined) { mapping[name] = value; } else { - // We're only interested in the first two fields so put the rest in the remainder - remainder = tokens.slice(2, tokens.length).join(" = "); if (mapping[name] === undefined) { mapping[name] = {}; + } else if (typeof mapping[name] !== "object") { + existing = mapping[name]; + mapping[name] = { existing: null }; } mapping[name][value] = remainder; } From a1ea3933242c959636d72aae376582161befa97a Mon Sep 17 00:00:00 2001 From: wangyix Date: Mon, 11 Aug 2014 09:14:01 -0700 Subject: [PATCH 072/206] added a comment --- libraries/audio/src/MixedProcessedAudioStream.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index e1a54e4449..b85637a288 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -41,6 +41,8 @@ private: private: int _outputFormatChannelsTimesSampleRate; + // this buffer keeps a copy of the network samples written during parseData() for the sole purpose + // of passing it on to the audio scope int16_t _networkSamples[10 * NETWORK_BUFFER_LENGTH_SAMPLES_STEREO]; int _networkSamplesWritten; }; From f12973d5d0ed88b0425cdcbe99b2786aeb4ec97d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Aug 2014 10:12:47 -0700 Subject: [PATCH 073/206] Cater for multiple mapping values --- examples/editModels.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 5fda380d55..8b54b9e48a 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -354,7 +354,7 @@ var modelUploader = (function () { remainder, existing; - mapping = {}; // { name : value | name : { value : remainder } } + mapping = {}; // { name : value | name : { value : [remainder] } } lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/); for (i = 0; i < lines.length; i += 1) { line = lines[i].trim(); @@ -373,11 +373,16 @@ var modelUploader = (function () { } else { if (mapping[name] === undefined) { mapping[name] = {}; + } else if (typeof mapping[name] !== "object") { existing = mapping[name]; - mapping[name] = { existing: null }; + mapping[name] = { existing : null }; } - mapping[name][value] = remainder; + + if (mapping[name][value] === undefined) { + mapping[name][value] = []; + } + mapping[name][value].push(remainder); } } } @@ -388,6 +393,7 @@ var modelUploader = (function () { var name, value, remainder, + i, string = ""; for (name in mapping) { @@ -397,11 +403,12 @@ var modelUploader = (function () { if (mapping[name].hasOwnProperty(value)) { remainder = mapping[name][value]; if (remainder === null) { - remainder = ""; + string += (name + " = " + value + "\n"); } else { - remainder = " = " + remainder; + for (i = 0; i < remainder.length; i += 1) { + string += (name + " = " + value + " = " + remainder[i] + "\n"); + } } - string += (name + " = " + value + remainder + "\n"); } } } else { From 0b979d2e1ecd786b07733282afa5cca93e420156 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Aug 2014 10:54:46 -0700 Subject: [PATCH 074/206] Disabled SVO model uploading: server doesn't support SVO file uploads --- examples/editModels.js | 43 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 8b54b9e48a..a047562dcd 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -288,7 +288,7 @@ var modelUploader = (function () { modelFile, fstBuffer, fbxBuffer, - svoBuffer, + //svoBuffer, mapping, geometry, API_URL = "https://data.highfidelity.io/api/v1/models", @@ -319,7 +319,7 @@ var modelUploader = (function () { function resetDataObjects() { fstBuffer = null; fbxBuffer = null; - svoBuffer = null; + //svoBuffer = null; mapping = {}; geometry = {}; geometry.textures = []; @@ -521,7 +521,7 @@ var modelUploader = (function () { function readModel() { var fbxFilename, - svoFilename, + //svoFilename, fileType; print("Reading model file: " + modelFile); @@ -536,8 +536,8 @@ var modelUploader = (function () { if (mapping.hasOwnProperty(FILENAME_FIELD)) { if (fileType === "fbx") { fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; - } else if (fileType === "svo") { - svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; + //} else if (fileType === "svo") { + // svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; } else { error("Unrecognized model type in FST file!"); return false; @@ -556,9 +556,9 @@ var modelUploader = (function () { fbxFilename = modelFile; mapping[FILENAME_FIELD] = modelFile.fileName(); - } else if (modelFile.toLowerCase().fileType() === "svo") { - svoFilename = modelFile; - mapping[FILENAME_FIELD] = modelFile.fileName(); + //} else if (modelFile.toLowerCase().fileType() === "svo") { + // svoFilename = modelFile; + // mapping[FILENAME_FIELD] = modelFile.fileName(); } else { error("Unrecognized file type: " + modelFile); @@ -575,12 +575,12 @@ var modelUploader = (function () { readGeometry(fbxBuffer); } - if (svoFilename) { - svoBuffer = readFile(svoFilename); - if (svoBuffer === null) { - return false; - } - } + //if (svoFilename) { + // svoBuffer = readFile(svoFilename); + // if (svoBuffer === null) { + // return false; + // } + //} // Add any missing basic mappings if (!mapping.hasOwnProperty(NAME_FIELD)) { @@ -680,12 +680,12 @@ var modelUploader = (function () { } // SVO file - if (svoBuffer) { - httpMultiPart.add({ - name : "svo", - buffer: svoBuffer - }); - } + //if (svoBuffer) { + // httpMultiPart.add({ + // name : "svo", + // buffer: svoBuffer + // }); + //} // LOD files lodCount = 0; @@ -1044,7 +1044,8 @@ var toolBar = (function () { toggleToolbar(false); file = Window.browse("Select your model file ...", Settings.getValue("LastModelUploadLocation").path(), - "Model files (*.fst *.fbx *.svo)"); + "Model files (*.fst *.fbx)"); + //"Model files (*.fst *.fbx *.svo)"); if (file !== null) { Settings.setValue("LastModelUploadLocation", file); modelUploader.upload(file, addModel); From 53602c2ef2dc1bb4216b6dec40c57bfd709123e1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 Aug 2014 10:55:31 -0700 Subject: [PATCH 075/206] Miscellaneous tidying --- examples/editModels.js | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index a047562dcd..3d93fca0bf 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -85,8 +85,8 @@ if (typeof String.prototype.path !== "function") { if (typeof String.prototype.regExpEscape !== "function") { String.prototype.regExpEscape = function () { - return this.replace(/([$^.+*?|\\\/{}()\[\]])/g, '\\$1'); - } + return this.replace(/([$\^.+*?|\\\/{}()\[\]])/g, '\\$1'); + }; } if (typeof String.prototype.toArrayBuffer !== "function") { @@ -333,7 +333,7 @@ var modelUploader = (function () { req.responseType = "arraybuffer"; req.send(); if (req.status !== 200) { - error("Could not read file: " + filename + " : " + req.status + " " + req.statusText); + error("Could not read file: " + filename + " : " + req.statusText); return null; } @@ -439,8 +439,7 @@ var modelUploader = (function () { propertyListLength, nameLength, name, - filename, - author; + filename; endOffset = view.getUint32(index, true); numProperties = view.getUint32(index + 4, true); @@ -480,7 +479,6 @@ var modelUploader = (function () { viewLength, charCode, charCodes, - author, filename; view = new Uint8Array(fbxBuffer.buffer); @@ -598,7 +596,6 @@ var modelUploader = (function () { function setProperties() { var form = [], - decimals = 3, directory, displayAs, validateAs; @@ -749,16 +746,16 @@ var modelUploader = (function () { error("Model upload failed: Internet request timed out!"); } - function debugResponse() { - print("req.errorCode = " + req.errorCode); - print("req.readyState = " + req.readyState); - print("req.status = " + req.status); - print("req.statusText = " + req.statusText); - print("req.responseType = " + req.responseType); - print("req.responseText = " + req.responseText); - print("req.response = " + req.response); - print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders()); - } + //function debugResponse() { + // print("req.errorCode = " + req.errorCode); + // print("req.readyState = " + req.readyState); + // print("req.status = " + req.status); + // print("req.statusText = " + req.statusText); + // print("req.responseType = " + req.responseType); + // print("req.responseText = " + req.responseText); + // print("req.response = " + req.response); + // print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders()); + //} function checkUploaded() { print("Checking uploaded model"); From 1f011bfe9d559e2d9069a12107ea1fa0ec97ce5d Mon Sep 17 00:00:00 2001 From: wangyix Date: Mon, 11 Aug 2014 11:22:37 -0700 Subject: [PATCH 076/206] repetition-with-fade option added, not implemented --- assignment-client/src/Agent.cpp | 2 +- assignment-client/src/audio/AudioMixer.cpp | 8 ++ .../resources/web/settings/describe.json | 6 ++ interface/src/Menu.cpp | 2 + interface/src/ui/PreferencesDialog.cpp | 8 +- interface/ui/preferencesDialog.ui | 99 ++++++++++++++++++- libraries/audio/src/InboundAudioStream.cpp | 4 +- libraries/audio/src/InboundAudioStream.h | 18 +++- 8 files changed, 130 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ecc6414622..e7b11f6029 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -43,7 +43,7 @@ Agent::Agent(const QByteArray& packet) : _receivedAudioStream(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, InboundAudioStream::Settings(0, false, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, false, DEFAULT_WINDOW_STARVE_THRESHOLD, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES, - DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION)), + DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false)), _avatarHashMap() { // be the parent of the script engine so it gets moved when we do diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 0f76d17da1..bd8877a128 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -477,6 +477,14 @@ void AudioMixer::run() { } qDebug() << "Window B length:" << _streamSettings._windowSecondsForDesiredReduction << "seconds"; + const QString REPETITION_WITH_FADE_JSON_KEY = "H-repetition-with-fade"; + _streamSettings._repetitionWithFade = audioGroupObject[REPETITION_WITH_FADE_JSON_KEY].toBool(); + if (_streamSettings._repetitionWithFade) { + qDebug() << "Repetition with fade enabled"; + } else { + qDebug() << "Repetition with fade disabled"; + } + const QString UNATTENUATED_ZONE_KEY = "Z-unattenuated-zone"; diff --git a/domain-server/resources/web/settings/describe.json b/domain-server/resources/web/settings/describe.json index bb63c5f0a0..db2809bffb 100644 --- a/domain-server/resources/web/settings/describe.json +++ b/domain-server/resources/web/settings/describe.json @@ -45,6 +45,12 @@ "placeholder": "10", "default": "10" }, + "H-repetition-with-fade": { + "type": "checkbox", + "label": "Repetition with Fade:", + "help": "If enabled, dropped frames and mixing during starves will repeat the last frame, eventually fading to silence", + "default": true + }, "Z-unattenuated-zone": { "label": "Unattenuated Zone", "help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)", diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ecf28bcb17..a0943ebb52 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -638,6 +638,7 @@ void Menu::loadSettings(QSettings* settings) { _receivedAudioStreamSettings._windowStarveThreshold = settings->value("windowStarveThreshold", DEFAULT_WINDOW_STARVE_THRESHOLD).toInt(); _receivedAudioStreamSettings._windowSecondsForDesiredCalcOnTooManyStarves = settings->value("windowSecondsForDesiredCalcOnTooManyStarves", DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES).toInt(); _receivedAudioStreamSettings._windowSecondsForDesiredReduction = settings->value("windowSecondsForDesiredReduction", DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION).toInt(); + _receivedAudioStreamSettings._repetitionWithFade = settings->value("repetitionWithFade", DEFAULT_REPETITION_WITH_FADE).toBool(); _fieldOfView = loadSetting(settings, "fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES); _realWorldFieldOfView = loadSetting(settings, "realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES); @@ -695,6 +696,7 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("windowStarveThreshold", _receivedAudioStreamSettings._windowStarveThreshold); settings->setValue("windowSecondsForDesiredCalcOnTooManyStarves", _receivedAudioStreamSettings._windowSecondsForDesiredCalcOnTooManyStarves); settings->setValue("windowSecondsForDesiredReduction", _receivedAudioStreamSettings._windowSecondsForDesiredReduction); + settings->setValue("repetitionWithFade", _receivedAudioStreamSettings._repetitionWithFade); settings->setValue("fieldOfView", _fieldOfView); settings->setValue("faceshiftEyeDeflection", _faceshiftEyeDeflection); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index bde3cfd65a..c585b6ba0c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -152,18 +152,13 @@ void PreferencesDialog::loadPreferences() { const InboundAudioStream::Settings& streamSettings = menuInstance->getReceivedAudioStreamSettings(); ui.dynamicJitterBuffersCheckBox->setChecked(streamSettings._dynamicJitterBuffers); - ui.staticDesiredJitterBufferFramesSpin->setValue(streamSettings._staticDesiredJitterBufferFrames); - ui.maxFramesOverDesiredSpin->setValue(streamSettings._maxFramesOverDesired); - ui.useStdevForJitterCalcCheckBox->setChecked(streamSettings._useStDevForJitterCalc); - ui.windowStarveThresholdSpin->setValue(streamSettings._windowStarveThreshold); - ui.windowSecondsForDesiredCalcOnTooManyStarvesSpin->setValue(streamSettings._windowSecondsForDesiredCalcOnTooManyStarves); - ui.windowSecondsForDesiredReductionSpin->setValue(streamSettings._windowSecondsForDesiredReduction); + ui.repetitionWithFadeCheckBox->setChecked(streamSettings._repetitionWithFade); ui.realWorldFieldOfViewSpin->setValue(menuInstance->getRealWorldFieldOfView()); @@ -261,6 +256,7 @@ void PreferencesDialog::savePreferences() { streamSettings._windowStarveThreshold = ui.windowStarveThresholdSpin->value(); streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = ui.windowSecondsForDesiredCalcOnTooManyStarvesSpin->value(); streamSettings._windowSecondsForDesiredReduction = ui.windowSecondsForDesiredReductionSpin->value(); + streamSettings._repetitionWithFade = ui.repetitionWithFadeCheckBox->isChecked(); Menu::getInstance()->setReceivedAudioStreamSettings(streamSettings); Application::getInstance()->getAudio()->setReceivedAudioStreamSettings(streamSettings); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index cddc0f1299..e35c66af5a 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -1683,7 +1683,7 @@ padding: 10px;margin-top:10px - + Arial @@ -1777,7 +1777,7 @@ padding: 10px;margin-top:10px - + Arial @@ -1867,7 +1867,7 @@ padding: 10px;margin-top:10px - + Arial @@ -1961,7 +1961,7 @@ padding: 10px;margin-top:10px - + Arial @@ -2055,7 +2055,7 @@ padding: 10px;margin-top:10px - + Arial @@ -2110,6 +2110,95 @@ padding: 10px;margin-top:10px + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Repetition with Fade + + + 15 + + + repetitionWithFadeCheckBox + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 32 + 0 + + + + + 0 + 0 + + + + + + + + 32 + 32 + + + + + + diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index f393a4af44..39cd544b15 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -41,7 +41,8 @@ InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacit _starveThreshold(settings._windowStarveThreshold), _framesAvailableStat(), _currentJitterBufferFrames(0), - _timeGapStatsForStatsPacket(0, STATS_FOR_STATS_PACKET_WINDOW_SECONDS) + _timeGapStatsForStatsPacket(0, STATS_FOR_STATS_PACKET_WINDOW_SECONDS), + _repetitionWithFade(settings._repetitionWithFade) { } @@ -333,6 +334,7 @@ void InboundAudioStream::setSettings(const Settings& settings) { setWindowStarveThreshold(settings._windowStarveThreshold); setWindowSecondsForDesiredCalcOnTooManyStarves(settings._windowSecondsForDesiredCalcOnTooManyStarves); setWindowSecondsForDesiredReduction(settings._windowSecondsForDesiredReduction); + setRepetitionWithFade(settings._repetitionWithFade); } void InboundAudioStream::setDynamicJitterBuffers(bool dynamicJitterBuffers) { diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index dac1bb138c..f41d9255ff 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -43,6 +43,7 @@ const bool DEFAULT_USE_STDEV_FOR_JITTER_CALC = false; const int DEFAULT_WINDOW_STARVE_THRESHOLD = 3; const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES = 50; const int DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION = 10; +const bool DEFAULT_REPETITION_WITH_FADE = true; class InboundAudioStream : public NodeData { Q_OBJECT @@ -56,19 +57,21 @@ public: _useStDevForJitterCalc(DEFAULT_USE_STDEV_FOR_JITTER_CALC), _windowStarveThreshold(DEFAULT_WINDOW_STARVE_THRESHOLD), _windowSecondsForDesiredCalcOnTooManyStarves(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES), - _windowSecondsForDesiredReduction(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION) + _windowSecondsForDesiredReduction(DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION), + _repetitionWithFade(DEFAULT_REPETITION_WITH_FADE) {} Settings(int maxFramesOverDesired, bool dynamicJitterBuffers, int staticDesiredJitterBufferFrames, bool useStDevForJitterCalc, int windowStarveThreshold, int windowSecondsForDesiredCalcOnTooManyStarves, - int _windowSecondsForDesiredReduction) + int _windowSecondsForDesiredReduction, bool repetitionWithFade) : _maxFramesOverDesired(maxFramesOverDesired), _dynamicJitterBuffers(dynamicJitterBuffers), _staticDesiredJitterBufferFrames(staticDesiredJitterBufferFrames), _useStDevForJitterCalc(useStDevForJitterCalc), _windowStarveThreshold(windowStarveThreshold), _windowSecondsForDesiredCalcOnTooManyStarves(windowSecondsForDesiredCalcOnTooManyStarves), - _windowSecondsForDesiredReduction(windowSecondsForDesiredCalcOnTooManyStarves) + _windowSecondsForDesiredReduction(windowSecondsForDesiredCalcOnTooManyStarves), + _repetitionWithFade(repetitionWithFade) {} // max number of frames over desired in the ringbuffer. @@ -86,6 +89,10 @@ public: int _windowStarveThreshold; int _windowSecondsForDesiredCalcOnTooManyStarves; int _windowSecondsForDesiredReduction; + + // if true, the prev frame will be repeated (fading to silence) for dropped frames. + // otherwise, silence will be inserted. + bool _repetitionWithFade; }; public: @@ -116,7 +123,8 @@ public: void setWindowStarveThreshold(int windowStarveThreshold) { _starveThreshold = windowStarveThreshold; } void setWindowSecondsForDesiredCalcOnTooManyStarves(int windowSecondsForDesiredCalcOnTooManyStarves); void setWindowSecondsForDesiredReduction(int windowSecondsForDesiredReduction); - + void setRepetitionWithFade(bool repetitionWithFade) { _repetitionWithFade = repetitionWithFade; } + virtual AudioStreamStats getAudioStreamStats() const; @@ -234,6 +242,8 @@ protected: int _currentJitterBufferFrames; MovingMinMaxAvg _timeGapStatsForStatsPacket; + + bool _repetitionWithFade; }; #endif // hifi_InboundAudioStream_h From 96ee9e3f750fa0326956616c1c5239782f2a0823 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 11 Aug 2014 13:54:36 -0700 Subject: [PATCH 077/206] Work on Recording structure --- interface/src/Recorder.cpp | 70 +++++++++++++++++++++++++++++++++----- interface/src/Recorder.h | 40 ++++++++++++++++++++-- 2 files changed, 99 insertions(+), 11 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 85e2535141..87352fed98 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -11,6 +11,18 @@ #include "Recorder.h" +void RecordingFrame::setBlendshapeCoefficients(QVector blendshapeCoefficients) { + _blendshapeCoefficients = blendshapeCoefficients; +} + +void RecordingFrame::setJointRotations(QVector jointRotations) { + _jointRotations = jointRotations; +} + +void RecordingFrame::setTranslation(glm::vec3 translation) { + _translation = translation; +} + void Recording::addFrame(int timestamp, RecordingFrame &frame) { _timestamps << timestamp; _frames << frame; @@ -21,16 +33,9 @@ void Recording::clear() { _frames.clear(); } -void writeRecordingToFile(Recording& recording, QString file) { - qDebug() << "Writing recording to " << file; +Recorder::Recorder(AvatarData* avatar) : _avatar(avatar) { } -Recording& readRecordingFromFile(QString file) { - qDebug() << "Reading recording from " << file; - return *(new Recording()); -} - - bool Recorder::isRecording() const { return _timer.isValid(); } @@ -58,4 +63,53 @@ void Recorder::saveToFile(QString file) { } writeRecordingToFile(_recording, file); +} + +void Recorder::record() { + qDebug() << "Recording " << _avatar; + RecordingFrame frame; + frame.setBlendshapeCoefficients(_avatar->_) +} + +Player::Player(AvatarData* avatar) : _avatar(avatar) { +} + +bool Player::isPlaying() const { + return _timer.isValid(); +} + +qint64 Player::elapsed() const { + if (isPlaying()) { + return _timer.elapsed(); + } else { + return 0; + } +} + +void Player::startPlaying() { + _timer.start(); +} + +void Player::stopPlaying() { + _timer.invalidate(); +} + +void Player::loadFromFile(QString file) { + _recording.clear(); + readRecordingFromFile(_recording, file); +} + +void Player::play() { + qDebug() << "Playing " << _avatar; +} + +void writeRecordingToFile(Recording& recording, QString file) { + // TODO + qDebug() << "Writing recording to " << file; +} + +Recording& readRecordingFromFile(Recording& recording, QString file) { + // TODO + qDebug() << "Reading recording from " << file; + return recording; } \ No newline at end of file diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index ffb013723c..2d32e4a9b0 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -17,8 +17,14 @@ #include #include +#include +#include + +#include #include +class Recording; + /// Stores the different values associated to one recording frame class RecordingFrame { public: @@ -26,11 +32,19 @@ public: QVector getJointRotations() const { return _jointRotations; } glm::vec3 getTranslation() const { return _translation; } -private: +protected: + void setBlendshapeCoefficients(QVector blendshapeCoefficients); + void setJointRotations(QVector jointRotations); + void setTranslation(glm::vec3 translation); +private: QVector _blendshapeCoefficients; QVector _jointRotations; glm::vec3 _translation; + + friend class Recorder; + friend void writeRecordingToFile(Recording& recording, QString file); + friend Recording* readRecordingFromFile(QString file); }; /// Stores a recording @@ -51,15 +65,16 @@ private: QVector _frames; friend class Recorder; + friend class Player; friend void writeRecordingToFile(Recording& recording, QString file); - friend Recording& readRecordingFromFile(QString file); + friend Recording* readRecordingFromFile(QString file); }; /// Records a recording class Recorder { public: - Recorder(); + Recorder(AvatarData* avatar); bool isRecording() const; qint64 elapsed() const; @@ -68,18 +83,37 @@ public slots: void startRecording(); void stopRecording(); void saveToFile(QString file); + void record(); private: QElapsedTimer _timer; Recording _recording; + + AvatarData* _avatar; }; /// Plays back a recording class Player { public: + Player(AvatarData* avatar); + + bool isPlaying() const; + qint64 elapsed() const; + +public slots: + void startPlaying(); + void stopPlaying(); + void loadFromFile(QString file); + void play(); private: + QElapsedTimer _timer; Recording _recording; + + AvatarData* _avatar; }; +void writeRecordingToFile(Recording& recording, QString file); +Recording& readRecordingFromFile(Recording& recording, QString file); + #endif // hifi_Recorder_h \ No newline at end of file From e276d15ed4a82070cdedd9cdeef870ca4ef92d1d Mon Sep 17 00:00:00 2001 From: wangyix Date: Mon, 11 Aug 2014 16:25:43 -0700 Subject: [PATCH 078/206] repetition-with-fade implemented; testing interface crash --- assignment-client/src/audio/AudioMixer.cpp | 37 ++++-- .../resources/web/settings/describe.json | 4 +- interface/src/Audio.cpp | 111 ++++++++++++++---- interface/src/Audio.h | 17 ++- libraries/audio/src/AudioRingBuffer.cpp | 42 +++++++ libraries/audio/src/AudioRingBuffer.h | 6 +- libraries/audio/src/InboundAudioStream.cpp | 46 +++++++- libraries/audio/src/InboundAudioStream.h | 11 +- .../audio/src/MixedProcessedAudioStream.cpp | 42 ++++--- .../audio/src/MixedProcessedAudioStream.h | 20 ++-- 10 files changed, 261 insertions(+), 75 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index bd8877a128..509a965bf4 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -95,6 +95,26 @@ const float ATTENUATION_EPSILON_DISTANCE = 0.1f; int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd, AvatarAudioStream* listeningNodeStream) { + // If repetition with fade is enabled: + // If streamToAdd could not provide a frame (it was starved), then we'll mix its previously-mixed frame + // This is preferable to not mixing it at all since that's equivalent to inserting silence. + // Basically, we'll repeat that last frame until it has a frame to mix. Depending on how many times + // we've repeated that frame in a row, we'll gradually fade that repeated frame into silence. + // This improves the perceived quality of the audio slightly. + + float repeatedFrameFadeFactor = 1.0f; + + if (!streamToAdd->lastPopSucceeded()) { + if (_streamSettings._repetitionWithFade) { + repeatedFrameFadeFactor = calculateRepeatedFrameFadeFactor(streamToAdd->getConsecutiveNotMixedCount() - 1); + if (repeatedFrameFadeFactor == 0.0f) { + return 0; + } + } else { + return 0; + } + } + float bearingRelativeAngleToSource = 0.0f; float attenuationCoefficient = 1.0f; int numSamplesDelay = 0; @@ -216,12 +236,13 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* int delayedChannelIndex = 0; const int SINGLE_STEREO_OFFSET = 2; + float attenuationAndFade = attenuationCoefficient * repeatedFrameFadeFactor; for (int s = 0; s < NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; s += 4) { // setup the int16_t variables for the two sample sets - correctStreamSample[0] = streamPopOutput[s / 2] * attenuationCoefficient; - correctStreamSample[1] = streamPopOutput[(s / 2) + 1] * attenuationCoefficient; + correctStreamSample[0] = streamPopOutput[s / 2] * attenuationAndFade; + correctStreamSample[1] = streamPopOutput[(s / 2) + 1] * attenuationAndFade; delayedChannelIndex = s + (numSamplesDelay * 2) + delayedChannelOffset; @@ -237,7 +258,7 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* if (numSamplesDelay > 0) { // if there was a sample delay for this stream, we need to pull samples prior to the popped output // to stick at the beginning - float attenuationAndWeakChannelRatio = attenuationCoefficient * weakChannelAmplitudeRatio; + float attenuationAndWeakChannelRatioAndFade = attenuationCoefficient * weakChannelAmplitudeRatio * repeatedFrameFadeFactor; AudioRingBuffer::ConstIterator delayStreamPopOutput = streamPopOutput - numSamplesDelay; // TODO: delayStreamPopOutput may be inside the last frame written if the ringbuffer is completely full @@ -245,7 +266,7 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* for (int i = 0; i < numSamplesDelay; i++) { int parentIndex = i * 2; - _clientSamples[parentIndex + delayedChannelOffset] += *delayStreamPopOutput * attenuationAndWeakChannelRatio; + _clientSamples[parentIndex + delayedChannelOffset] += *delayStreamPopOutput * attenuationAndWeakChannelRatioAndFade; ++delayStreamPopOutput; } } @@ -256,8 +277,10 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* attenuationCoefficient = 1.0f; } + float attenuationAndFade = attenuationCoefficient * repeatedFrameFadeFactor; + for (int s = 0; s < NETWORK_BUFFER_LENGTH_SAMPLES_STEREO; s++) { - _clientSamples[s] = glm::clamp(_clientSamples[s] + (int)(streamPopOutput[s / stereoDivider] * attenuationCoefficient), + _clientSamples[s] = glm::clamp(_clientSamples[s] + (int)(streamPopOutput[s / stereoDivider] * attenuationAndFade), MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); } } @@ -285,7 +308,6 @@ int AudioMixer::prepareMixForListeningNode(Node* node) { PositionalAudioStream* otherNodeStream = i.value(); if ((*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) - && otherNodeStream->lastPopSucceeded() && otherNodeStream->getLastPopOutputFrameLoudness() > 0.0f) { //&& otherNodeStream->getLastPopOutputTrailingLoudness() > 0.0f) { @@ -627,6 +649,7 @@ void AudioMixer::run() { } // send mixed audio packet + if (nodeData->getOutgoingSequenceNumber() % 100 < 50) nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); @@ -654,6 +677,4 @@ void AudioMixer::run() { usleep(usecToSleep); } } - - delete[] clientMixBuffer; } diff --git a/domain-server/resources/web/settings/describe.json b/domain-server/resources/web/settings/describe.json index db2809bffb..cfb7e1ed79 100644 --- a/domain-server/resources/web/settings/describe.json +++ b/domain-server/resources/web/settings/describe.json @@ -7,7 +7,7 @@ "type": "checkbox", "label": "Dynamic Jitter Buffers", "help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing", - "default": true + "default": false }, "B-static-desired-jitter-buffer-frames": { "label": "Static Desired Jitter Buffer Frames", @@ -49,7 +49,7 @@ "type": "checkbox", "label": "Repetition with Fade:", "help": "If enabled, dropped frames and mixing during starves will repeat the last frame, eventually fading to silence", - "default": true + "default": false }, "Z-unattenuated-zone": { "label": "Unattenuated Zone", diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 8aac32849e..0e481e15c2 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -105,6 +105,7 @@ Audio::Audio(QObject* parent) : _scopeInput(0), _scopeOutputLeft(0), _scopeOutputRight(0), + _scopeLastFrame(), _statsEnabled(false), _statsShowInjectedStreams(false), _outgoingAvatarAudioSequenceNumber(0), @@ -113,15 +114,17 @@ Audio::Audio(QObject* parent) : _audioOutputMsecsUnplayedStats(1, FRAMES_AVAILABLE_STATS_WINDOW_SECONDS), _lastSentAudioPacket(0), _packetSentTimeGaps(1, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS), - _audioOutputIODevice(*this) + _audioOutputIODevice(_receivedAudioStream) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); // Create the noise sample array _noiseSampleFrames = new float[NUMBER_OF_NOISE_SAMPLE_FRAMES]; - connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &Audio::processReceivedAudioStreamSamples, Qt::DirectConnection); - connect(&_receivedAudioStream, &MixedProcessedAudioStream::dataParsed, this, &Audio::updateScopeBuffers, Qt::DirectConnection); + connect(&_receivedAudioStream, &MixedProcessedAudioStream::addedSilence, this, &Audio::addStereoSilenceToScope, Qt::DirectConnection); + connect(&_receivedAudioStream, &MixedProcessedAudioStream::addedLastFrameRepeatedWithFade, this, &Audio::addLastFrameRepeatedWithFadeToScope, Qt::DirectConnection); + connect(&_receivedAudioStream, &MixedProcessedAudioStream::addedStereoSamples, this, &Audio::addStereoSamplesToScope, Qt::DirectConnection); + connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &Audio::processReceivedSamples, Qt::DirectConnection); } void Audio::init(QGLWidget *parent) { @@ -657,9 +660,7 @@ void Audio::handleAudioInput() { if (!_isStereoInput && _scopeEnabled && !_scopeEnabledPause) { unsigned int numMonoAudioChannels = 1; unsigned int monoAudioChannel = 0; - addBufferToScope(_scopeInput, _scopeInputOffset, networkAudioSamples, monoAudioChannel, numMonoAudioChannels); - _scopeInputOffset += NETWORK_SAMPLES_PER_FRAME; - _scopeInputOffset %= _samplesPerScope; + _scopeInputOffset = addBufferToScope(_scopeInput, _scopeInputOffset, networkAudioSamples, NETWORK_SAMPLES_PER_FRAME, monoAudioChannel, numMonoAudioChannels); } NodeList* nodeList = NodeList::getInstance(); @@ -733,7 +734,48 @@ void Audio::handleAudioInput() { } } -void Audio::processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) { +const int STEREO_FACTOR = 2; + +void Audio::addStereoSilenceToScope(int silentSamplesPerChannel) { + if (!_scopeEnabled || _scopeEnabledPause) { + return; + } + addSilenceToScope(_scopeOutputLeft, _scopeOutputOffset, silentSamplesPerChannel); + _scopeOutputOffset = addSilenceToScope(_scopeOutputRight, _scopeOutputOffset, silentSamplesPerChannel); +} + +void Audio::addStereoSamplesToScope(const QByteArray& samples) { + if (!_scopeEnabled || _scopeEnabledPause) { + return; + } + const int16_t* samplesData = reinterpret_cast(samples.data()); + int samplesPerChannel = samples.size() / sizeof(int16_t) / STEREO_FACTOR; + + addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, samplesData, samplesPerChannel, 0, STEREO_FACTOR); + _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, samplesData, samplesPerChannel, 1, STEREO_FACTOR); + + _scopeLastFrame = samples.right(NETWORK_BUFFER_LENGTH_BYTES_STEREO); +} + +void Audio::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { + printf("addLastFrameRepeatedWithFadeToScope"); + const int16_t* lastFrameData = reinterpret_cast(_scopeLastFrame.data()); + + int samplesRemaining = samplesPerChannel; + int indexOfRepeat = 0; + do { + int samplesToWriteThisIteration = std::min(samplesRemaining, (int)NETWORK_SAMPLES_PER_FRAME); + float fade = calculateRepeatedFrameFadeFactor(indexOfRepeat); + printf("%f ", fade); + addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 0, STEREO_FACTOR, fade); + _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 1, STEREO_FACTOR, fade); + + samplesRemaining -= samplesToWriteThisIteration; + } while (samplesRemaining > 0); + printf("\n"); +} + +void Audio::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) { const int numNetworkOutputSamples = inputBuffer.size() / sizeof(int16_t); const int numDeviceOutputSamples = numNetworkOutputSamples * (_outputFormat.sampleRate() * _outputFormat.channelCount()) @@ -780,7 +822,7 @@ void Audio::processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QBy _desiredOutputFormat, _outputFormat); } -void Audio::updateScopeBuffers() { +/*void Audio::updateScopeBuffers() { if (_scopeEnabled && !_scopeEnabledPause) { unsigned int numAudioChannels = _desiredOutputFormat.channelCount(); const int16_t* samples = _receivedAudioStream.getNetworkSamples(); @@ -794,7 +836,7 @@ void Audio::updateScopeBuffers() { samples, audioChannel, numAudioChannels); audioChannel = 1; - addBufferToScope( + _scopeOutputOffset = addBufferToScope( _scopeOutputRight, _scopeOutputOffset, samples, audioChannel, numAudioChannels); @@ -806,7 +848,7 @@ void Audio::updateScopeBuffers() { } _receivedAudioStream.clearNetworkSamples(); -} +}*/ void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) { @@ -1259,12 +1301,15 @@ void Audio::freeScope() { } } -void Audio::addBufferToScope( - QByteArray* byteArray, unsigned int frameOffset, const int16_t* source, unsigned int sourceChannel, unsigned int sourceNumberOfChannels) { +int Audio::addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamplesPerChannel, + unsigned int sourceChannel, unsigned int sourceNumberOfChannels, float fade) { // Constant multiplier to map sample value to vertical size of scope float multiplier = (float)MULTIPLIER_SCOPE_HEIGHT / logf(2.0f); + // Used to scale each sample. (logf(sample) + fadeOffset) is same as logf(sample * fade). + float fadeOffset = logf(fade); + // Temporary variable receives sample value float sample; @@ -1275,17 +1320,41 @@ void Audio::addBufferToScope( // Short int pointer to mapped samples in byte array int16_t* destination = (int16_t*) byteArray->data(); - for (unsigned int i = 0; i < NETWORK_SAMPLES_PER_FRAME; i++) { + for (int i = 0; i < sourceSamplesPerChannel; i++) { sample = (float)source[i * sourceNumberOfChannels + sourceChannel]; - if (sample > 0) { - value = (int16_t)(multiplier * logf(sample)); - } else if (sample < 0) { - value = (int16_t)(-multiplier * logf(-sample)); + if (sample > 1) { + value = (int16_t)(multiplier * (logf(sample) + fadeOffset)); + } else if (sample < -1) { + value = (int16_t)(-multiplier * (logf(-sample) + fadeOffset)); } else { value = 0; } - destination[i + frameOffset] = value; + destination[frameOffset] = value; + frameOffset = (frameOffset == _samplesPerScope - 1) ? 0 : frameOffset + 1; } + return frameOffset; +} + +int Audio::addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples) { + + QMutexLocker lock(&_guard); + // Short int pointer to mapped samples in byte array + int16_t* destination = (int16_t*)byteArray->data(); + + if (silentSamples >= _samplesPerScope) { + memset(destination, 0, byteArray->size()); + return frameOffset; + } + + int samplesToBufferEnd = _samplesPerScope - frameOffset; + if (silentSamples > samplesToBufferEnd) { + memset(destination + frameOffset, 0, samplesToBufferEnd * sizeof(int16_t)); + memset(destination, 0, silentSamples - samplesToBufferEnd * sizeof(int16_t)); + } else { + memset(destination + frameOffset, 0, silentSamples * sizeof(int16_t)); + } + + return (frameOffset + silentSamples) % _samplesPerScope; } void Audio::renderStats(const float* color, int width, int height) { @@ -1761,13 +1830,11 @@ float Audio::getInputRingBufferMsecsAvailable() const { } qint64 Audio::AudioOutputIODevice::readData(char * data, qint64 maxSize) { - MixedProcessedAudioStream& receivedAUdioStream = _parent._receivedAudioStream; - int samplesRequested = maxSize / sizeof(int16_t); int samplesPopped; int bytesWritten; - if ((samplesPopped = receivedAUdioStream.popSamples(samplesRequested, false)) > 0) { - AudioRingBuffer::ConstIterator lastPopOutput = receivedAUdioStream.getLastPopOutput(); + if ((samplesPopped = _receivedAudioStream.popSamples(samplesRequested, false)) > 0) { + AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput(); lastPopOutput.readSamples((int16_t*)data, samplesPopped); bytesWritten = samplesPopped * sizeof(int16_t); } else { diff --git a/interface/src/Audio.h b/interface/src/Audio.h index c080557576..a8ba46a35e 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -48,14 +48,14 @@ public: class AudioOutputIODevice : public QIODevice { public: - AudioOutputIODevice(Audio& parent) : _parent(parent) {}; + AudioOutputIODevice(MixedProcessedAudioStream& receivedAudioStream) : _receivedAudioStream(receivedAudioStream) {}; void start() { open(QIODevice::ReadOnly); } void stop() { close(); } qint64 readData(char * data, qint64 maxSize); qint64 writeData(const char * data, qint64 maxSize) { return 0; } private: - Audio& _parent; + MixedProcessedAudioStream& _receivedAudioStream; }; @@ -105,8 +105,6 @@ public slots: void addReceivedAudioToStream(const QByteArray& audioByteArray); void parseAudioStreamStatsPacket(const QByteArray& packet); void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples); - void processReceivedAudioStreamSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); - void updateScopeBuffers(); void handleAudioInput(); void reset(); void resetStats(); @@ -123,6 +121,11 @@ public slots: void selectAudioScopeFiveFrames(); void selectAudioScopeTwentyFrames(); void selectAudioScopeFiftyFrames(); + + void addStereoSilenceToScope(int silentSamplesPerChannel); + void addLastFrameRepeatedWithFadeToScope(int samplesPerChannel); + void addStereoSamplesToScope(const QByteArray& samples); + void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); virtual void handleAudioByteArray(const QByteArray& audioByteArray); @@ -244,8 +247,9 @@ private: void reallocateScope(int frames); // Audio scope methods for data acquisition - void addBufferToScope( - QByteArray* byteArray, unsigned int frameOffset, const int16_t* source, unsigned int sourceChannel, unsigned int sourceNumberOfChannels); + int addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamples, + unsigned int sourceChannel, unsigned int sourceNumberOfChannels, float fade = 1.0f); + int addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples); // Audio scope methods for rendering void renderBackground(const float* color, int x, int y, int width, int height); @@ -272,6 +276,7 @@ private: QByteArray* _scopeInput; QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputRight; + QByteArray _scopeLastFrame; #ifdef _WIN32 static const unsigned int STATS_WIDTH = 1500; #else diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index d9cb34ac1b..31c714a1cc 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -224,3 +224,45 @@ float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const { float AudioRingBuffer::getNextOutputFrameLoudness() const { return getFrameLoudness(_nextOutput); } + +int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) { + int samplesToCopy = std::min(maxSamples, _sampleCapacity); + int samplesRoomFor = _sampleCapacity - samplesAvailable(); + if (samplesToCopy > samplesRoomFor) { + // there's not enough room for this write. erase old data to make room for this new data + int samplesToDelete = samplesToCopy - samplesRoomFor; + _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); + _overflowCount++; + qDebug() << "Overflowed ring buffer! Overwriting old data"; + } + + int16_t* bufferLast = _buffer + _bufferLength - 1; + for (int i = 0; i < samplesToCopy; i++) { + *_endOfLastWrite = *source; + _endOfLastWrite = (_endOfLastWrite == bufferLast) ? _buffer : _endOfLastWrite + 1; + ++source; + } + + return samplesToCopy; +} + +int AudioRingBuffer::writeSamplesWithFade(ConstIterator source, int maxSamples, float fade) { + int samplesToCopy = std::min(maxSamples, _sampleCapacity); + int samplesRoomFor = _sampleCapacity - samplesAvailable(); + if (samplesToCopy > samplesRoomFor) { + // there's not enough room for this write. erase old data to make room for this new data + int samplesToDelete = samplesToCopy - samplesRoomFor; + _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); + _overflowCount++; + qDebug() << "Overflowed ring buffer! Overwriting old data"; + } + + int16_t* bufferLast = _buffer + _bufferLength - 1; + for (int i = 0; i < samplesToCopy; i++) { + *_endOfLastWrite = (int16_t)((float)(*source) * fade); + _endOfLastWrite = (_endOfLastWrite == bufferLast) ? _buffer : _endOfLastWrite + 1; + ++source; + } + + return samplesToCopy; +} diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index be4dcaf545..65e6947115 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -189,8 +189,12 @@ public: }; ConstIterator nextOutput() const { return ConstIterator(_buffer, _bufferLength, _nextOutput); } - + ConstIterator lastFrameWritten() const { return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples; } + float getFrameLoudness(ConstIterator frameStart) const; + + int writeSamples(ConstIterator source, int maxSamples); + int writeSamplesWithFade(ConstIterator source, int maxSamples, float fade); }; #endif // hifi_AudioRingBuffer_h \ No newline at end of file diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 39cd544b15..70a4086eb0 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -162,8 +162,6 @@ int InboundAudioStream::parseData(const QByteArray& packet) { framesAvailableChanged(); - emit dataParsed(); - return readBytes; } @@ -418,9 +416,31 @@ void InboundAudioStream::packetReceivedUpdateTimingStats() { } int InboundAudioStream::writeSamplesForDroppedPackets(int networkSamples) { + if (_repetitionWithFade) { + return writeLastFrameRepeatedWithFade(networkSamples); + } return writeDroppableSilentSamples(networkSamples); } +int InboundAudioStream::writeLastFrameRepeatedWithFade(int samples) { + AudioRingBuffer::ConstIterator frameToRepeat = _ringBuffer.lastFrameWritten(); + int frameSize = _ringBuffer.getNumFrameSamples(); + int samplesToWrite = samples; + int indexOfRepeat = 0; + do { + int samplesToWriteThisIteration = std::min(samplesToWrite, frameSize); + float fade = calculateRepeatedFrameFadeFactor(indexOfRepeat); + if (fade == 1.0f) { + samplesToWrite -= _ringBuffer.writeSamples(frameToRepeat, samplesToWriteThisIteration); + } else { + samplesToWrite -= _ringBuffer.writeSamplesWithFade(frameToRepeat, samplesToWriteThisIteration, fade); + } + indexOfRepeat++; + } while (samplesToWrite > 0); + + return samples; +} + float InboundAudioStream::getLastPopOutputFrameLoudness() const { return _ringBuffer.getFrameLoudness(_lastPopOutput); } @@ -448,3 +468,25 @@ AudioStreamStats InboundAudioStream::getAudioStreamStats() const { return streamStats; } + +float calculateRepeatedFrameFadeFactor(int indexOfRepeat) { + // fade factor scheme is from this paper: + // http://inst.eecs.berkeley.edu/~ee290t/sp04/lectures/packet_loss_recov_paper11.pdf + + const float INITIAL_MSECS_NO_FADE = 20.0f; + const float MSECS_FADE_TO_ZERO = 320.0f; + + const float INITIAL_FRAMES_NO_FADE = INITIAL_MSECS_NO_FADE * (float)USECS_PER_MSEC / (float)BUFFER_SEND_INTERVAL_USECS; + const float FRAMES_FADE_TO_ZERO = MSECS_FADE_TO_ZERO * (float)USECS_PER_MSEC / (float)BUFFER_SEND_INTERVAL_USECS; + + const float SAMPLE_RANGE = std::numeric_limits::max(); + + if (indexOfRepeat <= INITIAL_FRAMES_NO_FADE) { + return 1.0f; + } else if (indexOfRepeat <= INITIAL_FRAMES_NO_FADE + FRAMES_FADE_TO_ZERO) { + return pow(SAMPLE_RANGE, -(indexOfRepeat - INITIAL_FRAMES_NO_FADE) / FRAMES_FADE_TO_ZERO); + + //return 1.0f - ((indexOfRepeat - INITIAL_FRAMES_NO_FADE) / FRAMES_FADE_TO_ZERO); + } + return 0.0f; +} diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index f41d9255ff..f8413f8d75 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -33,7 +33,7 @@ const int STATS_FOR_STATS_PACKET_WINDOW_SECONDS = 30; // this controls the window size of the time-weighted avg of frames available. Every time the window fills up, // _currentJitterBufferFrames is updated with the time-weighted avg and the running time-weighted avg is reset. -const int FRAMES_AVAILABLE_STAT_WINDOW_USECS = 2 * USECS_PER_SECOND; +const int FRAMES_AVAILABLE_STAT_WINDOW_USECS = 10 * USECS_PER_SECOND; // default values for members of the Settings struct const int DEFAULT_MAX_FRAMES_OVER_DESIRED = 10; @@ -157,9 +157,6 @@ public: int getPacketsReceived() const { return _incomingSequenceNumberStats.getReceived(); } -signals: - void dataParsed(); - public slots: /// This function should be called every second for all the stats to function properly. If dynamic jitter buffers /// is enabled, those stats are used to calculate _desiredJitterBufferFrames. @@ -191,6 +188,10 @@ protected: /// writes silent samples to the buffer that may be dropped to reduce latency caused by the buffer virtual int writeDroppableSilentSamples(int silentSamples); + + /// writes the last written frame repeatedly, gradually fading to silence. + /// used for writing samples for dropped packets. + virtual int writeLastFrameRepeatedWithFade(int samples); protected: @@ -246,4 +247,6 @@ protected: bool _repetitionWithFade; }; +float calculateRepeatedFrameFadeFactor(int indexOfRepeat); + #endif // hifi_InboundAudioStream_h diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index 5693af7c6e..4b28e2f2c1 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -11,9 +11,10 @@ #include "MixedProcessedAudioStream.h" +static const int STEREO_FACTOR = 2; + MixedProcessedAudioStream::MixedProcessedAudioStream(int numFrameSamples, int numFramesCapacity, const InboundAudioStream::Settings& settings) - : InboundAudioStream(numFrameSamples, numFramesCapacity, settings), - _networkSamplesWritten(0) + : InboundAudioStream(numFrameSamples, numFramesCapacity, settings) { } @@ -23,30 +24,33 @@ void MixedProcessedAudioStream::outputFormatChanged(int outputFormatChannelCount _ringBuffer.resizeForFrameSize(deviceOutputFrameSize); } -int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples) { - - memcpy(&_networkSamples[_networkSamplesWritten], packetAfterStreamProperties.data(), packetAfterStreamProperties.size()); - _networkSamplesWritten += packetAfterStreamProperties.size() / sizeof(int16_t); - - QByteArray outputBuffer; - emit processSamples(packetAfterStreamProperties, outputBuffer); - - _ringBuffer.writeData(outputBuffer.data(), outputBuffer.size()); - - return packetAfterStreamProperties.size(); -} - int MixedProcessedAudioStream::writeDroppableSilentSamples(int silentSamples) { int deviceSilentSamplesWritten = InboundAudioStream::writeDroppableSilentSamples(networkToDeviceSamples(silentSamples)); - int networkSilentSamplesWritten = deviceToNetworkSamples(deviceSilentSamplesWritten); - memset(&_networkSamples[_networkSamplesWritten], 0, networkSilentSamplesWritten * sizeof(int16_t)); - _networkSamplesWritten += networkSilentSamplesWritten; + emit addedSilence(deviceToNetworkSamples(deviceSilentSamplesWritten) / STEREO_FACTOR); return deviceSilentSamplesWritten; } -static const int STEREO_FACTOR = 2; +int MixedProcessedAudioStream::writeLastFrameRepeatedWithFade(int samples) { + int deviceSamplesWritten = InboundAudioStream::writeLastFrameRepeatedWithFade(networkToDeviceSamples(samples)); + + emit addedLastFrameRepeatedWithFade(deviceToNetworkSamples(deviceSamplesWritten) / STEREO_FACTOR); + + return deviceSamplesWritten; +} + +int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples) { + + emit addedStereoSamples(packetAfterStreamProperties); + + QByteArray outputBuffer; + emit processSamples(packetAfterStreamProperties, outputBuffer); + + _ringBuffer.writeData(outputBuffer.data(), outputBuffer.size()); + + return packetAfterStreamProperties.size(); +} int MixedProcessedAudioStream::networkToDeviceSamples(int networkSamples) { return networkSamples * _outputFormatChannelsTimesSampleRate / (STEREO_FACTOR * SAMPLE_RATE); diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index b85637a288..fd1f93a6a1 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -14,6 +14,8 @@ #include "InboundAudioStream.h" +class Audio; + class MixedProcessedAudioStream : public InboundAudioStream { Q_OBJECT public: @@ -21,30 +23,26 @@ public: signals: + void addedSilence(int silentSamplesPerChannel); + void addedLastFrameRepeatedWithFade(int samplesPerChannel); + void addedStereoSamples(const QByteArray& samples); + void processSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); public: void outputFormatChanged(int outputFormatChannelCountTimesSampleRate); - const int16_t* getNetworkSamples() const { return _networkSamples; } - int getNetworkSamplesWritten() const { return _networkSamplesWritten; } - - void clearNetworkSamples() { _networkSamplesWritten = 0; } - protected: - int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); int writeDroppableSilentSamples(int silentSamples); + int writeLastFrameRepeatedWithFade(int samples); + int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); private: int networkToDeviceSamples(int networkSamples); int deviceToNetworkSamples(int deviceSamples); + private: int _outputFormatChannelsTimesSampleRate; - - // this buffer keeps a copy of the network samples written during parseData() for the sole purpose - // of passing it on to the audio scope - int16_t _networkSamples[10 * NETWORK_BUFFER_LENGTH_SAMPLES_STEREO]; - int _networkSamplesWritten; }; #endif // hifi_MixedProcessedAudioStream_h From 6437ca1b5073070c00bff3b6976882c0a09cf021 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 11 Aug 2014 16:52:06 -0700 Subject: [PATCH 079/206] Update commands for speechControl.js --- examples/speechControl.js | 197 +++++++++++++++++++++++++++++--------- 1 file changed, 150 insertions(+), 47 deletions(-) diff --git a/examples/speechControl.js b/examples/speechControl.js index aee2f31fd0..e2fb9699b7 100644 --- a/examples/speechControl.js +++ b/examples/speechControl.js @@ -9,90 +9,193 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var ACCELERATION = 80; +var STEP_DURATION = 1.0; // Duration of a step command in seconds +var TURN_DEGREES = 90; +var SLIGHT_TURN_DEGREES = 45; +var TURN_AROUND_DEGREES = 180; +var TURN_RATE = 90; // Turn rate in degrees per second + + +/*****************************************************************************/ +/** COMMANDS *****************************************************************/ var CMD_MOVE_FORWARD = "Move forward"; var CMD_MOVE_BACKWARD = "Move backward"; var CMD_MOVE_UP = "Move up"; var CMD_MOVE_DOWN = "Move down"; -var CMD_STRAFE_LEFT = "Strafe left"; -var CMD_STRAFE_RIGHT = "Strafe right"; +var CMD_MOVE_LEFT = "Move left"; +var CMD_MOVE_RIGHT = "Move right"; + +var CMD_STEP_FORWARD = "Step forward"; +var CMD_STEP_BACKWARD = "Step backward"; +var CMD_STEP_LEFT = "Step left"; +var CMD_STEP_RIGHT = "Step right"; +var CMD_STEP_UP = "Step up"; +var CMD_STEP_DOWN = "Step down"; + +var CMD_TURN_LEFT = "Turn left"; +var CMD_TURN_SLIGHT_LEFT = "Turn slight left"; +var CMD_TURN_RIGHT = "Turn right"; +var CMD_TURN_SLIGHT_RIGHT = "Turn slight right"; +var CMD_TURN_AROUND = "Turn around"; + var CMD_STOP = "Stop"; var CMD_SHOW_COMMANDS = "Show commands"; -var commands = [ +var MOVE_COMMANDS = [ CMD_MOVE_FORWARD, CMD_MOVE_BACKWARD, CMD_MOVE_UP, CMD_MOVE_DOWN, - CMD_STRAFE_LEFT, - CMD_STRAFE_RIGHT, + CMD_MOVE_LEFT, + CMD_MOVE_RIGHT, +]; + +var STEP_COMMANDS = [ + CMD_STEP_FORWARD, + CMD_STEP_BACKWARD, + CMD_STEP_UP, + CMD_STEP_DOWN, + CMD_STEP_LEFT, + CMD_STEP_RIGHT, +]; + +var TURN_COMMANDS = [ + CMD_TURN_LEFT, + CMD_TURN_SLIGHT_LEFT, + CMD_TURN_RIGHT, + CMD_TURN_SLIGHT_RIGHT, + CMD_TURN_AROUND, +]; + +var OTHER_COMMANDS = [ CMD_STOP, CMD_SHOW_COMMANDS, ]; -var moveForward = 0; -var moveRight = 0; -var turnRight = 0; -var moveUp = 0; +var ALL_COMMANDS = [] + .concat(MOVE_COMMANDS) + .concat(STEP_COMMANDS) + .concat(TURN_COMMANDS) + .concat(OTHER_COMMANDS); -function commandRecognized(command) { - if (command === CMD_MOVE_FORWARD) { - accel = { x: 0, y: 0, z: 1 }; - } else if (command == CMD_MOVE_BACKWARD) { - accel = { x: 0, y: 0, z: -1 }; - } else if (command === CMD_MOVE_UP) { - accel = { x: 0, y: 1, z: 0 }; - } else if (command == CMD_MOVE_DOWN) { - accel = { x: 0, y: -1, z: 0 }; - } else if (command == CMD_STRAFE_LEFT) { - accel = { x: -1, y: 0, z: 0 }; - } else if (command == CMD_STRAFE_RIGHT) { - accel = { x: 1, y: 0, z: 0 }; +/** END OF COMMANDS **********************************************************/ +/*****************************************************************************/ + + +var currentCommandFunc = null; + +function handleCommandRecognized(command) { + if (MOVE_COMMANDS.indexOf(command) > -1 || STEP_COMMANDS.indexOf(command) > -1) { + // If this is a STEP_* command, we will want to countdown the duration + // of time to move. MOVE_* commands don't stop. + var timeRemaining = MOVE_COMMANDS.indexOf(command) > -1 ? 0 : STEP_DURATION; + var accel = { x: 0, y: 0, z: 0 }; + + if (command == CMD_MOVE_FORWARD || command == CMD_STEP_FORWARD) { + accel = { x: 0, y: 0, z: 1 }; + } else if (command == CMD_MOVE_BACKWARD || command == CMD_STEP_BACKWARD) { + accel = { x: 0, y: 0, z: -1 }; + } else if (command === CMD_MOVE_UP || command == CMD_STEP_UP) { + accel = { x: 0, y: 1, z: 0 }; + } else if (command == CMD_MOVE_DOWN || command == CMD_STEP_DOWN) { + accel = { x: 0, y: -1, z: 0 }; + } else if (command == CMD_MOVE_LEFT || command == CMD_STEP_LEFT) { + accel = { x: -1, y: 0, z: 0 }; + } else if (command == CMD_MOVE_RIGHT || command == CMD_STEP_RIGHT) { + accel = { x: 1, y: 0, z: 0 }; + } + + currentCommandFunc = function(dt) { + if (timeRemaining > 0 && dt >= timeRemaining) { + dt = timeRemaining; + } + + var headOrientation = MyAvatar.headOrientation; + var front = Quat.getFront(headOrientation); + var right = Quat.getRight(headOrientation); + var up = Quat.getUp(headOrientation); + + var thrust = Vec3.multiply(front, accel.z * ACCELERATION); + thrust = Vec3.sum(thrust, Vec3.multiply(right, accel.x * ACCELERATION)); + thrust = Vec3.sum(thrust, Vec3.multiply(up, accel.y * ACCELERATION)); + MyAvatar.addThrust(thrust); + + if (timeRemaining > 0) { + timeRemaining -= dt; + return timeRemaining > 0; + } + + return true; + }; + } else if (TURN_COMMANDS.indexOf(command) > -1) { + var degreesRemaining; + var sign; + if (command == CMD_TURN_LEFT) { + sign = 1; + degreesRemaining = TURN_DEGREES; + } else if (command == CMD_TURN_RIGHT) { + sign = -1; + degreesRemaining = TURN_DEGREES; + } else if (command == CMD_TURN_SLIGHT_LEFT) { + sign = 1; + degreesRemaining = SLIGHT_TURN_DEGREES; + } else if (command == CMD_TURN_SLIGHT_RIGHT) { + sign = -1; + degreesRemaining = SLIGHT_TURN_DEGREES; + } else if (command == CMD_TURN_AROUND) { + sign = 1; + degreesRemaining = TURN_AROUND_DEGREES; + } + currentCommandFunc = function(dt) { + // Determine how much to turn by + var turnAmount = TURN_RATE * dt; + if (turnAmount > degreesRemaining) { + turnAmount = degreesRemaining; + } + + // Apply turn + var orientation = MyAvatar.orientation; + var deltaOrientation = Quat.fromPitchYawRollDegrees(0, sign * turnAmount, 0); + MyAvatar.orientation = Quat.multiply(orientation, deltaOrientation); + + degreesRemaining -= turnAmount; + return turnAmount > 0; + } } else if (command == CMD_STOP) { - accel = { x: 0, y: 0, z: 0 }; + currentCommandFunc = null; } else if (command == CMD_SHOW_COMMANDS) { var msg = ""; - for (var i = 0; i < commands.length; i++) { - msg += commands[i] + "\n"; + for (var i = 0; i < ALL_COMMANDS.length; i++) { + msg += ALL_COMMANDS[i] + "\n"; } Window.alert(msg); } } -var accel = { x: 0, y: 0, z: 0 }; - -var ACCELERATION = 80; -var initialized = false; - function update(dt) { - var headOrientation = MyAvatar.headOrientation; - var front = Quat.getFront(headOrientation); - var right = Quat.getRight(headOrientation); - var up = Quat.getUp(headOrientation); - - var thrust = Vec3.multiply(front, accel.z * ACCELERATION); - thrust = Vec3.sum(thrust, Vec3.multiply(right, accel.x * ACCELERATION)); - thrust = Vec3.sum(thrust, Vec3.multiply(up, accel.y * ACCELERATION)); - // print(thrust.x + ", " + thrust.y + ", " + thrust.z); - MyAvatar.addThrust(thrust); + if (currentCommandFunc) { + if (currentCommandFunc(dt) === false) { + currentCommandFunc = null; + } + } } function setup() { - for (var i = 0; i < commands.length; i++) { - SpeechRecognizer.addCommand(commands[i]); + for (var i = 0; i < ALL_COMMANDS.length; i++) { + SpeechRecognizer.addCommand(ALL_COMMANDS[i]); } - print("SETTING UP"); } function scriptEnding() { - print("ENDING"); - for (var i = 0; i < commands.length; i++) { - SpeechRecognizer.removeCommand(commands[i]); + for (var i = 0; i < ALL_COMMANDS.length; i++) { + SpeechRecognizer.removeCommand(ALL_COMMANDS[i]); } } Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); -SpeechRecognizer.commandRecognized.connect(commandRecognized); +SpeechRecognizer.commandRecognized.connect(handleCommandRecognized); setup(); From a31d53544b3a78313ac2f7bf5168abf668cf83ca Mon Sep 17 00:00:00 2001 From: wangyix Date: Mon, 11 Aug 2014 17:58:01 -0700 Subject: [PATCH 080/206] repetition-with-fade seems good; continue testing --- assignment-client/src/audio/AudioMixer.cpp | 1 - interface/src/Audio.cpp | 19 ++++++++++++++----- libraries/audio/src/InboundAudioStream.cpp | 5 +++-- .../audio/src/MixedProcessedAudioStream.cpp | 14 ++++++++------ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 509a965bf4..1372c92b1f 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -649,7 +649,6 @@ void AudioMixer::run() { } // send mixed audio packet - if (nodeData->getOutgoingSequenceNumber() % 100 < 50) nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 0e481e15c2..662fa8f2d6 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -740,8 +740,11 @@ void Audio::addStereoSilenceToScope(int silentSamplesPerChannel) { if (!_scopeEnabled || _scopeEnabledPause) { return; } + printf("\t Audio::addStereoSilenceToScope %d per channel\n", silentSamplesPerChannel); addSilenceToScope(_scopeOutputLeft, _scopeOutputOffset, silentSamplesPerChannel); _scopeOutputOffset = addSilenceToScope(_scopeOutputRight, _scopeOutputOffset, silentSamplesPerChannel); + + printf("\t end\n"); } void Audio::addStereoSamplesToScope(const QByteArray& samples) { @@ -750,29 +753,35 @@ void Audio::addStereoSamplesToScope(const QByteArray& samples) { } const int16_t* samplesData = reinterpret_cast(samples.data()); int samplesPerChannel = samples.size() / sizeof(int16_t) / STEREO_FACTOR; + printf("\t Audio::addStereoSamplesToScope %d samples per channel\n", samplesPerChannel); addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, samplesData, samplesPerChannel, 0, STEREO_FACTOR); _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, samplesData, samplesPerChannel, 1, STEREO_FACTOR); _scopeLastFrame = samples.right(NETWORK_BUFFER_LENGTH_BYTES_STEREO); + + printf("\t end\n"); } void Audio::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { - printf("addLastFrameRepeatedWithFadeToScope"); + printf("addLastFrameRepeatedWithFadeToScope %d per channel\n", samplesPerChannel); const int16_t* lastFrameData = reinterpret_cast(_scopeLastFrame.data()); int samplesRemaining = samplesPerChannel; int indexOfRepeat = 0; do { int samplesToWriteThisIteration = std::min(samplesRemaining, (int)NETWORK_SAMPLES_PER_FRAME); - float fade = calculateRepeatedFrameFadeFactor(indexOfRepeat); - printf("%f ", fade); + float fade = calculateRepeatedFrameFadeFactor(indexOfRepeat); + printf("%f ", fade, samplesToWriteThisIteration); addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 0, STEREO_FACTOR, fade); _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 1, STEREO_FACTOR, fade); + printf("scopeOutputOffset %d\n", _scopeOutputOffset); + samplesRemaining -= samplesToWriteThisIteration; + indexOfRepeat++; } while (samplesRemaining > 0); - printf("\n"); + printf("\t end\n"); } void Audio::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) { @@ -1755,7 +1764,7 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) // setup our general output device for audio-mixer audio _audioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); _audioOutput->setBufferSize(AUDIO_OUTPUT_BUFFER_SIZE_FRAMES * _outputFrameSize * sizeof(int16_t)); - qDebug() << "Ring Buffer capacity in frames: " << _audioOutput->bufferSize() / sizeof(int16_t) / (float)_outputFrameSize; + qDebug() << "Output Buffer capacity in frames: " << _audioOutput->bufferSize() / sizeof(int16_t) / (float)_outputFrameSize; _audioOutputIODevice.start(); _audioOutput->start(&_audioOutputIODevice); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 70a4086eb0..d10c9da05c 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -176,7 +176,6 @@ int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packet } int InboundAudioStream::writeDroppableSilentSamples(int silentSamples) { - // calculate how many silent frames we should drop. int samplesPerFrame = _ringBuffer.getNumFrameSamples(); int desiredJitterBufferFramesPlusPadding = _desiredJitterBufferFrames + DESIRED_JITTER_BUFFER_FRAMES_PADDING; @@ -198,7 +197,9 @@ int InboundAudioStream::writeDroppableSilentSamples(int silentSamples) { _framesAvailableStat.reset(); } - return _ringBuffer.addSilentSamples(silentSamples - numSilentFramesToDrop * samplesPerFrame); + int ret = _ringBuffer.addSilentSamples(silentSamples - numSilentFramesToDrop * samplesPerFrame); + + return ret; } int InboundAudioStream::popSamples(int maxSamples, bool allOrNothing, bool starveIfNoSamplesPopped) { diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index 4b28e2f2c1..844adf36b3 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -20,23 +20,25 @@ MixedProcessedAudioStream::MixedProcessedAudioStream(int numFrameSamples, int nu void MixedProcessedAudioStream::outputFormatChanged(int outputFormatChannelCountTimesSampleRate) { _outputFormatChannelsTimesSampleRate = outputFormatChannelCountTimesSampleRate; - int deviceOutputFrameSize = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * _outputFormatChannelsTimesSampleRate / SAMPLE_RATE; + int deviceOutputFrameSize = networkToDeviceSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO); _ringBuffer.resizeForFrameSize(deviceOutputFrameSize); } int MixedProcessedAudioStream::writeDroppableSilentSamples(int silentSamples) { + int deviceSilentSamplesWritten = InboundAudioStream::writeDroppableSilentSamples(networkToDeviceSamples(silentSamples)); - + emit addedSilence(deviceToNetworkSamples(deviceSilentSamplesWritten) / STEREO_FACTOR); return deviceSilentSamplesWritten; } int MixedProcessedAudioStream::writeLastFrameRepeatedWithFade(int samples) { + int deviceSamplesWritten = InboundAudioStream::writeLastFrameRepeatedWithFade(networkToDeviceSamples(samples)); emit addedLastFrameRepeatedWithFade(deviceToNetworkSamples(deviceSamplesWritten) / STEREO_FACTOR); - + return deviceSamplesWritten; } @@ -48,14 +50,14 @@ int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& emit processSamples(packetAfterStreamProperties, outputBuffer); _ringBuffer.writeData(outputBuffer.data(), outputBuffer.size()); - + return packetAfterStreamProperties.size(); } int MixedProcessedAudioStream::networkToDeviceSamples(int networkSamples) { - return networkSamples * _outputFormatChannelsTimesSampleRate / (STEREO_FACTOR * SAMPLE_RATE); + return (quint64)networkSamples * (quint64)_outputFormatChannelsTimesSampleRate / (quint64)(STEREO_FACTOR * SAMPLE_RATE); } int MixedProcessedAudioStream::deviceToNetworkSamples(int deviceSamples) { - return deviceSamples * (STEREO_FACTOR * SAMPLE_RATE) / _outputFormatChannelsTimesSampleRate; + return (quint64)deviceSamples * (quint64)(STEREO_FACTOR * SAMPLE_RATE) / (quint64)_outputFormatChannelsTimesSampleRate; } From 8565e93ba49b4bba0d44973f58bc81b727c34373 Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 12 Aug 2014 10:50:34 -0700 Subject: [PATCH 081/206] more test code (10% drop both directions) --- assignment-client/src/audio/AudioMixer.cpp | 1 + interface/src/Audio.cpp | 3 +-- libraries/script-engine/src/ScriptEngine.cpp | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 1372c92b1f..f402050265 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -649,6 +649,7 @@ void AudioMixer::run() { } // send mixed audio packet + if (rand() % 100 < 90) nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 662fa8f2d6..84b3911771 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -724,6 +724,7 @@ void Audio::handleAudioInput() { } int packetBytes = currentPacketPtr - audioDataPacket; + if (rand() % 100 < 90) nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; @@ -776,8 +777,6 @@ void Audio::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 0, STEREO_FACTOR, fade); _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 1, STEREO_FACTOR, fade); - printf("scopeOutputOffset %d\n", _scopeOutputOffset); - samplesRemaining -= samplesToWriteThisIteration; indexOfRepeat++; } while (samplesRemaining > 0); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2891055b65..58b0c90daa 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -519,6 +519,7 @@ void ScriptEngine::run() { memcpy(audioPacket.data() + numPreSequenceNumberBytes, &sequence, sizeof(quint16)); // send audio packet + if (rand() % 100 < 90) nodeList->writeDatagram(audioPacket, node); } } From 7f53ae0e4f9d0871976f371688a278d9a23eac71 Mon Sep 17 00:00:00 2001 From: wangyix Date: Tue, 12 Aug 2014 12:22:13 -0700 Subject: [PATCH 082/206] fixed warnings --- interface/src/Audio.cpp | 4 +--- libraries/audio/src/InboundAudioStream.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 84b3911771..9d00b4ffd6 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -672,8 +672,6 @@ void Audio::handleAudioInput() { glm::quat headOrientation = interfaceAvatar->getHead()->getFinalOrientationInWorldFrame(); quint8 isStereo = _isStereoInput ? 1 : 0; - int numPacketBytes = 0; - PacketType packetType; if (_lastInputLoudness == 0) { packetType = PacketTypeSilentAudioFrame; @@ -773,7 +771,7 @@ void Audio::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { do { int samplesToWriteThisIteration = std::min(samplesRemaining, (int)NETWORK_SAMPLES_PER_FRAME); float fade = calculateRepeatedFrameFadeFactor(indexOfRepeat); - printf("%f ", fade, samplesToWriteThisIteration); + printf("%f ", fade); addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 0, STEREO_FACTOR, fade); _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 1, STEREO_FACTOR, fade); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index d10c9da05c..7880602133 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -32,9 +32,9 @@ InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacit _incomingSequenceNumberStats(STATS_FOR_STATS_PACKET_WINDOW_SECONDS), _lastPacketReceivedTime(0), _timeGapStatsForDesiredCalcOnTooManyStarves(0, settings._windowSecondsForDesiredCalcOnTooManyStarves), + _calculatedJitterBufferFramesUsingMaxGap(0), _stdevStatsForDesiredCalcOnTooManyStarves(), _calculatedJitterBufferFramesUsingStDev(0), - _calculatedJitterBufferFramesUsingMaxGap(0), _timeGapStatsForDesiredReduction(0, settings._windowSecondsForDesiredReduction), _starveHistoryWindowSeconds(settings._windowSecondsForDesiredCalcOnTooManyStarves), _starveHistory(STARVE_HISTORY_CAPACITY), From 18ff49744368e23cb743137cf282d2123a61880b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 12 Aug 2014 16:11:53 -0700 Subject: [PATCH 083/206] Added all relevant curves to RecordingFrames --- interface/src/Recorder.cpp | 208 ++++++++++++++++++++++++++++++++++--- interface/src/Recorder.h | 56 +++++++++- 2 files changed, 248 insertions(+), 16 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 87352fed98..c15e17597e 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "Recorder.h" void RecordingFrame::setBlendshapeCoefficients(QVector blendshapeCoefficients) { @@ -23,6 +25,34 @@ void RecordingFrame::setTranslation(glm::vec3 translation) { _translation = translation; } +void RecordingFrame::setRotation(glm::quat rotation) { + _rotation = rotation; +} + +void RecordingFrame::setScale(float scale) { + _scale = scale; +} + +void RecordingFrame::setHeadRotation(glm::quat headRotation) { + _headRotation = headRotation; +} + +void RecordingFrame::setLeanSideways(float leanSideways) { + _leanSideways = leanSideways; +} + +void RecordingFrame::setLeanForward(float leanForward) { + _leanForward = leanForward; +} + +void RecordingFrame::setEstimatedEyePitch(float estimatedEyePitch) { + _estimatedEyePitch = estimatedEyePitch; +} + +void RecordingFrame::setEstimatedEyeYaw(float estimatedEyeYaw) { + _estimatedEyeYaw = estimatedEyeYaw; +} + void Recording::addFrame(int timestamp, RecordingFrame &frame) { _timestamps << timestamp; _frames << frame; @@ -33,7 +63,10 @@ void Recording::clear() { _frames.clear(); } -Recorder::Recorder(AvatarData* avatar) : _avatar(avatar) { +Recorder::Recorder(AvatarData* avatar) : + _recording(new Recording()), + _avatar(avatar) +{ } bool Recorder::isRecording() const { @@ -49,29 +82,72 @@ qint64 Recorder::elapsed() const { } void Recorder::startRecording() { - _recording.clear(); + qDebug() << "Recorder::startRecording()"; + _recording->clear(); _timer.start(); + + RecordingFrame frame; + frame.setBlendshapeCoefficients(_avatar->getHeadData()->getBlendshapeCoefficients()); + frame.setJointRotations(_avatar->getJointRotations()); + frame.setTranslation(_avatar->getPosition()); + frame.setRotation(_avatar->getOrientation()); + frame.setScale(_avatar->getTargetScale()); + + // TODO + const HeadData* head = _avatar->getHeadData(); + glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(), + head->getFinalYaw(), + head->getFinalRoll()))); + frame.setHeadRotation(rotation); + // TODO + //frame.setEstimatedEyePitch(); + //frame.setEstimatedEyeYaw(); + + _recording->addFrame(0, frame); } void Recorder::stopRecording() { + qDebug() << "Recorder::stopRecording()"; _timer.invalidate(); + + qDebug().nospace() << "Recorded " << _recording->getFrameNumber() << " during " << _recording->getLength() << " msec (" << _recording->getFrameNumber() / (_recording->getLength() / 1000.0f) << " fps)"; } void Recorder::saveToFile(QString file) { - if (_recording.isEmpty()) { + if (_recording->isEmpty()) { qDebug() << "Cannot save recording to file, recording is empty."; } - writeRecordingToFile(_recording, file); + writeRecordingToFile(*_recording, file); } void Recorder::record() { - qDebug() << "Recording " << _avatar; - RecordingFrame frame; - frame.setBlendshapeCoefficients(_avatar->_) + if (isRecording()) { + const RecordingFrame& referenceFrame = _recording->getFrame(0); + RecordingFrame frame; + frame.setBlendshapeCoefficients(_avatar->getHeadData()->getBlendshapeCoefficients()); + frame.setJointRotations(_avatar->getJointRotations()); + frame.setTranslation(_avatar->getPosition() - referenceFrame.getTranslation()); + frame.setRotation(glm::inverse(referenceFrame.getRotation()) * _avatar->getOrientation()); + frame.setScale(_avatar->getTargetScale() / referenceFrame.getScale()); + // TODO + //frame.setHeadTranslation(); + const HeadData* head = _avatar->getHeadData(); + glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(), + head->getFinalYaw(), + head->getFinalRoll()))); + frame.setHeadRotation(glm::inverse(referenceFrame.getHeadRotation()) * rotation); + // TODO + //frame.setEstimatedEyePitch(); + //frame.setEstimatedEyeYaw(); + _recording->addFrame(_timer.elapsed(), frame); + } } -Player::Player(AvatarData* avatar) : _avatar(avatar) { +Player::Player(AvatarData* avatar) : + _recording(new Recording()), + _avatar(avatar) +{ } bool Player::isPlaying() const { @@ -86,21 +162,129 @@ qint64 Player::elapsed() const { } } +QVector Player::getBlendshapeCoefficients() { + computeCurrentFrame(); + return _recording->getFrame(_currentFrame).getBlendshapeCoefficients(); +} + +QVector Player::getJointRotations() { + computeCurrentFrame(); + return _recording->getFrame(_currentFrame).getJointRotations(); +} + +glm::quat Player::getRotation() { + computeCurrentFrame(); + return _recording->getFrame(_currentFrame).getRotation(); +} + +float Player::getScale() { + computeCurrentFrame(); + return _recording->getFrame(_currentFrame).getScale(); +} + +glm::quat Player::getHeadRotation() { + computeCurrentFrame(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(0).getHeadRotation() * + _recording->getFrame(_currentFrame - 1).getHeadRotation(); + } + if (_currentFrame == 0) { + return _recording->getFrame(_currentFrame).getHeadRotation(); + } + + return _recording->getFrame(0).getHeadRotation() * + _recording->getFrame(_currentFrame).getHeadRotation(); + } + qWarning() << "Incorrect use of Player::getHeadRotation()"; + return glm::quat(); +} + +float Player::getEstimatedEyePitch() { + computeCurrentFrame(); + return _recording->getFrame(_currentFrame).getEstimatedEyePitch(); +} + +float Player::getEstimatedEyeYaw() { + computeCurrentFrame(); + return _recording->getFrame(_currentFrame).getEstimatedEyeYaw(); +} + + void Player::startPlaying() { - _timer.start(); + if (_recording && _recording->getFrameNumber() > 0) { + qDebug() << "Recorder::startPlaying()"; + _timer.start(); + _currentFrame = 0; + } } void Player::stopPlaying() { + qDebug() << "Recorder::stopPlaying()"; _timer.invalidate(); } void Player::loadFromFile(QString file) { - _recording.clear(); - readRecordingFromFile(_recording, file); + if (_recording) { + _recording->clear(); + } else { + _recording = RecordingPointer(new Recording()); + } + readRecordingFromFile(*_recording, file); +} + +void Player::loadRecording(RecordingPointer recording) { + _recording = recording; } void Player::play() { - qDebug() << "Playing " << _avatar; + qDebug() << "Playing " << _timer.elapsed() / 1000.0f; + computeCurrentFrame(); + if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber()) { + // If it's the end of the recording, stop playing + stopPlaying(); + return; + } + if (_currentFrame == 0) { + _avatar->setPosition(_recording->getFrame(_currentFrame).getTranslation()); + _avatar->setOrientation(_recording->getFrame(_currentFrame).getRotation()); + _avatar->setTargetScale(_recording->getFrame(_currentFrame).getScale()); + HeadData* head = const_cast(_avatar->getHeadData()); + head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); + // TODO + // HEAD: Coeff, Translation, estimated eye rotations + // BODY: Joint Rotations + } else { + _avatar->setPosition(_recording->getFrame(0).getTranslation() + + _recording->getFrame(_currentFrame).getTranslation()); + _avatar->setOrientation(_recording->getFrame(0).getRotation() * + _recording->getFrame(_currentFrame).getRotation()); + _avatar->setTargetScale(_recording->getFrame(0).getScale() * + _recording->getFrame(_currentFrame).getScale()); + HeadData* head = const_cast(_avatar->getHeadData()); + head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); + // TODO + // HEAD: Coeff, Translation, estimated eye rotations + // BODY: Joint Rotations + } +} + +void Player::computeCurrentFrame() { + if (!isPlaying()) { + qDebug() << "Not Playing"; + _currentFrame = -1; + return; + } + if (_currentFrame < 0) { + qDebug() << "Reset to 0"; + _currentFrame = 0; + } + + while (_currentFrame < _recording->getFrameNumber() && + _recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) { + qDebug() << "Loop"; + ++_currentFrame; + } } void writeRecordingToFile(Recording& recording, QString file) { diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index 2d32e4a9b0..798e3f5ba8 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -15,7 +15,9 @@ #include #include #include +#include #include +#include #include #include @@ -23,7 +25,15 @@ #include #include +class Recorder; class Recording; +class Player; + +typedef QSharedPointer RecordingPointer; +typedef QSharedPointer RecorderPointer; +typedef QWeakPointer WeakRecorderPointer; +typedef QSharedPointer PlayerPointer; +typedef QWeakPointer WeakPlayerPointer; /// Stores the different values associated to one recording frame class RecordingFrame { @@ -31,20 +41,41 @@ public: QVector getBlendshapeCoefficients() const { return _blendshapeCoefficients; } QVector getJointRotations() const { return _jointRotations; } glm::vec3 getTranslation() const { return _translation; } + glm::quat getRotation() const { return _rotation; } + float getScale() const { return _scale; } + glm::quat getHeadRotation() const { return _headRotation; } + float getLeanSideways() const { return _leanSideways; } + float getLeanForward() const { return _leanForward; } + float getEstimatedEyePitch() const { return _estimatedEyePitch; } + float getEstimatedEyeYaw() const { return _estimatedEyeYaw; } protected: void setBlendshapeCoefficients(QVector blendshapeCoefficients); void setJointRotations(QVector jointRotations); void setTranslation(glm::vec3 translation); + void setRotation(glm::quat rotation); + void setScale(float scale); + void setHeadRotation(glm::quat headRotation); + void setLeanSideways(float leanSideways); + void setLeanForward(float leanForward); + void setEstimatedEyePitch(float estimatedEyePitch); + void setEstimatedEyeYaw(float estimatedEyeYaw); private: QVector _blendshapeCoefficients; QVector _jointRotations; glm::vec3 _translation; + glm::quat _rotation; + float _scale; + glm::quat _headRotation; + float _leanSideways; + float _leanForward; + float _estimatedEyePitch; + float _estimatedEyeYaw; friend class Recorder; friend void writeRecordingToFile(Recording& recording, QString file); - friend Recording* readRecordingFromFile(QString file); + friend RecordingPointer readRecordingFromFile(QString file); }; /// Stores a recording @@ -52,6 +83,7 @@ class Recording { public: bool isEmpty() const { return _timestamps.isEmpty(); } int getLength() const { return _timestamps.last(); } // in ms + int getFrameNumber() const { return _frames.size(); } qint32 getFrameTimestamp(int i) const { return _timestamps[i]; } const RecordingFrame& getFrame(int i) const { return _frames[i]; } @@ -67,7 +99,7 @@ private: friend class Recorder; friend class Player; friend void writeRecordingToFile(Recording& recording, QString file); - friend Recording* readRecordingFromFile(QString file); + friend RecordingPointer readRecordingFromFile(QString file); }; @@ -79,6 +111,8 @@ public: bool isRecording() const; qint64 elapsed() const; + RecordingPointer getRecording() const { return _recording; } + public slots: void startRecording(); void stopRecording(); @@ -87,7 +121,7 @@ public slots: private: QElapsedTimer _timer; - Recording _recording; + RecordingPointer _recording; AvatarData* _avatar; }; @@ -100,15 +134,29 @@ public: bool isPlaying() const; qint64 elapsed() const; + // Those should only be called if isPlaying() returns true + QVector getBlendshapeCoefficients(); + QVector getJointRotations(); + glm::quat getRotation(); + float getScale(); + glm::vec3 getHeadTranslation(); + glm::quat getHeadRotation(); + float getEstimatedEyePitch(); + float getEstimatedEyeYaw(); + public slots: void startPlaying(); void stopPlaying(); void loadFromFile(QString file); + void loadRecording(RecordingPointer recording); void play(); private: + void computeCurrentFrame(); + QElapsedTimer _timer; - Recording _recording; + RecordingPointer _recording; + int _currentFrame; AvatarData* _avatar; }; From ff0a5df2d6848e2f819f6d967ca5efa26b4511fc Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 12 Aug 2014 16:12:34 -0700 Subject: [PATCH 084/206] Joint rotations manipulation helper --- libraries/avatars/src/AvatarData.cpp | 15 +++++++++++++++ libraries/avatars/src/AvatarData.h | 2 ++ libraries/avatars/src/HeadData.h | 1 + 3 files changed, 18 insertions(+) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 039ccae4e9..fad07bedfc 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -683,6 +683,21 @@ glm::quat AvatarData::getJointRotation(const QString& name) const { return getJointRotation(getJointIndex(name)); } +QVector AvatarData::getJointRotations() const { + if (QThread::currentThread() != thread()) { + QVector result; + QMetaObject::invokeMethod(const_cast(this), + "getJointRotation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVector, result)); + return result; + } + QVector jointRotations(_jointData.size()); + for (int i = 0; i < _jointData.size(); ++i) { + jointRotations[i] = _jointData[i].rotation; + } + return jointRotations; +} + bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) { QDataStream packetStream(packet); packetStream.skipRawData(numBytesForPacketHeader(packet)); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a4bb0d48bb..eb3341ee26 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -210,6 +210,8 @@ public: Q_INVOKABLE void clearJointData(const QString& name); Q_INVOKABLE bool isJointDataValid(const QString& name) const; Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; + + QVector getJointRotations() const; /// Returns the index of the joint with the specified name, or -1 if not found/unknown. Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return _jointIndices.value(name) - 1; } diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 782386c649..b6a3268ec0 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -56,6 +56,7 @@ public: void setBlendshape(QString name, float val); const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } + void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } float getPupilDilation() const { return _pupilDilation; } void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; } From 792f779bbff7600682bceb7907da2fcadf4862d6 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 12 Aug 2014 16:16:01 -0700 Subject: [PATCH 085/206] Hooked up MyAvatar to playbacks --- interface/src/avatar/Avatar.cpp | 13 +++++++ interface/src/avatar/Avatar.h | 7 +++- interface/src/avatar/Head.cpp | 19 ++++++---- interface/src/avatar/MyAvatar.cpp | 62 +++++++++++++++++++++++++++++-- interface/src/avatar/MyAvatar.h | 9 +++++ 5 files changed, 97 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index c9e03d15cc..498281c98c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -29,6 +29,7 @@ #include "Menu.h" #include "ModelReferential.h" #include "Physics.h" +#include "Recorder.h" #include "world.h" #include "devices/OculusManager.h" #include "renderer/TextureCache.h" @@ -63,6 +64,7 @@ Avatar::Avatar() : _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), _collisionGroups(0), + _player(NULL), _initialized(false), _shouldRenderBillboard(true) { @@ -725,6 +727,17 @@ bool Avatar::findCollisions(const QVector& shapes, CollisionList& return collided; } +QVector Avatar::getJointRotations() const { + if (QThread::currentThread() != thread()) { + return AvatarData::getJointRotations(); + } + QVector jointRotations(_skeletonModel.getJointStateCount()); + for (int i = 0; i < _skeletonModel.getJointStateCount(); ++i) { + _skeletonModel.getJointState(i, jointRotations[i]); + } + return jointRotations; +} + glm::quat Avatar::getJointRotation(int index) const { if (QThread::currentThread() != thread()) { return AvatarData::getJointRotation(index); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 555a0f6d32..35dec3b8d0 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -23,6 +23,7 @@ #include "Hand.h" #include "Head.h" #include "InterfaceConfig.h" +#include "Recorder.h" #include "SkeletonModel.h" #include "world.h" @@ -121,6 +122,7 @@ public: virtual bool isMyAvatar() { return false; } + virtual QVector getJointRotations() const; virtual glm::quat getJointRotation(int index) const; virtual int getJointIndex(const QString& name) const; virtual QStringList getJointNames() const; @@ -186,6 +188,9 @@ protected: bool _moving; ///< set when position is changing quint32 _collisionGroups; + + RecorderPointer _recorder; + PlayerPointer _player; // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } @@ -220,8 +225,6 @@ private: void renderBillboard(); float getBillboardSize() const; - - }; #endif // hifi_Avatar_h diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 995f3f2390..d547dc41c2 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -64,13 +64,18 @@ void Head::reset() { void Head::simulate(float deltaTime, bool isMine, bool billboard) { // Update audio trailing average for rendering facial animations if (isMine) { - FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker(); - if ((_isFaceshiftConnected = faceTracker)) { - _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); - _isFaceshiftConnected = true; - } else if (Application::getInstance()->getDDE()->isActive()) { - faceTracker = Application::getInstance()->getDDE(); - _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); + MyAvatar* myAvatar = static_cast(_owningAvatar); + + // Only use face trackers when not playing back a recording. + if (!myAvatar->isPlaying()) { + FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker(); + if ((_isFaceshiftConnected = faceTracker)) { + _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); + _isFaceshiftConnected = true; + } else if (Application::getInstance()->getDDE()->isActive()) { + faceTracker = Application::getInstance()->getDDE(); + _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); + } } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f59064732c..353c240507 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -35,6 +35,7 @@ #include "ModelReferential.h" #include "MyAvatar.h" #include "Physics.h" +#include "Recorder.h" #include "devices/Faceshift.h" #include "devices/OculusManager.h" #include "ui/TextRenderer.h" @@ -135,6 +136,12 @@ void MyAvatar::update(float deltaTime) { void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); + + // Play back recording + if (_player && _player->isPlaying()) { + _player->play(); + } + if (_scale != _targetScale) { float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; setScale(scale); @@ -147,7 +154,7 @@ void MyAvatar::simulate(float deltaTime) { updateOrientation(deltaTime); updatePosition(deltaTime); } - + { PerformanceTimer perfTimer("hand"); // update avatar skeleton and simulate hand and head @@ -242,6 +249,11 @@ void MyAvatar::simulate(float deltaTime) { } } + // Record avatars movements. + if (_recorder && _recorder->isRecording()) { + _recorder->record(); + } + // consider updating our billboard maybeUpdateBillboard(); } @@ -250,7 +262,10 @@ void MyAvatar::simulate(float deltaTime) { void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; - if (Application::getInstance()->getPrioVR()->hasHeadRotation()) { + if (isPlaying()) { + //estimatedPosition = _player->getHeadTranslation(); + estimatedRotation = glm::degrees(safeEulerAngles(_player->getHeadRotation())); + } else if (Application::getInstance()->getPrioVR()->hasHeadRotation()) { estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation())); estimatedRotation.x *= -1.0f; estimatedRotation.z *= -1.0f; @@ -286,7 +301,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { Head* head = getHead(); - if (OculusManager::isConnected()) { + if (OculusManager::isConnected() || isPlaying()) { head->setDeltaPitch(estimatedRotation.x); head->setDeltaYaw(estimatedRotation.y); } else { @@ -297,7 +312,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { head->setDeltaRoll(estimatedRotation.z); // the priovr can give us exact lean - if (Application::getInstance()->getPrioVR()->isActive()) { + if (Application::getInstance()->getPrioVR()->isActive() && !isPlaying()) { glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation())); head->setLeanSideways(eulers.z); head->setLeanForward(eulers.x); @@ -474,6 +489,45 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) { } } +bool MyAvatar::isRecording() const { + return _recorder && _recorder->isRecording(); +} + +RecorderPointer MyAvatar::startRecording() { + if (!_recorder) { + _recorder = RecorderPointer(new Recorder(this)); + } + _recorder->startRecording(); + return _recorder; +} + +void MyAvatar::stopRecording() { + if (_recorder) { + _recorder->stopRecording(); + } +} + +bool MyAvatar::isPlaying() const { + return _player && _player->isPlaying(); +} + +PlayerPointer MyAvatar::startPlaying() { + if (!_player) { + _player = PlayerPointer(new Player(this)); + } + if (_recorder) { + _player->loadRecording(_recorder->getRecording()); + _player->startPlaying(); + } + return _player; +} + +void MyAvatar::stopPlaying() { + if (_player) { + _player->stopPlaying(); + } +} + void MyAvatar::setLocalGravity(glm::vec3 gravity) { _motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY; // Environmental and Local gravities are incompatible. Since Local is being set here diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 4f2802a35a..cd211a3530 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -155,6 +155,15 @@ public slots: bool setModelReferential(int id); bool setJointReferential(int id, int jointIndex); + bool isRecording() const; + RecorderPointer startRecording(); + void stopRecording(); + + bool isPlaying() const; + PlayerPointer startPlaying(); + void stopPlaying(); + + signals: void transformChanged(); From 73a3a13c59769a1cfb5520914fa578e5beb14ad9 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 12 Aug 2014 18:15:48 -0700 Subject: [PATCH 086/206] More recording work --- interface/src/Application.cpp | 13 ++- interface/src/Recorder.cpp | 123 +++++++++++++++++++------ interface/src/Recorder.h | 12 +-- interface/src/avatar/Avatar.cpp | 1 + interface/src/avatar/Head.h | 5 - interface/src/avatar/MyAvatar.cpp | 17 +++- interface/src/avatar/MyAvatar.h | 3 + interface/src/avatar/SkeletonModel.cpp | 8 +- libraries/avatars/src/AvatarData.cpp | 14 +++ libraries/avatars/src/AvatarData.h | 3 +- libraries/avatars/src/HeadData.h | 9 ++ 11 files changed, 158 insertions(+), 50 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6f7212e68f..198a3f9a8b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1048,10 +1048,19 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_R: if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode); + if (_myAvatar->isRecording()) { + _myAvatar->stopRecording(); + } else { + _myAvatar->startRecording(); + } + } else { + if (_myAvatar->isPlaying()) { + _myAvatar->stopPlaying(); + } else { + _myAvatar->startPlaying(); + } } break; - break; case Qt::Key_Percent: Menu::getInstance()->triggerOption(MenuOption::Stats); break; diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index c15e17597e..de29a887fb 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -45,14 +45,6 @@ void RecordingFrame::setLeanForward(float leanForward) { _leanForward = leanForward; } -void RecordingFrame::setEstimatedEyePitch(float estimatedEyePitch) { - _estimatedEyePitch = estimatedEyePitch; -} - -void RecordingFrame::setEstimatedEyeYaw(float estimatedEyeYaw) { - _estimatedEyeYaw = estimatedEyeYaw; -} - void Recording::addFrame(int timestamp, RecordingFrame &frame) { _timestamps << timestamp; _frames << frame; @@ -93,15 +85,13 @@ void Recorder::startRecording() { frame.setRotation(_avatar->getOrientation()); frame.setScale(_avatar->getTargetScale()); - // TODO const HeadData* head = _avatar->getHeadData(); glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(), head->getFinalYaw(), head->getFinalRoll()))); frame.setHeadRotation(rotation); - // TODO - //frame.setEstimatedEyePitch(); - //frame.setEstimatedEyeYaw(); + frame.setLeanForward(_avatar->getHeadData()->getLeanForward()); + frame.setLeanSideways(_avatar->getHeadData()->getLeanSideways()); _recording->addFrame(0, frame); } @@ -130,16 +120,16 @@ void Recorder::record() { frame.setTranslation(_avatar->getPosition() - referenceFrame.getTranslation()); frame.setRotation(glm::inverse(referenceFrame.getRotation()) * _avatar->getOrientation()); frame.setScale(_avatar->getTargetScale() / referenceFrame.getScale()); - // TODO - //frame.setHeadTranslation(); + + const HeadData* head = _avatar->getHeadData(); glm::quat rotation = glm::quat(glm::radians(glm::vec3(head->getFinalPitch(), head->getFinalYaw(), head->getFinalRoll()))); - frame.setHeadRotation(glm::inverse(referenceFrame.getHeadRotation()) * rotation); - // TODO - //frame.setEstimatedEyePitch(); - //frame.setEstimatedEyeYaw(); + frame.setHeadRotation(rotation); + frame.setLeanForward(_avatar->getHeadData()->getLeanForward()); + frame.setLeanSideways(_avatar->getHeadData()->getLeanSideways()); + _recording->addFrame(_timer.elapsed(), frame); } } @@ -164,22 +154,82 @@ qint64 Player::elapsed() const { QVector Player::getBlendshapeCoefficients() { computeCurrentFrame(); - return _recording->getFrame(_currentFrame).getBlendshapeCoefficients(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(_currentFrame - 1).getBlendshapeCoefficients(); + } + + return _recording->getFrame(_currentFrame).getBlendshapeCoefficients(); + } + qWarning() << "Incorrect use of Player::getBlendshapeCoefficients()"; + return QVector(); } QVector Player::getJointRotations() { computeCurrentFrame(); - return _recording->getFrame(_currentFrame).getJointRotations(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(_currentFrame - 1).getJointRotations(); + } + + return _recording->getFrame(_currentFrame).getJointRotations(); + } + qWarning() << "Incorrect use of Player::getJointRotations()"; + return QVector(); +} + +glm::vec3 Player::getPosition() { + computeCurrentFrame(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(0).getTranslation() + + _recording->getFrame(_currentFrame - 1).getTranslation(); + } + if (_currentFrame == 0) { + return _recording->getFrame(_currentFrame).getTranslation(); + } + + return _recording->getFrame(0).getTranslation() + + _recording->getFrame(_currentFrame).getTranslation(); + } + qWarning() << "Incorrect use of Player::getTranslation()"; + return glm::vec3(); } glm::quat Player::getRotation() { computeCurrentFrame(); - return _recording->getFrame(_currentFrame).getRotation(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(0).getRotation() * + _recording->getFrame(_currentFrame - 1).getRotation(); + } + if (_currentFrame == 0) { + return _recording->getFrame(_currentFrame).getRotation(); + } + + return _recording->getFrame(0).getRotation() * + _recording->getFrame(_currentFrame).getRotation(); + } + qWarning() << "Incorrect use of Player::getRotation()"; + return glm::quat(); } float Player::getScale() { computeCurrentFrame(); - return _recording->getFrame(_currentFrame).getScale(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(0).getScale() * + _recording->getFrame(_currentFrame - 1).getScale(); + } + if (_currentFrame == 0) { + return _recording->getFrame(_currentFrame).getScale(); + } + + return _recording->getFrame(0).getScale() * + _recording->getFrame(_currentFrame).getScale(); + } + qWarning() << "Incorrect use of Player::getScale()"; + return 1.0f; } glm::quat Player::getHeadRotation() { @@ -200,14 +250,30 @@ glm::quat Player::getHeadRotation() { return glm::quat(); } -float Player::getEstimatedEyePitch() { +float Player::getLeanSideways() { computeCurrentFrame(); - return _recording->getFrame(_currentFrame).getEstimatedEyePitch(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(_currentFrame - 1).getLeanSideways(); + } + + return _recording->getFrame(_currentFrame).getLeanSideways(); + } + qWarning() << "Incorrect use of Player::getLeanSideways()"; + return 0.0f; } -float Player::getEstimatedEyeYaw() { +float Player::getLeanForward() { computeCurrentFrame(); - return _recording->getFrame(_currentFrame).getEstimatedEyeYaw(); + if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { + if (_currentFrame == _recording->getFrameNumber()) { + return _recording->getFrame(_currentFrame - 1).getLeanForward(); + } + + return _recording->getFrame(_currentFrame).getLeanForward(); + } + qWarning() << "Incorrect use of Player::getLeanForward()"; + return 0.0f; } @@ -249,10 +315,10 @@ void Player::play() { _avatar->setPosition(_recording->getFrame(_currentFrame).getTranslation()); _avatar->setOrientation(_recording->getFrame(_currentFrame).getRotation()); _avatar->setTargetScale(_recording->getFrame(_currentFrame).getScale()); + _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); HeadData* head = const_cast(_avatar->getHeadData()); head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); // TODO - // HEAD: Coeff, Translation, estimated eye rotations // BODY: Joint Rotations } else { _avatar->setPosition(_recording->getFrame(0).getTranslation() + @@ -261,10 +327,10 @@ void Player::play() { _recording->getFrame(_currentFrame).getRotation()); _avatar->setTargetScale(_recording->getFrame(0).getScale() * _recording->getFrame(_currentFrame).getScale()); + _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); HeadData* head = const_cast(_avatar->getHeadData()); head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); // TODO - // HEAD: Coeff, Translation, estimated eye rotations // BODY: Joint Rotations } } @@ -282,7 +348,6 @@ void Player::computeCurrentFrame() { while (_currentFrame < _recording->getFrameNumber() && _recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) { - qDebug() << "Loop"; ++_currentFrame; } } diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index 798e3f5ba8..f2a6f6a1c4 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -46,8 +46,6 @@ public: glm::quat getHeadRotation() const { return _headRotation; } float getLeanSideways() const { return _leanSideways; } float getLeanForward() const { return _leanForward; } - float getEstimatedEyePitch() const { return _estimatedEyePitch; } - float getEstimatedEyeYaw() const { return _estimatedEyeYaw; } protected: void setBlendshapeCoefficients(QVector blendshapeCoefficients); @@ -58,8 +56,6 @@ protected: void setHeadRotation(glm::quat headRotation); void setLeanSideways(float leanSideways); void setLeanForward(float leanForward); - void setEstimatedEyePitch(float estimatedEyePitch); - void setEstimatedEyeYaw(float estimatedEyeYaw); private: QVector _blendshapeCoefficients; @@ -70,8 +66,6 @@ private: glm::quat _headRotation; float _leanSideways; float _leanForward; - float _estimatedEyePitch; - float _estimatedEyeYaw; friend class Recorder; friend void writeRecordingToFile(Recording& recording, QString file); @@ -137,12 +131,12 @@ public: // Those should only be called if isPlaying() returns true QVector getBlendshapeCoefficients(); QVector getJointRotations(); + glm::vec3 getPosition(); glm::quat getRotation(); float getScale(); - glm::vec3 getHeadTranslation(); glm::quat getHeadRotation(); - float getEstimatedEyePitch(); - float getEstimatedEyeYaw(); + float getLeanSideways(); + float getLeanForward(); public slots: void startPlaying(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 498281c98c..d50e9232ed 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -735,6 +735,7 @@ QVector Avatar::getJointRotations() const { for (int i = 0; i < _skeletonModel.getJointStateCount(); ++i) { _skeletonModel.getJointState(i, jointRotations[i]); } + qDebug() << "Get Joints"; return jointRotations; } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 6d1e82b97f..1bcaadec73 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -48,8 +48,6 @@ public: void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; } void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } - void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; } - void setLeanForward(float leanForward) { _leanForward = leanForward; } /// \return orientationBase+Delta glm::quat getFinalOrientationInLocalFrame() const; @@ -57,7 +55,6 @@ public: /// \return orientationBody * (orientationBase+Delta) glm::quat getFinalOrientationInWorldFrame() const; - /// \return orientationBody * orientationBasePitch glm::quat getCameraOrientation () const; @@ -71,8 +68,6 @@ public: glm::vec3 getRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } glm::vec3 getUpDirection() const { return getOrientation() * IDENTITY_UP; } glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; } - float getLeanSideways() const { return _leanSideways; } - float getLeanForward() const { return _leanForward; } float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; } float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 353c240507..1133ac01c3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -263,7 +263,6 @@ void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; if (isPlaying()) { - //estimatedPosition = _player->getHeadTranslation(); estimatedRotation = glm::degrees(safeEulerAngles(_player->getHeadRotation())); } else if (Application::getInstance()->getPrioVR()->hasHeadRotation()) { estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation())); @@ -311,14 +310,18 @@ void MyAvatar::updateFromTrackers(float deltaTime) { } head->setDeltaRoll(estimatedRotation.z); + if (isPlaying()) { + head->setLeanSideways(_player->getLeanSideways()); + head->setLeanForward(_player->getLeanForward()); + return; + } // the priovr can give us exact lean - if (Application::getInstance()->getPrioVR()->isActive() && !isPlaying()) { + if (Application::getInstance()->getPrioVR()->isActive()) { glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation())); head->setLeanSideways(eulers.z); head->setLeanForward(eulers.x); return; } - // Update torso lean distance based on accelerometer data const float TORSO_LENGTH = 0.5f; glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f); @@ -910,6 +913,14 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const { const float JOINT_PRIORITY = 2.0f; +void MyAvatar::setJointRotations(QVector jointRotations) { + for (int i = 0; i < jointRotations.size(); ++i) { + if (i < _jointData.size()) { + _skeletonModel.setJointState(i, true, jointRotations[i]); + } + } +} + void MyAvatar::setJointData(int index, const glm::quat& rotation) { Avatar::setJointData(index, rotation); if (QThread::currentThread() == thread()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cd211a3530..bf57bf2367 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -112,6 +112,7 @@ public: void updateLookAtTargetAvatar(); void clearLookAtTargetAvatar(); + virtual void setJointRotations(QVector jointRotations); virtual void setJointData(int index, const glm::quat& rotation); virtual void clearJointData(int index); virtual void setFaceModelURL(const QUrl& faceModelURL); @@ -155,10 +156,12 @@ public slots: bool setModelReferential(int id); bool setJointReferential(int id, int jointIndex); + const RecorderPointer getRecorder() const { return _recorder; } bool isRecording() const; RecorderPointer startRecording(); void stopRecording(); + const PlayerPointer getPlayer() const { return _player; } bool isPlaying() const; PlayerPointer startPlaying(); void stopPlaying(); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 5e593526be..f4a507a8d3 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -59,9 +59,15 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { Model::simulate(deltaTime, fullUpdate); - if (!(isActive() && _owningAvatar->isMyAvatar())) { + if (!isActive() || !_owningAvatar->isMyAvatar()) { return; // only simulate for own avatar } + + MyAvatar* myAvatar = static_cast(_owningAvatar); + if (myAvatar->isPlaying()) { + // Don't take inputs if playing back a recording. + return; + } const FBXGeometry& geometry = _geometry->getFBXGeometry(); PrioVR* prioVR = Application::getInstance()->getPrioVR(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index fad07bedfc..569e099c2c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -698,6 +698,20 @@ QVector AvatarData::getJointRotations() const { return jointRotations; } +void AvatarData::setJointRotations(QVector jointRotations) { + if (QThread::currentThread() != thread()) { + QVector result; + QMetaObject::invokeMethod(const_cast(this), + "setJointRotation", Qt::BlockingQueuedConnection, + Q_ARG(QVector, jointRotations)); + } + for (int i = 0; i < jointRotations.size(); ++i) { + if (i < _jointData.size()) { + setJointData(i, jointRotations[i]); + } + } +} + bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) { QDataStream packetStream(packet); packetStream.skipRawData(numBytesForPacketHeader(packet)); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index eb3341ee26..7dec55b7e9 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -211,7 +211,8 @@ public: Q_INVOKABLE bool isJointDataValid(const QString& name) const; Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; - QVector getJointRotations() const; + Q_INVOKABLE virtual QVector getJointRotations() const; + Q_INVOKABLE virtual void setJointRotations(QVector jointRotations); /// Returns the index of the joint with the specified name, or -1 if not found/unknown. Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return _jointIndices.value(name) - 1; } diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index b6a3268ec0..310437689c 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -69,6 +69,15 @@ public: const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; } + + float getLeanSideways() const { return _leanSideways; } + float getLeanForward() const { return _leanForward; } + virtual float getFinalLeanSideways() const { return _leanSideways; } + virtual float getFinalLeanForward() const { return _leanForward; } + + void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; } + void setLeanForward(float leanForward) { _leanForward = leanForward; } + friend class AvatarData; protected: From 23b3d06260ed65d213dc1373cc3c5491203308d4 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 09:46:43 -0700 Subject: [PATCH 087/206] forgot null check for AudioMixer repeat mix of lastPopOutput --- assignment-client/src/audio/AudioMixer.cpp | 4 +++- libraries/audio/src/AudioRingBuffer.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index f402050265..63b4716f82 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -105,7 +105,9 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* float repeatedFrameFadeFactor = 1.0f; if (!streamToAdd->lastPopSucceeded()) { - if (_streamSettings._repetitionWithFade) { + if (_streamSettings._repetitionWithFade && !streamToAdd->getLastPopOutput().isNull()) { + // reptition with fade is enabled, and we do have a valid previous frame to repeat. + // calculate its fade factor, which depends on how many times it's already been repeated. repeatedFrameFadeFactor = calculateRepeatedFrameFadeFactor(streamToAdd->getConsecutiveNotMixedCount() - 1); if (repeatedFrameFadeFactor == 0.0f) { return 0; diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 65e6947115..522c1b8fff 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -108,6 +108,8 @@ public: _bufferLast(bufferFirst + capacity - 1), _at(at) {} + bool isNull() const { return _at == NULL; } + bool operator==(const ConstIterator& rhs) { return _at == rhs._at; } bool operator!=(const ConstIterator& rhs) { return _at != rhs._at; } const int16_t& operator*() { return *_at; } From 8c913fe8c1a899677d947c3385d2fb6bae5f1364 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 13 Aug 2014 10:23:59 -0700 Subject: [PATCH 088/206] Add progress dialog and ability to cancel during the model upload --- examples/editModels.js | 262 +++++++++++++++++++++++++++++++++-------- 1 file changed, 216 insertions(+), 46 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index b606567ca6..dbae4fd08c 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -175,6 +175,140 @@ if (typeof DataView.prototype.string !== "function") { }; } +var progressDialog = (function () { + var that = {}, + progressBackground, + progressMessage, + cancelButton, + displayed = false, + backgroundWidth = 300, + backgroundHeight = 100, + messageHeight = 32, + cancelWidth = 70, + cancelHeight = 32, + textColor = { red: 255, green: 255, blue: 255 }, + textBackground = { red: 52, green: 52, blue: 52 }, + backgroundUrl = "http://ctrlaltstudio.com/hifi/progress-background.svg", // DJRTODO: Update with HiFi location. + //backgroundUrl = "http://public.highfidelity.io/images/tools/progress-background.svg", + windowDimensions; + + progressBackground = Overlays.addOverlay("image", { + width: backgroundWidth, + height: backgroundHeight, + imageURL: backgroundUrl, + alpha: 0.9, + visible: false + }); + + progressMessage = Overlays.addOverlay("text", { + width: backgroundWidth - 40, + height: messageHeight, + text: "", + textColor: textColor, + backgroundColor: textBackground, + alpha: 0.9, + visible: false + }); + + cancelButton = Overlays.addOverlay("text", { + width: cancelWidth, + height: cancelHeight, + text: "Cancel", + textColor: textColor, + backgroundColor: textBackground, + alpha: 0.9, + visible: false + }); + + function move() { + var progressX, + progressY; + + if (displayed) { + + if (windowDimensions.x === Window.innerWidth && windowDimensions.y === Window.innerHeight) { + return; + } + windowDimensions.x = Window.innerWidth; + windowDimensions.y = Window.innerHeight; + + progressX = (windowDimensions.x - backgroundWidth) / 2; // Center. + progressY = windowDimensions.y / 2 - backgroundHeight; // A little up from center. + + Overlays.editOverlay(progressBackground, { x: progressX, y: progressY }); + Overlays.editOverlay(progressMessage, { x: progressX + 20, y: progressY + 15 }); + Overlays.editOverlay(cancelButton, { + x: progressX + backgroundWidth - cancelWidth - 20, + y: progressY + backgroundHeight - cancelHeight - 15 + }); + } + } + that.move = move; + + that.onCancel = undefined; + + function open(message) { + if (!displayed) { + windowDimensions = { x: 0, y : 0 }; + displayed = true; + move(); + Overlays.editOverlay(progressBackground, { visible: true }); + Overlays.editOverlay(progressMessage, { visible: true, text: message }); + Overlays.editOverlay(cancelButton, { visible: true }); + } else { + throw new Error("open() called on progressDialog when already open"); + } + } + that.open = open; + + function isOpen() { + return displayed; + } + that.isOpen = isOpen; + + function update(message) { + if (displayed) { + Overlays.editOverlay(progressMessage, { text: message }); + } else { + throw new Error("update() called on progressDialog when not open"); + } + } + that.update = update; + + function close() { + if (displayed) { + Overlays.editOverlay(cancelButton, { visible: false }); + Overlays.editOverlay(progressMessage, { visible: false }); + Overlays.editOverlay(progressBackground, { visible: false }); + displayed = false; + } else { + throw new Error("close() called on progressDialog when not open"); + } + } + that.close = close; + + function mousePressEvent(event) { + if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === cancelButton) { + if (typeof this.onCancel === "function") { + close(); + this.onCancel(); + } + return true; + } + return false; + } + that.mousePressEvent = mousePressEvent; + + function cleanup() { + Overlays.deleteOverlay(cancelButton); + Overlays.deleteOverlay(progressMessage); + Overlays.deleteOverlay(progressBackground); + } + that.cleanup = cleanup; + + return that; +}()); + var httpMultiPart = (function () { var that = {}, parts, @@ -287,6 +421,10 @@ var httpMultiPart = (function () { var modelUploader = (function () { var that = {}, modelFile, + modelName, + modelURL, + modelCallback, + isProcessing, fstBuffer, fbxBuffer, //svoBuffer, @@ -300,7 +438,19 @@ var modelUploader = (function () { TEXDIR_FIELD = "texdir", MAX_TEXTURE_SIZE = 1024; + function info(message) { + if (progressDialog.isOpen()) { + progressDialog.update(message); + } else { + progressDialog.open(message); + } + print(message); + } + function error(message) { + if (progressDialog.isOpen()) { + progressDialog.close(); + } print(message); Window.alert(message); } @@ -523,7 +673,8 @@ var modelUploader = (function () { //svoFilename, fileType; - print("Reading model file: " + modelFile); + info("Reading model file"); + print("Model file: " + modelFile); if (modelFile.toLowerCase().fileType() === "fst") { fstBuffer = readFile(modelFile); @@ -565,12 +716,16 @@ var modelUploader = (function () { } } + if (!isProcessing) { return false; } + if (fbxFilename) { fbxBuffer = readFile(fbxFilename); if (fbxBuffer === null) { return false; } + if (!isProcessing) { return false; } + readGeometry(fbxBuffer); } @@ -601,6 +756,7 @@ var modelUploader = (function () { displayAs, validateAs; + progressDialog.close(); print("Setting model properties"); form.push({ label: "Name:", value: mapping[NAME_FIELD] }); @@ -636,8 +792,9 @@ var modelUploader = (function () { return true; } - function createHttpMessage() { - var lodCount, + function createHttpMessage(callback) { + var multiparts = [], + lodCount, lodFile, lodBuffer, textureBuffer, @@ -645,25 +802,23 @@ var modelUploader = (function () { textureTargetFormat, i; - print("Preparing to send model"); - - httpMultiPart.clear(); + info("Preparing to send model"); // Model name if (mapping.hasOwnProperty(NAME_FIELD)) { - httpMultiPart.add({ + multiparts.push({ name : "model_name", string : mapping[NAME_FIELD] }); } else { error("Model name is missing"); httpMultiPart.clear(); - return false; + return; } // FST file if (fstBuffer) { - httpMultiPart.add({ + multiparts.push({ name : "fst", buffer: fstBuffer }); @@ -671,7 +826,7 @@ var modelUploader = (function () { // FBX file if (fbxBuffer) { - httpMultiPart.add({ + multiparts.push({ name : "fbx", buffer: fbxBuffer }); @@ -679,7 +834,7 @@ var modelUploader = (function () { // SVO file //if (svoBuffer) { - // httpMultiPart.add({ + // multiparts.push({ // name : "svo", // buffer: svoBuffer // }); @@ -691,14 +846,15 @@ var modelUploader = (function () { if (mapping.lod.hasOwnProperty(lodFile)) { lodBuffer = readFile(modelFile.path() + "\/" + lodFile); if (lodBuffer === null) { - return false; + return; } - httpMultiPart.add({ + multiparts.push({ name: "lod" + lodCount, buffer: lodBuffer }); lodCount += 1; } + if (!isProcessing) { return; } } // Textures @@ -707,7 +863,7 @@ var modelUploader = (function () { + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") + geometry.textures[i]); if (textureBuffer === null) { - return false; + return; } textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); @@ -715,25 +871,37 @@ var modelUploader = (function () { textureBuffer.buffer = textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE); textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat; - httpMultiPart.add({ + multiparts.push({ name: "texture" + i, buffer: textureBuffer }); + if (!isProcessing) { return; } } // Model category - httpMultiPart.add({ + multiparts.push({ name : "model_category", string : "content" }); - return true; + // Create HTTP message + httpMultiPart.clear(); + Script.setTimeout(function addMultipart() { + var multipart = multiparts.shift(); + httpMultiPart.add(multipart); + + if (!isProcessing) { return; } + + if (multiparts.length > 0) { + Script.setTimeout(addMultipart, 25); + } else { + callback(); + } + }, 25); } - function sendToHighFidelity(addModelCallback) { + function sendToHighFidelity() { var req, - modelName, - modelURL, uploadedChecks, HTTP_GET_TIMEOUT = 60, // 1 minute HTTP_SEND_TIMEOUT = 900, // 15 minutes @@ -759,7 +927,9 @@ var modelUploader = (function () { //} function checkUploaded() { - print("Checking uploaded model"); + if (!isProcessing) { return; } + + info("Checking uploaded model"); req = new XMLHttpRequest(); req.open("HEAD", modelURL, true); @@ -775,8 +945,9 @@ var modelUploader = (function () { if (req.status === 200) { // Note: Unlike avatar models, for content models we don't need to refresh texture cache. print("Model uploaded: " + modelURL); + progressDialog.close(); if (Window.confirm("Your model has been uploaded as: " + modelURL + "\nDo you want to rez it?")) { - addModelCallback(modelURL); + modelCallback(modelURL); } } else if (req.status === 404) { if (uploadedChecks > 0) { @@ -797,6 +968,8 @@ var modelUploader = (function () { function uploadModel(method) { var url; + if (!isProcessing) { return; } + req = new XMLHttpRequest(); if (method === "PUT") { url = API_URL + "\/" + modelName; @@ -828,6 +1001,8 @@ var modelUploader = (function () { function requestUpload() { var url; + if (!isProcessing) { return; } + url = API_URL + "\/" + modelName; // XMLHttpRequest automatically handles authorization of API requests. req = new XMLHttpRequest(); req.open("GET", url, true); //print("GET " + url); @@ -864,41 +1039,34 @@ var modelUploader = (function () { } }; - print("Sending model to High Fidelity"); - - modelName = mapping[NAME_FIELD]; - modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST + info("Sending model to High Fidelity"); requestUpload(); } - that.upload = function (file, addModelCallback) { + that.upload = function (file, callback) { modelFile = file; + modelCallback = callback; + + isProcessing = true; + + progressDialog.onCancel = function () { + print("User cancelled uploading model"); + isProcessing = false; + }; resetDataObjects(); - // Read model content ... - if (!readModel()) { - resetDataObjects(); - return; - } + if (readModel()) { + if (setProperties()) { + modelName = mapping[NAME_FIELD]; + modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST - // Set model properties ... - if (!setProperties()) { - resetDataObjects(); - return; + createHttpMessage(sendToHighFidelity); + } } - // Put model in HTTP message ... - if (!createHttpMessage()) { - resetDataObjects(); - return; - } - - // Send model to High Fidelity ... - sendToHighFidelity(addModelCallback); - resetDataObjects(); }; @@ -2107,6 +2275,7 @@ function checkController(deltaTime) { } toolBar.move(); + progressDialog.move(); } var modelSelected = false; @@ -2190,7 +2359,7 @@ function mousePressEvent(event) { modelSelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - if (toolBar.mousePressEvent(event)) { + if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { // Event handled; do nothing. return; } else { @@ -2484,6 +2653,7 @@ function cleanupModelMenus() { function scriptEnding() { leftController.cleanup(); rightController.cleanup(); + progressDialog.cleanup(); toolBar.cleanup(); cleanupModelMenus(); tooltip.cleanup(); From f34f0a719b6389b8e540c062c44ffab3d091e2d7 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 10:26:01 -0700 Subject: [PATCH 089/206] fixed lastPopOutput null ptr errors for frame loudness check --- assignment-client/src/audio/AudioMixer.cpp | 12 +++--- .../src/audio/AudioMixerClientData.cpp | 19 ++++----- interface/src/Audio.cpp | 41 ------------------- libraries/audio/src/AudioRingBuffer.cpp | 3 ++ 4 files changed, 17 insertions(+), 58 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 63b4716f82..fc1f3a903b 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -117,6 +117,12 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* } } + // at this point, we know streamToAdd's last pop output is valid + + if (streamToAdd->getLastPopOutputFrameLoudness() == 0.0f) { + return 0; + } + float bearingRelativeAngleToSource = 0.0f; float attenuationCoefficient = 1.0f; int numSamplesDelay = 0; @@ -309,10 +315,7 @@ int AudioMixer::prepareMixForListeningNode(Node* node) { for (i = otherNodeAudioStreams.constBegin(); i != otherNodeAudioStreams.constEnd(); i++) { PositionalAudioStream* otherNodeStream = i.value(); - if ((*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) - && otherNodeStream->getLastPopOutputFrameLoudness() > 0.0f) { - //&& otherNodeStream->getLastPopOutputTrailingLoudness() > 0.0f) { - + if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) { streamsMixed += addStreamToMixForListeningNodeWithStream(otherNodeStream, nodeAudioStream); } } @@ -651,7 +654,6 @@ void AudioMixer::run() { } // send mixed audio packet - if (rand() % 100 < 90) nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 333357fbf4..6799515e26 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -101,18 +101,13 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(AABox* checkSourceZone, A QHash::ConstIterator i; for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) { PositionalAudioStream* stream = i.value(); - if (stream->popFrames(1, true) > 0) { - // this is a ring buffer that is ready to go - - // calculate the trailing avg loudness for the next frame - // that would be mixed in - stream->updateLastPopOutputTrailingLoudness(); - - if (checkSourceZone && checkSourceZone->contains(stream->getPosition())) { - stream->setListenerUnattenuatedZone(listenerZone); - } else { - stream->setListenerUnattenuatedZone(NULL); - } + + stream->popFrames(1, true); + + if (checkSourceZone && checkSourceZone->contains(stream->getPosition())) { + stream->setListenerUnattenuatedZone(listenerZone); + } else { + stream->setListenerUnattenuatedZone(NULL); } } } diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 9d00b4ffd6..b14150fdd6 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -722,7 +722,6 @@ void Audio::handleAudioInput() { } int packetBytes = currentPacketPtr - audioDataPacket; - if (rand() % 100 < 90) nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; @@ -739,11 +738,8 @@ void Audio::addStereoSilenceToScope(int silentSamplesPerChannel) { if (!_scopeEnabled || _scopeEnabledPause) { return; } - printf("\t Audio::addStereoSilenceToScope %d per channel\n", silentSamplesPerChannel); addSilenceToScope(_scopeOutputLeft, _scopeOutputOffset, silentSamplesPerChannel); _scopeOutputOffset = addSilenceToScope(_scopeOutputRight, _scopeOutputOffset, silentSamplesPerChannel); - - printf("\t end\n"); } void Audio::addStereoSamplesToScope(const QByteArray& samples) { @@ -752,18 +748,14 @@ void Audio::addStereoSamplesToScope(const QByteArray& samples) { } const int16_t* samplesData = reinterpret_cast(samples.data()); int samplesPerChannel = samples.size() / sizeof(int16_t) / STEREO_FACTOR; - printf("\t Audio::addStereoSamplesToScope %d samples per channel\n", samplesPerChannel); addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, samplesData, samplesPerChannel, 0, STEREO_FACTOR); _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, samplesData, samplesPerChannel, 1, STEREO_FACTOR); _scopeLastFrame = samples.right(NETWORK_BUFFER_LENGTH_BYTES_STEREO); - - printf("\t end\n"); } void Audio::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { - printf("addLastFrameRepeatedWithFadeToScope %d per channel\n", samplesPerChannel); const int16_t* lastFrameData = reinterpret_cast(_scopeLastFrame.data()); int samplesRemaining = samplesPerChannel; @@ -771,14 +763,12 @@ void Audio::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { do { int samplesToWriteThisIteration = std::min(samplesRemaining, (int)NETWORK_SAMPLES_PER_FRAME); float fade = calculateRepeatedFrameFadeFactor(indexOfRepeat); - printf("%f ", fade); addBufferToScope(_scopeOutputLeft, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 0, STEREO_FACTOR, fade); _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, lastFrameData, samplesToWriteThisIteration, 1, STEREO_FACTOR, fade); samplesRemaining -= samplesToWriteThisIteration; indexOfRepeat++; } while (samplesRemaining > 0); - printf("\t end\n"); } void Audio::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer) { @@ -828,34 +818,6 @@ void Audio::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& ou _desiredOutputFormat, _outputFormat); } -/*void Audio::updateScopeBuffers() { - if (_scopeEnabled && !_scopeEnabledPause) { - unsigned int numAudioChannels = _desiredOutputFormat.channelCount(); - const int16_t* samples = _receivedAudioStream.getNetworkSamples(); - int numNetworkOutputSamples = _receivedAudioStream.getNetworkSamplesWritten(); - for (int numSamples = numNetworkOutputSamples / numAudioChannels; numSamples > 0; numSamples -= NETWORK_SAMPLES_PER_FRAME) { - - unsigned int audioChannel = 0; - addBufferToScope( - _scopeOutputLeft, - _scopeOutputOffset, - samples, audioChannel, numAudioChannels); - - audioChannel = 1; - _scopeOutputOffset = addBufferToScope( - _scopeOutputRight, - _scopeOutputOffset, - samples, audioChannel, numAudioChannels); - - _scopeOutputOffset += NETWORK_SAMPLES_PER_FRAME; - _scopeOutputOffset %= _samplesPerScope; - samples += NETWORK_SAMPLES_PER_FRAME * numAudioChannels; - } - } - - _receivedAudioStream.clearNetworkSamples(); -}*/ - void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) { if (_audioOutput) { @@ -866,9 +828,6 @@ void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) { Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size()); } - - - void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) { int numBytesPacketHeader = numBytesForPacketHeader(packet); diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 31c714a1cc..baf40c530f 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -218,6 +218,9 @@ float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const { } float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const { + if (frameStart.isNull()) { + return 0.0f; + } return getFrameLoudness(&(*frameStart)); } From ddc8bec1ec74c0e7e98294664497119f1e84b5d1 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 10:41:25 -0700 Subject: [PATCH 090/206] moved lastpopframeloudness calculation to checkBuffersBeforeFrameSend --- assignment-client/src/audio/AudioMixer.cpp | 4 +++- .../src/audio/AudioMixerClientData.cpp | 4 +++- libraries/audio/src/InboundAudioStream.cpp | 4 ---- libraries/audio/src/InboundAudioStream.h | 4 +--- libraries/audio/src/PositionalAudioStream.cpp | 18 ++++++++++++++---- libraries/audio/src/PositionalAudioStream.h | 6 ++++++ 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index fc1f3a903b..70b66e60b5 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -119,7 +119,9 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* // at this point, we know streamToAdd's last pop output is valid - if (streamToAdd->getLastPopOutputFrameLoudness() == 0.0f) { + // if the frame we're about to mix is silent, bail + if (streamToAdd->getLastPopOutputLoudness() == 0.0f) { + printf("about to mix silent frame\n"); return 0; } diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 6799515e26..3b9491a5ea 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -102,7 +102,9 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(AABox* checkSourceZone, A for (i = _audioStreams.constBegin(); i != _audioStreams.constEnd(); i++) { PositionalAudioStream* stream = i.value(); - stream->popFrames(1, true); + if (stream->popFrames(1, true) > 0) { + stream->updateLastPopOutputLoudness(); + } if (checkSourceZone && checkSourceZone->contains(stream->getPosition())) { stream->setListenerUnattenuatedZone(listenerZone); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 7880602133..4c938b5bc2 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -442,10 +442,6 @@ int InboundAudioStream::writeLastFrameRepeatedWithFade(int samples) { return samples; } -float InboundAudioStream::getLastPopOutputFrameLoudness() const { - return _ringBuffer.getFrameLoudness(_lastPopOutput); -} - AudioStreamStats InboundAudioStream::getAudioStreamStats() const { AudioStreamStats streamStats; diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index f8413f8d75..ca9591a746 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -99,7 +99,7 @@ public: InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings); void reset(); - void resetStats(); + virtual void resetStats(); void clearBuffer(); virtual int parseData(const QByteArray& packet); @@ -137,8 +137,6 @@ public: /// returns the desired number of jitter buffer frames using Freddy's method int getCalculatedJitterBufferFramesUsingMaxGap() const { return _calculatedJitterBufferFramesUsingMaxGap; } - - float getLastPopOutputFrameLoudness() const; int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; } int getMaxFramesOverDesired() const { return _maxFramesOverDesired; } diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index d2c1ade85c..82b40cd2b7 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -30,22 +30,28 @@ PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, b _shouldLoopbackForNode(false), _isStereo(isStereo), _lastPopOutputTrailingLoudness(0.0f), + _lastPopOutputLoudness(0.0f), _listenerUnattenuatedZone(NULL) { } +void PositionalAudioStream::resetStats() { + _lastPopOutputTrailingLoudness = 0.0f; + _lastPopOutputLoudness = 0.0f; +} + void PositionalAudioStream::updateLastPopOutputTrailingLoudness() { - float lastPopLoudness = _ringBuffer.getFrameLoudness(_lastPopOutput); + _lastPopOutputLoudness = _ringBuffer.getFrameLoudness(_lastPopOutput); const int TRAILING_AVERAGE_FRAMES = 100; const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; const float LOUDNESS_EPSILON = 0.000001f; - if (lastPopLoudness >= _lastPopOutputTrailingLoudness) { - _lastPopOutputTrailingLoudness = lastPopLoudness; + if (_lastPopOutputLoudness >= _lastPopOutputTrailingLoudness) { + _lastPopOutputTrailingLoudness = _lastPopOutputLoudness; } else { - _lastPopOutputTrailingLoudness = (_lastPopOutputTrailingLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * lastPopLoudness); + _lastPopOutputTrailingLoudness = (_lastPopOutputTrailingLoudness * PREVIOUS_FRAMES_RATIO) + (CURRENT_FRAME_RATIO * _lastPopOutputLoudness); if (_lastPopOutputTrailingLoudness < LOUDNESS_EPSILON) { _lastPopOutputTrailingLoudness = 0; @@ -53,6 +59,10 @@ void PositionalAudioStream::updateLastPopOutputTrailingLoudness() { } } +void PositionalAudioStream::updateLastPopOutputLoudness() { + _lastPopOutputLoudness = _ringBuffer.getFrameLoudness(_lastPopOutput); +} + int PositionalAudioStream::parsePositionalData(const QByteArray& positionalByteArray) { QDataStream packetStream(positionalByteArray); diff --git a/libraries/audio/src/PositionalAudioStream.h b/libraries/audio/src/PositionalAudioStream.h index d1d5e013e7..c117046344 100644 --- a/libraries/audio/src/PositionalAudioStream.h +++ b/libraries/audio/src/PositionalAudioStream.h @@ -29,11 +29,16 @@ public: PositionalAudioStream(PositionalAudioStream::Type type, bool isStereo, const InboundAudioStream::Settings& settings); + virtual void resetStats(); + virtual AudioStreamStats getAudioStreamStats() const; void updateLastPopOutputTrailingLoudness(); float getLastPopOutputTrailingLoudness() const { return _lastPopOutputTrailingLoudness; } + void updateLastPopOutputLoudness(); + float getLastPopOutputLoudness() const { return _lastPopOutputLoudness; } + bool shouldLoopbackForNode() const { return _shouldLoopbackForNode; } bool isStereo() const { return _isStereo; } PositionalAudioStream::Type getType() const { return _type; } @@ -59,6 +64,7 @@ protected: bool _isStereo; float _lastPopOutputTrailingLoudness; + float _lastPopOutputLoudness; AABox* _listenerUnattenuatedZone; }; From 746893cc94180f8730af1505c695f749bf5b8e8f Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 10:48:47 -0700 Subject: [PATCH 091/206] updateLastOutputLoudnessAndTrailingLoudness --- assignment-client/src/audio/AudioMixerClientData.cpp | 2 +- libraries/audio/src/PositionalAudioStream.cpp | 6 +----- libraries/audio/src/PositionalAudioStream.h | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 3b9491a5ea..5c8bc4f5d3 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -103,7 +103,7 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(AABox* checkSourceZone, A PositionalAudioStream* stream = i.value(); if (stream->popFrames(1, true) > 0) { - stream->updateLastPopOutputLoudness(); + stream->updateLastPopOutputLoudnessAndTrailingLoudness(); } if (checkSourceZone && checkSourceZone->contains(stream->getPosition())) { diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index 82b40cd2b7..ae30022268 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -40,7 +40,7 @@ void PositionalAudioStream::resetStats() { _lastPopOutputLoudness = 0.0f; } -void PositionalAudioStream::updateLastPopOutputTrailingLoudness() { +void PositionalAudioStream::updateLastPopOutputLoudnessAndTrailingLoudness() { _lastPopOutputLoudness = _ringBuffer.getFrameLoudness(_lastPopOutput); const int TRAILING_AVERAGE_FRAMES = 100; @@ -59,10 +59,6 @@ void PositionalAudioStream::updateLastPopOutputTrailingLoudness() { } } -void PositionalAudioStream::updateLastPopOutputLoudness() { - _lastPopOutputLoudness = _ringBuffer.getFrameLoudness(_lastPopOutput); -} - int PositionalAudioStream::parsePositionalData(const QByteArray& positionalByteArray) { QDataStream packetStream(positionalByteArray); diff --git a/libraries/audio/src/PositionalAudioStream.h b/libraries/audio/src/PositionalAudioStream.h index c117046344..2b615a575b 100644 --- a/libraries/audio/src/PositionalAudioStream.h +++ b/libraries/audio/src/PositionalAudioStream.h @@ -33,10 +33,8 @@ public: virtual AudioStreamStats getAudioStreamStats() const; - void updateLastPopOutputTrailingLoudness(); + void updateLastPopOutputLoudnessAndTrailingLoudness(); float getLastPopOutputTrailingLoudness() const { return _lastPopOutputTrailingLoudness; } - - void updateLastPopOutputLoudness(); float getLastPopOutputLoudness() const { return _lastPopOutputLoudness; } bool shouldLoopbackForNode() const { return _shouldLoopbackForNode; } From a405cd9a7249458553dc3d19f6823e44c14e5ada Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 11:01:15 -0700 Subject: [PATCH 092/206] repetition-with-fade ready for commit --- assignment-client/src/audio/AudioMixer.cpp | 1 - libraries/script-engine/src/ScriptEngine.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 70b66e60b5..9ffebbc90b 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -121,7 +121,6 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* // if the frame we're about to mix is silent, bail if (streamToAdd->getLastPopOutputLoudness() == 0.0f) { - printf("about to mix silent frame\n"); return 0; } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 58b0c90daa..2891055b65 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -519,7 +519,6 @@ void ScriptEngine::run() { memcpy(audioPacket.data() + numPreSequenceNumberBytes, &sequence, sizeof(quint16)); // send audio packet - if (rand() % 100 < 90) nodeList->writeDatagram(audioPacket, node); } } From 014346094bccd03cf9ff3290e5ce97b3583ced96 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 11:28:41 -0700 Subject: [PATCH 093/206] fixed compile error with LIBOVR --- interface/src/devices/OculusManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 7d7375fad5..b60f55636f 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -504,7 +504,7 @@ void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) { } glm::vec3 OculusManager::getRelativePosition() { -#if defined(__APPLE__) || defined(_WIN32) +#if defined(HAVE_LIBOVR) && (defined(__APPLE__) || defined(_WIN32)) ovrTrackingState trackingState = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); ovrVector3f headPosition = trackingState.HeadPose.ThePose.Position; From b17c9102c90155bb9b459fc680e565adc2924c90 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 13:30:02 -0700 Subject: [PATCH 094/206] added stats for readPendingDatagrams in audiomixer --- assignment-client/src/audio/AudioMixer.cpp | 44 +++++++++++++++++----- assignment-client/src/audio/AudioMixer.h | 17 ++++++++- libraries/shared/src/MovingMinMaxAvg.h | 2 + 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index dab41625bd..3fe872d57c 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -81,7 +81,12 @@ AudioMixer::AudioMixer(const QByteArray& packet) : _sumMixes(0), _sourceUnattenuatedZone(NULL), _listenerUnattenuatedZone(NULL), - _lastSendAudioStreamStatsTime(usecTimestampNow()) + _lastPerSecondCallbackTime(usecTimestampNow()), + _sendAudioStreamStats(false), + _datagramsReadPerCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS), + _timeSpentPerCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS), + _timeSpentPerHashMatchCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS), + _readPendingCallsPerSecondStats(1, READ_DATAGRAMS_STATS_WINDOW_SECONDS) { } @@ -328,12 +333,18 @@ int AudioMixer::prepareMixForListeningNode(Node* node) { } void AudioMixer::readPendingDatagrams() { + quint64 readPendingDatagramsStart = usecTimestampNow(); + QByteArray receivedPacket; HifiSockAddr senderSockAddr; NodeList* nodeList = NodeList::getInstance(); + int datagramsRead = 0; while (readAvailableDatagram(receivedPacket, senderSockAddr)) { - if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + quint64 packetVersionAndHashMatchStart = usecTimestampNow(); + bool match = nodeList->packetVersionAndHashMatch(receivedPacket); + _timeSpentPerHashMatchCallStats.update(usecTimestampNow() - packetVersionAndHashMatchStart); + if (match) { // pull any new audio data from nodes off of the network stack PacketType mixerPacketType = packetTypeForPacket(receivedPacket); if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho @@ -352,13 +363,16 @@ void AudioMixer::readPendingDatagrams() { nodeList->writeDatagram(packet, packet.size(), node); } } - } else { // let processNodeData handle it. nodeList->processNodeData(senderSockAddr, receivedPacket); } } + datagramsRead++; } + + _timeSpentPerCallStats.update(usecTimestampNow() - readPendingDatagramsStart); + _datagramsReadPerCallStats.update(datagramsRead); } void AudioMixer::sendStatsPacket() { @@ -609,12 +623,11 @@ void AudioMixer::run() { if (!hasRatioChanged) { ++framesSinceCutoffEvent; } - - bool sendAudioStreamStats = false; + quint64 now = usecTimestampNow(); - if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) { - _lastSendAudioStreamStatsTime = now; - sendAudioStreamStats = true; + if (now - _lastPerSecondCallbackTime > USECS_PER_SECOND) { + perSecondActions(); + _lastPerSecondCallbackTime = now; } bool streamStatsPrinted = false; @@ -667,14 +680,14 @@ void AudioMixer::run() { nodeData->incrementOutgoingMixedAudioSequenceNumber(); // send an audio stream stats packet if it's time - if (sendAudioStreamStats) { + if (_sendAudioStreamStats) { nodeData->sendAudioStreamStatsPackets(node); - if (_printStreamStats) { printf("\nStats for agent %s:\n", node->getUUID().toString().toLatin1().data()); nodeData->printUpstreamDownstreamStats(); streamStatsPrinted = true; } + _sendAudioStreamStats = false; } ++_sumListeners; @@ -700,3 +713,14 @@ void AudioMixer::run() { } } } + +void AudioMixer::perSecondActions() { + _sendAudioStreamStats = true; + + int callsLastSecond = _datagramsReadPerCallStats.getCurrentIntervalSamples(); + _readPendingCallsPerSecondStats.update(callsLastSecond); + + _datagramsReadPerCallStats.currentIntervalComplete(); + _timeSpentPerCallStats.currentIntervalComplete(); + _timeSpentPerHashMatchCallStats.currentIntervalComplete(); +} diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index b620b1cd85..e47c5f3811 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -21,7 +21,8 @@ class AvatarAudioStream; const int SAMPLE_PHASE_DELAY_AT_90 = 20; -const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND; +const int READ_DATAGRAMS_STATS_WINDOW_SECONDS = 30; + /// Handles assignments of type AudioMixer - mixing streams of audio and re-distributing to various clients. class AudioMixer : public ThreadedAssignment { @@ -50,6 +51,9 @@ private: // client samples capacity is larger than what will be sent to optimize mixing // we are MMX adding 4 samples at a time so we need client samples to have an extra 4 int16_t _clientSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)]; + + void perSecondActions(); + float _trailingSleepRatio; float _minAudibilityThreshold; @@ -64,7 +68,16 @@ private: static bool _printStreamStats; - quint64 _lastSendAudioStreamStatsTime; + quint64 _lastPerSecondCallbackTime; + + bool _sendAudioStreamStats; + + // stats + MovingMinMaxAvg _datagramsReadPerCallStats; // update with # of datagrams read for each readPendingDatagrams call + MovingMinMaxAvg _timeSpentPerCallStats; // update with usecs spent inside each readPendingDatagrams call + MovingMinMaxAvg _timeSpentPerHashMatchCallStats; // update with usecs spent inside each packetVersionAndHashMatch call + + MovingMinMaxAvg _readPendingCallsPerSecondStats; // update with # of readPendingDatagrams calls in the last second }; #endif // hifi_AudioMixer_h diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 628d3b4353..16fcb94dcf 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -156,6 +156,8 @@ public: T getWindowMax() const { return _windowStats.getMax(); } double getWindowAverage() const { return _windowStats.getAverage(); } + int getCurrentIntervalSamples() const { return _windowStats._samples; } + const MinMaxAvg& getOverallStats() const{ return _overallStats; } const MinMaxAvg& getWindowStats() const{ return _windowStats; } From a2e7c9b75c2b81c754d0e74aad95512750587516 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 13 Aug 2014 14:53:26 -0700 Subject: [PATCH 095/206] Update build for speechRecognizer --- interface/CMakeLists.txt | 11 +++++++++-- interface/src/Application.cpp | 2 ++ interface/src/Menu.h | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 86d818af6d..1a152e4707 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -46,13 +46,20 @@ configure_file(InterfaceConfig.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceCon configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVersion.h") # grab the implementation and header files from src dirs -file(GLOB INTERFACE_OBJCPP_SRCS src/*.mm) file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles models) file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}") endforeach(SUBDIR) +# grab .mm files for OSX +if (APPLE) + file(GLOB INTERFACE_OBJCPP_SRCS src/SpeechRecognizer.mm) + set(INTERFACE_SRCS ${INTERFACE_SRCS} ${INTERFACE_OBJCPP_SRCS}) +else () + list(REMOVE_ITEM "src/SpeechRecognizer.h") +endif () + find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools) # grab the ui files in resources/ui @@ -96,7 +103,7 @@ if (APPLE) endif() # create the executable, make it a bundle on OS X -add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${INTERFACE_OBJCPP_SRCS} ${QM}) +add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) # link in the hifi shared library include(${MACRO_DIR}/LinkHifiLibrary.cmake) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 87f63c4b78..f303cc1c5b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3659,7 +3659,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->registerGlobalObject("Camera", cameraScriptable); connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater())); +#ifdef Q_OS_MAC scriptEngine->registerGlobalObject("SpeechRecognizer", Menu::getInstance()->getSpeechRecognizer()); +#endif ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface(); scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 9f43898214..75371230ef 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -22,7 +22,10 @@ #include #include #include + +#ifdef Q_OS_MAC #include "SpeechRecognizer.h" +#endif #include "location/LocationManager.h" #include "ui/PreferencesDialog.h" From bf9e887cee7e5b7c13739d85282250497f519fe2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 13 Aug 2014 16:16:11 -0700 Subject: [PATCH 096/206] Fix SpeechRecognizer in CMakeLists --- interface/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 1a152e4707..18d5b9bd70 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -52,12 +52,13 @@ foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels pa set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}") endforeach(SUBDIR) -# grab .mm files for OSX -if (APPLE) - file(GLOB INTERFACE_OBJCPP_SRCS src/SpeechRecognizer.mm) +# Add SpeechRecognizer if on OS X, otherwise remove +if (APPLE) + file(GLOB INTERFACE_OBJCPP_SRCS "src/SpeechRecognizer.mm") set(INTERFACE_SRCS ${INTERFACE_SRCS} ${INTERFACE_OBJCPP_SRCS}) else () - list(REMOVE_ITEM "src/SpeechRecognizer.h") + get_filename_component(SPEECHRECOGNIZER_H "src/SpeechRecognizer.h" ABSOLUTE) + list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_H}) endif () find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools) From 81fa5ed41fd090864732516aab6d9e7239271866 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 16:48:06 -0700 Subject: [PATCH 097/206] readPendingDatagrams stats printed and sent to domain page jittertester now prints out send or receive error msgs --- assignment-client/src/audio/AudioMixer.cpp | 95 ++++++++++++++++++---- assignment-client/src/audio/AudioMixer.h | 1 + libraries/shared/src/MovingMinMaxAvg.h | 20 ++++- tests/jitter/src/main.cpp | 11 ++- 4 files changed, 108 insertions(+), 19 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 3fe872d57c..d32f14ac0f 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -404,9 +404,24 @@ void AudioMixer::sendStatsPacket() { int sizeOfStats = 0; int TOO_BIG_FOR_MTU = 1200; // some extra space for JSONification + QString property = "readPendingDatagramsStats"; + QString value = getReadPendingDatagramsStatsString(); + statsObject2[qPrintable(property)] = value; + somethingToSend = true; + sizeOfStats += property.size() + value.size(); + NodeList* nodeList = NodeList::getInstance(); int clientNumber = 0; foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + + // if we're too large, send the packet + if (sizeOfStats > TOO_BIG_FOR_MTU) { + nodeList->sendStatsToDomainServer(statsObject2); + sizeOfStats = 0; + statsObject2 = QJsonObject(); // clear it + somethingToSend = false; + } + clientNumber++; AudioMixerClientData* clientData = static_cast(node->getLinkedData()); if (clientData) { @@ -416,14 +431,6 @@ void AudioMixer::sendStatsPacket() { somethingToSend = true; sizeOfStats += property.size() + value.size(); } - - // if we're too large, send the packet - if (sizeOfStats > TOO_BIG_FOR_MTU) { - nodeList->sendStatsToDomainServer(statsObject2); - sizeOfStats = 0; - statsObject2 = QJsonObject(); // clear it - somethingToSend = false; - } } if (somethingToSend) { @@ -682,11 +689,6 @@ void AudioMixer::run() { // send an audio stream stats packet if it's time if (_sendAudioStreamStats) { nodeData->sendAudioStreamStatsPackets(node); - if (_printStreamStats) { - printf("\nStats for agent %s:\n", node->getUUID().toString().toLatin1().data()); - nodeData->printUpstreamDownstreamStats(); - streamStatsPrinted = true; - } _sendAudioStreamStats = false; } @@ -694,9 +696,6 @@ void AudioMixer::run() { } } } - if (streamStatsPrinted) { - printf("\n----------------------------------------------------------------\n"); - } ++_numStatFrames; @@ -720,7 +719,71 @@ void AudioMixer::perSecondActions() { int callsLastSecond = _datagramsReadPerCallStats.getCurrentIntervalSamples(); _readPendingCallsPerSecondStats.update(callsLastSecond); + if (_printStreamStats) { + + printf("\n================================================================================\n\n"); + + printf(" readPendingDatagram() calls per second | avg: %.2f, avg_30s: %.2f, last_second: %d\n", + _readPendingCallsPerSecondStats.getAverage(), + _readPendingCallsPerSecondStats.getWindowAverage(), + callsLastSecond); + + printf(" Datagrams read per call | avg: %.2f, avg_30s: %.2f, last_second: %.2f\n", + _datagramsReadPerCallStats.getAverage(), + _datagramsReadPerCallStats.getWindowAverage(), + _datagramsReadPerCallStats.getCurrentIntervalAverage()); + + printf(" Usecs spent per readPendingDatagram() call | avg: %.2f, avg_30s: %.2f, last_second: %.2f\n", + _timeSpentPerCallStats.getAverage(), + _timeSpentPerCallStats.getWindowAverage(), + _timeSpentPerCallStats.getCurrentIntervalAverage()); + + printf(" Usecs spent per packetVersionAndHashMatch() call | avg: %.2f, avg_30s: %.2f, last_second: %.2f\n", + _timeSpentPerHashMatchCallStats.getAverage(), + _timeSpentPerHashMatchCallStats.getWindowAverage(), + _timeSpentPerHashMatchCallStats.getCurrentIntervalAverage()); + + double WINDOW_LENGTH_USECS = READ_DATAGRAMS_STATS_WINDOW_SECONDS * USECS_PER_SECOND; + + printf(" %% time spent in readPendingDatagram() calls | avg_30s: %.6f%%, last_second: %.6f%%\n", + _timeSpentPerCallStats.getWindowSum() / WINDOW_LENGTH_USECS * 100.0, + _timeSpentPerCallStats.getCurrentIntervalSum() / USECS_PER_SECOND * 100.0); + + printf("%% time spent in packetVersionAndHashMatch() calls: | avg_30s: %.6f%%, last_second: %.6f%%\n", + _timeSpentPerHashMatchCallStats.getWindowSum() / WINDOW_LENGTH_USECS * 100.0, + _timeSpentPerHashMatchCallStats.getCurrentIntervalSum() / USECS_PER_SECOND * 100.0); + + foreach(const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + if (node->getLinkedData()) { + AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); + + if (node->getType() == NodeType::Agent && node->getActiveSocket()) { + printf("\nStats for agent %s --------------------------------\n", + node->getUUID().toString().toLatin1().data()); + nodeData->printUpstreamDownstreamStats(); + } + } + } + } + _datagramsReadPerCallStats.currentIntervalComplete(); _timeSpentPerCallStats.currentIntervalComplete(); _timeSpentPerHashMatchCallStats.currentIntervalComplete(); } + +QString AudioMixer::getReadPendingDatagramsStatsString() const { + QString result + = "calls_per_sec_avg_30s: " + QString::number(_readPendingCallsPerSecondStats.getWindowAverage(), 'f', 2) + + " calls_last_sec: " + QString::number(_readPendingCallsPerSecondStats.getLastCompleteIntervalStats().getSum() + 0.5, 'f', 0) + + " pkts_per_call_avg_30s: " + QString::number(_datagramsReadPerCallStats.getWindowAverage(), 'f', 2) + + " pkts_per_call_avg_1s: " + QString::number(_datagramsReadPerCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) + + " usecs_per_call_avg_30s: " + QString::number(_timeSpentPerCallStats.getWindowAverage(), 'f', 2) + + " usecs_per_call_avg_1s: " + QString::number(_timeSpentPerCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) + + " usecs_per_hashmatch_avg_30s: " + QString::number(_timeSpentPerHashMatchCallStats.getWindowAverage(), 'f', 2) + + " usecs_per_hashmatch_avg_1s: " + QString::number(_timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) + + " prct_time_in_call_30s: " + QString::number(_timeSpentPerCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS*USECS_PER_SECOND) * 100.0, 'f', 6) + "%" + + " prct_time_in_call_1s: " + QString::number(_timeSpentPerCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0, 'f', 6) + "%" + + " prct_time_in_hashmatch_30s: " + QString::number(_timeSpentPerHashMatchCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS*USECS_PER_SECOND) * 100.0, 'f', 6) + "%" + + " prct_time_in_hashmatch_1s: " + QString::number(_timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0, 'f', 6) + "%"; + return result; +} \ No newline at end of file diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index e47c5f3811..0c1378d54f 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -54,6 +54,7 @@ private: void perSecondActions(); + QString getReadPendingDatagramsStatsString() const; float _trailingSleepRatio; float _minAudibilityThreshold; diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 16fcb94dcf..4a044392c1 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -64,6 +64,7 @@ public: T getMax() const { return _max; } double getAverage() const { return _average; } int getSamples() const { return _samples; } + double getSum() const { return _samples * _average; } private: T _min; @@ -152,14 +153,29 @@ public: T getMin() const { return _overallStats.getMin(); } T getMax() const { return _overallStats.getMax(); } double getAverage() const { return _overallStats.getAverage(); } + int getSamples() const { return _overallStats.getSamples(); } + double getSum() const { return _overallStats.getSum(); } + T getWindowMin() const { return _windowStats.getMin(); } T getWindowMax() const { return _windowStats.getMax(); } double getWindowAverage() const { return _windowStats.getAverage(); } + int getWindowSamples() const { return _windowStats.getSamples(); } + double getWindowSum() const { return _windowStats.getSum(); } - int getCurrentIntervalSamples() const { return _windowStats._samples; } - + T getCurrentIntervalMin() const { return _currentIntervalStats.getMin(); } + T getCurrentIntervalMax() const { return _currentIntervalStats.getMax(); } + double getCurrentIntervalAverage() const { return _currentIntervalStats.getAverage(); } + int getCurrentIntervalSamples() const { return _currentIntervalStats.getSamples(); } + double getCurrentIntervalSum() const { return _currentIntervalStats.getSum(); } + const MinMaxAvg& getOverallStats() const{ return _overallStats; } const MinMaxAvg& getWindowStats() const{ return _windowStats; } + const MinMaxAvg& getCurrentIntervalStats() const { return _currentIntervalStats; } + + MinMaxAvg getLastCompleteIntervalStats() const { + const MinMaxAvg* stats = _intervalStats.getNewestEntry(); + return stats == NULL ? MinMaxAvg() : *stats; + } bool isWindowFilled() const { return _intervalStats.isFilled(); } diff --git a/tests/jitter/src/main.cpp b/tests/jitter/src/main.cpp index a33347f9ef..07dc7062a8 100644 --- a/tests/jitter/src/main.cpp +++ b/tests/jitter/src/main.cpp @@ -13,6 +13,7 @@ #include #include #endif +#include #include #include // for MovingMinMaxAvg @@ -103,7 +104,10 @@ void runSend(const char* addressOption, int port, int gap, int size, int report) // pack seq num memcpy(outputBuffer, &outgoingSequenceNumber, sizeof(quint16)); - sendto(sockfd, outputBuffer, size, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); + int n = sendto(sockfd, outputBuffer, size, 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); + if (n < 0) { + std::cout << "Send error: " << strerror(errno) << "\n"; + } outgoingSequenceNumber++; int gapDifferece = actualGap - gap; @@ -144,6 +148,7 @@ void runSend(const char* addressOption, int port, int gap, int size, int report) } } } + delete[] outputBuffer; } void runReceive(const char* addressOption, int port, int gap, int size, int report) { @@ -195,6 +200,9 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo while (true) { n = recvfrom(sockfd, inputBuffer, size, 0, NULL, NULL); // we don't care about where it came from + if (n < 0) { + std::cout << "Receive error: " << strerror(errno) << "\n"; + } // parse seq num quint16 incomingSequenceNumber = *(reinterpret_cast(inputBuffer)); @@ -260,5 +268,6 @@ void runReceive(const char* addressOption, int port, int gap, int size, int repo } } } + delete[] inputBuffer; } From 047c4dff655e9b2acd1a603d0581ff2f1f877b34 Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 17:05:53 -0700 Subject: [PATCH 098/206] removed unused var --- assignment-client/src/audio/AudioMixer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index d32f14ac0f..023a7a6ce9 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -636,8 +636,7 @@ void AudioMixer::run() { perSecondActions(); _lastPerSecondCallbackTime = now; } - - bool streamStatsPrinted = false; + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData()) { AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); From e50bd1bed9d81868d8e725b8aa69d6b1b709369f Mon Sep 17 00:00:00 2001 From: wangyix Date: Wed, 13 Aug 2014 17:57:04 -0700 Subject: [PATCH 099/206] separated readpendingdatagrams domain page stats --- assignment-client/src/audio/AudioMixer.cpp | 63 ++++++++++++++++------ assignment-client/src/audio/AudioMixer.h | 5 +- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 023a7a6ce9..036f1f4e50 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -404,12 +404,30 @@ void AudioMixer::sendStatsPacket() { int sizeOfStats = 0; int TOO_BIG_FOR_MTU = 1200; // some extra space for JSONification - QString property = "readPendingDatagramsStats"; - QString value = getReadPendingDatagramsStatsString(); + QString property = "readPendingDatagram_calls_stats"; + QString value = getReadPendingDatagramsCallsPerSecondsStatsString(); statsObject2[qPrintable(property)] = value; somethingToSend = true; sizeOfStats += property.size() + value.size(); + property = "readPendingDatagram_packets_per_call_stats"; + value = getReadPendingDatagramsPacketsPerCallStatsString(); + statsObject2[qPrintable(property)] = value; + somethingToSend = true; + sizeOfStats += property.size() + value.size(); + + property = "readPendingDatagram_packets_time_per_call_stats"; + value = getReadPendingDatagramsTimeStatsString(); + statsObject2[qPrintable(property)] = value; + somethingToSend = true; + sizeOfStats += property.size() + value.size(); + + property = "readPendingDatagram_hashmatch_time_per_call_stats"; + value = getReadPendingDatagramsHashMatchTimeStatsString(); + statsObject2[qPrintable(property)] = value; + somethingToSend = true; + sizeOfStats += property.size() + value.size(); + NodeList* nodeList = NodeList::getInstance(); int clientNumber = 0; foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { @@ -770,19 +788,30 @@ void AudioMixer::perSecondActions() { _timeSpentPerHashMatchCallStats.currentIntervalComplete(); } -QString AudioMixer::getReadPendingDatagramsStatsString() const { - QString result - = "calls_per_sec_avg_30s: " + QString::number(_readPendingCallsPerSecondStats.getWindowAverage(), 'f', 2) - + " calls_last_sec: " + QString::number(_readPendingCallsPerSecondStats.getLastCompleteIntervalStats().getSum() + 0.5, 'f', 0) - + " pkts_per_call_avg_30s: " + QString::number(_datagramsReadPerCallStats.getWindowAverage(), 'f', 2) - + " pkts_per_call_avg_1s: " + QString::number(_datagramsReadPerCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) - + " usecs_per_call_avg_30s: " + QString::number(_timeSpentPerCallStats.getWindowAverage(), 'f', 2) - + " usecs_per_call_avg_1s: " + QString::number(_timeSpentPerCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) - + " usecs_per_hashmatch_avg_30s: " + QString::number(_timeSpentPerHashMatchCallStats.getWindowAverage(), 'f', 2) - + " usecs_per_hashmatch_avg_1s: " + QString::number(_timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) - + " prct_time_in_call_30s: " + QString::number(_timeSpentPerCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS*USECS_PER_SECOND) * 100.0, 'f', 6) + "%" - + " prct_time_in_call_1s: " + QString::number(_timeSpentPerCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0, 'f', 6) + "%" - + " prct_time_in_hashmatch_30s: " + QString::number(_timeSpentPerHashMatchCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS*USECS_PER_SECOND) * 100.0, 'f', 6) + "%" - + " prct_time_in_hashmatch_1s: " + QString::number(_timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0, 'f', 6) + "%"; +QString AudioMixer::getReadPendingDatagramsCallsPerSecondsStatsString() const { + QString result = "calls_per_sec_avg_30s: " + QString::number(_readPendingCallsPerSecondStats.getWindowAverage(), 'f', 2) + + " calls_last_sec: " + QString::number(_readPendingCallsPerSecondStats.getLastCompleteIntervalStats().getSum() + 0.5, 'f', 0); return result; -} \ No newline at end of file +} + +QString AudioMixer::getReadPendingDatagramsPacketsPerCallStatsString() const { + QString result = "pkts_per_call_avg_30s: " + QString::number(_datagramsReadPerCallStats.getWindowAverage(), 'f', 2) + + " pkts_per_call_avg_1s: " + QString::number(_datagramsReadPerCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2); + return result; +} + +QString AudioMixer::getReadPendingDatagramsTimeStatsString() const { + QString result = "usecs_per_call_avg_30s: " + QString::number(_timeSpentPerCallStats.getWindowAverage(), 'f', 2) + + " usecs_per_call_avg_1s: " + QString::number(_timeSpentPerCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) + + " prct_time_in_call_30s: " + QString::number(_timeSpentPerCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS*USECS_PER_SECOND) * 100.0, 'f', 6) + "%" + + " prct_time_in_call_1s: " + QString::number(_timeSpentPerCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0, 'f', 6) + "%"; + return result; +} + +QString AudioMixer::getReadPendingDatagramsHashMatchTimeStatsString() const { + QString result = "usecs_per_hashmatch_avg_30s: " + QString::number(_timeSpentPerHashMatchCallStats.getWindowAverage(), 'f', 2) + + " usecs_per_hashmatch_avg_1s: " + QString::number(_timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getAverage(), 'f', 2) + + " prct_time_in_hashmatch_30s: " + QString::number(_timeSpentPerHashMatchCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS*USECS_PER_SECOND) * 100.0, 'f', 6) + "%" + + " prct_time_in_hashmatch_1s: " + QString::number(_timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0, 'f', 6) + "%"; + return result; +} diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 0c1378d54f..7b8dc7af43 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -54,7 +54,10 @@ private: void perSecondActions(); - QString getReadPendingDatagramsStatsString() const; + QString getReadPendingDatagramsCallsPerSecondsStatsString() const; + QString getReadPendingDatagramsPacketsPerCallStatsString() const; + QString getReadPendingDatagramsTimeStatsString() const; + QString getReadPendingDatagramsHashMatchTimeStatsString() const; float _trailingSleepRatio; float _minAudibilityThreshold; From db5aeddc0f67dc2c93587fbe5a8979877b333383 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 14 Aug 2014 10:58:00 -0700 Subject: [PATCH 100/206] fix for joints not playing back --- interface/src/avatar/MyAvatar.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1133ac01c3..9e302c45fe 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -916,7 +916,8 @@ const float JOINT_PRIORITY = 2.0f; void MyAvatar::setJointRotations(QVector jointRotations) { for (int i = 0; i < jointRotations.size(); ++i) { if (i < _jointData.size()) { - _skeletonModel.setJointState(i, true, jointRotations[i]); + // TODO change animation priority to proper value + _skeletonModel.setJointState(i, true, jointRotations[i], 100.0f); } } } From 29f4f5b21aaa14e8de2ef1c4162e89878ae1cd2b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Aug 2014 11:18:10 -0700 Subject: [PATCH 101/206] Support FBX files with embedded textures --- examples/editModels.js | 85 +++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index dbae4fd08c..b6552f67f2 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -474,6 +474,7 @@ var modelUploader = (function () { mapping = {}; geometry = {}; geometry.textures = []; + geometry.embedded = []; } function readFile(filename) { @@ -575,7 +576,8 @@ var modelUploader = (function () { var textures, view, index, - EOF; + EOF, + previousNodeFilename; // Reference: // http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ @@ -609,12 +611,19 @@ var modelUploader = (function () { name = view.string(index, nameLength).toLowerCase(); index += nameLength; + if (name === "content" && previousNodeFilename !== "") { + geometry.embedded.push(previousNodeFilename); + } + if (name === "relativefilename") { filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName(); if (!textures.hasOwnProperty(filename)) { textures[filename] = ""; geometry.textures.push(filename); } + previousNodeFilename = filename; + } else { + previousNodeFilename = ""; } index += (propertyListLength); @@ -630,31 +639,49 @@ var modelUploader = (function () { viewLength, charCode, charCodes, - filename; + numCharCodes, + filename, + relativeFilename = "", + MAX_CHAR_CODES = 250; view = new Uint8Array(fbxBuffer.buffer); viewLength = view.byteLength; charCodes = []; + numCharCodes = 0; for (index = 0; index < viewLength; index += 1) { charCode = view[index]; - if (charCode === 10) { // Can ignore EOF - line = String.fromCharCode.apply(String, charCodes).trim(); - if (line.slice(0, 17).toLowerCase() === "relativefilename:") { - filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); - if (!textures.hasOwnProperty(filename)) { - textures[filename] = ""; - geometry.textures.push(filename); + if (charCode !== 9 && charCode !== 32) { + if (charCode === 10) { // EOL. Can ignore EOF. + line = String.fromCharCode.apply(String, charCodes).toLowerCase(); + // For embedded textures, "Content:" line immediately follows "RelativeFilename:" line. + if (line.slice(0, 8) === "content:" && relativeFilename !== "") { + geometry.embedded.push(relativeFilename); + } + if (line.slice(0, 17) === "relativefilename:") { + filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); + if (!textures.hasOwnProperty(filename)) { + textures[filename] = ""; + geometry.textures.push(filename); + } + relativeFilename = filename; + } else { + relativeFilename = ""; + } + charCodes = []; + numCharCodes = 0; + } else { + if (numCharCodes < MAX_CHAR_CODES) { // Only interested in start of line + charCodes.push(charCode); + numCharCodes += 1; } } - charCodes = []; - } else { - charCodes.push(charCode); } } } if (view.string(0, 18) === "Kaydara FBX Binary") { + previousNodeFilename = ""; index = 27; while (index < view.byteLength - 39 && !EOF) { @@ -800,6 +827,7 @@ var modelUploader = (function () { textureBuffer, textureSourceFormat, textureTargetFormat, + embeddedTextures, i; info("Preparing to send model"); @@ -858,23 +886,28 @@ var modelUploader = (function () { } // Textures + embeddedTextures = "|" + geometry.embedded.join("|") + "|"; for (i = 0; i < geometry.textures.length; i += 1) { - textureBuffer = readFile(modelFile.path() + "\/" - + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") - + geometry.textures[i]); - if (textureBuffer === null) { - return; + if (embeddedTextures.indexOf("|" + geometry.textures[i].fileName() + "|") === -1) { + textureBuffer = readFile(modelFile.path() + "\/" + + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") + + geometry.textures[i]); + if (textureBuffer === null) { + return; + } + + textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); + textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png"); + textureBuffer.buffer = + textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE); + textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat; + + multiparts.push({ + name: "texture" + i, + buffer: textureBuffer + }); } - textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); - textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png"); - textureBuffer.buffer = textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE); - textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat; - - multiparts.push({ - name: "texture" + i, - buffer: textureBuffer - }); if (!isProcessing) { return; } } From cd0f2164197dfa9e8b358d4a966e3f6f84d0527f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 14 Aug 2014 11:39:36 -0700 Subject: [PATCH 102/206] Added Recorder and Player to Audio class --- interface/src/Audio.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 8fae6f3bdd..de87291b55 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -17,6 +17,7 @@ #include "InterfaceConfig.h" #include "AudioStreamStats.h" +#include "Recorder.h" #include "RingBufferHistory.h" #include "MovingMinMaxAvg.h" @@ -101,6 +102,9 @@ public: float getAudioOutputMsecsUnplayed() const; float getAudioOutputAverageMsecsUnplayed() const { return (float)_audioOutputMsecsUnplayedStats.getWindowAverage(); } + + void setRecorder(RecorderPointer recorder) { _recorder = recorder; } + void setPlayer(PlayerPointer player) { _player = player; } public slots: void start(); @@ -297,6 +301,9 @@ private: MovingMinMaxAvg _packetSentTimeGaps; AudioOutputIODevice _audioOutputIODevice; + + WeakRecorderPointer _recorder; + WeakPlayerPointer _player; }; From 7a8a8684d6f8c9956ca7e4f81eb8064b8dece58e Mon Sep 17 00:00:00 2001 From: wangyix Date: Thu, 14 Aug 2014 15:56:13 -0700 Subject: [PATCH 103/206] fixed crash when audioscope frame size is reduced --- interface/src/Audio.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 3009262625..6a22f139b7 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1236,8 +1236,6 @@ void Audio::selectAudioFilterSmiley() { void Audio::toggleScope() { _scopeEnabled = !_scopeEnabled; if (_scopeEnabled) { - _scopeInputOffset = 0; - _scopeOutputOffset = 0; allocateScope(); } else { freeScope(); @@ -1275,6 +1273,8 @@ void Audio::selectAudioScopeFiftyFrames() { } void Audio::allocateScope() { + _scopeInputOffset = 0; + _scopeOutputOffset = 0; int num = _samplesPerScope * sizeof(int16_t); _scopeInput = new QByteArray(num, 0); _scopeOutputLeft = new QByteArray(num, 0); From f7addad5acdcc03a330ae7facc78c2e8f39b2e7b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 14 Aug 2014 17:20:00 -0700 Subject: [PATCH 104/206] Update progress background image to use copy on S3 --- examples/editModels.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index b6552f67f2..b15ea58fc6 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -188,8 +188,7 @@ var progressDialog = (function () { cancelHeight = 32, textColor = { red: 255, green: 255, blue: 255 }, textBackground = { red: 52, green: 52, blue: 52 }, - backgroundUrl = "http://ctrlaltstudio.com/hifi/progress-background.svg", // DJRTODO: Update with HiFi location. - //backgroundUrl = "http://public.highfidelity.io/images/tools/progress-background.svg", + backgroundUrl = toolIconUrl + "progress-background.svg", windowDimensions; progressBackground = Overlays.addOverlay("image", { From a83ff4533555ef3869ec65b63966a15ffe0a3125 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 15 Aug 2014 18:09:58 -0700 Subject: [PATCH 105/206] Working on custom encodings for subdivisions. --- libraries/metavoxels/src/AttributeRegistry.h | 4 ++ libraries/metavoxels/src/MetavoxelData.cpp | 42 +++++++++++++++++++- libraries/metavoxels/src/MetavoxelData.h | 3 ++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index ddf6105662..a67fcd1083 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -221,6 +221,10 @@ public: virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); } virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); } + virtual void readSubdivision(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); } + virtual void writeSubdivision(Bitstream& out, void* value, void* reference, bool isLeaf) const { + write(out, value, isLeaf); } + virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; virtual void readMetavoxelRoot(MetavoxelData& data, MetavoxelStreamState& state); diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 3607441461..2c7542ef35 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -998,7 +998,7 @@ MetavoxelNode* MetavoxelNode::readSubdivision(MetavoxelStreamState& state) { for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); newNode->_children[i] = new MetavoxelNode(state.base.attribute); - newNode->_children[i]->read(nextState); + newNode->_children[i]->readSubdivided(nextState); } return newNode; } @@ -1037,7 +1037,7 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); - _children[i]->write(nextState); + _children[i]->writeSubdivided(nextState); } } } else if (!leaf) { @@ -1051,6 +1051,44 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { } } +void MetavoxelNode::readSubdivided(MetavoxelStreamState& state) { + clearChildren(state.base.attribute); + + if (!state.shouldSubdivide()) { + state.base.attribute->read(state.base.stream, _attributeValue, true); + return; + } + bool leaf; + state.base.stream >> leaf; + state.base.attribute->read(state.base.stream, _attributeValue, leaf); + if (!leaf) { + MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i] = new MetavoxelNode(state.base.attribute); + _children[i]->readSubdivided(nextState); + } + mergeChildren(state.base.attribute, true); + } +} + +void MetavoxelNode::writeSubdivided(MetavoxelStreamState& state) const { + if (!state.shouldSubdivide()) { + state.base.attribute->write(state.base.stream, _attributeValue, true); + return; + } + bool leaf = isLeaf(); + state.base.stream << leaf; + state.base.attribute->write(state.base.stream, _attributeValue, leaf); + if (!leaf) { + MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; + for (int i = 0; i < CHILD_COUNT; i++) { + nextState.setMinimum(state.minimum, i); + _children[i]->writeSubdivided(nextState); + } + } +} + void MetavoxelNode::writeSpanners(MetavoxelStreamState& state) const { foreach (const SharedObjectPointer& object, decodeInline(_attributeValue)) { if (static_cast(object.data())->testAndSetVisited(state.base.visit)) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 9e5b2f04d1..d01845eba6 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -225,6 +225,9 @@ public: MetavoxelNode* readSubdivision(MetavoxelStreamState& state); void writeSubdivision(MetavoxelStreamState& state) const; + void readSubdivided(MetavoxelStreamState& state); + void writeSubdivided(MetavoxelStreamState& state) const; + void writeSpanners(MetavoxelStreamState& state) const; void writeSpannerDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const; void writeSpannerSubdivision(MetavoxelStreamState& state) const; From 2eb1321cc2f1d207ed3b6cc812212516c45955a4 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 18 Aug 2014 12:28:44 -0700 Subject: [PATCH 106/206] Sound and AudioInjector tweaks --- libraries/audio/src/AudioInjectorOptions.h | 4 ++-- libraries/audio/src/Sound.cpp | 11 +++++++++++ libraries/audio/src/Sound.h | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index b90deb93f1..35575414d5 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -37,8 +37,8 @@ public: float getVolume() const { return _volume; } void setVolume(float volume) { _volume = volume; } - float getLoop() const { return _loop; } - void setLoop(float loop) { _loop = loop; } + bool getLoop() const { return _loop; } + void setLoop(bool loop) { _loop = loop; } const glm::quat& getOrientation() const { return _orientation; } void setOrientation(const glm::quat& orientation) { _orientation = orientation; } diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 03c9f6b8ee..f52f5c04dd 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -82,6 +82,17 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) : connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError))); } +Sound::Sound(const QByteArray byteArray, QObject* parent) : + QObject(parent), + _byteArray(byteArray), + _hasDownloaded(true) +{ +} + +void Sound::append(const QByteArray byteArray) { + _byteArray.append(byteArray); +} + void Sound::replyFinished() { QNetworkReply* reply = reinterpret_cast(sender()); diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index c473cdff83..7dae3679f1 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -22,6 +22,8 @@ class Sound : public QObject { public: Sound(const QUrl& sampleURL, QObject* parent = NULL); Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL); + Sound(const QByteArray byteArray, QObject* parent = NULL); + void append(const QByteArray byteArray); bool hasDownloaded() const { return _hasDownloaded; } From acb55ce65e3054864354551ba4955801afc8e366 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 18 Aug 2014 12:31:44 -0700 Subject: [PATCH 107/206] Switched audio play back to AudioInjector out of Audio --- interface/src/Application.cpp | 2 ++ interface/src/Audio.cpp | 9 +++-- interface/src/Recorder.cpp | 58 +++++++++++++++++++++++++++++---- interface/src/Recorder.h | 19 +++++++++-- interface/src/avatar/Avatar.cpp | 1 - 5 files changed, 77 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dba8174083..2fef2ced8a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1051,12 +1051,14 @@ void Application::keyPressEvent(QKeyEvent* event) { _myAvatar->stopRecording(); } else { _myAvatar->startRecording(); + _audio.setRecorder(_myAvatar->getRecorder()); } } else { if (_myAvatar->isPlaying()) { _myAvatar->stopPlaying(); } else { _myAvatar->startPlaying(); + _audio.setPlayer(_myAvatar->getPlayer()); } } break; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 0484860c65..bb8b79ab59 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -82,7 +82,6 @@ Audio::Audio(QObject* parent) : _noiseGateSampleCounter(0), _noiseGateOpen(false), _noiseGateEnabled(true), - _peqEnabled(false), _toneInjectionEnabled(false), _noiseGateFramesToClose(0), _totalInputAudioSamples(0), @@ -102,6 +101,7 @@ Audio::Audio(QObject* parent) : _scopeOutputOffset(0), _framesPerScope(DEFAULT_FRAMES_PER_SCOPE), _samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope), + _peqEnabled(false), _scopeInput(0), _scopeOutputLeft(0), _scopeOutputRight(0), @@ -475,7 +475,7 @@ void Audio::handleAudioInput() { int16_t* ioBuffer = (int16_t*)inputByteArray.data(); - _peq.render( ioBuffer, ioBuffer, inputByteArray.size() / sizeof(int16_t) ); + _peq.render(ioBuffer, ioBuffer, inputByteArray.size() / sizeof(int16_t)); } if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) { @@ -676,6 +676,11 @@ void Audio::handleAudioInput() { NodeList* nodeList = NodeList::getInstance(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); + + if (_recorder && _recorder.data()->isRecording()) { + _recorder.data()->record(reinterpret_cast(networkAudioSamples), numNetworkBytes); + } + if (audioMixer && audioMixer->getActiveSocket()) { MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar(); glm::vec3 headPosition = interfaceAvatar->getHead()->getPosition(); diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index de29a887fb..b0698b95cb 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -45,14 +45,30 @@ void RecordingFrame::setLeanForward(float leanForward) { _leanForward = leanForward; } +Recording::Recording() : _audio(NULL) { +} + +Recording::~Recording() { + delete _audio; +} + void Recording::addFrame(int timestamp, RecordingFrame &frame) { _timestamps << timestamp; _frames << frame; } +void Recording::addAudioPacket(QByteArray byteArray) { + if (!_audio) { + _audio = new Sound(byteArray); + } + _audio->append(byteArray); +} + void Recording::clear() { _timestamps.clear(); _frames.clear(); + delete _audio; + _audio = NULL; } Recorder::Recorder(AvatarData* avatar) : @@ -134,10 +150,19 @@ void Recorder::record() { } } +void Recorder::record(char* samples, int size) { + QByteArray byteArray(samples, size); + _recording->addAudioPacket(byteArray); +} + + Player::Player(AvatarData* avatar) : _recording(new Recording()), - _avatar(avatar) + _avatar(avatar), + _audioThread(NULL) { + _options.setLoop(false); + _options.setVolume(1.0f); } bool Player::isPlaying() const { @@ -276,18 +301,29 @@ float Player::getLeanForward() { return 0.0f; } - +#include void Player::startPlaying() { if (_recording && _recording->getFrameNumber() > 0) { qDebug() << "Recorder::startPlaying()"; - _timer.start(); _currentFrame = 0; + _options.setPosition(_avatar->getPosition()); + _options.setOrientation(_avatar->getOrientation()); + _injector.reset(new AudioInjector(_recording->getAudio(), _options)); + _audioThread = new QThread(); + _injector->moveToThread(_audioThread); + _audioThread->start(); + QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); + _timer.start(); } } void Player::stopPlaying() { qDebug() << "Recorder::stopPlaying()"; _timer.invalidate(); + _injector->stop(); + _injector.clear(); + _audioThread->exit(); + _audioThread->deleteLater(); } void Player::loadFromFile(QString file) { @@ -311,6 +347,7 @@ void Player::play() { stopPlaying(); return; } + if (_currentFrame == 0) { _avatar->setPosition(_recording->getFrame(_currentFrame).getTranslation()); _avatar->setOrientation(_recording->getFrame(_currentFrame).getRotation()); @@ -318,8 +355,6 @@ void Player::play() { _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); HeadData* head = const_cast(_avatar->getHeadData()); head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); - // TODO - // BODY: Joint Rotations } else { _avatar->setPosition(_recording->getFrame(0).getTranslation() + _recording->getFrame(_currentFrame).getTranslation()); @@ -330,11 +365,20 @@ void Player::play() { _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); HeadData* head = const_cast(_avatar->getHeadData()); head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); - // TODO - // BODY: Joint Rotations } } +void Player::playAudio() { + _options.setPosition(_avatar->getPosition()); + _options.setOrientation(_avatar->getOrientation()); + + qDebug() << "Play"; + if (_injector) { + _injector->injectAudio(); + } + qDebug() << "Played"; +} + void Player::computeCurrentFrame() { if (!isPlaying()) { qDebug() << "Not Playing"; diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index f2a6f6a1c4..9a412aadbf 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -22,8 +22,10 @@ #include #include +#include #include #include +#include class Recorder; class Recording; @@ -75,28 +77,34 @@ private: /// Stores a recording class Recording { public: + Recording(); + ~Recording(); + bool isEmpty() const { return _timestamps.isEmpty(); } int getLength() const { return _timestamps.last(); } // in ms + int getFrameNumber() const { return _frames.size(); } - qint32 getFrameTimestamp(int i) const { return _timestamps[i]; } const RecordingFrame& getFrame(int i) const { return _frames[i]; } + Sound* getAudio() const { return _audio; } protected: void addFrame(int timestamp, RecordingFrame& frame); + void addAudioPacket(QByteArray byteArray); void clear(); private: QVector _timestamps; QVector _frames; + Sound* _audio; + friend class Recorder; friend class Player; friend void writeRecordingToFile(Recording& recording, QString file); friend RecordingPointer readRecordingFromFile(QString file); }; - /// Records a recording class Recorder { public: @@ -112,6 +120,7 @@ public slots: void stopRecording(); void saveToFile(QString file); void record(); + void record(char* samples, int size); private: QElapsedTimer _timer; @@ -138,12 +147,14 @@ public: float getLeanSideways(); float getLeanForward(); + public slots: void startPlaying(); void stopPlaying(); void loadFromFile(QString file); void loadRecording(RecordingPointer recording); void play(); + void playAudio(); private: void computeCurrentFrame(); @@ -152,7 +163,11 @@ private: RecordingPointer _recording; int _currentFrame; + QSharedPointer _injector; + AudioInjectorOptions _options; + AvatarData* _avatar; + QThread* _audioThread; }; void writeRecordingToFile(Recording& recording, QString file); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index d50e9232ed..498281c98c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -735,7 +735,6 @@ QVector Avatar::getJointRotations() const { for (int i = 0; i < _skeletonModel.getJointStateCount(); ++i) { _skeletonModel.getJointState(i, jointRotations[i]); } - qDebug() << "Get Joints"; return jointRotations; } From 54851c5ced02f4125d449c18789e16c78bbacad5 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 Aug 2014 12:49:12 -0700 Subject: [PATCH 108/206] add Ragdoll::_accumulatedMovement --- libraries/shared/src/Ragdoll.cpp | 27 ++++++++++++++++++++++++++- libraries/shared/src/Ragdoll.h | 10 ++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/Ragdoll.cpp b/libraries/shared/src/Ragdoll.cpp index 7eeaf0b609..70ea63930b 100644 --- a/libraries/shared/src/Ragdoll.cpp +++ b/libraries/shared/src/Ragdoll.cpp @@ -19,7 +19,8 @@ #include "PhysicsSimulation.h" #include "SharedUtil.h" // for EPSILON -Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f), _simulation(NULL) { +Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f), + _accumulatedMovement(0.0f), _simulation(NULL) { } Ragdoll::~Ragdoll() { @@ -116,3 +117,27 @@ void Ragdoll::setMassScale(float scale) { _massScale = scale; } } + +void Ragdoll::removeRootOffset(bool accumulateMovement) { + const int numPoints = _points.size(); + if (numPoints > 0) { + // shift all points so that the root aligns with the the ragdoll's position in the simulation + glm::vec3 offset = _translationInSimulationFrame - _points[0]._position; + float offsetLength = glm::length(offset); + if (offsetLength > EPSILON) { + for (int i = 0; i < numPoints; ++i) { + _points[i].shift(offset); + } + const float MIN_ROOT_OFFSET = 0.02f; + if (accumulateMovement && offsetLength > MIN_ROOT_OFFSET) { + _accumulatedMovement -= (1.0f - MIN_ROOT_OFFSET / offsetLength) * offset; + } + } + } +} + +glm::vec3 Ragdoll::getAndClearAccumulatedMovement() { + glm::vec3 movement = _accumulatedMovement; + _accumulatedMovement = glm::vec3(0.0f); + return movement; +} diff --git a/libraries/shared/src/Ragdoll.h b/libraries/shared/src/Ragdoll.h index c82295d9a5..1ffbdb29ab 100644 --- a/libraries/shared/src/Ragdoll.h +++ b/libraries/shared/src/Ragdoll.h @@ -56,6 +56,10 @@ public: virtual void initPoints() = 0; virtual void buildConstraints() = 0; + void removeRootOffset(bool accumulateMovement); + + glm::vec3 getAndClearAccumulatedMovement(); + protected: float _massScale; glm::vec3 _translation; // world-frame @@ -66,6 +70,12 @@ protected: QVector _points; QVector _boneConstraints; QVector _fixedConstraints; + + // The collisions are typically done in a simulation frame that is slaved to the center of one of the Ragdolls. + // To allow the Ragdoll to provide feedback of its own displacement we store it in _accumulatedMovement. + // The owner of the Ragdoll can harvest this displacement to update the rest of the object positions in the simulation. + glm::vec3 _accumulatedMovement; + private: void updateSimulationTransforms(const glm::vec3& translation, const glm::quat& rotation); From fe5f9f8fe5a283fcf208f368026e47216baf0eb9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 Aug 2014 12:49:47 -0700 Subject: [PATCH 109/206] use relative mass when enforcing ContactPoint --- libraries/shared/src/ContactPoint.cpp | 31 +++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/libraries/shared/src/ContactPoint.cpp b/libraries/shared/src/ContactPoint.cpp index 27a496d445..02cf896594 100644 --- a/libraries/shared/src/ContactPoint.cpp +++ b/libraries/shared/src/ContactPoint.cpp @@ -96,10 +96,10 @@ float ContactPoint::enforce() { bool constraintViolation = (pDotN > CONTACT_PENETRATION_ALLOWANCE); // the contact point will be the average of the two points on the shapes - _contactPoint = 0.5f * (pointA + pointB); + _contactPoint = _relativeMassA * pointA + _relativeMassB * pointB; if (constraintViolation) { - for (int i = 0; i < _numPoints; ++i) { + for (int i = 0; i < _numPointsA; ++i) { VerletPoint* point = _points[i]; glm::vec3 offset = _offsets[i]; @@ -111,8 +111,31 @@ float ContactPoint::enforce() { // use the relative sizes of the components to decide how much perpenducular delta to use // perpendicular < parallel ==> static friction ==> perpFactor = 1.0 // perpendicular > parallel ==> dynamic friction ==> cap to length of paraDelta ==> perpFactor < 1.0 - float paraLength = glm::length(paraDelta); - float perpLength = glm::length(perpDelta); + float paraLength = _relativeMassB * glm::length(paraDelta); + float perpLength = _relativeMassA * glm::length(perpDelta); + float perpFactor = (perpLength > paraLength && perpLength > EPSILON) ? (paraLength / perpLength) : 1.0f; + + // recombine the two components to get the final delta + delta = paraDelta + perpFactor * perpDelta; + + glm::vec3 targetPosition = point->_position + delta; + _distances[i] = glm::distance(_contactPoint, targetPosition); + point->_position += delta; + } + for (int i = _numPointsA; i < _numPoints; ++i) { + VerletPoint* point = _points[i]; + glm::vec3 offset = _offsets[i]; + + // split delta into parallel and perpendicular components + glm::vec3 delta = _contactPoint + offset - point->_position; + glm::vec3 paraDelta = glm::dot(delta, _normal) * _normal; + glm::vec3 perpDelta = delta - paraDelta; + + // use the relative sizes of the components to decide how much perpenducular delta to use + // perpendicular < parallel ==> static friction ==> perpFactor = 1.0 + // perpendicular > parallel ==> dynamic friction ==> cap to length of paraDelta ==> perpFactor < 1.0 + float paraLength = _relativeMassA * glm::length(paraDelta); + float perpLength = _relativeMassB * glm::length(perpDelta); float perpFactor = (perpLength > paraLength && perpLength > EPSILON) ? (paraLength / perpLength) : 1.0f; // recombine the two components to get the final delta From 3e2095332fdf1338fca49a0395e30ddbe6adb4ce Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 Aug 2014 12:50:07 -0700 Subject: [PATCH 110/206] make SkeletonRagdoll::updateMuscles() protected --- interface/src/avatar/SkeletonRagdoll.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/avatar/SkeletonRagdoll.h b/interface/src/avatar/SkeletonRagdoll.h index f9f99395ac..ae9bec9116 100644 --- a/interface/src/avatar/SkeletonRagdoll.h +++ b/interface/src/avatar/SkeletonRagdoll.h @@ -33,7 +33,9 @@ public: virtual void initPoints(); virtual void buildConstraints(); +protected: void updateMuscles(); + private: Model* _model; QVector _muscleConstraints; From 7e7978de1a83051160aee4b45606db5df418b523 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 Aug 2014 12:53:04 -0700 Subject: [PATCH 111/206] compute and store Ragdoll::_accumulatedMovement --- interface/src/avatar/SkeletonModel.cpp | 23 ++++++++++++---------- interface/src/avatar/SkeletonRagdoll.cpp | 5 +---- libraries/shared/src/PhysicsSimulation.cpp | 21 +++++++++++++++----- libraries/shared/src/PhysicsSimulation.h | 6 ++++-- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ffe711b03b..536f957143 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -14,14 +14,11 @@ #include #include -#include -#include #include "Application.h" #include "Avatar.h" #include "Hand.h" #include "Menu.h" -#include "MuscleConstraint.h" #include "SkeletonModel.h" #include "SkeletonRagdoll.h" @@ -606,6 +603,7 @@ void SkeletonModel::buildShapes() { float uniformScale = extractUniformScale(_scale); const int numStates = _jointStates.size(); + float totalMass = 0.0f; for (int i = 0; i < numStates; i++) { JointState& state = _jointStates[i]; const FBXJoint& joint = state.getFBXJoint(); @@ -624,26 +622,31 @@ void SkeletonModel::buildShapes() { if (type == Shape::SPHERE_SHAPE) { shape = new VerletSphereShape(radius, &(points[i])); shape->setEntity(this); - points[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume())); + float mass = massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume()); + points[i].setMass(mass); + totalMass += mass; } else if (type == Shape::CAPSULE_SHAPE) { assert(parentIndex != -1); shape = new VerletCapsuleShape(radius, &(points[parentIndex]), &(points[i])); shape->setEntity(this); - points[i].setMass(massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume())); + float mass = massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume()); + points[i].setMass(mass); + totalMass += mass; } if (parentIndex != -1) { // always disable collisions between joint and its parent if (shape) { disableCollisions(i, parentIndex); } - } else { - // give the base joint a very large mass since it doesn't actually move - // in the local-frame simulation (it defines the origin) - points[i].setMass(VERY_BIG_MASS); - } + } _shapes.push_back(shape); } + // set the mass of the root + if (numStates > 0) { + points[0].setMass(totalMass); + } + // This method moves the shapes to their default positions in Model frame. computeBoundingShape(geometry); diff --git a/interface/src/avatar/SkeletonRagdoll.cpp b/interface/src/avatar/SkeletonRagdoll.cpp index 503f38f00f..6318323990 100644 --- a/interface/src/avatar/SkeletonRagdoll.cpp +++ b/interface/src/avatar/SkeletonRagdoll.cpp @@ -70,10 +70,7 @@ void SkeletonRagdoll::buildConstraints() { for (int i = 0; i < numPoints; ++i) { const JointState& state = jointStates.at(i); int parentIndex = state.getParentIndex(); - if (parentIndex == -1) { - FixedConstraint* anchor = new FixedConstraint(&_translationInSimulationFrame, &(_points[i])); - _fixedConstraints.push_back(anchor); - } else { + if (parentIndex != -1) { DistanceConstraint* bone = new DistanceConstraint(&(_points[i]), &(_points[parentIndex])); bone->setDistance(state.getDistanceToParent()); _boneConstraints.push_back(bone); diff --git a/libraries/shared/src/PhysicsSimulation.cpp b/libraries/shared/src/PhysicsSimulation.cpp index a62b3816af..6c4901bcd5 100644 --- a/libraries/shared/src/PhysicsSimulation.cpp +++ b/libraries/shared/src/PhysicsSimulation.cpp @@ -163,10 +163,10 @@ bool PhysicsSimulation::addRagdoll(Ragdoll* doll) { } void PhysicsSimulation::removeRagdoll(Ragdoll* doll) { - int numDolls = _otherRagdolls.size(); - if (doll->_simulation != this) { + if (!doll || doll->_simulation != this) { return; } + int numDolls = _otherRagdolls.size(); for (int i = 0; i < numDolls; ++i) { if (doll == _otherRagdolls[i]) { if (i == numDolls - 1) { @@ -205,10 +205,11 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter } } + bool collidedWithOtherRagdoll = false; int iterations = 0; float error = 0.0f; do { - computeCollisions(); + collidedWithOtherRagdoll = computeCollisions() || collidedWithOtherRagdoll; updateContacts(); resolveCollisions(); @@ -225,6 +226,14 @@ void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIter now = usecTimestampNow(); } while (_collisions.size() != 0 && (iterations < maxIterations) && (error > minError) && (now < expiry)); + // the collisions may have moved the main ragdoll from the simulation center + // so we remove this offset (potentially storing it as movement of the Ragdoll owner) + _ragdoll->removeRootOffset(collidedWithOtherRagdoll); + + // also remove any offsets from the other ragdolls + for (int i = 0; i < numDolls; ++i) { + _otherRagdolls[i]->removeRootOffset(false); + } pruneContacts(); } @@ -237,7 +246,7 @@ void PhysicsSimulation::moveRagdolls(float deltaTime) { } } -void PhysicsSimulation::computeCollisions() { +bool PhysicsSimulation::computeCollisions() { PerformanceTimer perfTimer("collide"); _collisions.clear(); @@ -258,11 +267,13 @@ void PhysicsSimulation::computeCollisions() { } // collide main ragdoll with others + bool otherCollisions = false; int numEntities = _otherEntities.size(); for (int i = 0; i < numEntities; ++i) { const QVector otherShapes = _otherEntities.at(i)->getShapes(); - ShapeCollider::collideShapesWithShapes(shapes, otherShapes, _collisions); + otherCollisions = ShapeCollider::collideShapesWithShapes(shapes, otherShapes, _collisions) || otherCollisions; } + return otherCollisions; } void PhysicsSimulation::resolveCollisions() { diff --git a/libraries/shared/src/PhysicsSimulation.h b/libraries/shared/src/PhysicsSimulation.h index 881007208b..1db56a46e2 100644 --- a/libraries/shared/src/PhysicsSimulation.h +++ b/libraries/shared/src/PhysicsSimulation.h @@ -53,9 +53,11 @@ public: protected: void moveRagdolls(float deltaTime); - void computeCollisions(); - void resolveCollisions(); + /// \return true if main ragdoll collides with other avatar + bool computeCollisions(); + + void resolveCollisions(); void enforceContacts(); void applyContactFriction(); void updateContacts(); From aa1a7307cc1219a4e965c7490af225bc5890c408 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 Aug 2014 12:53:34 -0700 Subject: [PATCH 112/206] use Ragdoll::_accumulatedMovement to move MyAvatar --- interface/src/avatar/MyAvatar.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ac44e1884e..50664d33c9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -206,12 +206,21 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("ragdoll"); - if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { + Ragdoll* ragdoll = _skeletonModel.getRagdoll(); + if (ragdoll && Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { const float minError = 0.00001f; const float maxIterations = 3; const quint64 maxUsec = 4000; _physicsSimulation.setTranslation(_position); _physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec); + + // harvest any displacement of the Ragdoll that is a result of collisions + glm::vec3 ragdollDisplacement = ragdoll->getAndClearAccumulatedMovement(); + const float MAX_RAGDOLL_DISPLACEMENT_2 = 1.0f; + float length2 = glm::length2(ragdollDisplacement); + if (length2 > EPSILON && length2 < MAX_RAGDOLL_DISPLACEMENT_2) { + setPosition(getPosition() + ragdollDisplacement); + } } else { _skeletonModel.moveShapesTowardJoints(1.0f); } From 543bf5224c118c74a571f26b7123d7c138efdac4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 Aug 2014 12:54:26 -0700 Subject: [PATCH 113/206] add VerletPoint::shift() --- libraries/shared/src/VerletPoint.cpp | 5 +++++ libraries/shared/src/VerletPoint.h | 1 + 2 files changed, 6 insertions(+) diff --git a/libraries/shared/src/VerletPoint.cpp b/libraries/shared/src/VerletPoint.cpp index d2dd985587..cf9aeca149 100644 --- a/libraries/shared/src/VerletPoint.cpp +++ b/libraries/shared/src/VerletPoint.cpp @@ -39,6 +39,11 @@ void VerletPoint::move(const glm::vec3& deltaPosition, const glm::quat& deltaRot _lastPosition += deltaPosition + (deltaRotation * arm - arm); } +void VerletPoint::shift(const glm::vec3& deltaPosition) { + _position += deltaPosition; + _lastPosition += deltaPosition; +} + void VerletPoint::setMass(float mass) { const float MIN_MASS = 1.0e-6f; const float MAX_MASS = 1.0e18f; diff --git a/libraries/shared/src/VerletPoint.h b/libraries/shared/src/VerletPoint.h index 6f94656966..3c73e5eb01 100644 --- a/libraries/shared/src/VerletPoint.h +++ b/libraries/shared/src/VerletPoint.h @@ -25,6 +25,7 @@ public: void accumulateDelta(const glm::vec3& delta); void applyAccumulatedDelta(); void move(const glm::vec3& deltaPosition, const glm::quat& deltaRotation, const glm::vec3& oldPivot); + void shift(const glm::vec3& deltaPosition); void setMass(float mass); float getMass() const { return _mass; } From 52640c8482351a0629fc9ba08ab85537d5fa0f34 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 18 Aug 2014 14:23:29 -0700 Subject: [PATCH 114/206] Various tweaks and code cleanup --- interface/src/Application.cpp | 2 ++ interface/src/Recorder.cpp | 29 ++++++++++++++-------------- interface/src/Recorder.h | 13 ++++++------- interface/src/avatar/MyAvatar.cpp | 12 ++++++++++-- interface/src/avatar/MyAvatar.h | 1 + interface/src/renderer/Model.cpp | 8 ++++++++ interface/src/renderer/Model.h | 3 +++ libraries/avatars/src/AvatarData.cpp | 6 ++++++ libraries/avatars/src/AvatarData.h | 4 +++- 9 files changed, 53 insertions(+), 25 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2fef2ced8a..5a689180e5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1047,6 +1047,8 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_R: if (isShifted) { + Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode); + } else if (isMeta) { if (_myAvatar->isRecording()) { _myAvatar->stopRecording(); } else { diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index b0698b95cb..a2101b28b8 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -75,6 +75,7 @@ Recorder::Recorder(AvatarData* avatar) : _recording(new Recording()), _avatar(avatar) { + _timer.invalidate(); } bool Recorder::isRecording() const { @@ -124,7 +125,7 @@ void Recorder::saveToFile(QString file) { qDebug() << "Cannot save recording to file, recording is empty."; } - writeRecordingToFile(*_recording, file); + writeRecordingToFile(_recording, file); } void Recorder::record() { @@ -161,6 +162,7 @@ Player::Player(AvatarData* avatar) : _avatar(avatar), _audioThread(NULL) { + _timer.invalidate(); _options.setLoop(false); _options.setVolume(1.0f); } @@ -318,8 +320,16 @@ void Player::startPlaying() { } void Player::stopPlaying() { + if (!isPlaying()) { + return; + } + qDebug() << "Recorder::stopPlaying()"; _timer.invalidate(); + + _avatar->clearJointsData(); + + // Cleanup audio thread _injector->stop(); _injector.clear(); _audioThread->exit(); @@ -332,7 +342,7 @@ void Player::loadFromFile(QString file) { } else { _recording = RecordingPointer(new Recording()); } - readRecordingFromFile(*_recording, file); + readRecordingFromFile(_recording, file); } void Player::loadRecording(RecordingPointer recording) { @@ -368,17 +378,6 @@ void Player::play() { } } -void Player::playAudio() { - _options.setPosition(_avatar->getPosition()); - _options.setOrientation(_avatar->getOrientation()); - - qDebug() << "Play"; - if (_injector) { - _injector->injectAudio(); - } - qDebug() << "Played"; -} - void Player::computeCurrentFrame() { if (!isPlaying()) { qDebug() << "Not Playing"; @@ -396,12 +395,12 @@ void Player::computeCurrentFrame() { } } -void writeRecordingToFile(Recording& recording, QString file) { +void writeRecordingToFile(RecordingPointer recording, QString file) { // TODO qDebug() << "Writing recording to " << file; } -Recording& readRecordingFromFile(Recording& recording, QString file) { +RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file) { // TODO qDebug() << "Reading recording from " << file; return recording; diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index 9a412aadbf..e1a1119449 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -70,8 +70,8 @@ private: float _leanForward; friend class Recorder; - friend void writeRecordingToFile(Recording& recording, QString file); - friend RecordingPointer readRecordingFromFile(QString file); + friend void writeRecordingToFile(RecordingPointer recording, QString file); + friend RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file); }; /// Stores a recording @@ -101,8 +101,8 @@ private: friend class Recorder; friend class Player; - friend void writeRecordingToFile(Recording& recording, QString file); - friend RecordingPointer readRecordingFromFile(QString file); + friend void writeRecordingToFile(RecordingPointer recording, QString file); + friend RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file); }; /// Records a recording @@ -154,7 +154,6 @@ public slots: void loadFromFile(QString file); void loadRecording(RecordingPointer recording); void play(); - void playAudio(); private: void computeCurrentFrame(); @@ -170,7 +169,7 @@ private: QThread* _audioThread; }; -void writeRecordingToFile(Recording& recording, QString file); -Recording& readRecordingFromFile(Recording& recording, QString file); +void writeRecordingToFile(RecordingPointer recording, QString file); +RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file); #endif // hifi_Recorder_h \ No newline at end of file diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9ff60dd610..5ed2e3c011 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -922,8 +922,7 @@ const float JOINT_PRIORITY = 2.0f; void MyAvatar::setJointRotations(QVector jointRotations) { for (int i = 0; i < jointRotations.size(); ++i) { if (i < _jointData.size()) { - // TODO change animation priority to proper value - _skeletonModel.setJointState(i, true, jointRotations[i], 100.0f); + _skeletonModel.setJointState(i, true, jointRotations[i], JOINT_PRIORITY + 1.0f); } } } @@ -942,6 +941,15 @@ void MyAvatar::clearJointData(int index) { } } +void MyAvatar::clearJointsData() { + for (int i = 0; i < _jointData.size(); ++i) { + Avatar::clearJointData(i); + if (QThread::currentThread() == thread()) { + _skeletonModel.clearJointState(i); + } + } +} + void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) { Avatar::setFaceModelURL(faceModelURL); _billboardValid = false; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index bf57bf2367..67d19b87eb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -115,6 +115,7 @@ public: virtual void setJointRotations(QVector jointRotations); virtual void setJointData(int index, const glm::quat& rotation); virtual void clearJointData(int index); + virtual void clearJointsData(); virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setAttachmentData(const QVector& attachmentData); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 2ec676de53..290f9b5c6f 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -692,6 +692,14 @@ bool Model::getVisibleJointState(int index, glm::quat& rotation) const { return !state.rotationIsDefault(rotation); } +void Model::clearJointState(int index) { + if (index != -1 && index < _jointStates.size()) { + JointState& state = _jointStates[index]; + state.setRotationInConstrainedFrame(glm::quat()); + state._animationPriority = 0.0f; + } +} + void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 7a29b61420..e45b8091d3 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -118,6 +118,9 @@ public: /// \return whether or not the joint state is "valid" (that is, non-default) bool getVisibleJointState(int index, glm::quat& rotation) const; + /// Clear the joint states + void clearJointState(int index); + /// Sets the joint state at the specified index. void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 569e099c2c..9653999555 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -712,6 +712,12 @@ void AvatarData::setJointRotations(QVector jointRotations) { } } +void AvatarData::clearJointsData() { + for (int i = 0; i < _jointData.size(); ++i) { + clearJointData(i); + } +} + bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) { QDataStream packetStream(packet); packetStream.skipRawData(numBytesForPacketHeader(packet)); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 7dec55b7e9..fa884c0229 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -213,7 +213,9 @@ public: Q_INVOKABLE virtual QVector getJointRotations() const; Q_INVOKABLE virtual void setJointRotations(QVector jointRotations); - + + Q_INVOKABLE virtual void clearJointsData(); + /// Returns the index of the joint with the specified name, or -1 if not found/unknown. Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return _jointIndices.value(name) - 1; } From 698699dea8cf608564f7919ee62e6a2a4a6f3838 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 18 Aug 2014 15:56:27 -0700 Subject: [PATCH 115/206] CR --- interface/src/Recorder.cpp | 143 +++++++------------------------------ interface/src/Recorder.h | 7 +- 2 files changed, 26 insertions(+), 124 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index a2101b28b8..b782112778 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -11,6 +11,8 @@ #include +#include + #include "Recorder.h" void RecordingFrame::setBlendshapeCoefficients(QVector blendshapeCoefficients) { @@ -179,131 +181,37 @@ qint64 Player::elapsed() const { } } -QVector Player::getBlendshapeCoefficients() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(_currentFrame - 1).getBlendshapeCoefficients(); - } - - return _recording->getFrame(_currentFrame).getBlendshapeCoefficients(); - } - qWarning() << "Incorrect use of Player::getBlendshapeCoefficients()"; - return QVector(); -} - -QVector Player::getJointRotations() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(_currentFrame - 1).getJointRotations(); - } - - return _recording->getFrame(_currentFrame).getJointRotations(); - } - qWarning() << "Incorrect use of Player::getJointRotations()"; - return QVector(); -} - -glm::vec3 Player::getPosition() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(0).getTranslation() + - _recording->getFrame(_currentFrame - 1).getTranslation(); - } - if (_currentFrame == 0) { - return _recording->getFrame(_currentFrame).getTranslation(); - } - - return _recording->getFrame(0).getTranslation() + - _recording->getFrame(_currentFrame).getTranslation(); - } - qWarning() << "Incorrect use of Player::getTranslation()"; - return glm::vec3(); -} - -glm::quat Player::getRotation() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(0).getRotation() * - _recording->getFrame(_currentFrame - 1).getRotation(); - } - if (_currentFrame == 0) { - return _recording->getFrame(_currentFrame).getRotation(); - } - - return _recording->getFrame(0).getRotation() * - _recording->getFrame(_currentFrame).getRotation(); - } - qWarning() << "Incorrect use of Player::getRotation()"; - return glm::quat(); -} - -float Player::getScale() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(0).getScale() * - _recording->getFrame(_currentFrame - 1).getScale(); - } - if (_currentFrame == 0) { - return _recording->getFrame(_currentFrame).getScale(); - } - - return _recording->getFrame(0).getScale() * - _recording->getFrame(_currentFrame).getScale(); - } - qWarning() << "Incorrect use of Player::getScale()"; - return 1.0f; -} - glm::quat Player::getHeadRotation() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(0).getHeadRotation() * - _recording->getFrame(_currentFrame - 1).getHeadRotation(); - } - if (_currentFrame == 0) { - return _recording->getFrame(_currentFrame).getHeadRotation(); - } - - return _recording->getFrame(0).getHeadRotation() * - _recording->getFrame(_currentFrame).getHeadRotation(); + if (computeCurrentFrame()) { + qWarning() << "Incorrect use of Player::getHeadRotation()"; + return glm::quat(); } - qWarning() << "Incorrect use of Player::getHeadRotation()"; - return glm::quat(); + + if (_currentFrame == 0) { + return _recording->getFrame(_currentFrame).getHeadRotation(); + } + return _recording->getFrame(0).getHeadRotation() * + _recording->getFrame(_currentFrame).getHeadRotation(); } float Player::getLeanSideways() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(_currentFrame - 1).getLeanSideways(); - } - - return _recording->getFrame(_currentFrame).getLeanSideways(); + if (computeCurrentFrame()) { + qWarning() << "Incorrect use of Player::getLeanSideways()"; + return 0.0f; } - qWarning() << "Incorrect use of Player::getLeanSideways()"; - return 0.0f; + + return _recording->getFrame(_currentFrame).getLeanSideways(); } float Player::getLeanForward() { - computeCurrentFrame(); - if (_currentFrame >= 0 && _currentFrame <= _recording->getFrameNumber()) { - if (_currentFrame == _recording->getFrameNumber()) { - return _recording->getFrame(_currentFrame - 1).getLeanForward(); - } - - return _recording->getFrame(_currentFrame).getLeanForward(); + if (computeCurrentFrame()) { + qWarning() << "Incorrect use of Player::getLeanForward()"; + return 0.0f; } - qWarning() << "Incorrect use of Player::getLeanForward()"; - return 0.0f; + + return _recording->getFrame(_currentFrame).getLeanForward(); } -#include void Player::startPlaying() { if (_recording && _recording->getFrameNumber() > 0) { qDebug() << "Recorder::startPlaying()"; @@ -350,7 +258,6 @@ void Player::loadRecording(RecordingPointer recording) { } void Player::play() { - qDebug() << "Playing " << _timer.elapsed() / 1000.0f; computeCurrentFrame(); if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber()) { // If it's the end of the recording, stop playing @@ -378,14 +285,12 @@ void Player::play() { } } -void Player::computeCurrentFrame() { +bool Player::computeCurrentFrame() { if (!isPlaying()) { - qDebug() << "Not Playing"; _currentFrame = -1; - return; + return false; } if (_currentFrame < 0) { - qDebug() << "Reset to 0"; _currentFrame = 0; } @@ -393,6 +298,8 @@ void Player::computeCurrentFrame() { _recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) { ++_currentFrame; } + + return true; } void writeRecordingToFile(RecordingPointer recording, QString file) { diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index e1a1119449..9f7eb66ec6 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -138,11 +138,6 @@ public: qint64 elapsed() const; // Those should only be called if isPlaying() returns true - QVector getBlendshapeCoefficients(); - QVector getJointRotations(); - glm::vec3 getPosition(); - glm::quat getRotation(); - float getScale(); glm::quat getHeadRotation(); float getLeanSideways(); float getLeanForward(); @@ -156,7 +151,7 @@ public slots: void play(); private: - void computeCurrentFrame(); + bool computeCurrentFrame(); QElapsedTimer _timer; RecordingPointer _recording; From c2a5c33b7b5411c8faea25b20abb66ed2f8a8049 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 18 Aug 2014 16:07:15 -0700 Subject: [PATCH 116/206] Added ability to encode subdivisions relative to ancestor (not currently using for heightfields, since absolute lossy encoding is more efficient). --- .../metavoxels/src/AttributeRegistry.cpp | 132 +++++++++++++++++- libraries/metavoxels/src/AttributeRegistry.h | 19 ++- libraries/metavoxels/src/MetavoxelData.cpp | 22 +-- libraries/metavoxels/src/MetavoxelData.h | 4 +- 4 files changed, 160 insertions(+), 17 deletions(-) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 1e30aee576..5ffa593004 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -204,6 +204,16 @@ Attribute::Attribute(const QString& name) : Attribute::~Attribute() { } +void Attribute::readSubdivided(MetavoxelStreamState& state, void*& value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const { + read(state.base.stream, value, isLeaf); +} + +void Attribute::writeSubdivided(MetavoxelStreamState& state, void* value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const { + write(state.base.stream, value, isLeaf); +} + MetavoxelNode* Attribute::createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const { return new MetavoxelNode(value); } @@ -495,14 +505,14 @@ HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) { enum HeightfieldImage { NULL_HEIGHTFIELD_IMAGE, NORMAL_HEIGHTFIELD_IMAGE, DEFLATED_HEIGHTFIELD_IMAGE }; -static QByteArray encodeHeightfieldImage(const QImage& image) { +static QByteArray encodeHeightfieldImage(const QImage& image, bool lossless = false) { if (image.isNull()) { return QByteArray(1, NULL_HEIGHTFIELD_IMAGE); } QBuffer buffer; buffer.open(QIODevice::WriteOnly); const int JPEG_ENCODE_THRESHOLD = 16; - if (image.width() >= JPEG_ENCODE_THRESHOLD && image.height() >= JPEG_ENCODE_THRESHOLD) { + if (image.width() >= JPEG_ENCODE_THRESHOLD && image.height() >= JPEG_ENCODE_THRESHOLD && !lossless) { qint32 offsetX = image.offset().x(), offsetY = image.offset().y(); buffer.write((char*)&offsetX, sizeof(qint32)); buffer.write((char*)&offsetY, sizeof(qint32)); @@ -579,6 +589,63 @@ HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldData } } +HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& ancestor, + const glm::vec3& minimum, float size, bool color) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + subdivision.data = in.readAligned(bytes); + subdivision.ancestor = ancestor; + QImage image = decodeHeightfieldImage(subdivision.data); + if (image.isNull()) { + return; + } + image = image.convertToFormat(QImage::Format_RGB888); + int destSize = image.width(); + const uchar* src = image.constBits(); + const QByteArray& ancestorContents = ancestor->getContents(); + if (color) { + int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + int ancestorStride = ancestorSize * COLOR_BYTES; + + _contents = QByteArray(destSize * destSize * COLOR_BYTES, 0); + char* dest = _contents.data(); + int stride = image.width() * COLOR_BYTES; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; + float ancestorX = minimum.x * ancestorSize; + for (char* end = dest + stride; dest != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; + *dest++ = *ref++ + *src++; + *dest++ = *ref++ + *src++; + *dest++ = *ref++ + *src++; + } + } + } else { + int ancestorSize = glm::sqrt((float)ancestorContents.size()); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + + _contents = QByteArray(destSize * destSize, 0); + char* dest = _contents.data(); + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; + float ancestorX = minimum.x * ancestorSize; + for (char* end = dest + destSize; dest != end; src += COLOR_BYTES, ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX; + *dest++ = *ref++ + *src; + } + } + } +} + void HeightfieldData::write(Bitstream& out, bool color) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { @@ -689,6 +756,67 @@ void HeightfieldData::writeDelta(Bitstream& out, const HeightfieldDataPointer& r out.writeAligned(reference->_encodedDelta); } +void HeightfieldData::writeSubdivided(Bitstream& out, const HeightfieldDataPointer& ancestor, + const glm::vec3& minimum, float size, bool color) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + if (subdivision.data.isEmpty() || subdivision.ancestor != ancestor) { + QImage image; + const QByteArray& ancestorContents = ancestor->getContents(); + const uchar* src = (const uchar*)_contents.constData(); + if (color) { + int destSize = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + image = QImage(destSize, destSize, QImage::Format_RGB888); + uchar* dest = image.bits(); + int stride = destSize * COLOR_BYTES; + + int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + int ancestorStride = ancestorSize * COLOR_BYTES; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; + float ancestorX = minimum.x * ancestorSize; + for (const uchar* end = src + stride; src != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; + *dest++ = *src++ - *ref++; + *dest++ = *src++ - *ref++; + *dest++ = *src++ - *ref++; + } + } + } else { + int destSize = glm::sqrt((float)_contents.size()); + image = QImage(destSize, destSize, QImage::Format_RGB888); + uchar* dest = image.bits(); + + int ancestorSize = glm::sqrt((float)ancestorContents.size()); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; + float ancestorX = minimum.x * ancestorSize; + for (const uchar* end = src + destSize; src != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX; + uchar difference = *src++ - *ref; + *dest++ = difference; + *dest++ = difference; + *dest++ = difference; + } + } + } + subdivision.data = encodeHeightfieldImage(image, true); + subdivision.ancestor = ancestor; + } + out << subdivision.data.size(); + out.writeAligned(subdivision.data); +} + void HeightfieldData::read(Bitstream& in, int bytes, bool color) { set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888), color); } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index a67fcd1083..c103855d2c 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -221,9 +221,10 @@ public: virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); } virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { write(out, value, isLeaf); } - virtual void readSubdivision(Bitstream& in, void*& value, void* reference, bool isLeaf) const { read(in, value, isLeaf); } - virtual void writeSubdivision(Bitstream& out, void* value, void* reference, bool isLeaf) const { - write(out, value, isLeaf); } + virtual void readSubdivided(MetavoxelStreamState& state, void*& value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const; + virtual void writeSubdivided(MetavoxelStreamState& state, void* value, + const MetavoxelStreamState& ancestorState, void* ancestorValue, bool isLeaf) const; virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const; @@ -437,11 +438,15 @@ public: HeightfieldData(const QByteArray& contents); HeightfieldData(Bitstream& in, int bytes, bool color); HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color); + HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& ancestor, + const glm::vec3& minimum, float size, bool color); const QByteArray& getContents() const { return _contents; } void write(Bitstream& out, bool color); void writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color); + void writeSubdivided(Bitstream& out, const HeightfieldDataPointer& ancestor, + const glm::vec3& minimum, float size, bool color); private: @@ -455,6 +460,14 @@ private: HeightfieldDataPointer _deltaData; QByteArray _encodedDelta; QMutex _encodedDeltaMutex; + + class EncodedSubdivision { + public: + HeightfieldDataPointer ancestor; + QByteArray data; + }; + QVector _encodedSubdivisions; + QMutex _encodedSubdivisionsMutex; }; /// An attribute that stores heightfield data. diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 2c7542ef35..67fafe1633 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -998,7 +998,7 @@ MetavoxelNode* MetavoxelNode::readSubdivision(MetavoxelStreamState& state) { for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); newNode->_children[i] = new MetavoxelNode(state.base.attribute); - newNode->_children[i]->readSubdivided(nextState); + newNode->_children[i]->readSubdivided(nextState, state, _attributeValue); } return newNode; } @@ -1037,7 +1037,7 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); - _children[i]->writeSubdivided(nextState); + _children[i]->writeSubdivided(nextState, state, _attributeValue); } } } else if (!leaf) { @@ -1051,40 +1051,42 @@ void MetavoxelNode::writeSubdivision(MetavoxelStreamState& state) const { } } -void MetavoxelNode::readSubdivided(MetavoxelStreamState& state) { +void MetavoxelNode::readSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, + void* ancestorValue) { clearChildren(state.base.attribute); if (!state.shouldSubdivide()) { - state.base.attribute->read(state.base.stream, _attributeValue, true); + state.base.attribute->readSubdivided(state, _attributeValue, ancestorState, ancestorValue, true); return; } bool leaf; state.base.stream >> leaf; - state.base.attribute->read(state.base.stream, _attributeValue, leaf); + state.base.attribute->readSubdivided(state, _attributeValue, ancestorState, ancestorValue, leaf); if (!leaf) { MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); _children[i] = new MetavoxelNode(state.base.attribute); - _children[i]->readSubdivided(nextState); + _children[i]->readSubdivided(nextState, ancestorState, ancestorValue); } mergeChildren(state.base.attribute, true); } } -void MetavoxelNode::writeSubdivided(MetavoxelStreamState& state) const { +void MetavoxelNode::writeSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, + void* ancestorValue) const { if (!state.shouldSubdivide()) { - state.base.attribute->write(state.base.stream, _attributeValue, true); + state.base.attribute->writeSubdivided(state, _attributeValue, ancestorState, ancestorValue, true); return; } bool leaf = isLeaf(); state.base.stream << leaf; - state.base.attribute->write(state.base.stream, _attributeValue, leaf); + state.base.attribute->writeSubdivided(state, _attributeValue, ancestorState, ancestorValue, leaf); if (!leaf) { MetavoxelStreamState nextState = { state.base, glm::vec3(), state.size * 0.5f }; for (int i = 0; i < CHILD_COUNT; i++) { nextState.setMinimum(state.minimum, i); - _children[i]->writeSubdivided(nextState); + _children[i]->writeSubdivided(nextState, ancestorState, ancestorValue); } } } diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index d01845eba6..8308c3c69b 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -225,8 +225,8 @@ public: MetavoxelNode* readSubdivision(MetavoxelStreamState& state); void writeSubdivision(MetavoxelStreamState& state) const; - void readSubdivided(MetavoxelStreamState& state); - void writeSubdivided(MetavoxelStreamState& state) const; + void readSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, void* ancestorValue); + void writeSubdivided(MetavoxelStreamState& state, const MetavoxelStreamState& ancestorState, void* ancestorValue) const; void writeSpanners(MetavoxelStreamState& state) const; void writeSpannerDelta(const MetavoxelNode& reference, MetavoxelStreamState& state) const; From c113dd350eabb6f2c18911d660d70355a8437dcf Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 18 Aug 2014 16:25:38 -0700 Subject: [PATCH 117/206] Moved members to MyAvatar --- interface/src/avatar/Avatar.cpp | 1 - interface/src/avatar/Avatar.h | 3 --- interface/src/avatar/MyAvatar.h | 3 +++ 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 498281c98c..41912afd09 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -64,7 +64,6 @@ Avatar::Avatar() : _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), _collisionGroups(0), - _player(NULL), _initialized(false), _shouldRenderBillboard(true) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 35dec3b8d0..c8ecb23913 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -188,9 +188,6 @@ protected: bool _moving; ///< set when position is changing quint32 _collisionGroups; - - RecorderPointer _recorder; - PlayerPointer _player; // protected methods... glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 67d19b87eb..2c1695a499 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -205,6 +205,9 @@ private: QList _animationHandles; PhysicsSimulation _physicsSimulation; + RecorderPointer _recorder; + PlayerPointer _player; + // private methods float computeDistanceToFloor(const glm::vec3& startPoint); void updateOrientation(float deltaTime); From 5b8ff074494aab14f0b1432722d8a70b460cd2c6 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 18 Aug 2014 18:21:48 -0700 Subject: [PATCH 118/206] Working on heightfield textures. --- interface/src/renderer/TextureCache.cpp | 31 +++++-- interface/src/renderer/TextureCache.h | 6 +- interface/src/ui/MetavoxelEditor.cpp | 25 +++++- interface/src/ui/MetavoxelEditor.h | 23 +++++ .../metavoxels/src/AttributeRegistry.cpp | 40 ++++++++- libraries/metavoxels/src/AttributeRegistry.h | 25 +++++- .../metavoxels/src/MetavoxelMessages.cpp | 88 +++++++++++++++++++ libraries/metavoxels/src/MetavoxelMessages.h | 19 ++++ 8 files changed, 245 insertions(+), 12 deletions(-) diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 01c3dc1cc1..11c4778c19 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -382,12 +382,24 @@ void ImageReader::run() { qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height(); image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio); } + int imageArea = image.width() * image.height(); + const int EIGHT_BIT_MAXIMUM = 255; if (!image.hasAlphaChannel()) { if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } - QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false)); + int redTotal = 0, greenTotal = 0, blueTotal = 0; + for (int y = 0; y < image.height(); y++) { + for (int x = 0; x < image.width(); x++) { + QRgb rgb = image.pixel(x, y); + redTotal += qRed(rgb); + greenTotal += qGreen(rgb); + blueTotal += qBlue(rgb); + } + } + QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false), + Q_ARG(const QColor&, QColor(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea))); return; } if (image.format() != QImage::Format_ARGB32) { @@ -397,11 +409,15 @@ void ImageReader::run() { // check for translucency/false transparency int opaquePixels = 0; int translucentPixels = 0; - const int EIGHT_BIT_MAXIMUM = 255; - const int RGB_BITS = 24; + int redTotal = 0, greenTotal = 0, blueTotal = 0, alphaTotal = 0; for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { - int alpha = image.pixel(x, y) >> RGB_BITS; + QRgb rgb = image.pixel(x, y); + redTotal += qRed(rgb); + greenTotal += qGreen(rgb); + blueTotal += qBlue(rgb); + int alpha = qAlpha(rgb); + alphaTotal += alpha; if (alpha == EIGHT_BIT_MAXIMUM) { opaquePixels++; } else if (alpha != 0) { @@ -409,13 +425,13 @@ void ImageReader::run() { } } } - int imageArea = image.width() * image.height(); if (opaquePixels == imageArea) { qDebug() << "Image with alpha channel is completely opaque:" << _url; image = image.convertToFormat(QImage::Format_RGB888); } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), - Q_ARG(bool, translucentPixels >= imageArea / 2)); + Q_ARG(bool, translucentPixels >= imageArea / 2), Q_ARG(const QColor&, QColor(redTotal / imageArea, + greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea))); } void NetworkTexture::downloadFinished(QNetworkReply* reply) { @@ -427,8 +443,9 @@ void NetworkTexture::loadContent(const QByteArray& content) { QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content)); } -void NetworkTexture::setImage(const QImage& image, bool translucent) { +void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor) { _translucent = translucent; + _averageColor = averageColor; finishedLoading(true); imageLoaded(image); diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 248a451e3a..58f9345c4a 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -127,18 +127,22 @@ public: /// (majority of pixels neither fully opaque or fully transparent). bool isTranslucent() const { return _translucent; } + /// Returns the lazily-computed average texture color. + const QColor& getAverageColor() const { return _averageColor; } + protected: virtual void downloadFinished(QNetworkReply* reply); Q_INVOKABLE void loadContent(const QByteArray& content); - Q_INVOKABLE void setImage(const QImage& image, bool translucent); + Q_INVOKABLE void setImage(const QImage& image, bool translucent, const QColor& averageColor); virtual void imageLoaded(const QImage& image); private: bool _translucent; + QColor _averageColor; }; /// Caches derived, dilated textures. diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index b7057532fb..1873f92df8 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -120,6 +120,7 @@ MetavoxelEditor::MetavoxelEditor() : addTool(new EraseHeightfieldTool(this)); addTool(new HeightfieldHeightBrushTool(this)); addTool(new HeightfieldColorBrushTool(this)); + addTool(new HeightfieldTextureBrushTool(this)); updateAttributes(); @@ -1153,5 +1154,27 @@ HeightfieldColorBrushTool::HeightfieldColorBrushTool(MetavoxelEditor* editor) : } QVariant HeightfieldColorBrushTool::createEdit(bool alternate) { - return QVariant::fromValue(PaintHeightfieldColorEdit(_position, _radius->value(), _color->getColor())); + return QVariant::fromValue(PaintHeightfieldColorEdit(_position, _radius->value(), + alternate ? QColor() : _color->getColor())); +} + +HeightfieldTextureBrushTool::HeightfieldTextureBrushTool(MetavoxelEditor* editor) : + HeightfieldBrushTool(editor, "Texture Brush") { + + _form->addRow("URL:", _url = new QUrlEditor(this)); + _url->setURL(QUrl()); + connect(_url, &QUrlEditor::urlChanged, this, &HeightfieldTextureBrushTool::updateTexture); +} + +QVariant HeightfieldTextureBrushTool::createEdit(bool alternate) { + if (alternate) { + return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), QUrl(), QColor())); + } else { + return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), _url->getURL(), + _texture ? _texture->getAverageColor() : QColor())); + } +} + +void HeightfieldTextureBrushTool::updateTexture() { + _texture = Application::getInstance()->getTextureCache()->getTexture(_url->getURL()); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 87d95a6927..6413c51e1c 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -26,6 +26,7 @@ class QListWidget; class QPushButton; class QScrollArea; class QSpinBox; +class QUrlEditor; class MetavoxelTool; class Vec3Editor; @@ -359,4 +360,26 @@ private: QColorEditor* _color; }; +/// Allows texturing parts of the heightfield. +class HeightfieldTextureBrushTool : public HeightfieldBrushTool { + Q_OBJECT + +public: + + HeightfieldTextureBrushTool(MetavoxelEditor* editor); + +protected: + + virtual QVariant createEdit(bool alternate); + +private slots: + + void updateTexture(); + +private: + + QUrlEditor* _url; + QSharedPointer _texture; +}; + #endif // hifi_MetavoxelEditor_h diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 5ffa593004..b1865c258c 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -25,6 +25,7 @@ REGISTER_META_OBJECT(SpannerQRgbAttribute) REGISTER_META_OBJECT(SpannerPackedNormalAttribute) REGISTER_META_OBJECT(HeightfieldAttribute) REGISTER_META_OBJECT(HeightfieldColorAttribute) +REGISTER_META_OBJECT(HeightfieldTextureAttribute) REGISTER_META_OBJECT(SharedObjectAttribute) REGISTER_META_OBJECT(SharedObjectSetAttribute) REGISTER_META_OBJECT(SpannerSetAttribute) @@ -49,7 +50,8 @@ AttributeRegistry::AttributeRegistry() : _spannerNormalAttribute(registerAttribute(new SpannerPackedNormalAttribute("spannerNormal"))), _spannerMaskAttribute(registerAttribute(new FloatAttribute("spannerMask"))), _heightfieldAttribute(registerAttribute(new HeightfieldAttribute("heightfield"))), - _heightfieldColorAttribute(registerAttribute(new HeightfieldColorAttribute("heightfieldColor"))) { + _heightfieldColorAttribute(registerAttribute(new HeightfieldColorAttribute("heightfieldColor"))), + _heightfieldTextureAttribute(registerAttribute(new HeightfieldTextureAttribute("heightfieldTexture"))) { // our baseline LOD threshold is for voxels; spanners and heightfields are a different story const float SPANNER_LOD_THRESHOLD_MULTIPLIER = 8.0f; @@ -58,6 +60,7 @@ AttributeRegistry::AttributeRegistry() : const float HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER = 32.0f; _heightfieldAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); _heightfieldColorAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); + _heightfieldTextureAttribute->setLODThresholdMultiplier(HEIGHTFIELD_LOD_THRESHOLD_MULTIPLIER); } static QScriptValue qDebugFunction(QScriptContext* context, QScriptEngine* engine) { @@ -1099,6 +1102,41 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post return false; } +HeightfieldTextureAttribute::HeightfieldTextureAttribute(const QString& name) : + InlineAttribute(name) { +} + +void HeightfieldTextureAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { + +} + +void HeightfieldTextureAttribute::write(Bitstream& out, void* value, bool isLeaf) const { + +} + +void HeightfieldTextureAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { + +} + +void HeightfieldTextureAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { + +} + +bool HeightfieldTextureAttribute::merge(void*& parent, void* children[], bool postRead) const { + int maxSize = 0; + for (int i = 0; i < MERGE_COUNT; i++) { + HeightfieldDataPointer pointer = decodeInline(children[i]); + if (pointer) { + maxSize = qMax(maxSize, pointer->getContents().size()); + } + } + if (maxSize == 0) { + *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + return true; + } + return false; +} + SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject, const SharedObjectPointer& defaultValue) : InlineAttribute(name, defaultValue), diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index c103855d2c..85bc5b418c 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -96,12 +96,15 @@ public: /// Returns a reference to the standard "spannerMask" attribute. const AttributePointer& getSpannerMaskAttribute() const { return _spannerMaskAttribute; } - /// Returns a reference to the standard HeightfieldPointer "heightfield" attribute. + /// Returns a reference to the standard HeightfieldDataPointer "heightfield" attribute. const AttributePointer& getHeightfieldAttribute() const { return _heightfieldAttribute; } - /// Returns a reference to the standard HeightfieldColorPointer "heightfieldColor" attribute. + /// Returns a reference to the standard HeightfieldDataPointer "heightfieldColor" attribute. const AttributePointer& getHeightfieldColorAttribute() const { return _heightfieldColorAttribute; } + /// Returns a reference to the standard HeightfieldDataPointer "heightfieldTexture" attribute. + const AttributePointer& getHeightfieldTextureAttribute() const { return _heightfieldTextureAttribute; } + private: static QScriptValue getAttribute(QScriptContext* context, QScriptEngine* engine); @@ -119,6 +122,7 @@ private: AttributePointer _spannerMaskAttribute; AttributePointer _heightfieldAttribute; AttributePointer _heightfieldColorAttribute; + AttributePointer _heightfieldTextureAttribute; }; /// Converts a value to a void pointer. @@ -504,6 +508,23 @@ public: virtual bool merge(void*& parent, void* children[], bool postRead = false) const; }; +/// An attribute that stores heightfield textures. +class HeightfieldTextureAttribute : public InlineAttribute { + Q_OBJECT + +public: + + Q_INVOKABLE HeightfieldTextureAttribute(const QString& name = QString()); + + virtual void read(Bitstream& in, void*& value, bool isLeaf) const; + virtual void write(Bitstream& out, void* value, bool isLeaf) const; + + virtual void readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const; + virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; + + virtual bool merge(void*& parent, void* children[], bool postRead = false) const; +}; + /// An attribute that takes the form of QObjects of a given meta-type (a subclass of SharedObject). class SharedObjectAttribute : public InlineAttribute { Q_OBJECT diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index df6e8172e4..34bd6707fd 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -492,3 +492,91 @@ void PaintHeightfieldColorEdit::apply(MetavoxelData& data, const WeakSharedObjec data.guide(visitor); } +PaintHeightfieldTextureEdit::PaintHeightfieldTextureEdit(const glm::vec3& position, float radius, + const QUrl& url, const QColor& averageColor) : + position(position), + radius(radius), + url(url), + averageColor(averageColor) { +} + +class PaintHeightfieldTextureEditVisitor : public MetavoxelVisitor { +public: + + PaintHeightfieldTextureEditVisitor(const PaintHeightfieldTextureEdit& edit); + + virtual int visit(MetavoxelInfo& info); + +private: + + PaintHeightfieldTextureEdit _edit; + Box _bounds; +}; + +PaintHeightfieldTextureEditVisitor::PaintHeightfieldTextureEditVisitor(const PaintHeightfieldTextureEdit& edit) : + MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() << + AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector() << + AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() << + AttributeRegistry::getInstance()->getHeightfieldColorAttribute()), + _edit(edit) { + + glm::vec3 extents(_edit.radius, _edit.radius, _edit.radius); + _bounds = Box(_edit.position - extents, _edit.position + extents); +} + +int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + HeightfieldDataPointer pointer = info.inputValues.at(1).getInlineValue(); + if (!pointer) { + return STOP_RECURSION; + } + QByteArray contents(pointer->getContents()); + const int BYTES_PER_PIXEL = 3; + int size = glm::sqrt((float)contents.size() / BYTES_PER_PIXEL); + int highest = size - 1; + float heightScale = size / info.size; + + glm::vec3 center = (_edit.position - info.minimum) * heightScale; + float scaledRadius = _edit.radius * heightScale; + glm::vec3 extents(scaledRadius, scaledRadius, scaledRadius); + + glm::vec3 start = glm::floor(center - extents); + glm::vec3 end = glm::ceil(center + extents); + + // paint all points within the radius + float z = qMax(start.z, 0.0f); + float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); + int stride = size * BYTES_PER_PIXEL; + char* lineDest = contents.data() + (int)z * stride + (int)startX * BYTES_PER_PIXEL; + float squaredRadius = scaledRadius * scaledRadius; + char red = _edit.averageColor.red(), green = _edit.averageColor.green(), blue = _edit.averageColor.blue(); + bool changed = false; + for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { + char* dest = lineDest; + for (float x = startX; x <= endX; x += 1.0f, dest += BYTES_PER_PIXEL) { + float dx = x - center.x, dz = z - center.z; + if (dx * dx + dz * dz <= squaredRadius) { + dest[0] = red; + dest[1] = green; + dest[2] = blue; + changed = true; + } + } + lineDest += stride; + } + if (changed) { + HeightfieldDataPointer newPointer(new HeightfieldData(contents)); + info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline(newPointer)); + } + return STOP_RECURSION; +} + +void PaintHeightfieldTextureEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + PaintHeightfieldTextureEditVisitor visitor(*this); + data.guide(visitor); +} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 2fc8cbf030..0455e6790e 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -241,4 +241,23 @@ public: DECLARE_STREAMABLE_METATYPE(PaintHeightfieldColorEdit) +/// An edit that sets a region of a heightfield texture. +class PaintHeightfieldTextureEdit : public MetavoxelEdit { + STREAMABLE + +public: + + STREAM glm::vec3 position; + STREAM float radius; + STREAM QUrl url; + STREAM QColor averageColor; + + PaintHeightfieldTextureEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, const QUrl& url = QUrl(), + const QColor& averageColor = QColor()); + + virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; +}; + +DECLARE_STREAMABLE_METATYPE(PaintHeightfieldTextureEdit) + #endif // hifi_MetavoxelMessages_h From 23a4c21ce11f9849a42a58ec0921dbb8bdc92cf6 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 18 Aug 2014 18:29:19 -0700 Subject: [PATCH 119/206] Only allow heightfield brushes on heightfield attributes. --- interface/src/ui/MetavoxelEditor.cpp | 4 ++++ interface/src/ui/MetavoxelEditor.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 1873f92df8..0b1ebabe71 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1101,6 +1101,10 @@ HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QStrin _radius->setValue(1.0); } +bool HeightfieldBrushTool::appliesTo(const AttributePointer& attribute) const { + return attribute->inherits("HeightfieldAttribute"); +} + void HeightfieldBrushTool::render() { if (Application::getInstance()->isMouseHidden()) { return; diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 6413c51e1c..63f71c6ca4 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -312,6 +312,8 @@ public: HeightfieldBrushTool(MetavoxelEditor* editor, const QString& name); + virtual bool appliesTo(const AttributePointer& attribute) const; + virtual void render(); virtual bool eventFilter(QObject* watched, QEvent* event); From 45ccb012972989fcb8baa04bc676bc1ad19f8345 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 18 Aug 2014 21:05:03 -0700 Subject: [PATCH 120/206] Update options position and orientation correctly in the AudioInjector --- libraries/audio/src/AudioInjector.cpp | 14 ++++++++++++-- libraries/audio/src/AudioInjector.h | 1 + libraries/audio/src/AudioInjectorOptions.cpp | 9 ++++++++- libraries/audio/src/AudioInjectorOptions.h | 1 + 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index e5c1230832..114ab1c95c 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -27,7 +27,6 @@ AudioInjector::AudioInjector(QObject* parent) : _options(), _shouldStop(false) { - } AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions) : @@ -35,7 +34,10 @@ AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorO _options(injectorOptions), _shouldStop(false) { - +} + +void AudioInjector::setOptions(AudioInjectorOptions& options) { + _options = options; } const uchar MAX_INJECTOR_VOLUME = 0xFF; @@ -73,9 +75,11 @@ void AudioInjector::injectAudio() { packetStream << loopbackFlag; // pack the position for injected audio + int positionOptionOffset = injectAudioPacket.size(); packetStream.writeRawData(reinterpret_cast(&_options.getPosition()), sizeof(_options.getPosition())); // pack our orientation for injected audio + int orientationOptionOffset = injectAudioPacket.size(); packetStream.writeRawData(reinterpret_cast(&_options.getOrientation()), sizeof(_options.getOrientation())); // pack zero for radius @@ -101,6 +105,12 @@ void AudioInjector::injectAudio() { int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, soundByteArray.size() - currentSendPosition); + memcpy(injectAudioPacket.data() + positionOptionOffset, + &_options.getPosition(), + sizeof(_options.getPosition())); + memcpy(injectAudioPacket.data() + orientationOptionOffset, + &_options.getOrientation(), + sizeof(_options.getOrientation())); // resize the QByteArray to the right size injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy); diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 08fe544255..966a4dd1cf 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -29,6 +29,7 @@ public: public slots: void injectAudio(); void stop() { _shouldStop = true; } + void setOptions(AudioInjectorOptions& options); signals: void finished(); private: diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 49f1571c98..01aa43a0cd 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -19,7 +19,6 @@ AudioInjectorOptions::AudioInjectorOptions(QObject* parent) : _orientation(glm::vec3(0.0f, 0.0f, 0.0f)), _loopbackAudioInterface(NULL) { - } AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) { @@ -29,3 +28,11 @@ AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) { _orientation = other._orientation; _loopbackAudioInterface = other._loopbackAudioInterface; } + +void AudioInjectorOptions::operator=(const AudioInjectorOptions& other) { + _position = other._position; + _volume = other._volume; + _loop = other._loop; + _orientation = other._orientation; + _loopbackAudioInterface = other._loopbackAudioInterface; +} \ No newline at end of file diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index 35575414d5..64936e4bc9 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -30,6 +30,7 @@ class AudioInjectorOptions : public QObject { public: AudioInjectorOptions(QObject* parent = 0); AudioInjectorOptions(const AudioInjectorOptions& other); + void operator=(const AudioInjectorOptions& other); const glm::vec3& getPosition() const { return _position; } void setPosition(const glm::vec3& position) { _position = position; } From 29830916bafd60414ffd150e6f47142f46a2ca3b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 18 Aug 2014 21:06:32 -0700 Subject: [PATCH 121/206] Fix bug introduced in CR --- interface/src/Recorder.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index b782112778..30b14ac589 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -182,7 +182,7 @@ qint64 Player::elapsed() const { } glm::quat Player::getHeadRotation() { - if (computeCurrentFrame()) { + if (!computeCurrentFrame()) { qWarning() << "Incorrect use of Player::getHeadRotation()"; return glm::quat(); } @@ -195,7 +195,7 @@ glm::quat Player::getHeadRotation() { } float Player::getLeanSideways() { - if (computeCurrentFrame()) { + if (!computeCurrentFrame()) { qWarning() << "Incorrect use of Player::getLeanSideways()"; return 0.0f; } @@ -204,7 +204,7 @@ float Player::getLeanSideways() { } float Player::getLeanForward() { - if (computeCurrentFrame()) { + if (!computeCurrentFrame()) { qWarning() << "Incorrect use of Player::getLeanForward()"; return 0.0f; } @@ -216,13 +216,16 @@ void Player::startPlaying() { if (_recording && _recording->getFrameNumber() > 0) { qDebug() << "Recorder::startPlaying()"; _currentFrame = 0; + + // Setup audio thread + _audioThread = new QThread(); _options.setPosition(_avatar->getPosition()); _options.setOrientation(_avatar->getOrientation()); - _injector.reset(new AudioInjector(_recording->getAudio(), _options)); - _audioThread = new QThread(); + _injector.reset(new AudioInjector(_recording->getAudio(), _options), &QObject::deleteLater); _injector->moveToThread(_audioThread); _audioThread->start(); QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); + _timer.start(); } } @@ -232,7 +235,6 @@ void Player::stopPlaying() { return; } - qDebug() << "Recorder::stopPlaying()"; _timer.invalidate(); _avatar->clearJointsData(); @@ -242,6 +244,7 @@ void Player::stopPlaying() { _injector.clear(); _audioThread->exit(); _audioThread->deleteLater(); + qDebug() << "Recorder::stopPlaying()"; } void Player::loadFromFile(QString file) { @@ -259,7 +262,7 @@ void Player::loadRecording(RecordingPointer recording) { void Player::play() { computeCurrentFrame(); - if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber()) { + if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber() - 1) { // If it's the end of the recording, stop playing stopPlaying(); return; @@ -283,6 +286,10 @@ void Player::play() { HeadData* head = const_cast(_avatar->getHeadData()); head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); } + + _options.setPosition(_avatar->getPosition()); + _options.setOrientation(_avatar->getOrientation()); + _injector->setOptions(_options); } bool Player::computeCurrentFrame() { @@ -294,7 +301,7 @@ bool Player::computeCurrentFrame() { _currentFrame = 0; } - while (_currentFrame < _recording->getFrameNumber() && + while (_currentFrame < _recording->getFrameNumber() - 1 && _recording->getFrameTimestamp(_currentFrame) < _timer.elapsed()) { ++_currentFrame; } From 39b370979c613bf9ff151299c22eb0bcc80e2202 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 18 Aug 2014 21:32:40 -0700 Subject: [PATCH 122/206] JS calls for head motion/rotation that allow avatar flying with head movement --- examples/hydraMove.js | 66 ++++++++++++++++++++++-- interface/src/avatar/MyAvatar.h | 9 +++- interface/src/devices/DdeFaceTracker.cpp | 12 ++--- interface/src/devices/Faceshift.cpp | 24 +++++++-- interface/src/devices/Faceshift.h | 3 ++ 5 files changed, 100 insertions(+), 14 deletions(-) diff --git a/examples/hydraMove.js b/examples/hydraMove.js index 675a885b6d..ee7f16412c 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -21,10 +21,10 @@ var position = { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.pos var joysticksCaptured = false; var THRUST_CONTROLLER = 0; var VIEW_CONTROLLER = 1; -var INITIAL_THRUST_MULTPLIER = 1.0; +var INITIAL_THRUST_MULTIPLIER = 1.0; var THRUST_INCREASE_RATE = 1.05; var MAX_THRUST_MULTIPLIER = 75.0; -var thrustMultiplier = INITIAL_THRUST_MULTPLIER; +var thrustMultiplier = INITIAL_THRUST_MULTIPLIER; var grabDelta = { x: 0, y: 0, z: 0}; var grabStartPosition = { x: 0, y: 0, z: 0}; var grabDeltaVelocity = { x: 0, y: 0, z: 0}; @@ -34,6 +34,8 @@ var grabbingWithRightHand = false; var wasGrabbingWithRightHand = false; var grabbingWithLeftHand = false; var wasGrabbingWithLeftHand = false; +var movingWithHead = false; +var headStartPosition, headStartPitch, headStartYaw; var EPSILON = 0.000001; var velocity = { x: 0, y: 0, z: 0}; var THRUST_MAG_UP = 100.0; @@ -241,6 +243,46 @@ function handleGrabBehavior(deltaTime) { wasGrabbingWithLeftHand = grabbingWithLeftHand; } +var HEAD_MOVE_DEAD_ZONE = 0.0; +var HEAD_STRAFE_DEAD_ZONE = 0.025; +var HEAD_ROTATE_DEAD_ZONE = 0.0; +var HEAD_THRUST_MULTIPLIER = 10000.0; +var HEAD_YAW_RATE = 2.0; +var HEAD_PITCH_RATE = 2.0; + +function moveWithHead(deltaTime) { + if (movingWithHead) { + var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw; + var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartPitch; + print("delta pitch = " + deltaPitch); + + + var bodyLocalCurrentHeadVector = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position); + bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.angleAxis(-deltaYaw, {x:0, y: 1, z:0}), bodyLocalCurrentHeadVector); + var headDelta = Vec3.subtract(bodyLocalCurrentHeadVector, headStartPosition); + headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta); + headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion + Vec3.print("head delta ", headDelta); + // Lateral thrust (strafe) + //if (Math.abs(headDelta.z) > HEAD_STRAFE_DEAD_ZONE) { + // if (headDelta.z > 0) { + MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_MULTIPLIER * deltaTime)); + MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_MULTIPLIER * deltaTime)); + // } + //} + //if (Vec3.length(headDelta) > HEAD_MOVE_DEAD_ZONE) { + // MyAvatar.addThrust(Vec3.multiply(headDelta, HEAD_THRUST_MULTIPLIER * deltaTime)); + //} + + if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) { + //print("yaw = " + deltaYaw); + var orientation = Quat.multiply(Quat.angleAxis(deltaYaw * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation); + MyAvatar.orientation = orientation; + } + MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime; + } +} + // Update for joysticks and move button function flyWithHydra(deltaTime) { var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER); @@ -262,7 +304,7 @@ function flyWithHydra(deltaTime) { thrustJoystickPosition.x * thrustMultiplier * deltaTime); MyAvatar.addThrust(thrustRight); } else { - thrustMultiplier = INITIAL_THRUST_MULTPLIER; + thrustMultiplier = INITIAL_THRUST_MULTIPLIER; } // View Controller @@ -280,6 +322,7 @@ function flyWithHydra(deltaTime) { MyAvatar.headPitch = newPitch; } handleGrabBehavior(deltaTime); + moveWithHead(deltaTime); displayDebug(); } @@ -296,3 +339,20 @@ function scriptEnding() { } Script.scriptEnding.connect(scriptEnding); +Controller.keyPressEvent.connect(function(event) { + if (event.text == "z" && !movingWithHead) { + movingWithHead = true; + headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position); + headStartPitch = MyAvatar.getHeadDeltaPitch(); + headStartYaw = MyAvatar.getHeadFinalYaw(); + Vec3.print("head start position = ", headStartPosition); + print(" yaw = " + headStartYaw + " pitch = " + headStartPitch); + } +}); +Controller.keyReleaseEvent.connect(function(event) { + if (event.text == "z") { + movingWithHead = false; + print("move ended"); + } +}); + diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 4f2802a35a..25afc51191 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -106,7 +106,14 @@ public: virtual int parseDataAtOffset(const QByteArray& packet, int offset); static void sendKillAvatar(); - + + Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); } + Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); } + Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); } + Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); } + + Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); } + Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); } void updateLookAtTargetAvatar(); diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index ae5beb8c85..7b353e986d 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -242,13 +242,11 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { // Set blendshapes float EYE_MAGNIFIER = 4.0f; - - float rightEye = (updateAndGetCoefficient(_rightEye, packet.expressions[0])) * EYE_MAGNIFIER; + float rightEye = glm::clamp((updateAndGetCoefficient(_rightEye, packet.expressions[0])) * EYE_MAGNIFIER, 0.0f, 1.0f); _blendshapeCoefficients[_rightBlinkIndex] = rightEye; - float leftEye = (updateAndGetCoefficient(_leftEye, packet.expressions[1])) * EYE_MAGNIFIER; + float leftEye = glm::clamp((updateAndGetCoefficient(_leftEye, packet.expressions[1])) * EYE_MAGNIFIER, 0.0f, 1.0f); _blendshapeCoefficients[_leftBlinkIndex] = leftEye; - // Right eye = packet.expressions[0]; float leftBrow = 1.0f - rescaleCoef(packet.expressions[14]); if (leftBrow < 0.5f) { @@ -270,9 +268,9 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { float JAW_OPEN_MAGNIFIER = 1.4f; _blendshapeCoefficients[_jawOpenIndex] = rescaleCoef(packet.expressions[21]) * JAW_OPEN_MAGNIFIER; - - _blendshapeCoefficients[_mouthSmileLeftIndex] = rescaleCoef(packet.expressions[24]); - _blendshapeCoefficients[_mouthSmileRightIndex] = rescaleCoef(packet.expressions[23]); + float SMILE_MULTIPLIER = 2.0f; + _blendshapeCoefficients[_mouthSmileLeftIndex] = glm::clamp(packet.expressions[24] * SMILE_MULTIPLIER, 0.f, 1.f); + _blendshapeCoefficients[_mouthSmileRightIndex] = glm::clamp(packet.expressions[23] * SMILE_MULTIPLIER, 0.f, 1.f); } else { diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index b5cba8348c..491585f3a5 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -31,6 +31,10 @@ Faceshift::Faceshift() : _tcpEnabled(true), _tcpRetryCount(0), _lastTrackingStateReceived(0), + _headAngularVelocity(0), + _headLinearVelocity(0), + _lastHeadTranslation(0), + _filteredHeadTranslation(0), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftYaw(0.0f), _eyeGazeRightPitch(0.0f), @@ -195,6 +199,7 @@ void Faceshift::send(const std::string& message) { void Faceshift::receive(const QByteArray& buffer) { #ifdef HAVE_FACESHIFT + float AVERAGE_FACESHIFT_FRAME_TIME = 0.033f; _stream.received(buffer.size(), buffer.constData()); fsMsgPtr msg; for (fsMsgPtr msg; (msg = _stream.get_message()); ) { @@ -209,7 +214,6 @@ void Faceshift::receive(const QByteArray& buffer) { float theta = 2 * acos(r.w); if (theta > EPSILON) { float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); - float AVERAGE_FACESHIFT_FRAME_TIME = 0.033f; _headAngularVelocity = theta / AVERAGE_FACESHIFT_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag; } else { _headAngularVelocity = glm::vec3(0,0,0); @@ -217,8 +221,22 @@ void Faceshift::receive(const QByteArray& buffer) { _headRotation = newRotation; const float TRANSLATION_SCALE = 0.02f; - _headTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, - -data.m_headTranslation.z) * TRANSLATION_SCALE; + glm::vec3 newHeadTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, + -data.m_headTranslation.z) * TRANSLATION_SCALE; + + + _headLinearVelocity = (newHeadTranslation - _lastHeadTranslation) / AVERAGE_FACESHIFT_FRAME_TIME; + //qDebug() << "Head vel: " << glm::length(_headLinearVelocity); + + // Velocity filter the faceshift head translation because it's noisy + float velocityFilter = glm::clamp(1.0f - glm::length(_headLinearVelocity), 0.0f, 1.0f); + _filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * newHeadTranslation; + + _lastHeadTranslation = newHeadTranslation; + + _headTranslation = _filteredHeadTranslation; + //_headTranslation = newHeadTranslation; + _eyeGazeLeftPitch = -data.m_eyeGazeLeftPitch; _eyeGazeLeftYaw = data.m_eyeGazeLeftYaw; _eyeGazeRightPitch = -data.m_eyeGazeRightPitch; diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index 25abd8c0eb..957a5af154 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -100,6 +100,9 @@ private: quint64 _lastTrackingStateReceived; glm::vec3 _headAngularVelocity; + glm::vec3 _headLinearVelocity; + glm::vec3 _lastHeadTranslation; + glm::vec3 _filteredHeadTranslation; // degrees float _eyeGazeLeftPitch; From 7b86f668a6851167ff42d499c0d7d6b0d754e7a0 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 18 Aug 2014 23:22:55 -0700 Subject: [PATCH 123/206] Add SPACE to scripting JS, change reset to apostrophe --- examples/hydraMove.js | 21 +++++---------------- interface/src/Application.cpp | 2 +- interface/src/avatar/Avatar.h | 8 ++++---- interface/src/devices/CaraFaceTracker.cpp | 1 - libraries/script-engine/src/EventTypes.cpp | 4 ++++ 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/examples/hydraMove.js b/examples/hydraMove.js index ee7f16412c..64f3d3c0c0 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -254,28 +254,17 @@ function moveWithHead(deltaTime) { if (movingWithHead) { var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw; var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartPitch; - print("delta pitch = " + deltaPitch); - var bodyLocalCurrentHeadVector = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position); bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.angleAxis(-deltaYaw, {x:0, y: 1, z:0}), bodyLocalCurrentHeadVector); var headDelta = Vec3.subtract(bodyLocalCurrentHeadVector, headStartPosition); headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta); headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion - Vec3.print("head delta ", headDelta); - // Lateral thrust (strafe) - //if (Math.abs(headDelta.z) > HEAD_STRAFE_DEAD_ZONE) { - // if (headDelta.z > 0) { - MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_MULTIPLIER * deltaTime)); - MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_MULTIPLIER * deltaTime)); - // } - //} - //if (Vec3.length(headDelta) > HEAD_MOVE_DEAD_ZONE) { - // MyAvatar.addThrust(Vec3.multiply(headDelta, HEAD_THRUST_MULTIPLIER * deltaTime)); - //} + + MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_MULTIPLIER * deltaTime)); + MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_MULTIPLIER * deltaTime)); if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) { - //print("yaw = " + deltaYaw); var orientation = Quat.multiply(Quat.angleAxis(deltaYaw * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation); MyAvatar.orientation = orientation; } @@ -340,7 +329,7 @@ function scriptEnding() { Script.scriptEnding.connect(scriptEnding); Controller.keyPressEvent.connect(function(event) { - if (event.text == "z" && !movingWithHead) { + if (event.text == "SPACE" && !movingWithHead) { movingWithHead = true; headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position); headStartPitch = MyAvatar.getHeadDeltaPitch(); @@ -350,7 +339,7 @@ Controller.keyPressEvent.connect(function(event) { } }); Controller.keyReleaseEvent.connect(function(event) { - if (event.text == "z") { + if (event.text == "SPACE") { movingWithHead = false; print("move ended"); } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 093f5e6610..1141c0d741 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -894,7 +894,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_Space: + case Qt::Key_Apostrophe: resetSensors(); break; diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index c8ecb23913..46780e50ea 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -91,7 +91,7 @@ public: const QVector& getAttachmentModels() const { return _attachmentModels; } glm::vec3 getChestPosition() const; float getScale() const { return _scale; } - const glm::vec3& getVelocity() const { return _velocity; } + Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; } const Head* getHead() const { return static_cast(_headData); } Head* getHead() { return static_cast(_headData); } Hand* getHand() { return static_cast(_handData); } @@ -152,9 +152,9 @@ public: Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const; Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const; - glm::vec3 getAcceleration() const { return _acceleration; } - glm::vec3 getAngularVelocity() const { return _angularVelocity; } - glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } + Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; } + Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; } + Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } /// Scales a world space position vector relative to the avatar position and scale diff --git a/interface/src/devices/CaraFaceTracker.cpp b/interface/src/devices/CaraFaceTracker.cpp index 27cf3b175b..9f056fab9b 100644 --- a/interface/src/devices/CaraFaceTracker.cpp +++ b/interface/src/devices/CaraFaceTracker.cpp @@ -389,7 +389,6 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) { if (theta > EPSILON) { float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); const float AVERAGE_CARA_FRAME_TIME = 0.04f; - const float ANGULAR_VELOCITY_MIN = 1.2f; const float YAW_STANDARD_DEV_DEG = 2.5f; _headAngularVelocity = theta / AVERAGE_CARA_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag; diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index 9cf6c5b1a0..0e6a27bc42 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -78,6 +78,8 @@ KeyEvent::KeyEvent(const QKeyEvent& event) { text = "LEFT"; } else if (key == Qt::Key_Right) { text = "RIGHT"; + } else if (key == Qt::Key_Space) { + text = "SPACE"; } else if (key == Qt::Key_Escape) { text = "ESC"; } else if (key == Qt::Key_Tab) { @@ -220,6 +222,8 @@ void keyEventFromScriptValue(const QScriptValue& object, KeyEvent& event) { } else if (event.text.toUpper() == "RIGHT") { event.key = Qt::Key_Right; event.isKeypad = true; + } else if (event.text.toUpper() == "SPACE") { + event.key = Qt::Key_Space; } else if (event.text.toUpper() == "ESC") { event.key = Qt::Key_Escape; } else if (event.text.toUpper() == "TAB") { From 51922d68ab48395255b794969444cb10b805b1b2 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Mon, 18 Aug 2014 23:42:42 -0700 Subject: [PATCH 124/206] dead zones --- examples/hydraMove.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/hydraMove.js b/examples/hydraMove.js index 64f3d3c0c0..18e9c73a39 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -244,11 +244,11 @@ function handleGrabBehavior(deltaTime) { } var HEAD_MOVE_DEAD_ZONE = 0.0; -var HEAD_STRAFE_DEAD_ZONE = 0.025; +var HEAD_STRAFE_DEAD_ZONE = 0.0; var HEAD_ROTATE_DEAD_ZONE = 0.0; var HEAD_THRUST_MULTIPLIER = 10000.0; var HEAD_YAW_RATE = 2.0; -var HEAD_PITCH_RATE = 2.0; +var HEAD_PITCH_RATE = 1.0; function moveWithHead(deltaTime) { if (movingWithHead) { @@ -261,9 +261,12 @@ function moveWithHead(deltaTime) { headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta); headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion - MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_MULTIPLIER * deltaTime)); - MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_MULTIPLIER * deltaTime)); - + if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) { + MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_MULTIPLIER * deltaTime)); + } + if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) { + MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_MULTIPLIER * deltaTime)); + } if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) { var orientation = Quat.multiply(Quat.angleAxis(deltaYaw * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation); MyAvatar.orientation = orientation; @@ -334,14 +337,11 @@ Controller.keyPressEvent.connect(function(event) { headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position); headStartPitch = MyAvatar.getHeadDeltaPitch(); headStartYaw = MyAvatar.getHeadFinalYaw(); - Vec3.print("head start position = ", headStartPosition); - print(" yaw = " + headStartYaw + " pitch = " + headStartPitch); } }); Controller.keyReleaseEvent.connect(function(event) { if (event.text == "SPACE") { movingWithHead = false; - print("move ended"); } }); From 9d44f5e4f7ca36c025260433dd078db6b85370bf Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 19 Aug 2014 08:18:19 -0700 Subject: [PATCH 125/206] measure face shift frame time rather than use constant --- interface/src/devices/Faceshift.cpp | 16 +++++++++++----- interface/src/devices/Faceshift.h | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 491585f3a5..9d79549099 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -26,11 +26,13 @@ using namespace fs; using namespace std; const quint16 FACESHIFT_PORT = 33433; +float STARTING_FACESHIFT_FRAME_TIME = 0.033f; Faceshift::Faceshift() : _tcpEnabled(true), _tcpRetryCount(0), _lastTrackingStateReceived(0), + _averageFrameTime(STARTING_FACESHIFT_FRAME_TIME), _headAngularVelocity(0), _headLinearVelocity(0), _lastHeadTranslation(0), @@ -199,7 +201,6 @@ void Faceshift::send(const std::string& message) { void Faceshift::receive(const QByteArray& buffer) { #ifdef HAVE_FACESHIFT - float AVERAGE_FACESHIFT_FRAME_TIME = 0.033f; _stream.received(buffer.size(), buffer.constData()); fsMsgPtr msg; for (fsMsgPtr msg; (msg = _stream.get_message()); ) { @@ -214,7 +215,7 @@ void Faceshift::receive(const QByteArray& buffer) { float theta = 2 * acos(r.w); if (theta > EPSILON) { float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); - _headAngularVelocity = theta / AVERAGE_FACESHIFT_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag; + _headAngularVelocity = theta / _averageFrameTime * glm::vec3(r.x, r.y, r.z) / rMag; } else { _headAngularVelocity = glm::vec3(0,0,0); } @@ -225,8 +226,7 @@ void Faceshift::receive(const QByteArray& buffer) { -data.m_headTranslation.z) * TRANSLATION_SCALE; - _headLinearVelocity = (newHeadTranslation - _lastHeadTranslation) / AVERAGE_FACESHIFT_FRAME_TIME; - //qDebug() << "Head vel: " << glm::length(_headLinearVelocity); + _headLinearVelocity = (newHeadTranslation - _lastHeadTranslation) / _averageFrameTime; // Velocity filter the faceshift head translation because it's noisy float velocityFilter = glm::clamp(1.0f - glm::length(_headLinearVelocity), 0.0f, 1.0f); @@ -243,7 +243,13 @@ void Faceshift::receive(const QByteArray& buffer) { _eyeGazeRightYaw = data.m_eyeGazeRightYaw; _blendshapeCoefficients = QVector::fromStdVector(data.m_coeffs); - _lastTrackingStateReceived = usecTimestampNow(); + const float FRAME_AVERAGING_FACTOR = 0.99f; + quint64 usecsNow = usecTimestampNow(); + if (_lastTrackingStateReceived != 0) { + _averageFrameTime = FRAME_AVERAGING_FACTOR * _averageFrameTime + + (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastTrackingStateReceived) / 1000000.f; + } + _lastTrackingStateReceived = usecsNow; } break; } diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index 957a5af154..e7d87827eb 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -98,6 +98,7 @@ private: int _tcpRetryCount; bool _tracking; quint64 _lastTrackingStateReceived; + float _averageFrameTime; glm::vec3 _headAngularVelocity; glm::vec3 _headLinearVelocity; From 1d13a6c05d9f4307d800f814a284d77b561b1def Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 19 Aug 2014 09:37:11 -0700 Subject: [PATCH 126/206] Stabilize face shift head camera movement with velocity filtering --- interface/src/devices/Faceshift.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 9d79549099..d4f9cd6b43 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -219,23 +219,23 @@ void Faceshift::receive(const QByteArray& buffer) { } else { _headAngularVelocity = glm::vec3(0,0,0); } - _headRotation = newRotation; + const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f; + _headRotation = safeMix(_headRotation, newRotation, glm::clamp(glm::length(_headAngularVelocity) * + ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f)); const float TRANSLATION_SCALE = 0.02f; glm::vec3 newHeadTranslation = glm::vec3(data.m_headTranslation.x, data.m_headTranslation.y, -data.m_headTranslation.z) * TRANSLATION_SCALE; - _headLinearVelocity = (newHeadTranslation - _lastHeadTranslation) / _averageFrameTime; - // Velocity filter the faceshift head translation because it's noisy - float velocityFilter = glm::clamp(1.0f - glm::length(_headLinearVelocity), 0.0f, 1.0f); + const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f; + float velocityFilter = glm::clamp(1.0f - glm::length(_headLinearVelocity) * + LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); _filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * newHeadTranslation; _lastHeadTranslation = newHeadTranslation; - _headTranslation = _filteredHeadTranslation; - //_headTranslation = newHeadTranslation; _eyeGazeLeftPitch = -data.m_eyeGazeLeftPitch; _eyeGazeLeftYaw = data.m_eyeGazeLeftYaw; From 8fe1457776ddedd8f52d1775767de6cf7faff6c0 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 19 Aug 2014 10:14:13 -0700 Subject: [PATCH 127/206] removed unneeded audio and light stats from main stats screen --- interface/src/ui/Stats.cpp | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index c4e7d6ff30..58a93fa0ae 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -282,19 +282,10 @@ void Stats::display( pingVoxel = totalPingVoxel/voxelServerCount; } - - Audio* audio = Application::getInstance()->getAudio(); - lines = _expanded ? 4 : 3; drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; - char audioJitter[30]; - sprintf(audioJitter, - "Buffer msecs %.1f", - audio->getDesiredJitterBufferFrames() * BUFFER_SEND_INTERVAL_USECS / (float)USECS_PER_MSEC); - drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color); - char audioPing[30]; sprintf(audioPing, "Audio ping: %d", pingAudio); @@ -698,27 +689,6 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); } - - // draw local light stats - QVector localLights = Application::getInstance()->getAvatarManager().getLocalLights(); - verticalOffset = 400; - horizontalOffset = 20; - - char buffer[128]; - for (int i = 0; i < localLights.size(); i++) { - glm::vec3 lightDirection = localLights.at(i).direction; - snprintf(buffer, sizeof(buffer), "Light %d direction (%.2f, %.2f, %.2f)", i, lightDirection.x, lightDirection.y, lightDirection.z); - drawText(horizontalOffset, verticalOffset, scale, rotation, font, buffer, color); - - verticalOffset += STATS_PELS_PER_LINE; - - glm::vec3 lightColor = localLights.at(i).color; - snprintf(buffer, sizeof(buffer), "Light %d color (%.2f, %.2f, %.2f)", i, lightColor.x, lightColor.y, lightColor.z); - drawText(horizontalOffset, verticalOffset, scale, rotation, font, buffer, color); - - verticalOffset += STATS_PELS_PER_LINE; - } - } From 1a80bd98f0c14548213b9df163c314473bb3ff5d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 19 Aug 2014 10:25:18 -0700 Subject: [PATCH 128/206] Ignore empty "embedded" textures Blender 2.71 exporter "embeds" external textures as empty binary blobs --- examples/editModels.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/editModels.js b/examples/editModels.js index b15ea58fc6..411431791c 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -611,7 +611,10 @@ var modelUploader = (function () { index += nameLength; if (name === "content" && previousNodeFilename !== "") { - geometry.embedded.push(previousNodeFilename); + // Blender 2.71 exporter "embeds" external textures as empty binary blobs so ignore these + if (propertyListLength > 5) { + geometry.embedded.push(previousNodeFilename); + } } if (name === "relativefilename") { From f511fe2657a1b9a7192ae819b3af61d6ccbf87d0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 19 Aug 2014 11:31:50 -0700 Subject: [PATCH 129/206] Ragdoll cannot assume skeleton's rootIndex is 0 some Models have extra "joints" not part of the normal skeleton --- interface/src/avatar/SkeletonModel.cpp | 19 +++++++++---------- interface/src/avatar/SkeletonRagdoll.cpp | 12 ++++++------ libraries/shared/src/Ragdoll.cpp | 16 +++++++++------- libraries/shared/src/Ragdoll.h | 5 +++++ 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index f9a73f2431..dd398f2b2b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -576,6 +576,7 @@ SkeletonRagdoll* SkeletonModel::buildRagdoll() { if (!_ragdoll) { _ragdoll = new SkeletonRagdoll(this); if (_enableShapes) { + clearShapes(); buildShapes(); } } @@ -600,6 +601,7 @@ void SkeletonModel::buildShapes() { if (!_ragdoll) { _ragdoll = new SkeletonRagdoll(this); } + _ragdoll->setRootIndex(geometry.rootJointIndex); _ragdoll->initPoints(); QVector& points = _ragdoll->getPoints(); @@ -614,15 +616,14 @@ void SkeletonModel::buildShapes() { float radius = uniformScale * joint.boneRadius; float halfHeight = 0.5f * uniformScale * joint.distanceToParent; Shape::Type type = joint.shapeType; - if (i == 0 || (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON)) { + int parentIndex = joint.parentIndex; + if (parentIndex == -1 || radius < EPSILON) { + type = Shape::UNKNOWN_SHAPE; + } else if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) { // this shape is forced to be a sphere type = Shape::SPHERE_SHAPE; } - if (radius < EPSILON) { - type = Shape::UNKNOWN_SHAPE; - } Shape* shape = NULL; - int parentIndex = joint.parentIndex; if (type == Shape::SPHERE_SHAPE) { shape = new VerletSphereShape(radius, &(points[i])); shape->setEntity(this); @@ -637,18 +638,16 @@ void SkeletonModel::buildShapes() { points[i].setMass(mass); totalMass += mass; } - if (parentIndex != -1) { + if (shape && parentIndex != -1) { // always disable collisions between joint and its parent - if (shape) { - disableCollisions(i, parentIndex); - } + disableCollisions(i, parentIndex); } _shapes.push_back(shape); } // set the mass of the root if (numStates > 0) { - points[0].setMass(totalMass); + points[_ragdoll->getRootIndex()].setMass(totalMass); } // This method moves the shapes to their default positions in Model frame. diff --git a/interface/src/avatar/SkeletonRagdoll.cpp b/interface/src/avatar/SkeletonRagdoll.cpp index 6318323990..7c0e056826 100644 --- a/interface/src/avatar/SkeletonRagdoll.cpp +++ b/interface/src/avatar/SkeletonRagdoll.cpp @@ -36,8 +36,9 @@ void SkeletonRagdoll::stepForward(float deltaTime) { void SkeletonRagdoll::slamPointPositions() { QVector& jointStates = _model->getJointStates(); - int numStates = jointStates.size(); - for (int i = 0; i < numStates; ++i) { + const int numPoints = _points.size(); + assert(numPoints == jointStates.size()); + for (int i = _rootIndex; i < numPoints; ++i) { _points[i].initPosition(jointStates.at(i).getPosition()); } } @@ -49,8 +50,7 @@ void SkeletonRagdoll::initPoints() { initTransform(); // one point for each joint - QVector& jointStates = _model->getJointStates(); - int numStates = jointStates.size(); + int numStates = _model->getJointStates().size(); _points.fill(VerletPoint(), numStates); slamPointPositions(); } @@ -67,7 +67,7 @@ void SkeletonRagdoll::buildConstraints() { float minBone = FLT_MAX; float maxBone = -FLT_MAX; QMultiMap families; - for (int i = 0; i < numPoints; ++i) { + for (int i = _rootIndex; i < numPoints; ++i) { const JointState& state = jointStates.at(i); int parentIndex = state.getParentIndex(); if (parentIndex != -1) { @@ -105,7 +105,7 @@ void SkeletonRagdoll::buildConstraints() { float MAX_STRENGTH = 0.6f; float MIN_STRENGTH = 0.05f; // each joint gets a MuscleConstraint to its parent - for (int i = 1; i < numPoints; ++i) { + for (int i = _rootIndex + 1; i < numPoints; ++i) { const JointState& state = jointStates.at(i); int p = state.getParentIndex(); if (p == -1) { diff --git a/libraries/shared/src/Ragdoll.cpp b/libraries/shared/src/Ragdoll.cpp index 70ea63930b..c0f0eb4b27 100644 --- a/libraries/shared/src/Ragdoll.cpp +++ b/libraries/shared/src/Ragdoll.cpp @@ -20,7 +20,7 @@ #include "SharedUtil.h" // for EPSILON Ragdoll::Ragdoll() : _massScale(1.0f), _translation(0.0f), _translationInSimulationFrame(0.0f), - _accumulatedMovement(0.0f), _simulation(NULL) { + _rootIndex(0), _accumulatedMovement(0.0f), _simulation(NULL) { } Ragdoll::~Ragdoll() { @@ -35,7 +35,7 @@ void Ragdoll::stepForward(float deltaTime) { updateSimulationTransforms(_translation - _simulation->getTranslation(), _rotation); } int numPoints = _points.size(); - for (int i = 0; i < numPoints; ++i) { + for (int i = _rootIndex; i < numPoints; ++i) { _points[i].integrateForward(); } } @@ -77,7 +77,9 @@ void Ragdoll::initTransform() { } void Ragdoll::setTransform(const glm::vec3& translation, const glm::quat& rotation) { - _translation = translation; + if (translation != _translation) { + _translation = translation; + } _rotation = rotation; } @@ -95,7 +97,7 @@ void Ragdoll::updateSimulationTransforms(const glm::vec3& translation, const glm // apply the deltas to all ragdollPoints int numPoints = _points.size(); - for (int i = 0; i < numPoints; ++i) { + for (int i = _rootIndex; i < numPoints; ++i) { _points[i].move(deltaPosition, deltaRotation, _translationInSimulationFrame); } @@ -111,7 +113,7 @@ void Ragdoll::setMassScale(float scale) { if (scale != _massScale) { float rescale = scale / _massScale; int numPoints = _points.size(); - for (int i = 0; i < numPoints; ++i) { + for (int i = _rootIndex; i < numPoints; ++i) { _points[i].setMass(rescale * _points[i].getMass()); } _massScale = scale; @@ -122,10 +124,10 @@ void Ragdoll::removeRootOffset(bool accumulateMovement) { const int numPoints = _points.size(); if (numPoints > 0) { // shift all points so that the root aligns with the the ragdoll's position in the simulation - glm::vec3 offset = _translationInSimulationFrame - _points[0]._position; + glm::vec3 offset = _translationInSimulationFrame - _points[_rootIndex]._position; float offsetLength = glm::length(offset); if (offsetLength > EPSILON) { - for (int i = 0; i < numPoints; ++i) { + for (int i = _rootIndex; i < numPoints; ++i) { _points[i].shift(offset); } const float MIN_ROOT_OFFSET = 0.02f; diff --git a/libraries/shared/src/Ragdoll.h b/libraries/shared/src/Ragdoll.h index 1ffbdb29ab..5234397833 100644 --- a/libraries/shared/src/Ragdoll.h +++ b/libraries/shared/src/Ragdoll.h @@ -52,6 +52,10 @@ public: void setMassScale(float scale); float getMassScale() const { return _massScale; } + // the ragdoll's rootIndex (within a Model's joints) is not always zero so must be settable + void setRootIndex(int index) { _rootIndex = index; } + int getRootIndex() const { return _rootIndex; } + void clearConstraintsAndPoints(); virtual void initPoints() = 0; virtual void buildConstraints() = 0; @@ -66,6 +70,7 @@ protected: glm::quat _rotation; // world-frame glm::vec3 _translationInSimulationFrame; glm::quat _rotationInSimulationFrame; + int _rootIndex; QVector _points; QVector _boneConstraints; From e854656a7f32d6e3f64e1d094d0fba716a8c30a7 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 19 Aug 2014 14:20:47 -0700 Subject: [PATCH 130/206] Rather than using a boolean to indicate "colorness," use a different class. More texture bits. --- interface/src/MetavoxelSystem.cpp | 8 +- interface/src/ui/MetavoxelEditor.cpp | 36 +- interface/src/ui/MetavoxelEditor.h | 4 +- .../metavoxels/src/AttributeRegistry.cpp | 572 ++++++++++-------- libraries/metavoxels/src/AttributeRegistry.h | 92 ++- .../metavoxels/src/MetavoxelMessages.cpp | 22 +- libraries/metavoxels/src/MetavoxelMessages.h | 6 +- libraries/metavoxels/src/SharedObject.cpp | 6 +- libraries/metavoxels/src/SharedObject.h | 6 +- 9 files changed, 465 insertions(+), 287 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 433c8af23e..db19901334 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -1011,7 +1011,7 @@ int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf && info.size > _buffer->getScale()) { return DEFAULT_ORDER; } - HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue(); if (!height) { return STOP_RECURSION; } @@ -1067,7 +1067,7 @@ int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { if (colorSize == 0) { return STOP_RECURSION; } - HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); if (!color) { return STOP_RECURSION; } @@ -1149,14 +1149,14 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { return DEFAULT_ORDER; } HeightfieldBuffer* buffer = NULL; - HeightfieldDataPointer height = info.inputValues.at(0).getInlineValue(); + HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue(); if (height) { const QByteArray& heightContents = height->getContents(); int size = glm::sqrt(heightContents.size()); int extendedSize = size + HeightfieldBuffer::HEIGHT_EXTENSION; int heightContentsSize = extendedSize * extendedSize; - HeightfieldDataPointer color = info.inputValues.at(1).getInlineValue(); + HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); int colorContentsSize = 0; if (color) { const QByteArray& colorContents = color->getContents(); diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 0b1ebabe71..0a6e7e5326 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -957,14 +957,28 @@ void ImportHeightfieldTool::apply() { HeightfieldBuffer* buffer = static_cast(bufferData.data()); MetavoxelData data; data.setSize(scale); - HeightfieldDataPointer heightPointer(new HeightfieldData(buffer->getUnextendedHeight())); + + QByteArray height = buffer->getUnextendedHeight(); + HeightfieldHeightDataPointer heightPointer(new HeightfieldHeightData(height)); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer)))); - if (!buffer->getColor().isEmpty()) { - HeightfieldDataPointer colorPointer(new HeightfieldData(buffer->getUnextendedColor())); - data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( - AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); + + QByteArray color; + if (buffer->getColor().isEmpty()) { + const int WHITE_VALUE = 0xFF; + color = QByteArray(height.size() * HeightfieldData::COLOR_BYTES, WHITE_VALUE); + } else { + color = buffer->getUnextendedColor(); } + HeightfieldColorDataPointer colorPointer(new HeightfieldColorData(color)); + data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( + AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); + + QByteArray texture(height.size(), 0); + HeightfieldDataPointer texturePointer(new HeightfieldData(texture)); + data.setRoot(AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), new MetavoxelNode(AttributeValue( + AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), encodeInline(texturePointer)))); + MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit( _translation->getValue() + buffer->getTranslation() * scale, data)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); @@ -1165,20 +1179,20 @@ QVariant HeightfieldColorBrushTool::createEdit(bool alternate) { HeightfieldTextureBrushTool::HeightfieldTextureBrushTool(MetavoxelEditor* editor) : HeightfieldBrushTool(editor, "Texture Brush") { - _form->addRow("URL:", _url = new QUrlEditor(this)); - _url->setURL(QUrl()); - connect(_url, &QUrlEditor::urlChanged, this, &HeightfieldTextureBrushTool::updateTexture); + _form->addRow(_textureEditor = new SharedObjectEditor(&HeightfieldTexture::staticMetaObject, false)); + connect(_textureEditor, &SharedObjectEditor::objectChanged, this, &HeightfieldTextureBrushTool::updateTexture); } QVariant HeightfieldTextureBrushTool::createEdit(bool alternate) { if (alternate) { - return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), QUrl(), QColor())); + return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), SharedObjectPointer(), QColor())); } else { - return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), _url->getURL(), + return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), _textureEditor->getObject(), _texture ? _texture->getAverageColor() : QColor())); } } void HeightfieldTextureBrushTool::updateTexture() { - _texture = Application::getInstance()->getTextureCache()->getTexture(_url->getURL()); + HeightfieldTexture* texture = static_cast(_textureEditor->getObject().data()); + _texture = Application::getInstance()->getTextureCache()->getTexture(texture->getURL()); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 63f71c6ca4..7e37b819d7 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -26,9 +26,9 @@ class QListWidget; class QPushButton; class QScrollArea; class QSpinBox; -class QUrlEditor; class MetavoxelTool; +class SharedObjectEditor; class Vec3Editor; /// Allows editing metavoxels. @@ -380,7 +380,7 @@ private slots: private: - QUrlEditor* _url; + SharedObjectEditor* _textureEditor; QSharedPointer _texture; }; diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index b1865c258c..1ac04c9f82 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -23,6 +23,7 @@ REGISTER_META_OBJECT(QRgbAttribute) REGISTER_META_OBJECT(PackedNormalAttribute) REGISTER_META_OBJECT(SpannerQRgbAttribute) REGISTER_META_OBJECT(SpannerPackedNormalAttribute) +REGISTER_META_OBJECT(HeightfieldTexture) REGISTER_META_OBJECT(HeightfieldAttribute) REGISTER_META_OBJECT(HeightfieldColorAttribute) REGISTER_META_OBJECT(HeightfieldTextureAttribute) @@ -502,8 +503,7 @@ HeightfieldData::HeightfieldData(const QByteArray& contents) : _contents(contents) { } -HeightfieldData::HeightfieldData(Bitstream& in, int bytes, bool color) { - read(in, bytes, color); +HeightfieldData::~HeightfieldData() { } enum HeightfieldImage { NULL_HEIGHTFIELD_IMAGE, NORMAL_HEIGHTFIELD_IMAGE, DEFLATED_HEIGHTFIELD_IMAGE }; @@ -549,51 +549,48 @@ const QImage decodeHeightfieldImage(const QByteArray& data) { } } -HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color) { +HeightfieldHeightData::HeightfieldHeightData(const QByteArray& contents) : + HeightfieldData(contents) { +} + +HeightfieldHeightData::HeightfieldHeightData(Bitstream& in, int bytes) { + read(in, bytes); +} + +HeightfieldHeightData::HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& reference) { if (!reference) { - read(in, bytes, color); + read(in, bytes); return; } - QMutexLocker locker(&reference->_encodedDeltaMutex); - reference->_encodedDelta = in.readAligned(bytes); - reference->_deltaData = this; - _contents = reference->_contents; - QImage image = decodeHeightfieldImage(reference->_encodedDelta); + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + reference->setEncodedDelta(in.readAligned(bytes)); + reference->setDeltaData(HeightfieldDataPointer(this)); + _contents = reference->getContents(); + QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); if (image.isNull()) { return; } QPoint offset = image.offset(); image = image.convertToFormat(QImage::Format_RGB888); if (offset.x() == 0) { - set(image, color); + set(image); return; } int minX = offset.x() - 1; int minY = offset.y() - 1; - if (color) { - int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); - char* dest = _contents.data() + (minY * size + minX) * COLOR_BYTES; - int destStride = size * COLOR_BYTES; - int srcStride = image.width() * COLOR_BYTES; - for (int y = 0; y < image.height(); y++) { - memcpy(dest, image.constScanLine(y), srcStride); - dest += destStride; - } - } else { - int size = glm::sqrt((float)_contents.size()); - char* lineDest = _contents.data() + minY * size + minX; - for (int y = 0; y < image.height(); y++) { - const uchar* src = image.constScanLine(y); - for (char* dest = lineDest, *end = dest + image.width(); dest != end; dest++, src += COLOR_BYTES) { - *dest = *src; - } - lineDest += size; + int size = glm::sqrt((float)_contents.size()); + char* lineDest = _contents.data() + minY * size + minX; + for (int y = 0; y < image.height(); y++) { + const uchar* src = image.constScanLine(y); + for (char* dest = lineDest, *end = dest + image.width(); dest != end; dest++, src += COLOR_BYTES) { + *dest = *src; } + lineDest += size; } } -HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& ancestor, - const glm::vec3& minimum, float size, bool color) { +HeightfieldHeightData::HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size) { QMutexLocker locker(&_encodedSubdivisionsMutex); int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; if (_encodedSubdivisions.size() <= index) { @@ -610,61 +607,35 @@ HeightfieldData::HeightfieldData(Bitstream& in, int bytes, const HeightfieldData int destSize = image.width(); const uchar* src = image.constBits(); const QByteArray& ancestorContents = ancestor->getContents(); - if (color) { - int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); - float ancestorY = minimum.z * ancestorSize; - float ancestorIncrement = size * ancestorSize / destSize; - int ancestorStride = ancestorSize * COLOR_BYTES; - - _contents = QByteArray(destSize * destSize * COLOR_BYTES, 0); - char* dest = _contents.data(); - int stride = image.width() * COLOR_BYTES; - - for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { - const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; - float ancestorX = minimum.x * ancestorSize; - for (char* end = dest + stride; dest != end; ancestorX += ancestorIncrement) { - const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; - *dest++ = *ref++ + *src++; - *dest++ = *ref++ + *src++; - *dest++ = *ref++ + *src++; - } - } - } else { - int ancestorSize = glm::sqrt((float)ancestorContents.size()); - float ancestorY = minimum.z * ancestorSize; - float ancestorIncrement = size * ancestorSize / destSize; - - _contents = QByteArray(destSize * destSize, 0); - char* dest = _contents.data(); - - for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { - const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; - float ancestorX = minimum.x * ancestorSize; - for (char* end = dest + destSize; dest != end; src += COLOR_BYTES, ancestorX += ancestorIncrement) { - const uchar* ref = lineRef + (int)ancestorX; - *dest++ = *ref++ + *src; - } + + int ancestorSize = glm::sqrt((float)ancestorContents.size()); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + + _contents = QByteArray(destSize * destSize, 0); + char* dest = _contents.data(); + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; + float ancestorX = minimum.x * ancestorSize; + for (char* end = dest + destSize; dest != end; src += COLOR_BYTES, ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX; + *dest++ = *ref++ + *src; } } } -void HeightfieldData::write(Bitstream& out, bool color) { +void HeightfieldHeightData::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { QImage image; - if (color) { - int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); - image = QImage((uchar*)_contents.data(), size, size, QImage::Format_RGB888); - } else { - int size = glm::sqrt((float)_contents.size()); - image = QImage(size, size, QImage::Format_RGB888); - uchar* dest = image.bits(); - for (const char* src = _contents.constData(), *end = src + _contents.size(); src != end; src++) { - *dest++ = *src; - *dest++ = *src; - *dest++ = *src; - } + int size = glm::sqrt((float)_contents.size()); + image = QImage(size, size, QImage::Format_RGB888); + uchar* dest = image.bits(); + for (const char* src = _contents.constData(), *end = src + _contents.size(); src != end; src++) { + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; } _encoded = encodeHeightfieldImage(image); } @@ -672,95 +643,58 @@ void HeightfieldData::write(Bitstream& out, bool color) { out.writeAligned(_encoded); } -void HeightfieldData::writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color) { +void HeightfieldHeightData::writeDelta(Bitstream& out, const HeightfieldHeightDataPointer& reference) { if (!reference || reference->getContents().size() != _contents.size()) { - write(out, color); + write(out); return; } - QMutexLocker locker(&reference->_encodedDeltaMutex); - if (reference->_encodedDelta.isEmpty() || reference->_deltaData != this) { + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { QImage image; - int minX, minY; - if (color) { - int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); - minX = size; - minY = size; - int maxX = -1, maxY = -1; - const char* src = _contents.constData(); - const char* ref = reference->_contents.constData(); - for (int y = 0; y < size; y++) { - bool difference = false; - for (int x = 0; x < size; x++, src += COLOR_BYTES, ref += COLOR_BYTES) { - if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) { - minX = qMin(minX, x); - maxX = qMax(maxX, x); - difference = true; - } - } - if (difference) { - minY = qMin(minY, y); - maxY = qMax(maxY, y); + int size = glm::sqrt((float)_contents.size()); + int minX = size, minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->getContents().constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++) { + if (*src++ != *ref++) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; } } - if (maxX >= minX) { - int width = maxX - minX + 1; - int height = maxY - minY + 1; - image = QImage(width, height, QImage::Format_RGB888); - src = _contents.constData() + (minY * size + minX) * COLOR_BYTES; - int srcStride = size * COLOR_BYTES; - int destStride = width * COLOR_BYTES; - for (int y = 0; y < height; y++) { - memcpy(image.scanLine(y), src, destStride); - src += srcStride; - } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); } - } else { - int size = glm::sqrt((float)_contents.size()); - minX = size; - minY = size; - int maxX = -1, maxY = -1; - const char* src = _contents.constData(); - const char* ref = reference->_contents.constData(); - for (int y = 0; y < size; y++) { - bool difference = false; - for (int x = 0; x < size; x++) { - if (*src++ != *ref++) { - minX = qMin(minX, x); - maxX = qMax(maxX, x); - difference = true; - } - } - if (difference) { - minY = qMin(minY, y); - maxY = qMax(maxY, y); - } - } - if (maxX >= minX) { - int width = qMax(maxX - minX + 1, 0); - int height = qMax(maxY - minY + 1, 0); - image = QImage(width, height, QImage::Format_RGB888); - const uchar* lineSrc = (const uchar*)_contents.constData() + minY * size + minX; - for (int y = 0; y < height; y++) { - uchar* dest = image.scanLine(y); - for (const uchar* src = lineSrc, *end = src + width; src != end; src++) { - *dest++ = *src; - *dest++ = *src; - *dest++ = *src; - } - lineSrc += size; + } + if (maxX >= minX) { + int width = qMax(maxX - minX + 1, 0); + int height = qMax(maxY - minY + 1, 0); + image = QImage(width, height, QImage::Format_RGB888); + const uchar* lineSrc = (const uchar*)_contents.constData() + minY * size + minX; + for (int y = 0; y < height; y++) { + uchar* dest = image.scanLine(y); + for (const uchar* src = lineSrc, *end = src + width; src != end; src++) { + *dest++ = *src; + *dest++ = *src; + *dest++ = *src; } + lineSrc += size; } } image.setOffset(QPoint(minX + 1, minY + 1)); - reference->_encodedDelta = encodeHeightfieldImage(image); - reference->_deltaData = this; + reference->setEncodedDelta(encodeHeightfieldImage(image)); + reference->setDeltaData(HeightfieldDataPointer(this)); } - out << reference->_encodedDelta.size(); - out.writeAligned(reference->_encodedDelta); + out << reference->getEncodedDelta().size(); + out.writeAligned(reference->getEncodedDelta()); } -void HeightfieldData::writeSubdivided(Bitstream& out, const HeightfieldDataPointer& ancestor, - const glm::vec3& minimum, float size, bool color) { +void HeightfieldHeightData::writeSubdivided(Bitstream& out, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size) { QMutexLocker locker(&_encodedSubdivisionsMutex); int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; if (_encodedSubdivisions.size() <= index) { @@ -771,48 +705,26 @@ void HeightfieldData::writeSubdivided(Bitstream& out, const HeightfieldDataPoint QImage image; const QByteArray& ancestorContents = ancestor->getContents(); const uchar* src = (const uchar*)_contents.constData(); - if (color) { - int destSize = glm::sqrt(_contents.size() / (float)COLOR_BYTES); - image = QImage(destSize, destSize, QImage::Format_RGB888); - uchar* dest = image.bits(); - int stride = destSize * COLOR_BYTES; - - int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); - float ancestorY = minimum.z * ancestorSize; - float ancestorIncrement = size * ancestorSize / destSize; - int ancestorStride = ancestorSize * COLOR_BYTES; - - for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { - const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; - float ancestorX = minimum.x * ancestorSize; - for (const uchar* end = src + stride; src != end; ancestorX += ancestorIncrement) { - const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; - *dest++ = *src++ - *ref++; - *dest++ = *src++ - *ref++; - *dest++ = *src++ - *ref++; - } - } - } else { - int destSize = glm::sqrt((float)_contents.size()); - image = QImage(destSize, destSize, QImage::Format_RGB888); - uchar* dest = image.bits(); - - int ancestorSize = glm::sqrt((float)ancestorContents.size()); - float ancestorY = minimum.z * ancestorSize; - float ancestorIncrement = size * ancestorSize / destSize; - - for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { - const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; - float ancestorX = minimum.x * ancestorSize; - for (const uchar* end = src + destSize; src != end; ancestorX += ancestorIncrement) { - const uchar* ref = lineRef + (int)ancestorX; - uchar difference = *src++ - *ref; - *dest++ = difference; - *dest++ = difference; - *dest++ = difference; - } - } - } + + int destSize = glm::sqrt((float)_contents.size()); + image = QImage(destSize, destSize, QImage::Format_RGB888); + uchar* dest = image.bits(); + + int ancestorSize = glm::sqrt((float)ancestorContents.size()); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorSize; + float ancestorX = minimum.x * ancestorSize; + for (const uchar* end = src + destSize; src != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX; + uchar difference = *src++ - *ref; + *dest++ = difference; + *dest++ = difference; + *dest++ = difference; + } + } subdivision.data = encodeHeightfieldImage(image, true); subdivision.ancestor = ancestor; } @@ -820,27 +732,211 @@ void HeightfieldData::writeSubdivided(Bitstream& out, const HeightfieldDataPoint out.writeAligned(subdivision.data); } -void HeightfieldData::read(Bitstream& in, int bytes, bool color) { - set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888), color); +void HeightfieldHeightData::read(Bitstream& in, int bytes) { + set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888)); } -void HeightfieldData::set(const QImage& image, bool color) { - if (color) { - _contents.resize(image.width() * image.height() * COLOR_BYTES); - memcpy(_contents.data(), image.constBits(), _contents.size()); - - } else { - _contents.resize(image.width() * image.height()); - char* dest = _contents.data(); - for (const uchar* src = image.constBits(), *end = src + _contents.size() * COLOR_BYTES; - src != end; src += COLOR_BYTES) { - *dest++ = *src; +void HeightfieldHeightData::set(const QImage& image) { + _contents.resize(image.width() * image.height()); + char* dest = _contents.data(); + for (const uchar* src = image.constBits(), *end = src + _contents.size() * COLOR_BYTES; + src != end; src += COLOR_BYTES) { + *dest++ = *src; + } +} + +HeightfieldColorData::HeightfieldColorData(const QByteArray& contents) : + HeightfieldData(contents) { +} + +HeightfieldColorData::HeightfieldColorData(Bitstream& in, int bytes) { + read(in, bytes); +} + +HeightfieldColorData::HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& reference) { + if (!reference) { + read(in, bytes); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + reference->setEncodedDelta(in.readAligned(bytes)); + reference->setDeltaData(HeightfieldDataPointer(this)); + _contents = reference->getContents(); + QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); + if (image.isNull()) { + return; + } + QPoint offset = image.offset(); + image = image.convertToFormat(QImage::Format_RGB888); + if (offset.x() == 0) { + set(image); + return; + } + int minX = offset.x() - 1; + int minY = offset.y() - 1; + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + char* dest = _contents.data() + (minY * size + minX) * COLOR_BYTES; + int destStride = size * COLOR_BYTES; + int srcStride = image.width() * COLOR_BYTES; + for (int y = 0; y < image.height(); y++) { + memcpy(dest, image.constScanLine(y), srcStride); + dest += destStride; + } +} + +HeightfieldColorData::HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + subdivision.data = in.readAligned(bytes); + subdivision.ancestor = ancestor; + QImage image = decodeHeightfieldImage(subdivision.data); + if (image.isNull()) { + return; + } + image = image.convertToFormat(QImage::Format_RGB888); + int destSize = image.width(); + const uchar* src = image.constBits(); + const QByteArray& ancestorContents = ancestor->getContents(); + + int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + int ancestorStride = ancestorSize * COLOR_BYTES; + + _contents = QByteArray(destSize * destSize * COLOR_BYTES, 0); + char* dest = _contents.data(); + int stride = image.width() * COLOR_BYTES; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; + float ancestorX = minimum.x * ancestorSize; + for (char* end = dest + stride; dest != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; + *dest++ = *ref++ + *src++; + *dest++ = *ref++ + *src++; + *dest++ = *ref++ + *src++; } } } +void HeightfieldColorData::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + QImage image; + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + image = QImage((uchar*)_contents.data(), size, size, QImage::Format_RGB888); + _encoded = encodeHeightfieldImage(image); + } + out << _encoded.size(); + out.writeAligned(_encoded); +} + +void HeightfieldColorData::writeDelta(Bitstream& out, const HeightfieldColorDataPointer& reference) { + if (!reference || reference->getContents().size() != _contents.size()) { + write(out); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { + QImage image; + int size = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + int minX = size, minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->getContents().constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++, src += COLOR_BYTES, ref += COLOR_BYTES) { + if (src[0] != ref[0] || src[1] != ref[1] || src[2] != ref[2]) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + if (maxX >= minX) { + int width = maxX - minX + 1; + int height = maxY - minY + 1; + image = QImage(width, height, QImage::Format_RGB888); + src = _contents.constData() + (minY * size + minX) * COLOR_BYTES; + int srcStride = size * COLOR_BYTES; + int destStride = width * COLOR_BYTES; + for (int y = 0; y < height; y++) { + memcpy(image.scanLine(y), src, destStride); + src += srcStride; + } + } + image.setOffset(QPoint(minX + 1, minY + 1)); + reference->setEncodedDelta(encodeHeightfieldImage(image)); + reference->setDeltaData(HeightfieldDataPointer(this)); + } + out << reference->getEncodedDelta().size(); + out.writeAligned(reference->getEncodedDelta()); +} + +void HeightfieldColorData::writeSubdivided(Bitstream& out, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size) { + QMutexLocker locker(&_encodedSubdivisionsMutex); + int index = (int)glm::round(glm::log(size) / glm::log(0.5f)) - 1; + if (_encodedSubdivisions.size() <= index) { + _encodedSubdivisions.resize(index + 1); + } + EncodedSubdivision& subdivision = _encodedSubdivisions[index]; + if (subdivision.data.isEmpty() || subdivision.ancestor != ancestor) { + QImage image; + const QByteArray& ancestorContents = ancestor->getContents(); + const uchar* src = (const uchar*)_contents.constData(); + + int destSize = glm::sqrt(_contents.size() / (float)COLOR_BYTES); + image = QImage(destSize, destSize, QImage::Format_RGB888); + uchar* dest = image.bits(); + int stride = destSize * COLOR_BYTES; + + int ancestorSize = glm::sqrt(ancestorContents.size() / (float)COLOR_BYTES); + float ancestorY = minimum.z * ancestorSize; + float ancestorIncrement = size * ancestorSize / destSize; + int ancestorStride = ancestorSize * COLOR_BYTES; + + for (int y = 0; y < destSize; y++, ancestorY += ancestorIncrement) { + const uchar* lineRef = (const uchar*)ancestorContents.constData() + (int)ancestorY * ancestorStride; + float ancestorX = minimum.x * ancestorSize; + for (const uchar* end = src + stride; src != end; ancestorX += ancestorIncrement) { + const uchar* ref = lineRef + (int)ancestorX * COLOR_BYTES; + *dest++ = *src++ - *ref++; + *dest++ = *src++ - *ref++; + *dest++ = *src++ - *ref++; + } + } + subdivision.data = encodeHeightfieldImage(image, true); + subdivision.ancestor = ancestor; + } + out << subdivision.data.size(); + out.writeAligned(subdivision.data); +} + +void HeightfieldColorData::read(Bitstream& in, int bytes) { + set(decodeHeightfieldImage(_encoded = in.readAligned(bytes)).convertToFormat(QImage::Format_RGB888)); +} + +void HeightfieldColorData::set(const QImage& image) { + _contents.resize(image.width() * image.height() * COLOR_BYTES); + memcpy(_contents.data(), image.constBits(), _contents.size()); +} + +HeightfieldTexture::HeightfieldTexture() { +} + HeightfieldAttribute::HeightfieldAttribute(const QString& name) : - InlineAttribute(name) { + InlineAttribute(name) { } void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { @@ -850,9 +946,9 @@ void HeightfieldAttribute::read(Bitstream& in, void*& value, bool isLeaf) const int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, false)); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(new HeightfieldHeightData(in, size)); } } @@ -860,9 +956,9 @@ void HeightfieldAttribute::write(Bitstream& out, void* value, bool isLeaf) const if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldHeightDataPointer data = decodeInline(value); if (data) { - data->write(out, false); + data->write(out); } else { out << 0; } @@ -875,10 +971,10 @@ void HeightfieldAttribute::readDelta(Bitstream& in, void*& value, void* referenc int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( - in, size, decodeInline(reference), false)); + *(HeightfieldHeightDataPointer*)&value = HeightfieldHeightDataPointer(new HeightfieldHeightData( + in, size, decodeInline(reference))); } } @@ -886,9 +982,9 @@ void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* referen if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldHeightDataPointer data = decodeInline(value); if (data) { - data->writeDelta(out, decodeInline(reference), false); + data->writeDelta(out, decodeInline(reference)); } else { out << 0; } @@ -897,20 +993,20 @@ void HeightfieldAttribute::writeDelta(Bitstream& out, void* value, void* referen bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) const { int maxSize = 0; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer pointer = decodeInline(children[i]); + HeightfieldHeightDataPointer pointer = decodeInline(children[i]); if (pointer) { maxSize = qMax(maxSize, pointer->getContents().size()); } } if (maxSize == 0) { - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + *(HeightfieldHeightDataPointer*)&parent = HeightfieldHeightDataPointer(); return true; } int size = glm::sqrt((float)maxSize); QByteArray contents(size * size, 0); int halfSize = size / 2; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer child = decodeInline(children[i]); + HeightfieldHeightDataPointer child = decodeInline(children[i]); if (!child) { continue; } @@ -920,7 +1016,7 @@ bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) int xIndex = i & INDEX_MASK; const int Y_SHIFT = 1; int yIndex = (i >> Y_SHIFT) & INDEX_MASK; - if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { + if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { continue; // bottom is overriden by top } const int HALF_RANGE = 128; @@ -959,12 +1055,12 @@ bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) } } } - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(new HeightfieldData(contents)); + *(HeightfieldHeightDataPointer*)&parent = HeightfieldHeightDataPointer(new HeightfieldHeightData(contents)); return false; } HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) : - InlineAttribute(name) { + InlineAttribute(name) { } void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { @@ -974,9 +1070,9 @@ void HeightfieldColorAttribute::read(Bitstream& in, void*& value, bool isLeaf) c int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData(in, size, true)); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(new HeightfieldColorData(in, size)); } } @@ -984,9 +1080,9 @@ void HeightfieldColorAttribute::write(Bitstream& out, void* value, bool isLeaf) if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldColorDataPointer data = decodeInline(value); if (data) { - data->write(out, true); + data->write(out); } else { out << 0; } @@ -999,10 +1095,10 @@ void HeightfieldColorAttribute::readDelta(Bitstream& in, void*& value, void* ref int size; in >> size; if (size == 0) { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(); } else { - *(HeightfieldDataPointer*)&value = HeightfieldDataPointer(new HeightfieldData( - in, size, decodeInline(reference), true)); + *(HeightfieldColorDataPointer*)&value = HeightfieldColorDataPointer(new HeightfieldColorData( + in, size, decodeInline(reference))); } } @@ -1010,9 +1106,9 @@ void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* re if (!isLeaf) { return; } - HeightfieldDataPointer data = decodeInline(value); + HeightfieldColorDataPointer data = decodeInline(value); if (data) { - data->writeDelta(out, decodeInline(reference), true); + data->writeDelta(out, decodeInline(reference)); } else { out << 0; } @@ -1021,20 +1117,20 @@ void HeightfieldColorAttribute::writeDelta(Bitstream& out, void* value, void* re bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool postRead) const { int maxSize = 0; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer pointer = decodeInline(children[i]); + HeightfieldColorDataPointer pointer = decodeInline(children[i]); if (pointer) { maxSize = qMax(maxSize, pointer->getContents().size()); } } if (maxSize == 0) { - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + *(HeightfieldColorDataPointer*)&parent = HeightfieldColorDataPointer(); return true; } int size = glm::sqrt(maxSize / (float)HeightfieldData::COLOR_BYTES); QByteArray contents(size * size * HeightfieldData::COLOR_BYTES, 0); int halfSize = size / 2; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer child = decodeInline(children[i]); + HeightfieldColorDataPointer child = decodeInline(children[i]); if (!child) { continue; } @@ -1044,7 +1140,7 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post int xIndex = i & INDEX_MASK; const int Y_SHIFT = 1; int yIndex = (i >> Y_SHIFT) & INDEX_MASK; - if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { + if (yIndex == 0 && decodeInline(children[i | (1 << Y_SHIFT)])) { continue; // bottom is overriden by top } int Z_SHIFT = 2; @@ -1098,7 +1194,7 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post } } } - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(new HeightfieldData(contents)); + *(HeightfieldColorDataPointer*)&parent = HeightfieldColorDataPointer(new HeightfieldColorData(contents)); return false; } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 85bc5b418c..5daff8bdc7 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "Bitstream.h" @@ -28,7 +29,9 @@ class QScriptEngine; class QScriptValue; class Attribute; +class HeightfieldColorData; class HeightfieldData; +class HeightfieldHeightData; class MetavoxelData; class MetavoxelLOD; class MetavoxelNode; @@ -439,23 +442,20 @@ public: static const int COLOR_BYTES = 3; - HeightfieldData(const QByteArray& contents); - HeightfieldData(Bitstream& in, int bytes, bool color); - HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& reference, bool color); - HeightfieldData(Bitstream& in, int bytes, const HeightfieldDataPointer& ancestor, - const glm::vec3& minimum, float size, bool color); + HeightfieldData(const QByteArray& contents = QByteArray()); + virtual ~HeightfieldData(); const QByteArray& getContents() const { return _contents; } - void write(Bitstream& out, bool color); - void writeDelta(Bitstream& out, const HeightfieldDataPointer& reference, bool color); - void writeSubdivided(Bitstream& out, const HeightfieldDataPointer& ancestor, - const glm::vec3& minimum, float size, bool color); - -private: + void setDeltaData(const HeightfieldDataPointer& deltaData) { _deltaData = deltaData; } + const HeightfieldDataPointer& getDeltaData() const { return _deltaData; } - void read(Bitstream& in, int bytes, bool color); - void set(const QImage& image, bool color); + void setEncodedDelta(const QByteArray& encodedDelta) { _encodedDelta = encodedDelta; } + const QByteArray& getEncodedDelta() const { return _encodedDelta; } + + QMutex& getEncodedDeltaMutex() { return _encodedDeltaMutex; } + +protected: QByteArray _contents; QByteArray _encoded; @@ -474,8 +474,70 @@ private: QMutex _encodedSubdivisionsMutex; }; +typedef QExplicitlySharedDataPointer HeightfieldHeightDataPointer; + +/// Contains a block of heightfield height data. +class HeightfieldHeightData : public HeightfieldData { +public: + + HeightfieldHeightData(const QByteArray& contents); + HeightfieldHeightData(Bitstream& in, int bytes); + HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& reference); + HeightfieldHeightData(Bitstream& in, int bytes, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size); + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldHeightDataPointer& reference); + void writeSubdivided(Bitstream& out, const HeightfieldHeightDataPointer& ancestor, + const glm::vec3& minimum, float size); + +private: + + void read(Bitstream& in, int bytes); + void set(const QImage& image); +}; + +typedef QExplicitlySharedDataPointer HeightfieldColorDataPointer; + +/// Contains a block of heightfield color data. +class HeightfieldColorData : public HeightfieldData { +public: + + HeightfieldColorData(const QByteArray& contents); + HeightfieldColorData(Bitstream& in, int bytes); + HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& reference); + HeightfieldColorData(Bitstream& in, int bytes, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size); + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldColorDataPointer& reference); + void writeSubdivided(Bitstream& out, const HeightfieldColorDataPointer& ancestor, + const glm::vec3& minimum, float size); + +private: + + void read(Bitstream& in, int bytes); + void set(const QImage& image); +}; + +/// Contains the description of a heightfield texture. +class HeightfieldTexture : public SharedObject { + Q_OBJECT + Q_PROPERTY(QUrl url MEMBER _url) + +public: + + Q_INVOKABLE HeightfieldTexture(); + + const QUrl& getURL() const { return _url; } + +private: + + QUrl _url; +}; + /// An attribute that stores heightfield data. -class HeightfieldAttribute : public InlineAttribute { +class HeightfieldAttribute : public InlineAttribute { Q_OBJECT public: @@ -492,7 +554,7 @@ public: }; /// An attribute that stores heightfield colors. -class HeightfieldColorAttribute : public InlineAttribute { +class HeightfieldColorAttribute : public InlineAttribute { Q_OBJECT public: diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 34bd6707fd..9ce6e4a6b8 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -354,7 +354,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf) { return DEFAULT_ORDER; } - HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); + HeightfieldHeightDataPointer pointer = info.inputValues.at(0).getInlineValue(); if (!pointer) { return STOP_RECURSION; } @@ -396,8 +396,8 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { lineDest += size; } if (changed) { - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + HeightfieldHeightDataPointer newPointer(new HeightfieldHeightData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); } return STOP_RECURSION; } @@ -442,7 +442,7 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf) { return DEFAULT_ORDER; } - HeightfieldDataPointer pointer = info.inputValues.at(0).getInlineValue(); + HeightfieldColorDataPointer pointer = info.inputValues.at(0).getInlineValue(); if (!pointer) { return STOP_RECURSION; } @@ -481,8 +481,8 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { lineDest += stride; } if (changed) { - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + HeightfieldColorDataPointer newPointer(new HeightfieldColorData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); } return STOP_RECURSION; } @@ -493,10 +493,10 @@ void PaintHeightfieldColorEdit::apply(MetavoxelData& data, const WeakSharedObjec } PaintHeightfieldTextureEdit::PaintHeightfieldTextureEdit(const glm::vec3& position, float radius, - const QUrl& url, const QColor& averageColor) : + const SharedObjectPointer& texture, const QColor& averageColor) : position(position), radius(radius), - url(url), + texture(texture), averageColor(averageColor) { } @@ -531,7 +531,7 @@ int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf) { return DEFAULT_ORDER; } - HeightfieldDataPointer pointer = info.inputValues.at(1).getInlineValue(); + HeightfieldColorDataPointer pointer = info.inputValues.at(1).getInlineValue(); if (!pointer) { return STOP_RECURSION; } @@ -570,8 +570,8 @@ int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { lineDest += stride; } if (changed) { - HeightfieldDataPointer newPointer(new HeightfieldData(contents)); - info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline(newPointer)); + HeightfieldColorDataPointer newPointer(new HeightfieldColorData(contents)); + info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline(newPointer)); } return STOP_RECURSION; } diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 0455e6790e..3d610b10df 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -249,11 +249,11 @@ public: STREAM glm::vec3 position; STREAM float radius; - STREAM QUrl url; + STREAM SharedObjectPointer texture; STREAM QColor averageColor; - PaintHeightfieldTextureEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, const QUrl& url = QUrl(), - const QColor& averageColor = QColor()); + PaintHeightfieldTextureEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, + const SharedObjectPointer& texture = SharedObjectPointer(), const QColor& averageColor = QColor()); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index 053ef57bad..c125e1352b 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -158,7 +158,7 @@ SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, bool nulla _type->addItem("(none)"); } foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(metaObject)) { - // add add constructable subclasses + // add constructable subclasses if (metaObject->constructorCount() > 0) { _type->addItem(metaObject->className(), QVariant::fromValue(metaObject)); } @@ -226,6 +226,7 @@ void SharedObjectEditor::updateType() { const QMetaObject* metaObject = _type->itemData(_type->currentIndex()).value(); if (!metaObject) { _object.reset(); + emit objectChanged(_object); return; } QObject* newObject = metaObject->newInstance(); @@ -259,7 +260,7 @@ void SharedObjectEditor::updateType() { } } } - _object = static_cast(newObject); + emit objectChanged(_object = static_cast(newObject)); } void SharedObjectEditor::propertyChanged() { @@ -275,6 +276,7 @@ void SharedObjectEditor::propertyChanged() { QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType()); property.write(object, widget->property(valuePropertyName)); } + emit objectChanged(_object); } void SharedObjectEditor::updateProperty() { diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h index 407fc820c8..157987ed6f 100644 --- a/libraries/metavoxels/src/SharedObject.h +++ b/libraries/metavoxels/src/SharedObject.h @@ -211,7 +211,7 @@ Q_DECLARE_METATYPE(SharedObjectSet) /// Allows editing shared object instances. class SharedObjectEditor : public QWidget { Q_OBJECT - Q_PROPERTY(SharedObjectPointer object READ getObject WRITE setObject USER true) + Q_PROPERTY(SharedObjectPointer object READ getObject WRITE setObject NOTIFY objectChanged USER true) public: @@ -222,6 +222,10 @@ public: /// "Detaches" the object pointer, copying it if anyone else is holding a reference. void detachObject(); +signals: + + void objectChanged(const SharedObjectPointer& object); + public slots: void setObject(const SharedObjectPointer& object); From 3136e4df76053106c495c9d14cdb85562ec05ab4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 19 Aug 2014 15:28:15 -0700 Subject: [PATCH 131/206] More texture progress. --- interface/src/ui/MetavoxelEditor.cpp | 2 +- .../metavoxels/src/AttributeRegistry.cpp | 151 +++++++++++++++++- libraries/metavoxels/src/AttributeRegistry.h | 22 ++- .../metavoxels/src/MetavoxelMessages.cpp | 62 +++---- 4 files changed, 197 insertions(+), 40 deletions(-) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 0a6e7e5326..6251448dce 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -975,7 +975,7 @@ void ImportHeightfieldTool::apply() { AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); QByteArray texture(height.size(), 0); - HeightfieldDataPointer texturePointer(new HeightfieldData(texture)); + HeightfieldTextureDataPointer texturePointer(new HeightfieldTextureData(texture)); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), encodeInline(texturePointer)))); diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 1ac04c9f82..0e56112957 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -932,6 +932,108 @@ void HeightfieldColorData::set(const QImage& image) { memcpy(_contents.data(), image.constBits(), _contents.size()); } +HeightfieldTextureData::HeightfieldTextureData(const QByteArray& contents) : + HeightfieldData(contents) { +} + +HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes) { + read(in, bytes); +} + +HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes, const HeightfieldTextureDataPointer& reference) { + if (!reference) { + read(in, bytes); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + reference->setEncodedDelta(in.readAligned(bytes)); + reference->setDeltaData(HeightfieldDataPointer(this)); + _contents = reference->getContents(); + QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); + if (image.isNull()) { + return; + } + QPoint offset = image.offset(); + if (offset.x() == 0) { + set(image); + return; + } + int minX = offset.x() - 1; + int minY = offset.y() - 1; + int size = glm::sqrt((float)_contents.size()); + char* dest = _contents.data() + minY * size + minX; + for (int y = 0; y < image.height(); y++) { + memcpy(dest, image.constScanLine(y), image.width()); + dest += size; + } +} + +void HeightfieldTextureData::write(Bitstream& out) { + QMutexLocker locker(&_encodedMutex); + if (_encoded.isEmpty()) { + QImage image; + int size = glm::sqrt((float)_contents.size()); + image = QImage((uchar*)_contents.data(), size, size, QImage::Format_Indexed8); + _encoded = encodeHeightfieldImage(image, true); + } + out << _encoded.size(); + out.writeAligned(_encoded); +} + +void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTextureDataPointer& reference) { + if (!reference || reference->getContents().size() != _contents.size()) { + write(out); + return; + } + QMutexLocker locker(&reference->getEncodedDeltaMutex()); + if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { + QImage image; + int size = glm::sqrt((float)_contents.size()); + int minX = size, minY = size; + int maxX = -1, maxY = -1; + const char* src = _contents.constData(); + const char* ref = reference->getContents().constData(); + for (int y = 0; y < size; y++) { + bool difference = false; + for (int x = 0; x < size; x++) { + if (*src++ != *ref++) { + minX = qMin(minX, x); + maxX = qMax(maxX, x); + difference = true; + } + } + if (difference) { + minY = qMin(minY, y); + maxY = qMax(maxY, y); + } + } + if (maxX >= minX) { + int width = maxX - minX + 1; + int height = maxY - minY + 1; + image = QImage(width, height, QImage::Format_Indexed8); + src = _contents.constData() + minY * size + minX; + for (int y = 0; y < height; y++) { + memcpy(image.scanLine(y), src, width); + src += size; + } + } + image.setOffset(QPoint(minX + 1, minY + 1)); + reference->setEncodedDelta(encodeHeightfieldImage(image, true)); + reference->setDeltaData(HeightfieldDataPointer(this)); + } + out << reference->getEncodedDelta().size(); + out.writeAligned(reference->getEncodedDelta()); +} + +void HeightfieldTextureData::read(Bitstream& in, int bytes) { + set(decodeHeightfieldImage(_encoded = in.readAligned(bytes))); +} + +void HeightfieldTextureData::set(const QImage& image) { + _contents.resize(image.width() * image.height()); + memcpy(_contents.data(), image.constBits(), _contents.size()); +} + HeightfieldTexture::HeightfieldTexture() { } @@ -1199,35 +1301,70 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post } HeightfieldTextureAttribute::HeightfieldTextureAttribute(const QString& name) : - InlineAttribute(name) { + InlineAttribute(name) { } void HeightfieldTextureAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(); + } else { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(new HeightfieldTextureData(in, size)); + } } void HeightfieldTextureAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - + if (!isLeaf) { + return; + } + HeightfieldTextureDataPointer data = decodeInline(value); + if (data) { + data->write(out); + } else { + out << 0; + } } void HeightfieldTextureAttribute::readDelta(Bitstream& in, void*& value, void* reference, bool isLeaf) const { - + if (!isLeaf) { + return; + } + int size; + in >> size; + if (size == 0) { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(); + } else { + *(HeightfieldTextureDataPointer*)&value = HeightfieldTextureDataPointer(new HeightfieldTextureData( + in, size, decodeInline(reference))); + } } void HeightfieldTextureAttribute::writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const { - + if (!isLeaf) { + return; + } + HeightfieldTextureDataPointer data = decodeInline(value); + if (data) { + data->writeDelta(out, decodeInline(reference)); + } else { + out << 0; + } } bool HeightfieldTextureAttribute::merge(void*& parent, void* children[], bool postRead) const { int maxSize = 0; for (int i = 0; i < MERGE_COUNT; i++) { - HeightfieldDataPointer pointer = decodeInline(children[i]); + HeightfieldTextureDataPointer pointer = decodeInline(children[i]); if (pointer) { maxSize = qMax(maxSize, pointer->getContents().size()); } } if (maxSize == 0) { - *(HeightfieldDataPointer*)&parent = HeightfieldDataPointer(); + *(HeightfieldTextureDataPointer*)&parent = HeightfieldTextureDataPointer(); return true; } return false; diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 5daff8bdc7..cceda9aa5d 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -32,6 +32,7 @@ class Attribute; class HeightfieldColorData; class HeightfieldData; class HeightfieldHeightData; +class HeightfieldTextureData; class MetavoxelData; class MetavoxelLOD; class MetavoxelNode; @@ -520,6 +521,25 @@ private: void set(const QImage& image); }; +typedef QExplicitlySharedDataPointer HeightfieldTextureDataPointer; + +/// Contains a block of heightfield texture data. +class HeightfieldTextureData : public HeightfieldData { +public: + + HeightfieldTextureData(const QByteArray& contents); + HeightfieldTextureData(Bitstream& in, int bytes); + HeightfieldTextureData(Bitstream& in, int bytes, const HeightfieldTextureDataPointer& reference); + + void write(Bitstream& out); + void writeDelta(Bitstream& out, const HeightfieldTextureDataPointer& reference); + +private: + + void read(Bitstream& in, int bytes); + void set(const QImage& image); +}; + /// Contains the description of a heightfield texture. class HeightfieldTexture : public SharedObject { Q_OBJECT @@ -571,7 +591,7 @@ public: }; /// An attribute that stores heightfield textures. -class HeightfieldTextureAttribute : public InlineAttribute { +class HeightfieldTextureAttribute : public InlineAttribute { Q_OBJECT public: diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 9ce6e4a6b8..2af19023f1 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -435,25 +435,18 @@ PaintHeightfieldColorEditVisitor::PaintHeightfieldColorEditVisitor(const PaintHe _bounds = Box(_edit.position - extents, _edit.position + extents); } -int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { - if (!info.getBounds().intersects(_bounds)) { - return STOP_RECURSION; - } - if (!info.isLeaf) { - return DEFAULT_ORDER; - } - HeightfieldColorDataPointer pointer = info.inputValues.at(0).getInlineValue(); +static void paintColor(MetavoxelInfo& info, int index, const glm::vec3& position, float radius, const QColor& color) { + HeightfieldColorDataPointer pointer = info.inputValues.at(index).getInlineValue(); if (!pointer) { - return STOP_RECURSION; + return; } QByteArray contents(pointer->getContents()); - const int BYTES_PER_PIXEL = 3; - int size = glm::sqrt((float)contents.size() / BYTES_PER_PIXEL); + int size = glm::sqrt((float)contents.size() / HeightfieldData::COLOR_BYTES); int highest = size - 1; float heightScale = size / info.size; - glm::vec3 center = (_edit.position - info.minimum) * heightScale; - float scaledRadius = _edit.radius * heightScale; + glm::vec3 center = (position - info.minimum) * heightScale; + float scaledRadius = radius * heightScale; glm::vec3 extents(scaledRadius, scaledRadius, scaledRadius); glm::vec3 start = glm::floor(center - extents); @@ -462,14 +455,14 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { // paint all points within the radius float z = qMax(start.z, 0.0f); float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); - int stride = size * BYTES_PER_PIXEL; - char* lineDest = contents.data() + (int)z * stride + (int)startX * BYTES_PER_PIXEL; + int stride = size * HeightfieldData::COLOR_BYTES; + char* lineDest = contents.data() + (int)z * stride + (int)startX * HeightfieldData::COLOR_BYTES; float squaredRadius = scaledRadius * scaledRadius; - char red = _edit.color.red(), green = _edit.color.green(), blue = _edit.color.blue(); + char red = color.red(), green = color.green(), blue = color.blue(); bool changed = false; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { char* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest += BYTES_PER_PIXEL) { + for (float x = startX; x <= endX; x += 1.0f, dest += HeightfieldData::COLOR_BYTES) { float dx = x - center.x, dz = z - center.z; if (dx * dx + dz * dz <= squaredRadius) { dest[0] = red; @@ -482,8 +475,19 @@ int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { } if (changed) { HeightfieldColorDataPointer newPointer(new HeightfieldColorData(contents)); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); + info.outputValues[index] = AttributeValue(info.inputValues.at(index).getAttribute(), + encodeInline(newPointer)); } +} + +int PaintHeightfieldColorEditVisitor::visit(MetavoxelInfo& info) { + if (!info.getBounds().intersects(_bounds)) { + return STOP_RECURSION; + } + if (!info.isLeaf) { + return DEFAULT_ORDER; + } + paintColor(info, 0, _edit.position, _edit.radius, _edit.color); return STOP_RECURSION; } @@ -531,13 +535,12 @@ int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { if (!info.isLeaf) { return DEFAULT_ORDER; } - HeightfieldColorDataPointer pointer = info.inputValues.at(1).getInlineValue(); + HeightfieldTextureDataPointer pointer = info.inputValues.at(0).getInlineValue(); if (!pointer) { return STOP_RECURSION; } QByteArray contents(pointer->getContents()); - const int BYTES_PER_PIXEL = 3; - int size = glm::sqrt((float)contents.size() / BYTES_PER_PIXEL); + int size = glm::sqrt((float)contents.size()); int highest = size - 1; float heightScale = size / info.size; @@ -551,28 +554,25 @@ int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { // paint all points within the radius float z = qMax(start.z, 0.0f); float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); - int stride = size * BYTES_PER_PIXEL; - char* lineDest = contents.data() + (int)z * stride + (int)startX * BYTES_PER_PIXEL; + char* lineDest = contents.data() + (int)z * size + (int)startX; float squaredRadius = scaledRadius * scaledRadius; - char red = _edit.averageColor.red(), green = _edit.averageColor.green(), blue = _edit.averageColor.blue(); bool changed = false; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { char* dest = lineDest; - for (float x = startX; x <= endX; x += 1.0f, dest += BYTES_PER_PIXEL) { + for (float x = startX; x <= endX; x += 1.0f, dest++) { float dx = x - center.x, dz = z - center.z; if (dx * dx + dz * dz <= squaredRadius) { - dest[0] = red; - dest[1] = green; - dest[2] = blue; + *dest = 1; changed = true; } } - lineDest += stride; + lineDest += size; } if (changed) { - HeightfieldColorDataPointer newPointer(new HeightfieldColorData(contents)); - info.outputValues[1] = AttributeValue(_outputs.at(1), encodeInline(newPointer)); + HeightfieldTextureDataPointer newPointer(new HeightfieldTextureData(contents)); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); } + paintColor(info, 1, _edit.position, _edit.radius, _edit.averageColor); return STOP_RECURSION; } From d685e5c8b95e72d9e1cdcd85dd60023802b9d746 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 19 Aug 2014 16:05:01 -0700 Subject: [PATCH 132/206] Added roll strafe, pitch ascend, more tuning movement --- examples/hydraMove.js | 23 +++++++++++++++++------ interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/hydraMove.js b/examples/hydraMove.js index 18e9c73a39..853c18ebce 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -35,7 +35,7 @@ var wasGrabbingWithRightHand = false; var grabbingWithLeftHand = false; var wasGrabbingWithLeftHand = false; var movingWithHead = false; -var headStartPosition, headStartPitch, headStartYaw; +var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw; var EPSILON = 0.000001; var velocity = { x: 0, y: 0, z: 0}; var THRUST_MAG_UP = 100.0; @@ -246,14 +246,17 @@ function handleGrabBehavior(deltaTime) { var HEAD_MOVE_DEAD_ZONE = 0.0; var HEAD_STRAFE_DEAD_ZONE = 0.0; var HEAD_ROTATE_DEAD_ZONE = 0.0; -var HEAD_THRUST_MULTIPLIER = 10000.0; +var HEAD_THRUST_FWD_SCALE = 12000.0; +var HEAD_THRUST_STRAFE_SCALE = 1000.0; var HEAD_YAW_RATE = 2.0; var HEAD_PITCH_RATE = 1.0; +var HEAD_ROLL_THRUST_SCALE = 75.0; +var HEAD_PITCH_LIFT_THRUST = 3.0; function moveWithHead(deltaTime) { if (movingWithHead) { var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw; - var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartPitch; + var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch; var bodyLocalCurrentHeadVector = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position); bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.angleAxis(-deltaYaw, {x:0, y: 1, z:0}), bodyLocalCurrentHeadVector); @@ -261,17 +264,23 @@ function moveWithHead(deltaTime) { headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta); headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion + // Thrust based on leaning forward and side-to-side if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) { - MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_MULTIPLIER * deltaTime)); + MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_FWD_SCALE * deltaTime)); } if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) { - MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_MULTIPLIER * deltaTime)); + MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_STRAFE_SCALE * deltaTime)); } if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) { var orientation = Quat.multiply(Quat.angleAxis(deltaYaw * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation); MyAvatar.orientation = orientation; } + // Thrust Up/Down based on head pitch + MyAvatar.addThrust(Vec3.multiply({ x:0, y:1, z:0 }, (MyAvatar.getHeadFinalPitch() - headStartFinalPitch) * HEAD_PITCH_LIFT_THRUST * deltaTime)); + // For head trackers, adjust pitch by head pitch MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime; + // Thrust strafe based on roll ange + MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), -(MyAvatar.getHeadFinalRoll() - headStartRoll) * HEAD_ROLL_THRUST_SCALE * deltaTime)); } } @@ -335,7 +344,9 @@ Controller.keyPressEvent.connect(function(event) { if (event.text == "SPACE" && !movingWithHead) { movingWithHead = true; headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position); - headStartPitch = MyAvatar.getHeadDeltaPitch(); + headStartDeltaPitch = MyAvatar.getHeadDeltaPitch(); + headStartFinalPitch = MyAvatar.getHeadFinalPitch(); + headStartRoll = MyAvatar.getHeadFinalRoll(); headStartYaw = MyAvatar.getHeadFinalYaw(); } }); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b4e77e4428..5ea01efab6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -109,6 +109,7 @@ public: Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); } Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); } + Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); } Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); } Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); } From db8869a34d7ef96707ad987658ed8fb369497e9e Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 19 Aug 2014 16:42:43 -0700 Subject: [PATCH 133/206] File format implementation + threading tweaks --- interface/src/Recorder.cpp | 263 +++++++++++++++++++++++++++++++++++-- 1 file changed, 253 insertions(+), 10 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 30b14ac589..52d183332d 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -11,7 +11,9 @@ #include +#include #include +#include #include "Recorder.h" @@ -221,7 +223,7 @@ void Player::startPlaying() { _audioThread = new QThread(); _options.setPosition(_avatar->getPosition()); _options.setOrientation(_avatar->getOrientation()); - _injector.reset(new AudioInjector(_recording->getAudio(), _options), &QObject::deleteLater); + _injector.reset(new AudioInjector(_recording->getAudio(), _options)); _injector->moveToThread(_audioThread); _audioThread->start(); QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); @@ -241,9 +243,14 @@ void Player::stopPlaying() { // Cleanup audio thread _injector->stop(); + QObject::connect(_injector.data(), &AudioInjector::finished, + _injector.data(), &AudioInjector::deleteLater); + QObject::connect(_injector.data(), &AudioInjector::destroyed, + _audioThread, &QThread::quit); + QObject::connect(_audioThread, &QThread::finished, + _audioThread, &QThread::deleteLater); _injector.clear(); - _audioThread->exit(); - _audioThread->deleteLater(); + _audioThread = NULL; qDebug() << "Recorder::stopPlaying()"; } @@ -309,13 +316,249 @@ bool Player::computeCurrentFrame() { return true; } -void writeRecordingToFile(RecordingPointer recording, QString file) { - // TODO - qDebug() << "Writing recording to " << file; +void writeRecordingToFile(RecordingPointer recording, QString filename) { + qDebug() << "Writing recording to " << filename; + QElapsedTimer timer; + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)){ + return; + } + qDebug() << file.fileName(); + + + QDataStream fileStream(&file); + + fileStream << recording->_timestamps; + + RecordingFrame& baseFrame = recording->_frames[0]; + int totalLength = 0; + + // Blendshape coefficients + fileStream << baseFrame._blendshapeCoefficients; + totalLength += baseFrame._blendshapeCoefficients.size(); + + // Joint Rotations + int jointRotationSize = baseFrame._jointRotations.size(); + fileStream << jointRotationSize; + for (int i = 0; i < jointRotationSize; ++i) { + fileStream << baseFrame._jointRotations[i].x << baseFrame._jointRotations[i].y << baseFrame._jointRotations[i].z << baseFrame._jointRotations[i].w; + } + totalLength += jointRotationSize; + + // Translation + fileStream << baseFrame._translation.x << baseFrame._translation.y << baseFrame._translation.z; + totalLength += 1; + + // Rotation + fileStream << baseFrame._rotation.x << baseFrame._rotation.y << baseFrame._rotation.z << baseFrame._rotation.w; + totalLength += 1; + + // Scale + fileStream << baseFrame._scale; + totalLength += 1; + + // Head Rotation + fileStream << baseFrame._headRotation.x << baseFrame._headRotation.y << baseFrame._headRotation.z << baseFrame._headRotation.w; + totalLength += 1; + + // Lean Sideways + fileStream << baseFrame._leanSideways; + totalLength += 1; + + // Lean Forward + fileStream << baseFrame._leanForward; + totalLength += 1; + + for (int i = 1; i < recording->_timestamps.size(); ++i) { + QBitArray mask(totalLength); + int maskIndex = 0; + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + RecordingFrame& previousFrame = recording->_frames[i - 1]; + RecordingFrame& frame = recording->_frames[i]; + + // Blendshape coefficients + for (int i = 0; i < frame._blendshapeCoefficients.size(); ++i) { + if (frame._blendshapeCoefficients[i] != previousFrame._blendshapeCoefficients[i]) { + stream << frame._blendshapeCoefficients[i]; + mask.setBit(maskIndex); + } + maskIndex++; + } + + // Joint Rotations + for (int i = 0; i < frame._jointRotations.size(); ++i) { + if (frame._jointRotations[i] != previousFrame._jointRotations[i]) { + stream << frame._jointRotations[i].x << frame._jointRotations[i].y << frame._jointRotations[i].z << frame._jointRotations[i].w; + mask.setBit(maskIndex); + } + maskIndex++; + } + + // Translation + if (frame._translation != previousFrame._translation) { + stream << frame._translation.x << frame._translation.y << frame._translation.z; + mask.setBit(maskIndex); + } + maskIndex++; + + // Rotation + if (frame._rotation != previousFrame._rotation) { + stream << frame._rotation.x << frame._rotation.y << frame._rotation.z << frame._rotation.w; + mask.setBit(maskIndex); + } + maskIndex++; + + // Scale + if (frame._scale != previousFrame._scale) { + stream << frame._scale; + mask.setBit(maskIndex); + } + maskIndex++; + + // Head Rotation + if (frame._headRotation != previousFrame._headRotation) { + stream << frame._headRotation.x << frame._headRotation.y << frame._headRotation.z << frame._headRotation.w; + mask.setBit(maskIndex); + } + maskIndex++; + + // Lean Sideways + if (frame._leanSideways != previousFrame._leanSideways) { + stream << frame._leanSideways; + mask.setBit(maskIndex); + } + maskIndex++; + + // Lean Forward + if (frame._leanForward != previousFrame._leanForward) { + stream << frame._leanForward; + mask.setBit(maskIndex); + } + maskIndex++; + + fileStream << mask; + fileStream << buffer; + } + + fileStream << recording->_audio->getByteArray(); + + qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed(); } -RecordingPointer readRecordingFromFile(RecordingPointer recording, QString file) { - // TODO - qDebug() << "Reading recording from " << file; +RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filename) { + qDebug() << "Reading recording from " << filename; + if (!recording) { + recording.reset(new Recording()); + } + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)){ + return recording; + } + + QDataStream fileStream(&file); + + fileStream >> recording->_timestamps; + RecordingFrame baseFrame; + + // Blendshape coefficients + fileStream >> baseFrame._blendshapeCoefficients; + + // Joint Rotations + int jointRotationSize; + fileStream >> jointRotationSize; + baseFrame._jointRotations.resize(jointRotationSize); + for (int i = 0; i < jointRotationSize; ++i) { + fileStream >> baseFrame._jointRotations[i].x >> baseFrame._jointRotations[i].y >> baseFrame._jointRotations[i].z >> baseFrame._jointRotations[i].w; + } + + fileStream >> baseFrame._translation.x >> baseFrame._translation.y >> baseFrame._translation.z; + fileStream >> baseFrame._rotation.x >> baseFrame._rotation.y >> baseFrame._rotation.z >> baseFrame._rotation.w; + fileStream >> baseFrame._scale; + fileStream >> baseFrame._headRotation.x >> baseFrame._headRotation.y >> baseFrame._headRotation.z >> baseFrame._headRotation.w; + fileStream >> baseFrame._leanSideways; + fileStream >> baseFrame._leanForward; + + recording->_frames << baseFrame; + + for (int i = 1; i < recording->_timestamps.size(); ++i) { + QBitArray mask; + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::ReadOnly); + RecordingFrame frame; + RecordingFrame& previousFrame = recording->_frames.last(); + + fileStream >> mask; + fileStream >> buffer; + int maskIndex = 0; + + // Blendshape Coefficients + frame._blendshapeCoefficients.resize(baseFrame._blendshapeCoefficients.size()); + for (int i = 0; i < baseFrame._blendshapeCoefficients.size(); ++i) { + if (mask[maskIndex++]) { + stream >> frame._blendshapeCoefficients[i]; + } else { + frame._blendshapeCoefficients[i] = previousFrame._blendshapeCoefficients[i]; + } + } + + // Joint Rotations + frame._jointRotations.resize(baseFrame._jointRotations.size()); + for (int i = 0; i < baseFrame._jointRotations.size(); ++i) { + if (mask[maskIndex++]) { + stream >> frame._jointRotations[i].x >> frame._jointRotations[i].y >> frame._jointRotations[i].z >> frame._jointRotations[i].w; + } else { + frame._jointRotations[i] = previousFrame._jointRotations[i]; + } + } + + if (mask[maskIndex++]) { + stream >> frame._translation.x >> frame._translation.y >> frame._translation.z; + } else { + frame._translation = previousFrame._translation; + } + + if (mask[maskIndex++]) { + stream >> frame._rotation.x >> frame._rotation.y >> frame._rotation.z >> frame._rotation.w; + } else { + frame._rotation = previousFrame._rotation; + } + + if (mask[maskIndex++]) { + stream >> frame._scale; + } else { + frame._scale = previousFrame._scale; + } + + if (mask[maskIndex++]) { + stream >> frame._headRotation.x >> frame._headRotation.y >> frame._headRotation.z >> frame._headRotation.w; + } else { + frame._headRotation = previousFrame._headRotation; + } + + if (mask[maskIndex++]) { + stream >> frame._leanSideways; + } else { + frame._leanSideways = previousFrame._leanSideways; + } + + if (mask[maskIndex++]) { + stream >> frame._leanForward; + } else { + frame._leanForward = previousFrame._leanForward; + } + + recording->_frames << frame; + } + + QByteArray audioArray; + fileStream >> audioArray; + recording->addAudioPacket(audioArray); + + + qDebug() << "Read " << file.size() << " bytes"; return recording; -} \ No newline at end of file +} + + From b13604f9682d3998d1d8bb2b96c1e4fd4efbbdf6 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 19 Aug 2014 16:43:26 -0700 Subject: [PATCH 134/206] JS API for recording --- interface/src/Application.cpp | 14 ---------- interface/src/avatar/MyAvatar.cpp | 39 +++++++++++++++++++++------- interface/src/avatar/MyAvatar.h | 13 +++++++--- interface/src/renderer/Model.cpp | 6 +++++ interface/src/renderer/Model.h | 3 +++ libraries/avatars/src/AvatarData.cpp | 4 +-- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 093f5e6610..072d798569 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1051,20 +1051,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_R: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::FrustumRenderMode); - } else if (isMeta) { - if (_myAvatar->isRecording()) { - _myAvatar->stopRecording(); - } else { - _myAvatar->startRecording(); - _audio.setRecorder(_myAvatar->getRecorder()); - } - } else { - if (_myAvatar->isPlaying()) { - _myAvatar->stopPlaying(); - } else { - _myAvatar->startPlaying(); - _audio.setPlayer(_myAvatar->getPlayer()); - } } break; case Qt::Key_Percent: diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e1d8274993..d5dfc4fd50 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -507,17 +507,16 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) { return false; } } - +QString recordingFile = "recording.rec"; bool MyAvatar::isRecording() const { return _recorder && _recorder->isRecording(); } -RecorderPointer MyAvatar::startRecording() { +void MyAvatar::startRecording() { if (!_recorder) { _recorder = RecorderPointer(new Recorder(this)); } _recorder->startRecording(); - return _recorder; } void MyAvatar::stopRecording() { @@ -526,19 +525,41 @@ void MyAvatar::stopRecording() { } } +void MyAvatar::saveRecording(QString filename) { + if (_recorder) { + _recorder->saveToFile(filename); + } +} + bool MyAvatar::isPlaying() const { return _player && _player->isPlaying(); } -PlayerPointer MyAvatar::startPlaying() { +void MyAvatar::loadRecording(QString filename) { if (!_player) { _player = PlayerPointer(new Player(this)); } - if (_recorder) { - _player->loadRecording(_recorder->getRecording()); - _player->startPlaying(); + + _player->loadFromFile(filename); +} + +void MyAvatar::loadLastRecording() { + if (!_recorder) { + return; } - return _player; + if (!_player) { + _player = PlayerPointer(new Player(this)); + } + + _player->loadRecording(_recorder->getRecording()); +} + +void MyAvatar::startPlaying() { + if (!_player) { + _player = PlayerPointer(new Player(this)); + } + + _player->startPlaying(); } void MyAvatar::stopPlaying() { @@ -955,7 +976,7 @@ void MyAvatar::clearJointsData() { for (int i = 0; i < _jointData.size(); ++i) { Avatar::clearJointData(i); if (QThread::currentThread() == thread()) { - _skeletonModel.clearJointState(i); + _skeletonModel.clearJointAnimationPriority(i); } } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2c1695a499..a298e02229 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -134,6 +134,10 @@ public: /// Renders a laser pointer for UI picking void renderLaserPointers(); glm::vec3 getLaserPointerTipPosition(const PalmData* palm); + + const RecorderPointer getRecorder() const { return _recorder; } + const PlayerPointer getPlayer() const { return _player; } + public slots: void goHome(); void increaseSize(); @@ -157,14 +161,15 @@ public slots: bool setModelReferential(int id); bool setJointReferential(int id, int jointIndex); - const RecorderPointer getRecorder() const { return _recorder; } bool isRecording() const; - RecorderPointer startRecording(); + void startRecording(); void stopRecording(); + void saveRecording(QString filename); - const PlayerPointer getPlayer() const { return _player; } bool isPlaying() const; - PlayerPointer startPlaying(); + void loadRecording(QString filename); + void loadLastRecording(); + void startPlaying(); void stopPlaying(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 290f9b5c6f..c955b902c9 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -700,6 +700,12 @@ void Model::clearJointState(int index) { } } +void Model::clearJointAnimationPriority(int index) { + if (index != -1 && index < _jointStates.size()) { + _jointStates[index]._animationPriority = 0.0f; + } +} + void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 431d17bf92..66baaac90d 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -121,6 +121,9 @@ public: /// Clear the joint states void clearJointState(int index); + /// Clear the joint animation priority + void clearJointAnimationPriority(int index); + /// Sets the joint state at the specified index. void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9653999555..0e7d3228f9 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -687,7 +687,7 @@ QVector AvatarData::getJointRotations() const { if (QThread::currentThread() != thread()) { QVector result; QMetaObject::invokeMethod(const_cast(this), - "getJointRotation", Qt::BlockingQueuedConnection, + "getJointRotations", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVector, result)); return result; } @@ -702,7 +702,7 @@ void AvatarData::setJointRotations(QVector jointRotations) { if (QThread::currentThread() != thread()) { QVector result; QMetaObject::invokeMethod(const_cast(this), - "setJointRotation", Qt::BlockingQueuedConnection, + "setJointRotations", Qt::BlockingQueuedConnection, Q_ARG(QVector, jointRotations)); } for (int i = 0; i < jointRotations.size(); ++i) { From 415e58c8bd207aa795f292ba0f54067a23d4c905 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 20 Aug 2014 02:19:53 +0200 Subject: [PATCH 135/206] Frisbee script --- examples/frisbee.js | 443 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 examples/frisbee.js diff --git a/examples/frisbee.js b/examples/frisbee.js new file mode 100644 index 0000000000..e893a29309 --- /dev/null +++ b/examples/frisbee.js @@ -0,0 +1,443 @@ +// +// frisbee.js +// examples +// +// Created by Thijs Wenker on 7/5/14. +// Copyright 2014 High Fidelity, Inc. +// +// Requirements: Razer Hydra's +// +// Fun game to throw frisbee's to eachother. Hold the trigger on any of the hydra's to create or catch a frisbee. +// +// Tip: use this together with the squeezeHands.js script to make it look nicer. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +Script.include("toolBars.js"); + +const LEFT_PALM = 0; +const LEFT_TIP = 1; +const LEFT_BUTTON_FWD = 5; +const LEFT_BUTTON_3 = 3; + +const RIGHT_PALM = 2; +const RIGHT_TIP = 3; +const RIGHT_BUTTON_FWD = 11; +const RIGHT_BUTTON_3 = 9; + +const FRISBEE_RADIUS = 0.08; +const GRAVITY_STRENGTH = 0.5; + +const CATCH_RADIUS = 0.5; +const MIN_SIMULATION_SPEED = 0.15; +const THROWN_VELOCITY_SCALING = 1.5; + +const SOUNDS_ENABLED = true; +const FRISBEE_BUTTON_URL = "http://test.thoys.nl/hifi/images/frisbee/frisbee_button_by_Judas.svg"; +const FRISBEE_MODEL_SCALE = 275; +const FRISBEE_MENU = "Toys>Frisbee"; +const FRISBEE_DESIGN_MENU = "Toys>Frisbee>Design"; +const FRISBEE_ENABLED_SETTING = "Frisbee>Enabled"; +const FRISBEE_CREATENEW_SETTING = "Frisbee>CreateNew"; +const FRISBEE_DESIGN_SETTING = "Frisbee>Design"; +const FRISBEE_FORCE_MOUSE_CONTROLS_SETTING = "Frisbee>ForceMouseControls"; + +//Add your own designs in FRISBEE_DESIGNS, be sure to put frisbee in the URL if you want others to be able to catch it without having a copy of your frisbee script. +const FRISBEE_DESIGNS = [ + {"name":"Interface", "model":"http://test.thoys.nl/hifi/models/frisbee/frisbee.fbx"}, + {"name":"Pizza", "model":"http://test.thoys.nl/hifi/models/frisbee/pizza.fbx"}, + {"name":"Swirl", "model":"http://test.thoys.nl/hifi/models/frisbee/swirl.fbx"}, + {"name":"Mayan", "model":"http://test.thoys.nl/hifi/models/frisbee/mayan.fbx"}, + ]; +const FRISBEE_MENU_DESIGN_POSTFIX = " Design"; +const FRISBEE_DESIGN_RANDOM = "Random"; + +const SPIN_MULTIPLIER = 1000; +const FRISBEE_LIFETIME = 300; // 5 minutes + +var windowDimensions = Controller.getViewportDimensions(); +var toolHeight = 50; +var toolWidth = 50; +var frisbeeToggle; +var toolBar; +var frisbeeEnabled = true; +var newfrisbeeEnabled = false; +var forceMouseControls = false; +var hydrasConnected = false; +var selectedDesign = FRISBEE_DESIGN_RANDOM; + +function loadSettings() { + frisbeeEnabled = Settings.getValue(FRISBEE_ENABLED_SETTING, "true") == "true"; + newfrisbeeEnabled = Settings.getValue(FRISBEE_CREATENEW_SETTING, "false") == "true"; + forceMouseControls = Settings.getValue(FRISBEE_FORCE_MOUSE_CONTROLS_SETTING, "false") == "true"; + selectedDesign = Settings.getValue(FRISBEE_DESIGN_SETTING, "Random"); +} + +function saveSettings() { + Settings.setValue(FRISBEE_ENABLED_SETTING, frisbeeEnabled ? "true" : "false"); + Settings.setValue(FRISBEE_CREATENEW_SETTING, newfrisbeeEnabled ? "true" : "false"); + Settings.setValue(FRISBEE_FORCE_MOUSE_CONTROLS_SETTING, forceMouseControls ? "true" : "false"); + Settings.setValue(FRISBEE_DESIGN_SETTING, selectedDesign); +} + +function moveOverlays() { + var newViewPort = Controller.getViewportDimensions(); + if (typeof(toolBar) === 'undefined') { + initToolBar(); + } else if (windowDimensions.x == newViewPort.x && + windowDimensions.y == newViewPort.y) { + return; + } + + windowDimensions = newViewPort; + var toolsX = windowDimensions.x - 8 - toolBar.width; + var toolsY = (windowDimensions.y - toolBar.height) / 2 + 80; + toolBar.move(toolsX, toolsY); +} + +function frisbeeURL() { + return selectedDesign == FRISBEE_DESIGN_RANDOM ? FRISBEE_DESIGNS[Math.floor(Math.random() * FRISBEE_DESIGNS.length)].model : getFrisbee(selectedDesign).model; +} + +//This function checks if the modelURL is inside of our Designs or contains "frisbee" in it. +function validFrisbeeURL(frisbeeURL) { + for (var frisbee in FRISBEE_DESIGNS) { + if (FRISBEE_DESIGNS[frisbee].model == frisbeeURL) { + return true; + } + } + return frisbeeURL.toLowerCase().indexOf("frisbee") !== -1; +} + +function getFrisbee(frisbeeName) { + for (var frisbee in FRISBEE_DESIGNS) { + if (FRISBEE_DESIGNS[frisbee].name == frisbeeName) { + return FRISBEE_DESIGNS[frisbee]; + } + } + return undefined; +} + +function Hand(name, palm, tip, forwardButton, button3, trigger) { + this.name = name; + this.palm = palm; + this.tip = tip; + this.forwardButton = forwardButton; + this.button3 = button3; + this.trigger = trigger; + this.holdingFrisbee = false; + this.particle = false; + this.palmPosition = function() { return Controller.getSpatialControlPosition(this.palm); } + this.grabButtonPressed = function() { + return ( + Controller.isButtonPressed(this.forwardButton) || + Controller.isButtonPressed(this.button3) || + Controller.getTriggerValue(this.trigger) > 0.5 + ) + }; + this.holdPosition = function() { return this.palm == LEFT_PALM ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); }; + this.holdRotation = function() { + var q = Controller.getSpatialControlRawRotation(this.palm); + q = Quat.multiply(MyAvatar.orientation, q); + return {x: q.x, y: q.y, z: q.z, w: q.w}; + }; + this.tipVelocity = function() { return Controller.getSpatialControlVelocity(this.tip); }; +} + +function MouseControl(button) { + this.button = button; +} + +var leftHand = new Hand("LEFT", LEFT_PALM, LEFT_TIP, LEFT_BUTTON_FWD, LEFT_BUTTON_3, 0); +var rightHand = new Hand("RIGHT", RIGHT_PALM, RIGHT_TIP, RIGHT_BUTTON_FWD, RIGHT_BUTTON_3, 1); + +var leftMouseControl = new MouseControl("LEFT"); +var middleMouseControl = new MouseControl("MIDDLE"); +var rightMouseControl = new MouseControl("RIGHT"); +var mouseControls = [leftMouseControl, middleMouseControl, rightMouseControl]; +var currentMouseControl = false; + +var newSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw"); +var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw"); +var throwSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw"); + +var simulatedFrisbees = []; + +var wantDebugging = false; +function debugPrint(message) { + if (wantDebugging) { + print(message); + } +} + +function playSound(sound, position) { + if (!SOUNDS_ENABLED) { + return; + } + var options = new AudioInjectionOptions(); + options.position = position; + options.volume = 1.0; + Audio.playSound(sound, options); +} + +function cleanupFrisbees() { + simulatedFrisbees = []; + var particles = Particles.findParticles(MyAvatar.position, 1000); + for (particle in particles) { + Particles.deleteParticle(particles[particle]); + } +} + +function checkControllerSide(hand) { + // If I don't currently have a frisbee in my hand, then try to catch closest one + if (!hand.holdingFrisbee && hand.grabButtonPressed()) { + var closestParticle = Particles.findClosestParticle(hand.palmPosition(), CATCH_RADIUS); + var modelUrl = Particles.getParticleProperties(closestParticle).modelURL; + if (closestParticle.isKnownID && validFrisbeeURL(Particles.getParticleProperties(closestParticle).modelURL)) { + Particles.editParticle(closestParticle, {modelScale: 1, inHand: true, position: hand.holdPosition(), shouldDie: true}); + Particles.deleteParticle(closestParticle); + debugPrint(hand.message + " HAND- CAUGHT SOMETHING!!"); + + var properties = { + position: hand.holdPosition(), + velocity: { x: 0, y: 0, z: 0}, + gravity: { x: 0, y: 0, z: 0}, + inHand: true, + radius: FRISBEE_RADIUS, + damping: 0.999, + modelURL: modelUrl, + modelScale: FRISBEE_MODEL_SCALE, + modelRotation: hand.holdRotation(), + lifetime: FRISBEE_LIFETIME + }; + + newParticle = Particles.addParticle(properties); + + hand.holdingFrisbee = true; + hand.particle = newParticle; + + playSound(catchSound, hand.holdPosition()); + + return; // exit early + } + } + + // If '3' is pressed, and not holding a frisbee, make a new one + if (hand.grabButtonPressed() && !hand.holdingFrisbee && newfrisbeeEnabled) { + var properties = { + position: hand.holdPosition(), + velocity: { x: 0, y: 0, z: 0}, + gravity: { x: 0, y: 0, z: 0}, + inHand: true, + radius: FRISBEE_RADIUS, + damping: 0.999, + modelURL: frisbeeURL(), + modelScale: FRISBEE_MODEL_SCALE, + modelRotation: hand.holdRotation(), + lifetime: FRISBEE_LIFETIME + }; + + newParticle = Particles.addParticle(properties); + hand.holdingFrisbee = true; + hand.particle = newParticle; + + // Play a new frisbee sound + playSound(newSound, hand.holdPosition()); + + return; // exit early + } + + if (hand.holdingFrisbee) { + // If holding the frisbee keep it in the palm + if (hand.grabButtonPressed()) { + debugPrint(">>>>> " + hand.name + "-FRISBEE IN HAND, grabbing, hold and move"); + var properties = { + position: hand.holdPosition(), + modelRotation: hand.holdRotation() + }; + Particles.editParticle(hand.particle, properties); + } else { + debugPrint(">>>>> " + hand.name + "-FRISBEE IN HAND, not grabbing, THROW!!!"); + // If frisbee just released, add velocity to it! + + var properties = { + velocity: Vec3.multiply(hand.tipVelocity(), THROWN_VELOCITY_SCALING), + inHand: false, + lifetime: FRISBEE_LIFETIME, + gravity: { x: 0, y: -GRAVITY_STRENGTH, z: 0}, + modelRotation: hand.holdRotation() + }; + + Particles.editParticle(hand.particle, properties); + + simulatedFrisbees.push(hand.particle); + + hand.holdingFrisbee = false; + hand.particle = false; + + playSound(throwSound, hand.holdPosition()); + } + } +} + +function initToolBar() { + toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); + frisbeeToggle = toolBar.addTool({ + imageURL: FRISBEE_BUTTON_URL, + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, + height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + enableNewFrisbee(newfrisbeeEnabled); +} + +function hydraCheck() { + var numberOfButtons = Controller.getNumberOfButtons(); + var numberOfTriggers = Controller.getNumberOfTriggers(); + var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); + var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + hydrasConnected = (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2); + return hydrasConnected; +} + +function checkController(deltaTime) { + moveOverlays(); + if (!frisbeeEnabled) { + return; + } + // this is expected for hydras + if (hydraCheck()) { + checkControllerSide(leftHand); + checkControllerSide(rightHand); + } + if (!hydrasConnected || forceMouseControls) { + //TODO: add mouse cursor control code here. + } +} + +function controlFrisbees(deltaTime) { + var killSimulations = []; + for (frisbee in simulatedFrisbees) { + var properties = Particles.getParticleProperties(simulatedFrisbees[frisbee]); + //get the horizon length from the velocity origin in order to get speed + var speed = Vec3.length({x:properties.velocity.x, y:0, z:properties.velocity.z}); + if (speed < MIN_SIMULATION_SPEED) { + //kill the frisbee simulation when speed is low + killSimulations.push(frisbee); + continue; + } + Particles.editParticle(simulatedFrisbees[frisbee], {modelRotation: Quat.multiply(properties.modelRotation, Quat.fromPitchYawRollDegrees(0, speed * deltaTime * SPIN_MULTIPLIER, 0))}); + + } + for (var i = killSimulations.length - 1; i >= 0; i--) { + simulatedFrisbees.splice(killSimulations[i], 1); + } +} + +//catches interfering calls of hydra-cursors +function withinBounds(coords) { + return coords.x >= 0 && coords.x < windowDimensions.x && coords.y >= 0 && coords.y < windowDimensions.y; +} + +function mouseMoveEvent(event) { + //TODO: mouse controls //print(withinBounds(event)); //print("move"+event.x); +} + +function mousePressEvent(event) { + print(event.x); + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + if (frisbeeToggle == toolBar.clicked(clickedOverlay)) { + newfrisbeeEnabled = !newfrisbeeEnabled; + saveSettings(); + enableNewFrisbee(newfrisbeeEnabled); + } +} + +function enableNewFrisbee(enable) { + if (toolBar.numberOfTools() > 0) { + toolBar.tools[0].select(enable); + } +} + +function mouseReleaseEvent(event) { + //TODO: mouse controls //print(JSON.stringify(event)); +} + +function setupMenus() { + Menu.addMenu(FRISBEE_MENU); + Menu.addMenuItem({ + menuName: FRISBEE_MENU, + menuItemName: "Frisbee Enabled", + isCheckable: true, + isChecked: frisbeeEnabled + }); + Menu.addMenuItem({ + menuName: FRISBEE_MENU, + menuItemName: "Cleanup Frisbees" + }); + Menu.addMenuItem({ + menuName: FRISBEE_MENU, + menuItemName: "Force Mouse Controls", + isCheckable: true, + isChecked: forceMouseControls + }); + Menu.addMenu(FRISBEE_DESIGN_MENU); + Menu.addMenuItem({ + menuName: FRISBEE_DESIGN_MENU, + menuItemName: FRISBEE_DESIGN_RANDOM + FRISBEE_MENU_DESIGN_POSTFIX, + isCheckable: true, + isChecked: selectedDesign == FRISBEE_DESIGN_RANDOM + }); + for (frisbee in FRISBEE_DESIGNS) { + Menu.addMenuItem({ + menuName: FRISBEE_DESIGN_MENU, + menuItemName: FRISBEE_DESIGNS[frisbee].name + FRISBEE_MENU_DESIGN_POSTFIX, + isCheckable: true, + isChecked: selectedDesign == FRISBEE_DESIGNS[frisbee].name + }); + } +} + +//startup calls: +loadSettings(); +setupMenus(); +function scriptEnding() { + toolBar.cleanup(); + Menu.removeMenu(FRISBEE_MENU); +} + +function menuItemEvent(menuItem) { + if (menuItem == "Cleanup Frisbees") { + cleanupFrisbees(); + return; + } else if (menuItem == "Frisbee Enabled") { + frisbeeEnabled = Menu.isOptionChecked(menuItem); + saveSettings(); + return; + } else if (menuItem == "Force Mouse Controls") { + forceMouseControls = Menu.isOptionChecked(menuItem); + saveSettings(); + return; + } + if (menuItem.indexOf(FRISBEE_MENU_DESIGN_POSTFIX, menuItem.length - FRISBEE_MENU_DESIGN_POSTFIX.length) !== -1) { + var item_name = menuItem.substring(0, menuItem.length - FRISBEE_MENU_DESIGN_POSTFIX.length); + if (item_name == FRISBEE_DESIGN_RANDOM || getFrisbee(item_name) != undefined) { + Menu.setIsOptionChecked(selectedDesign + FRISBEE_MENU_DESIGN_POSTFIX, false); + selectedDesign = item_name; + saveSettings(); + Menu.setIsOptionChecked(selectedDesign + FRISBEE_MENU_DESIGN_POSTFIX, true); + } + } +} + +// register the call back so it fires before each data send +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); +Menu.menuItemEvent.connect(menuItemEvent); +Script.scriptEnding.connect(scriptEnding); +Script.update.connect(checkController); +Script.update.connect(controlFrisbees); \ No newline at end of file From 540d07427038c47c7a85cad08e639e130d704edc Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 19 Aug 2014 17:20:14 -0700 Subject: [PATCH 136/206] More texture bits. --- .../metavoxels/src/AttributeRegistry.cpp | 16 ++-- libraries/metavoxels/src/AttributeRegistry.h | 7 +- .../metavoxels/src/MetavoxelMessages.cpp | 74 +++++++++++++++++-- 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 0e56112957..c09848c201 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -932,8 +932,9 @@ void HeightfieldColorData::set(const QImage& image) { memcpy(_contents.data(), image.constBits(), _contents.size()); } -HeightfieldTextureData::HeightfieldTextureData(const QByteArray& contents) : - HeightfieldData(contents) { +HeightfieldTextureData::HeightfieldTextureData(const QByteArray& contents, const QVector& textures) : + HeightfieldData(contents), + _textures(textures) { } HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes) { @@ -947,6 +948,7 @@ HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes, const H } QMutexLocker locker(&reference->getEncodedDeltaMutex()); reference->setEncodedDelta(in.readAligned(bytes)); + in.readDelta(_textures, reference->getTextures()); reference->setDeltaData(HeightfieldDataPointer(this)); _contents = reference->getContents(); QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); @@ -978,6 +980,7 @@ void HeightfieldTextureData::write(Bitstream& out) { } out << _encoded.size(); out.writeAligned(_encoded); + out << _textures; } void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTextureDataPointer& reference) { @@ -1023,10 +1026,12 @@ void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTexture } out << reference->getEncodedDelta().size(); out.writeAligned(reference->getEncodedDelta()); + out.writeDelta(_textures, reference->getTextures()); } void HeightfieldTextureData::read(Bitstream& in, int bytes) { set(decodeHeightfieldImage(_encoded = in.readAligned(bytes))); + in >> _textures; } void HeightfieldTextureData::set(const QImage& image) { @@ -1363,11 +1368,8 @@ bool HeightfieldTextureAttribute::merge(void*& parent, void* children[], bool po maxSize = qMax(maxSize, pointer->getContents().size()); } } - if (maxSize == 0) { - *(HeightfieldTextureDataPointer*)&parent = HeightfieldTextureDataPointer(); - return true; - } - return false; + *(HeightfieldTextureDataPointer*)&parent = HeightfieldTextureDataPointer(); + return maxSize == 0; } SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject, diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index cceda9aa5d..f3e6dc0b97 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -527,10 +527,13 @@ typedef QExplicitlySharedDataPointer HeightfieldTextureD class HeightfieldTextureData : public HeightfieldData { public: - HeightfieldTextureData(const QByteArray& contents); + HeightfieldTextureData(const QByteArray& contents, + const QVector& textures = QVector()); HeightfieldTextureData(Bitstream& in, int bytes); HeightfieldTextureData(Bitstream& in, int bytes, const HeightfieldTextureDataPointer& reference); + const QVector& getTextures() const { return _textures; } + void write(Bitstream& out); void writeDelta(Bitstream& out, const HeightfieldTextureDataPointer& reference); @@ -538,6 +541,8 @@ private: void read(Bitstream& in, int bytes); void set(const QImage& image); + + QVector _textures; }; /// Contains the description of a heightfield texture. diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 2af19023f1..33aea9fcb1 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -347,6 +347,8 @@ PaintHeightfieldHeightEditVisitor::PaintHeightfieldHeightEditVisitor(const Paint _bounds = Box(_edit.position - extents, _edit.position + extents); } +const int EIGHT_BIT_MAXIMUM = 255; + int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { if (!info.getBounds().intersects(_bounds)) { return STOP_RECURSION; @@ -375,8 +377,7 @@ int PaintHeightfieldHeightEditVisitor::visit(MetavoxelInfo& info) { float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); uchar* lineDest = (uchar*)contents.data() + (int)z * size + (int)startX; float squaredRadius = scaledRadius * scaledRadius; - float squaredRadiusReciprocal = 1.0f / squaredRadius; - const int EIGHT_BIT_MAXIMUM = 255; + float squaredRadiusReciprocal = 1.0f / squaredRadius; float scaledHeight = _edit.height * EIGHT_BIT_MAXIMUM / info.size; bool changed = false; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { @@ -528,6 +529,16 @@ PaintHeightfieldTextureEditVisitor::PaintHeightfieldTextureEditVisitor(const Pai _bounds = Box(_edit.position - extents, _edit.position + extents); } +static QHash countIndices(const QByteArray& contents) { + QHash counts; + for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) { + if (*src != 0) { + counts[*src]++; + } + } + return counts; +} + int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { if (!info.getBounds().intersects(_bounds)) { return STOP_RECURSION; @@ -539,7 +550,47 @@ int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { if (!pointer) { return STOP_RECURSION; } + QVector textures = pointer->getTextures(); QByteArray contents(pointer->getContents()); + uchar textureIndex = 0; + if (_edit.texture && static_cast(_edit.texture.data())->getURL().isValid()) { + // first look for a matching existing texture, noting the first reusable slot + int firstEmptyIndex = -1; + for (int i = 0; i < textures.size(); i++) { + const SharedObjectPointer& texture = textures.at(i); + if (texture) { + if (texture->equals(_edit.texture.data())) { + textureIndex = i + 1; + break; + } + } else if (firstEmptyIndex == -1) { + firstEmptyIndex = i; + } + } + // if nothing found, use the first empty slot or append + if (textureIndex == 0) { + if (firstEmptyIndex != -1) { + textures[firstEmptyIndex] = _edit.texture; + textureIndex = firstEmptyIndex + 1; + + } else if (textures.size() < EIGHT_BIT_MAXIMUM) { + textures.append(_edit.texture); + textureIndex = textures.size(); + + } else { + // last resort: find the least-used texture and remove it + QHash counts = countIndices(contents); + int lowestCount = INT_MAX; + for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { + if (it.value() < lowestCount) { + textureIndex = it.key(); + lowestCount = it.value(); + } + } + contents.replace((char)textureIndex, (char)0); + } + } + } int size = glm::sqrt((float)contents.size()); int highest = size - 1; float heightScale = size / info.size; @@ -554,22 +605,33 @@ int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { // paint all points within the radius float z = qMax(start.z, 0.0f); float startX = qMax(start.x, 0.0f), endX = qMin(end.x, (float)highest); - char* lineDest = contents.data() + (int)z * size + (int)startX; + uchar* lineDest = (uchar*)contents.data() + (int)z * size + (int)startX; float squaredRadius = scaledRadius * scaledRadius; bool changed = false; + QHash counts; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { - char* dest = lineDest; + uchar* dest = lineDest; for (float x = startX; x <= endX; x += 1.0f, dest++) { float dx = x - center.x, dz = z - center.z; if (dx * dx + dz * dz <= squaredRadius) { - *dest = 1; + *dest = textureIndex; changed = true; } } lineDest += size; } if (changed) { - HeightfieldTextureDataPointer newPointer(new HeightfieldTextureData(contents)); + // clear any unused textures + QHash counts = countIndices(contents); + for (int i = 0; i < textures.size(); i++) { + if (counts.value(i + 1) == 0) { + textures[i] = SharedObjectPointer(); + } + } + while (!(textures.isEmpty() || textures.last())) { + textures.removeLast(); + } + HeightfieldTextureDataPointer newPointer(new HeightfieldTextureData(contents, textures)); info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(newPointer)); } paintColor(info, 1, _edit.position, _edit.radius, _edit.averageColor); From b028b845f4536af9fe9153f2a148e737b51b1999 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 19 Aug 2014 17:22:05 -0700 Subject: [PATCH 137/206] Fox audio recording not being triggerred from JS --- interface/src/Recorder.cpp | 2 ++ interface/src/avatar/MyAvatar.cpp | 48 +++++++++++++++++++++++++++++-- interface/src/avatar/MyAvatar.h | 4 +-- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 52d183332d..d471a021f1 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -63,6 +63,7 @@ void Recording::addFrame(int timestamp, RecordingFrame &frame) { void Recording::addAudioPacket(QByteArray byteArray) { if (!_audio) { + qDebug() << "Current thread: " << QThread::currentThread(); _audio = new Sound(byteArray); } _audio->append(byteArray); @@ -441,6 +442,7 @@ void writeRecordingToFile(RecordingPointer recording, QString filename) { fileStream << buffer; } + qDebug() << QThread::currentThread(); fileStream << recording->_audio->getByteArray(); qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d5dfc4fd50..8e15be8ebe 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -508,34 +508,65 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) { } } QString recordingFile = "recording.rec"; -bool MyAvatar::isRecording() const { +bool MyAvatar::isRecording() { + if (QThread::currentThread() != thread()) { + bool result; + QMetaObject::invokeMethod(this, "isRecording", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result)); + return result; + } return _recorder && _recorder->isRecording(); } void MyAvatar::startRecording() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startRecording", Qt::BlockingQueuedConnection); + return; + } if (!_recorder) { _recorder = RecorderPointer(new Recorder(this)); } + Application::getInstance()->getAudio()->setRecorder(_recorder); _recorder->startRecording(); } void MyAvatar::stopRecording() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopRecording", Qt::BlockingQueuedConnection); + return; + } if (_recorder) { _recorder->stopRecording(); } } void MyAvatar::saveRecording(QString filename) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "saveRecording", Qt::BlockingQueuedConnection, + Q_ARG(QString, filename)); + return; + } if (_recorder) { _recorder->saveToFile(filename); } } -bool MyAvatar::isPlaying() const { +bool MyAvatar::isPlaying() { + if (QThread::currentThread() != thread()) { + bool result; + QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result)); + return result; + } return _player && _player->isPlaying(); } void MyAvatar::loadRecording(QString filename) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, + Q_ARG(QString, filename)); + return; + } if (!_player) { _player = PlayerPointer(new Player(this)); } @@ -544,6 +575,10 @@ void MyAvatar::loadRecording(QString filename) { } void MyAvatar::loadLastRecording() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "loadLastRecording", Qt::BlockingQueuedConnection); + return; + } if (!_recorder) { return; } @@ -555,14 +590,23 @@ void MyAvatar::loadLastRecording() { } void MyAvatar::startPlaying() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); + return; + } if (!_player) { _player = PlayerPointer(new Player(this)); } + Application::getInstance()->getAudio()->setPlayer(_player); _player->startPlaying(); } void MyAvatar::stopPlaying() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopPlaying", Qt::BlockingQueuedConnection); + return; + } if (_player) { _player->stopPlaying(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a298e02229..0af105725b 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -161,12 +161,12 @@ public slots: bool setModelReferential(int id); bool setJointReferential(int id, int jointIndex); - bool isRecording() const; + bool isRecording(); void startRecording(); void stopRecording(); void saveRecording(QString filename); - bool isPlaying() const; + bool isPlaying(); void loadRecording(QString filename); void loadLastRecording(); void startPlaying(); From 1a8a2d91560a77fa133501c818944414590b647b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 19 Aug 2014 17:26:01 -0700 Subject: [PATCH 138/206] Add Avatar submenu to main menu for non-debug stuff --- interface/src/Menu.cpp | 289 ++++++++++++------------- interface/src/Menu.h | 25 +-- interface/src/avatar/Avatar.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 2 +- 4 files changed, 149 insertions(+), 169 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c21b533695..6c5126d184 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -221,15 +221,6 @@ Menu::Menu() : addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments())); addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations())); - addDisabledActionAndSeparator(editMenu, "Physics"); - QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, - avatar, SLOT(updateMotionBehaviorsFromMenu())); - addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::StandOnNearbyFloors, 0, true, - avatar, SLOT(updateMotionBehaviorsFromMenu())); - - addAvatarCollisionSubMenu(editMenu); - QMenu* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, this, SLOT(showScriptEditor())); @@ -257,6 +248,44 @@ Menu::Menu() : this, SLOT(toggleConsole())); + QMenu* avatarMenu = addMenu("Avatar"); + + QMenu* avatarSizeMenu = avatarMenu->addMenu("Size"); + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::IncreaseAvatarSize, + Qt::Key_Plus, + appInstance->getAvatar(), + SLOT(increaseSize())); + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::DecreaseAvatarSize, + Qt::Key_Minus, + appInstance->getAvatar(), + SLOT(decreaseSize())); + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::ResetAvatarSize, + Qt::Key_Equal, + appInstance->getAvatar(), + SLOT(resetSize())); + + QObject* avatar = appInstance->getAvatar(); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ChatCircling, 0, false); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, + avatar, SLOT(updateMotionBehaviorsFromMenu())); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true, + avatar, SLOT(updateMotionBehaviorsFromMenu())); + + QMenu* collisionsMenu = avatarMenu->addMenu("Collide With..."); + addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll); + addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithAvatars, + 0, true, avatar, SLOT(updateCollisionGroups())); + addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithVoxels, + 0, false, avatar, SLOT(updateCollisionGroups())); + addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithParticles, + 0, true, avatar, SLOT(updateCollisionGroups())); + addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideWithEnvironment, + 0, false, avatar, SLOT(updateCollisionGroups())); + QMenu* viewMenu = addMenu("View"); #ifdef Q_OS_MAC @@ -304,25 +333,6 @@ Menu::Menu() : Qt::CTRL | Qt::SHIFT | Qt::Key_3, false, &nodeBounds, SLOT(setShowParticleNodes(bool))); - - QMenu* avatarSizeMenu = viewMenu->addMenu("Avatar Size"); - - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::IncreaseAvatarSize, - Qt::Key_Plus, - appInstance->getAvatar(), - SLOT(increaseSize())); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::DecreaseAvatarSize, - Qt::Key_Minus, - appInstance->getAvatar(), - SLOT(decreaseSize())); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::ResetAvatarSize, - Qt::Key_Equal, - appInstance->getAvatar(), - SLOT(resetSize())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::OffAxisProjection, 0, false); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MoveWithLean, 0, false); @@ -338,111 +348,97 @@ Menu::Menu() : QMenu* developerMenu = addMenu("Developer"); - QMenu* renderOptionsMenu = developerMenu->addMenu("Rendering Options"); - - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true); + QMenu* renderOptionsMenu = developerMenu->addMenu("Render"); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Avatars, 0, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); + + QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows"); + QActionGroup* shadowGroup = new QActionGroup(shadowMenu); + shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); + shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false)); + shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false)); + shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::AvatarsReceiveShadows, 0, true)); + + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, + MenuOption::Voxels, + Qt::SHIFT | Qt::Key_V, + true, + appInstance, + SLOT(setRenderVoxels(bool))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true); addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::GlowMode, 0, appInstance->getGlowEffect(), SLOT(cycleRenderMode())); - - QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows"); - QActionGroup* shadowGroup = new QActionGroup(shadowMenu); - shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); - shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false)); - shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false)); - - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::StringHair, 0, false); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); - QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options"); - - addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, - MenuOption::Voxels, - Qt::SHIFT | Qt::Key_V, - true, - appInstance, - SLOT(setRenderVoxels(bool))); - - addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); - addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion); - addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges); - addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableAutoAdjustLOD); - - QMenu* modelOptionsMenu = developerMenu->addMenu("Model Options"); - addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::Models, 0, true); - addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelBounds, 0, false); - addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementProxy, 0, false); - addCheckableActionToQMenuAndActionHash(modelOptionsMenu, MenuOption::DisplayModelElementChildProxies, 0, false); - - QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options"); - - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AvatarsReceiveShadows, 0, true); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagdoll); - - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false); + QMenu* avatarDebugMenu = developerMenu->addMenu("Avatar"); #ifdef HAVE_FACESHIFT - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Faceshift, 0, true, appInstance->getFaceshift(), SLOT(setTCPEnabled(bool))); #endif - #ifdef HAVE_FACEPLUS - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Faceplus, 0, true, - appInstance->getFaceplus(), SLOT(updateEnabled())); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Faceplus, 0, true, + appInstance->getFaceplus(), SLOT(updateEnabled())); #endif - #ifdef HAVE_VISAGE - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, false, - appInstance->getVisage(), SLOT(updateEnabled())); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Visage, 0, false, + appInstance->getVisage(), SLOT(updateEnabled())); #endif - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::FocusIndicators, 0, false); - - QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options"); - addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true); - addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseLasers, 0, true); - - QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); - - addCheckableActionToQMenuAndActionHash(handOptionsMenu, + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSkeletonCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); + + QMenu* modelDebugMenu = developerMenu->addMenu("Models"); + addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false); + addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false); + addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false); + + QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels"); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges); + addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableAutoAdjustLOD); + + QMenu* handOptionsMenu = developerMenu->addMenu("Hands"); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, false); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); + + QMenu* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense"); + addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::FilterSixense, 0, true, appInstance->getSixenseManager(), SLOT(setFilter(bool))); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, + addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::LowVelocityFilter, 0, true, appInstance, SLOT(setLowVelocityFilter(bool))); + addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true); + addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseLasers, 0, false); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true); - addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); - - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisableNackPackets, 0, false); - addCheckableActionToQMenuAndActionHash(developerMenu, + QMenu* networkMenu = developerMenu->addMenu("Network"); + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false); + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableActivityLogger, 0, false, @@ -451,9 +447,7 @@ Menu::Menu() : addActionToQMenuAndActionHash(developerMenu, MenuOption::WalletPrivateKey, 0, this, SLOT(changePrivateKey())); - addDisabledActionAndSeparator(developerMenu, "Testing"); - - QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools"); + QMenu* timingMenu = developerMenu->addMenu("Timing and Stats"); QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false); @@ -465,8 +459,10 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::TestPing, 0, true); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer); addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, this, SLOT(runTests())); + addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings); + addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); - QMenu* frustumMenu = developerMenu->addMenu("View Frustum Debugging Tools"); + QMenu* frustumMenu = developerMenu->addMenu("View Frustum"); addCheckableActionToQMenuAndActionHash(frustumMenu, MenuOption::DisplayFrustum, Qt::SHIFT | Qt::Key_F); addActionToQMenuAndActionHash(frustumMenu, MenuOption::FrustumRenderMode, @@ -476,11 +472,7 @@ Menu::Menu() : updateFrustumRenderModeAction(); - QMenu* renderDebugMenu = developerMenu->addMenu("Render Debugging Tools"); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings); - - QMenu* audioDebugMenu = developerMenu->addMenu("Audio Debugging Tools"); + QMenu* audioDebugMenu = developerMenu->addMenu("Audio"); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, @@ -493,7 +485,7 @@ Menu::Menu() : appInstance->getAudio(), SLOT(toggleAudioFilter())); - QMenu* audioFilterMenu = audioDebugMenu->addMenu("Audio Filter Options"); + QMenu* audioFilterMenu = audioDebugMenu->addMenu("Audio Filter"); addDisabledActionAndSeparator(audioFilterMenu, "Filter Response"); { QAction *flat = addCheckableActionToQMenuAndActionHash(audioFilterMenu, MenuOption::AudioFilterFlat, @@ -557,7 +549,7 @@ Menu::Menu() : appInstance->getAudio(), SLOT(toggleScopePause())); - QMenu* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope Options"); + QMenu* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); { QAction *fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames, @@ -648,13 +640,17 @@ Menu::Menu() : appInstance->getAudio(), SLOT(toggleStatsShowInjectedStreams())); + connect(appInstance->getAudio(), SIGNAL(muteToggled()), this, SLOT(audioMuteToggled())); + + QMenu* experimentalOptionsMenu = developerMenu->addMenu("Experimental"); + addCheckableActionToQMenuAndActionHash(experimentalOptionsMenu, MenuOption::BuckyBalls, 0, false); + addCheckableActionToQMenuAndActionHash(experimentalOptionsMenu, MenuOption::StringHair, 0, false); + addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel, Qt::CTRL | Qt::SHIFT | Qt::Key_V, this, SLOT(pasteToVoxel())); - connect(appInstance->getAudio(), SIGNAL(muteToggled()), this, SLOT(audioMuteToggled())); - #ifndef Q_OS_MAC QMenu* helpMenu = addMenu("Help"); QAction* helpAction = helpMenu->addAction(MenuOption::AboutApp); @@ -1457,7 +1453,9 @@ void Menu::toggleConsole() { void Menu::audioMuteToggled() { QAction *muteAction = _actionHash.value(MenuOption::MuteAudio); - muteAction->setChecked(Application::getInstance()->getAudio()->getMuted()); + if (muteAction) { + muteAction->setChecked(Application::getInstance()->getAudio()->getMuted()); + } } void Menu::bandwidthDetailsClosed() { @@ -1629,45 +1627,31 @@ void Menu::runTests() { void Menu::updateFrustumRenderModeAction() { QAction* frustumRenderModeAction = _actionHash.value(MenuOption::FrustumRenderMode); - switch (_frustumDrawMode) { - default: - case FRUSTUM_DRAW_MODE_ALL: - frustumRenderModeAction->setText("Render Mode - All"); - break; - case FRUSTUM_DRAW_MODE_VECTORS: - frustumRenderModeAction->setText("Render Mode - Vectors"); - break; - case FRUSTUM_DRAW_MODE_PLANES: - frustumRenderModeAction->setText("Render Mode - Planes"); - break; - case FRUSTUM_DRAW_MODE_NEAR_PLANE: - frustumRenderModeAction->setText("Render Mode - Near"); - break; - case FRUSTUM_DRAW_MODE_FAR_PLANE: - frustumRenderModeAction->setText("Render Mode - Far"); - break; - case FRUSTUM_DRAW_MODE_KEYHOLE: - frustumRenderModeAction->setText("Render Mode - Keyhole"); - break; + if (frustumRenderModeAction) { + switch (_frustumDrawMode) { + default: + case FRUSTUM_DRAW_MODE_ALL: + frustumRenderModeAction->setText("Render Mode - All"); + break; + case FRUSTUM_DRAW_MODE_VECTORS: + frustumRenderModeAction->setText("Render Mode - Vectors"); + break; + case FRUSTUM_DRAW_MODE_PLANES: + frustumRenderModeAction->setText("Render Mode - Planes"); + break; + case FRUSTUM_DRAW_MODE_NEAR_PLANE: + frustumRenderModeAction->setText("Render Mode - Near"); + break; + case FRUSTUM_DRAW_MODE_FAR_PLANE: + frustumRenderModeAction->setText("Render Mode - Far"); + break; + case FRUSTUM_DRAW_MODE_KEYHOLE: + frustumRenderModeAction->setText("Render Mode - Keyhole"); + break; + } } } -void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) { - // add avatar collisions subMenu to overMenu - QMenu* subMenu = overMenu->addMenu("Collision Options"); - - Application* appInstance = Application::getInstance(); - QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment, - 0, false, avatar, SLOT(updateCollisionGroups())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars, - 0, true, avatar, SLOT(updateCollisionGroups())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels, - 0, false, avatar, SLOT(updateCollisionGroups())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles, - 0, true, avatar, SLOT(updateCollisionGroups())); -} - QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) { QList menuActions; if (menu) { @@ -1887,10 +1871,9 @@ void Menu::removeMenuItem(const QString& menu, const QString& menuitem) { }; bool Menu::menuItemExists(const QString& menu, const QString& menuitem) { - QMenu* menuObj = getMenu(menu); QAction* menuItemAction = _actionHash.value(menuitem); - if (menuObj && menuItemAction) { - return true; + if (menuItemAction) { + return (getMenu(menu) != NULL); } return false; }; diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7ef744e62e..a3b6090280 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -246,8 +246,6 @@ private: void updateFrustumRenderModeAction(); - void addAvatarCollisionSubMenu(QMenu* overMenu); - QAction* getActionFromName(const QString& menuName, QMenu* menu); QMenu* getSubMenuFromName(const QString& menuName, QMenu* menu); QMenu* getMenuParent(const QString& menuName, QString& finalMenuPart); @@ -346,8 +344,8 @@ namespace MenuOption { const QString CascadedShadows = "Cascaded"; const QString Chat = "Chat..."; const QString ChatCircling = "Chat Circling"; - const QString CollideAsRagdoll = "Collide As Ragdoll"; - const QString CollideWithAvatars = "Collide With Avatars"; + const QString CollideAsRagdoll = "Collide With Self (Ragdoll)"; + const QString CollideWithAvatars = "Collide With Other Avatars"; const QString CollideWithEnvironment = "Collide With World Boundaries"; const QString CollideWithParticles = "Collide With Particles"; const QString CollideWithVoxels = "Collide With Voxels"; @@ -359,8 +357,8 @@ namespace MenuOption { const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; const QString DisableNackPackets = "Disable NACK Packets"; const QString DisplayFrustum = "Display Frustum"; - const QString DisplayHands = "Display Hands"; - const QString DisplayHandTargets = "Display Hand Targets"; + const QString DisplayHands = "Show Hand Info"; + const QString DisplayHandTargets = "Show Hand Targets"; const QString DisplayModelBounds = "Display Model Bounds"; const QString DisplayModelElementChildProxies = "Display Model Element Children"; const QString DisplayModelElementProxy = "Display Model Element Bounds"; @@ -380,7 +378,6 @@ namespace MenuOption { const QString Faceshift = "Faceshift"; const QString FilterSixense = "Smooth Sixense Movement"; const QString FirstPerson = "First Person"; - const QString FocusIndicators = "Focus Indicators"; const QString FrameTimer = "Show Timer"; const QString FrustumRenderMode = "Render Mode"; const QString Fullscreen = "Fullscreen"; @@ -391,7 +388,6 @@ namespace MenuOption { const QString GoToDomain = "Go To Domain..."; const QString GoTo = "Go To..."; const QString GoToLocation = "Go To Location..."; - const QString HandsCollideWithSelf = "Collide With Self"; const QString HeadMouse = "Head Mouse"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IncreaseVoxelSize = "Increase Voxel Size"; @@ -401,7 +397,6 @@ namespace MenuOption { const QString Login = "Login"; const QString Log = "Log"; const QString Logout = "Logout"; - const QString LookAtVectors = "Look-at Vectors"; const QString LowVelocityFilter = "Low Velocity Filter"; const QString MetavoxelEditor = "Metavoxel Editor..."; const QString Metavoxels = "Metavoxels"; @@ -421,13 +416,15 @@ namespace MenuOption { const QString Pair = "Pair"; const QString Particles = "Particles"; const QString PasteToVoxel = "Paste to Voxel..."; - const QString PipelineWarnings = "Show Render Pipeline Warnings"; + const QString PipelineWarnings = "Log Render Pipeline Warnings"; const QString Preferences = "Preferences..."; const QString Quit = "Quit"; const QString ReloadAllScripts = "Reload All Scripts"; - const QString RenderBoundingCollisionShapes = "Bounding Collision Shapes"; - const QString RenderHeadCollisionShapes = "Head Collision Shapes"; - const QString RenderSkeletonCollisionShapes = "Skeleton Collision Shapes"; + const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; + const QString RenderFocusIndicator = "Show Eye Focus"; + const QString RenderHeadCollisionShapes = "Show Head Collision Shapes"; + const QString RenderLookAtVectors = "Show Look-at Vectors"; + const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString RunningScripts = "Running Scripts"; const QString RunTimingTests = "Run Timing Tests"; @@ -459,7 +456,7 @@ namespace MenuOption { const QString VoxelMode = "Cycle Voxel Mode"; const QString Voxels = "Voxels"; const QString VoxelTextures = "Voxel Textures"; - const QString WalletPrivateKey = "Wallet Private Key"; + const QString WalletPrivateKey = "Wallet Private Key..."; } void sendFakeEnterEvent(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 41912afd09..3f38807346 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -349,7 +349,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { } // If this is the avatar being looked at, render a little ball above their head - if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::FocusIndicators)) { + if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) { const float LOOK_AT_INDICATOR_RADIUS = 0.03f; const float LOOK_AT_INDICATOR_OFFSET = 0.22f; const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.75f }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5cc8812b40..0a9cbfe762 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -82,7 +82,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { void AvatarManager::renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::renderAvatars()"); - bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors); + bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderLookAtVectors); glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); From 8c4f0968d8e41585aa75a2653d2661b3cfa612b1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 19 Aug 2014 17:33:06 -0700 Subject: [PATCH 139/206] remove unused JointState::setRotation() method --- interface/src/renderer/JointState.cpp | 6 +----- interface/src/renderer/JointState.h | 3 --- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp index 316dfeb9ca..94b4b37a3c 100644 --- a/interface/src/renderer/JointState.cpp +++ b/interface/src/renderer/JointState.cpp @@ -173,10 +173,6 @@ void JointState::clearTransformTranslation() { _visibleTransform[3][2] = 0.0f; } -void JointState::setRotation(const glm::quat& rotation, bool constrain, float priority) { - applyRotationDelta(rotation * glm::inverse(getRotation()), true, priority); -} - void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) { // NOTE: delta is in model-frame assert(_fbxJoint != NULL); @@ -267,4 +263,4 @@ void JointState::slaveVisibleTransform() { _visibleTransform = _transform; _visibleRotation = getRotation(); _visibleRotationInConstrainedFrame = _rotationInConstrainedFrame; -} \ No newline at end of file +} diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h index 21961ba48c..56044125f1 100644 --- a/interface/src/renderer/JointState.h +++ b/interface/src/renderer/JointState.h @@ -60,9 +60,6 @@ public: int getParentIndex() const { return _fbxJoint->parentIndex; } - /// \param rotation rotation of joint in model-frame - void setRotation(const glm::quat& rotation, bool constrain, float priority); - /// \param delta is in the model-frame void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f); From c21d1a41e524fa9585def89068ff43dd30a22ab2 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 19 Aug 2014 18:30:14 -0700 Subject: [PATCH 140/206] Removed debug messages --- interface/src/Recorder.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index d471a021f1..52d183332d 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -63,7 +63,6 @@ void Recording::addFrame(int timestamp, RecordingFrame &frame) { void Recording::addAudioPacket(QByteArray byteArray) { if (!_audio) { - qDebug() << "Current thread: " << QThread::currentThread(); _audio = new Sound(byteArray); } _audio->append(byteArray); @@ -442,7 +441,6 @@ void writeRecordingToFile(RecordingPointer recording, QString filename) { fileStream << buffer; } - qDebug() << QThread::currentThread(); fileStream << recording->_audio->getByteArray(); qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed(); From 7cf7f188f8a2cf33243dec774af034931098f11e Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 19 Aug 2014 18:55:18 -0700 Subject: [PATCH 141/206] Yet more texture bits. --- interface/src/MetavoxelSystem.cpp | 99 ++++++++++++++++++++++++---- interface/src/MetavoxelSystem.h | 19 +++++- interface/src/ui/MetavoxelEditor.cpp | 3 +- 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index db19901334..4cdd2498e6 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -449,9 +449,10 @@ void MetavoxelSystem::renderHeightfieldCursor(const glm::vec3& position, float r glDepthFunc(GL_LESS); } -void MetavoxelSystem::deleteTextures(int heightID, int colorID) { +void MetavoxelSystem::deleteTextures(int heightID, int colorID, int textureID) { glDeleteTextures(1, (GLuint*)&heightID); glDeleteTextures(1, (GLuint*)&colorID); + glDeleteTextures(1, (GLuint*)&textureID); } MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) { @@ -601,19 +602,24 @@ const int HeightfieldBuffer::SHARED_EDGE = 1; const int HeightfieldBuffer::HEIGHT_EXTENSION = 2 * HeightfieldBuffer::HEIGHT_BORDER + HeightfieldBuffer::SHARED_EDGE; HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, - const QByteArray& height, const QByteArray& color) : + const QByteArray& height, const QByteArray& color, const QByteArray& texture) : _translation(translation), _scale(scale), _heightBounds(translation, translation + glm::vec3(scale, scale, scale)), _colorBounds(_heightBounds), + _textureBounds(_heightBounds), _height(height), _color(color), + _texture(texture), _heightTextureID(0), _colorTextureID(0), + _textureTextureID(0), _heightSize(glm::sqrt(height.size())), _heightIncrement(scale / (_heightSize - HEIGHT_EXTENSION)), _colorSize(glm::sqrt(color.size() / HeightfieldData::COLOR_BYTES)), - _colorIncrement(scale / (_colorSize - SHARED_EDGE)) { + _colorIncrement(scale / (_colorSize - SHARED_EDGE)), + _textureSize(glm::sqrt(texture.size())), + _textureIncrement(scale / (_textureSize - SHARED_EDGE)) { _heightBounds.minimum.x -= _heightIncrement * HEIGHT_BORDER; _heightBounds.minimum.z -= _heightIncrement * HEIGHT_BORDER; @@ -622,16 +628,20 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, _colorBounds.maximum.x += _colorIncrement * SHARED_EDGE; _colorBounds.maximum.z += _colorIncrement * SHARED_EDGE; + + _textureBounds.maximum.x += _textureIncrement * SHARED_EDGE; + _textureBounds.maximum.z += _textureIncrement * SHARED_EDGE; } HeightfieldBuffer::~HeightfieldBuffer() { // the textures have to be deleted on the main thread (for its opengl context) if (QThread::currentThread() != Application::getInstance()->thread()) { QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels(), "deleteTextures", - Q_ARG(int, _heightTextureID), Q_ARG(int, _colorTextureID)); + Q_ARG(int, _heightTextureID), Q_ARG(int, _colorTextureID), Q_ARG(int, _textureTextureID)); } else { glDeleteTextures(1, &_heightTextureID); glDeleteTextures(1, &_colorTextureID); + glDeleteTextures(1, &_textureTextureID); } } @@ -692,6 +702,17 @@ void HeightfieldBuffer::render(bool cursor) { int colorSize = glm::sqrt(_color.size() / HeightfieldData::COLOR_BYTES); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, colorSize, colorSize, 0, GL_RGB, GL_UNSIGNED_BYTE, _color.constData()); } + + if (!_texture.isEmpty()) { + glGenTextures(1, &_textureTextureID); + glBindTexture(GL_TEXTURE_2D, _textureTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + int textureSize = glm::sqrt(_texture.size()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, textureSize, textureSize, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, _texture.constData()); + } } // create the buffer objects lazily int innerSize = _heightSize - 2 * HeightfieldBuffer::HEIGHT_BORDER; @@ -998,7 +1019,8 @@ private: HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << - AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector(), lod), + AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << + AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), QVector(), lod), _intersections(intersections) { } @@ -1065,11 +1087,11 @@ int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { int colorSize = _buffer->getColorSize(); if (colorSize == 0) { - return STOP_RECURSION; + continue; } HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); if (!color) { - return STOP_RECURSION; + continue; } const Box& colorBounds = _buffer->getColorBounds(); overlap = colorBounds.getIntersection(overlap); @@ -1113,6 +1135,50 @@ int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { } } } + + int textureSize = _buffer->getTextureSize(); + if (textureSize == 0) { + continue; + } + HeightfieldTextureDataPointer texture = info.inputValues.at(2).getInlineValue(); + if (!texture) { + continue; + } + const Box& textureBounds = _buffer->getTextureBounds(); + overlap = textureBounds.getIntersection(overlap); + float textureIncrement = _buffer->getTextureIncrement(); + destX = (overlap.minimum.x - textureBounds.minimum.x) / textureIncrement; + destY = (overlap.minimum.z - textureBounds.minimum.z) / textureIncrement; + destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / textureIncrement); + destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / textureIncrement); + dest = _buffer->getTexture().data() + destY * textureSize + destX; + + const QByteArray& srcTexture = texture->getContents(); + srcSize = glm::sqrt(srcTexture.size()); + srcIncrement = info.size / srcSize; + + if (srcIncrement == textureIncrement) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const char* src = srcTexture.constData() + srcY * srcSize + srcX; + for (int y = 0; y < destHeight; y++, src += srcSize, dest += textureSize) { + memcpy(dest, src, destWidth); + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = textureIncrement / srcIncrement; + for (int y = 0; y < destHeight; y++, dest += textureSize, srcY += srcAdvance) { + const char* src = srcTexture.constData() + (int)srcY * srcSize; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) { + *lineDest = src[(int)lineSrcX]; + } + } + } } return STOP_RECURSION; } @@ -1138,6 +1204,7 @@ private: HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << + AttributeRegistry::getInstance()->getHeightfieldTextureAttribute() << Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), QVector() << Application::getInstance()->getMetavoxels()->getHeightfieldBufferAttribute(), lod), regionBounds(glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX), glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX)), @@ -1165,18 +1232,28 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { colorContentsSize = extendedColorSize * extendedColorSize * HeightfieldData::COLOR_BYTES; } + HeightfieldTextureDataPointer texture = info.inputValues.at(2).getInlineValue(); + int textureContentsSize = 0; + if (texture) { + const QByteArray& textureContents = texture->getContents(); + int textureSize = glm::sqrt(textureContents.size()); + int extendedTextureSize = textureSize + HeightfieldBuffer::SHARED_EDGE; + textureContentsSize = extendedTextureSize * extendedTextureSize; + } + const HeightfieldBuffer* existingBuffer = static_cast( - info.inputValues.at(2).getInlineValue().data()); + info.inputValues.at(3).getInlineValue().data()); Box bounds = info.getBounds(); if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize && - existingBuffer->getColor().size() == colorContentsSize) { + existingBuffer->getColor().size() == colorContentsSize && + existingBuffer->getTexture().size() == textureContentsSize) { // we already have a buffer of the correct resolution addRegion(bounds, existingBuffer->getHeightBounds()); return STOP_RECURSION; } // we must create a new buffer and update its borders buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), - QByteArray(colorContentsSize, 0)); + QByteArray(colorContentsSize, 0), QByteArray(textureContentsSize, 0)); const Box& heightBounds = buffer->getHeightBounds(); addRegion(bounds, heightBounds); @@ -1249,7 +1326,7 @@ int HeightfieldUpdateVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } HeightfieldBuffer* newBuffer = new HeightfieldBuffer(info.minimum, info.size, - buffer->getHeight(), buffer->getColor()); + buffer->getHeight(), buffer->getColor(), buffer->getTexture()); _fetchVisitor.init(newBuffer); _data->guide(_fetchVisitor); info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(newBuffer))); diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 38d67bcaed..206c378fd1 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -49,7 +49,7 @@ public: Q_INVOKABLE float getHeightfieldHeight(const glm::vec3& location); - Q_INVOKABLE void deleteTextures(int heightID, int colorID); + Q_INVOKABLE void deleteTextures(int heightID, int colorID, int textureID); protected: @@ -139,7 +139,8 @@ public: static const int SHARED_EDGE; static const int HEIGHT_EXTENSION; - HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, const QByteArray& color); + HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, + const QByteArray& color, const QByteArray& texture); ~HeightfieldBuffer(); const glm::vec3& getTranslation() const { return _translation; } @@ -147,6 +148,7 @@ public: const Box& getHeightBounds() const { return _heightBounds; } const Box& getColorBounds() const { return _colorBounds; } + const Box& getTextureBounds() const { return _textureBounds; } QByteArray& getHeight() { return _height; } const QByteArray& getHeight() const { return _height; } @@ -154,6 +156,9 @@ public: QByteArray& getColor() { return _color; } const QByteArray& getColor() const { return _color; } + QByteArray& getTexture() { return _texture; } + const QByteArray& getTexture() const { return _texture; } + QByteArray getUnextendedHeight() const; QByteArray getUnextendedColor() const; @@ -163,6 +168,9 @@ public: int getColorSize() const { return _colorSize; } float getColorIncrement() const { return _colorIncrement; } + int getTextureSize() const { return _textureSize; } + float getTextureIncrement() const { return _textureIncrement; } + virtual void render(bool cursor = false); private: @@ -171,15 +179,20 @@ private: float _scale; Box _heightBounds; Box _colorBounds; + Box _textureBounds; QByteArray _height; QByteArray _color; + QByteArray _texture; GLuint _heightTextureID; GLuint _colorTextureID; + GLuint _textureTextureID; int _heightSize; float _heightIncrement; int _colorSize; float _colorIncrement; - + int _textureSize; + float _textureIncrement; + typedef QPair BufferPair; static QHash _bufferPairs; }; diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 6251448dce..2c99caf7d2 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1049,7 +1049,8 @@ void ImportHeightfieldTool::updatePreview() { columns * HeightfieldData::COLOR_BYTES); } } - buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, height, color))); + buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, + height, color, QByteArray()))); } } } From 72c60ee69ccf794dab0c3229dafb2642a8dbbba4 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 19 Aug 2014 19:01:50 -0700 Subject: [PATCH 142/206] Remove used variable --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8e15be8ebe..b3805d15c7 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -507,7 +507,7 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) { return false; } } -QString recordingFile = "recording.rec"; + bool MyAvatar::isRecording() { if (QThread::currentThread() != thread()) { bool result; From 8bc5f866668ec797ec1e0a682eecec019fcdebb4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 19 Aug 2014 19:21:53 -0700 Subject: [PATCH 143/206] Add Audio Sphere menu option --- interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/avatar/Avatar.cpp | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c21b533695..a9fac1e32c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -390,6 +390,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagdoll); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::BlueAudioMeter, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false); #ifdef HAVE_FACESHIFT diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7ef744e62e..a923babf57 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -342,6 +342,7 @@ namespace MenuOption { const QString AvatarsReceiveShadows = "Avatars Receive Shadows"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; + const QString BlueAudioMeter = "Blue Sphere While Speaking"; const QString BuckyBalls = "Bucky Balls"; const QString CascadedShadows = "Cascaded"; const QString Chat = "Chat..."; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 41912afd09..b929d5b75e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -368,7 +368,9 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { // quick check before falling into the code below: // (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m) const float MIN_VOICE_SPHERE_DISTANCE = 12.0f; - if (distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { + if (Menu::getInstance()->isOptionChecked(MenuOption::BlueAudioMeter) + && distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { + // render voice intensity sphere for avatars that are farther away const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE; const float MIN_SPHERE_ANGLE = 1.0f * RADIANS_PER_DEGREE; From 3b7260c434178de44945ef95a8745a8c8da67cee Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 19 Aug 2014 19:22:11 -0700 Subject: [PATCH 144/206] Fix blue audio sphere not showing correctly --- interface/src/avatar/Avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b929d5b75e..165b218873 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -302,7 +302,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { return; } - glm::vec3 toTarget = cameraPosition - Application::getInstance()->getAvatar()->getPosition(); + glm::vec3 toTarget = cameraPosition - getPosition(); float distanceToTarget = glm::length(toTarget); { From 42de56381a76c376913733d056b0d72dbc829e70 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 19 Aug 2014 19:40:27 -0700 Subject: [PATCH 145/206] Update blue audio sphere name and angle to draw at --- interface/src/Menu.cpp | 2 +- interface/src/Menu.h | 2 +- interface/src/avatar/Avatar.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a9fac1e32c..d535bb9930 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -390,7 +390,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagdoll); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::BlueAudioMeter, 0, true); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::BlueSpeechSphere, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false); #ifdef HAVE_FACESHIFT diff --git a/interface/src/Menu.h b/interface/src/Menu.h index a923babf57..ca062e0a75 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -342,7 +342,7 @@ namespace MenuOption { const QString AvatarsReceiveShadows = "Avatars Receive Shadows"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; - const QString BlueAudioMeter = "Blue Sphere While Speaking"; + const QString BlueSpeechSphere = "Blue Sphere While Speaking"; const QString BuckyBalls = "Bucky Balls"; const QString CascadedShadows = "Cascaded"; const QString Chat = "Chat..."; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 165b218873..f93813d56c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -368,12 +368,12 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { // quick check before falling into the code below: // (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m) const float MIN_VOICE_SPHERE_DISTANCE = 12.0f; - if (Menu::getInstance()->isOptionChecked(MenuOption::BlueAudioMeter) + if (Menu::getInstance()->isOptionChecked(MenuOption::BlueSpeechSphere) && distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { // render voice intensity sphere for avatars that are farther away const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE; - const float MIN_SPHERE_ANGLE = 1.0f * RADIANS_PER_DEGREE; + const float MIN_SPHERE_ANGLE = 0.5f * RADIANS_PER_DEGREE; const float MIN_SPHERE_SIZE = 0.01f; const float SPHERE_LOUDNESS_SCALING = 0.0005f; const float SPHERE_COLOR[] = { 0.5f, 0.8f, 0.8f }; From 3d940469de953a075d29e0630e2fca473bc07c10 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 19 Aug 2014 19:41:31 -0700 Subject: [PATCH 146/206] Increase display name drawing distance --- interface/src/avatar/Avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f93813d56c..615fda0710 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -394,7 +394,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { } } - const float DISPLAYNAME_DISTANCE = 10.0f; + const float DISPLAYNAME_DISTANCE = 20.0f; setShowDisplayName(renderMode == NORMAL_RENDER_MODE && distanceToTarget < DISPLAYNAME_DISTANCE); if (renderMode != NORMAL_RENDER_MODE || (isMyAvatar() && Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON)) { From c68ed81691deb6c3228dc09e01f41fefe62fcde7 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 19 Aug 2014 20:54:08 -0700 Subject: [PATCH 147/206] fixes per code review --- interface/src/devices/DdeFaceTracker.cpp | 10 +++++----- interface/src/devices/Faceshift.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 7b353e986d..aab3e1deb4 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -194,12 +194,12 @@ float updateAndGetCoefficient(float * coefficient, float currentValue, bool scal coefficient[AVG] = LONG_TERM_AVERAGE * coefficient[AVG] + (1.f - LONG_TERM_AVERAGE) * currentValue; if (coefficient[MAX] > coefficient[MIN]) { if (scaleToRange) { - return glm::clamp((currentValue - coefficient[AVG]) / (coefficient[MAX] - coefficient[MIN]), 0.f, 1.f); + return glm::clamp((currentValue - coefficient[AVG]) / (coefficient[MAX] - coefficient[MIN]), 0.0f, 1.0f); } else { - return glm::clamp(currentValue - coefficient[AVG], 0.f, 1.f); + return glm::clamp(currentValue - coefficient[AVG], 0.0f, 1.0f); } } else { - return 0.f; + return 0.0f; } } @@ -269,8 +269,8 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { _blendshapeCoefficients[_jawOpenIndex] = rescaleCoef(packet.expressions[21]) * JAW_OPEN_MAGNIFIER; float SMILE_MULTIPLIER = 2.0f; - _blendshapeCoefficients[_mouthSmileLeftIndex] = glm::clamp(packet.expressions[24] * SMILE_MULTIPLIER, 0.f, 1.f); - _blendshapeCoefficients[_mouthSmileRightIndex] = glm::clamp(packet.expressions[23] * SMILE_MULTIPLIER, 0.f, 1.f); + _blendshapeCoefficients[_mouthSmileLeftIndex] = glm::clamp(packet.expressions[24] * SMILE_MULTIPLIER, 0.0f, 1.0f); + _blendshapeCoefficients[_mouthSmileRightIndex] = glm::clamp(packet.expressions[23] * SMILE_MULTIPLIER, 0.0f, 1.0f); } else { diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index d4f9cd6b43..345e635045 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -247,7 +247,7 @@ void Faceshift::receive(const QByteArray& buffer) { quint64 usecsNow = usecTimestampNow(); if (_lastTrackingStateReceived != 0) { _averageFrameTime = FRAME_AVERAGING_FACTOR * _averageFrameTime + - (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastTrackingStateReceived) / 1000000.f; + (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastTrackingStateReceived) / 1000000.0f; } _lastTrackingStateReceived = usecsNow; } From 6b1671c5989743ac54242d53ebb4228236994171 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 19 Aug 2014 22:17:09 -0700 Subject: [PATCH 148/206] Add AppKit as a dependency in interface CMakeLists.txt --- interface/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index b8f25a3f2c..54edc97211 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -174,8 +174,9 @@ if (APPLE) find_library(CoreFoundation CoreFoundation) find_library(GLUT GLUT) find_library(OpenGL OpenGL) + find_library(AppKit AppKit) - target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation} ${GLUT} ${OpenGL}) + target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation} ${GLUT} ${OpenGL} ${AppKit}) # install command for OS X bundle INSTALL(TARGETS ${TARGET_NAME} From a01d3781d93bb329e4c0fed3b5c3f157b4dbbf85 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 20 Aug 2014 12:48:52 -0700 Subject: [PATCH 149/206] simplified SkeletonModel::maybeUpdateLeanRotation() --- interface/src/avatar/SkeletonModel.cpp | 17 +++++++++-------- interface/src/avatar/SkeletonModel.h | 2 +- interface/src/renderer/JointState.cpp | 5 +++++ interface/src/renderer/JointState.h | 1 + 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index dd398f2b2b..a087693615 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -243,7 +243,7 @@ void SkeletonModel::updateJointState(int index) { const JointState& parentState = _jointStates.at(joint.parentIndex); const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (index == geometry.leanJointIndex) { - maybeUpdateLeanRotation(parentState, joint, state); + maybeUpdateLeanRotation(parentState, state); } else if (index == geometry.neckJointIndex) { maybeUpdateNeckRotation(parentState, joint, state); @@ -260,17 +260,18 @@ void SkeletonModel::updateJointState(int index) { } } -void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { +void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, JointState& state) { if (!_owningAvatar->isMyAvatar() || Application::getInstance()->getPrioVR()->isActive()) { return; } // get the rotation axes in joint space and use them to adjust the rotation - glm::mat3 axes = glm::mat3_cast(glm::quat()); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.getTransform() * glm::translate(state.getDefaultTranslationInConstrainedFrame()) * - joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation))); - state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), - glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), - glm::normalize(inverse * axes[0])) * joint.rotation); + glm::vec3 xAxis(1.0f, 0.0f, 0.0f); + glm::vec3 zAxis(0.0f, 0.0f, 1.0f); + glm::quat inverse = glm::inverse(parentState.getRotation() * state.getDefaultRotationInParentFrame()); + state.setRotationInConstrainedFrame( + glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), inverse * zAxis) + * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis) + * state.getFBXJoint().rotation); } void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index b0d6ed7325..9bd8df745a 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -127,7 +127,7 @@ protected: /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); - void maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); + void maybeUpdateLeanRotation(const JointState& parentState, JointState& state); void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp index 94b4b37a3c..9492413f54 100644 --- a/interface/src/renderer/JointState.cpp +++ b/interface/src/renderer/JointState.cpp @@ -254,6 +254,11 @@ const bool JointState::rotationIsDefault(const glm::quat& rotation, float tolera glm::abs(rotation.w - defaultRotation.w) < tolerance; } +glm::quat JointState::getDefaultRotationInParentFrame() const { + // NOTE: the result is constant and could be cached in the FBXJoint + return _fbxJoint->preRotation * _fbxJoint->rotation * _fbxJoint->postRotation; +} + const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const { assert(_fbxJoint != NULL); return _fbxJoint->translation; diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h index 56044125f1..bc0c6dd51f 100644 --- a/interface/src/renderer/JointState.h +++ b/interface/src/renderer/JointState.h @@ -88,6 +88,7 @@ public: const bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const; + glm::quat getDefaultRotationInParentFrame() const; const glm::vec3& getDefaultTranslationInConstrainedFrame() const; From 416d9bac2ef25313f6ccd78b794b8cbfdbc46a91 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 20 Aug 2014 14:48:02 -0700 Subject: [PATCH 150/206] Working on texture rendering. --- .../shaders/metavoxel_heightfield_base.frag | 20 ++ .../shaders/metavoxel_heightfield_base.vert | 33 +++ .../shaders/metavoxel_heightfield_light.frag | 21 ++ .../shaders/metavoxel_heightfield_light.vert | 45 +++ ...heightfield_light_cascaded_shadow_map.frag | 44 +++ ...etavoxel_heightfield_light_shadow_map.frag | 33 +++ .../shaders/metavoxel_heightfield_splat.frag | 20 ++ .../shaders/metavoxel_heightfield_splat.vert | 33 +++ interface/src/MetavoxelSystem.cpp | 260 ++++++++++++------ interface/src/MetavoxelSystem.h | 49 +++- interface/src/renderer/TextureCache.cpp | 7 +- interface/src/renderer/TextureCache.h | 4 +- interface/src/ui/MetavoxelEditor.cpp | 6 +- .../metavoxels/src/MetavoxelMessages.cpp | 2 +- 14 files changed, 473 insertions(+), 104 deletions(-) create mode 100644 interface/resources/shaders/metavoxel_heightfield_base.frag create mode 100644 interface/resources/shaders/metavoxel_heightfield_base.vert create mode 100644 interface/resources/shaders/metavoxel_heightfield_light.frag create mode 100644 interface/resources/shaders/metavoxel_heightfield_light.vert create mode 100644 interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag create mode 100644 interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag create mode 100644 interface/resources/shaders/metavoxel_heightfield_splat.frag create mode 100644 interface/resources/shaders/metavoxel_heightfield_splat.vert diff --git a/interface/resources/shaders/metavoxel_heightfield_base.frag b/interface/resources/shaders/metavoxel_heightfield_base.frag new file mode 100644 index 0000000000..9b64a59e6f --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_base.frag @@ -0,0 +1,20 @@ +#version 120 + +// +// metavoxel_heightfield_base.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +void main(void) { + // compute the base color based on OpenGL lighting model + gl_FragColor = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_base.vert b/interface/resources/shaders/metavoxel_heightfield_base.vert new file mode 100644 index 0000000000..3e4b081d6f --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_base.vert @@ -0,0 +1,33 @@ +#version 120 + +// +// metavoxel_heightfield_base.vert +// vertex shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the height texture +uniform sampler2D heightMap; + +// the distance between height points in texture space +uniform float heightScale; + +// the scale between height and color textures +uniform float colorScale; + +void main(void) { + // add the height to the position + float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; + gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + + // the zero height should be invisible + gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + + // pass along the scaled/offset texture coordinates + gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * colorScale; +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light.frag b/interface/resources/shaders/metavoxel_heightfield_light.frag new file mode 100644 index 0000000000..ce3f23e142 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light.frag @@ -0,0 +1,21 @@ +#version 120 + +// +// metavoxel_heightfield_light.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalize(normal), gl_LightSource[0].position))); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light.vert b/interface/resources/shaders/metavoxel_heightfield_light.vert new file mode 100644 index 0000000000..228d575b81 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light.vert @@ -0,0 +1,45 @@ +#version 120 + +// +// metavoxel_heighfield_light.vert +// vertex shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the height texture +uniform sampler2D heightMap; + +// the distance between height points in texture space +uniform float heightScale; + +// the interpolated position +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // transform and store the normal for interpolation + vec2 heightCoord = gl_MultiTexCoord0.st; + float deltaX = texture2D(heightMap, heightCoord - vec2(heightScale, 0.0)).r - + texture2D(heightMap, heightCoord + vec2(heightScale, 0.0)).r; + float deltaZ = texture2D(heightMap, heightCoord - vec2(0.0, heightScale)).r - + texture2D(heightMap, heightCoord + vec2(0.0, heightScale)).r; + normal = normalize(gl_ModelViewMatrix * vec4(deltaX, heightScale, deltaZ, 0.0)); + + // add the height to the position + float height = texture2D(heightMap, heightCoord).r; + position = gl_ModelViewMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + gl_Position = gl_ProjectionMatrix * position; + + // the zero height should be invisible + gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + + // and the shadow texture coordinates + gl_TexCoord[1] = vec4(dot(gl_EyePlaneS[0], position), dot(gl_EyePlaneT[0], position), dot(gl_EyePlaneR[0], position), 1.0); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag new file mode 100644 index 0000000000..73382eb83c --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag @@ -0,0 +1,44 @@ +#version 120 + +// +// metavoxel_heightfield_light_cascaded_shadow_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position), + dot(gl_EyePlaneR[shadowIndex], position)); + + // compute the base color based on OpenGL lighting model + float diffuse = dot(normalize(normal), gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r); + gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag b/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag new file mode 100644 index 0000000000..4f2df8958b --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_light_shadow_map.frag @@ -0,0 +1,33 @@ +#version 120 + +// +// metavoxel_heightfield_light_shadow_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the base color based on OpenGL lighting model + float diffuse = dot(normalize(normal), gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + gl_FragColor = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.frag b/interface/resources/shaders/metavoxel_heightfield_splat.frag new file mode 100644 index 0000000000..7b73cf331b --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_splat.frag @@ -0,0 +1,20 @@ +#version 120 + +// +// metavoxel_heightfield_splat.frag +// fragment shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +void main(void) { + // compute the base color based on OpenGL lighting model + gl_FragColor = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st); +} diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.vert b/interface/resources/shaders/metavoxel_heightfield_splat.vert new file mode 100644 index 0000000000..0f36605454 --- /dev/null +++ b/interface/resources/shaders/metavoxel_heightfield_splat.vert @@ -0,0 +1,33 @@ +#version 120 + +// +// metavoxel_heighfield_splat.vert +// vertex shader +// +// Created by Andrzej Kapolka on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the height texture +uniform sampler2D heightMap; + +// the distance between height points in texture space +uniform float heightScale; + +// the scale between height and texture textures +uniform float textureScale; + +void main(void) { + // add the height to the position + float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; + gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + + // the zero height should be invisible + gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + + // pass along the scaled/offset texture coordinates + gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * textureScale; +} diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 4cdd2498e6..41ae254c71 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -602,24 +602,23 @@ const int HeightfieldBuffer::SHARED_EDGE = 1; const int HeightfieldBuffer::HEIGHT_EXTENSION = 2 * HeightfieldBuffer::HEIGHT_BORDER + HeightfieldBuffer::SHARED_EDGE; HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, - const QByteArray& height, const QByteArray& color, const QByteArray& texture) : + const QByteArray& height, const QByteArray& color, const QByteArray& texture, + const QVector& textures) : _translation(translation), _scale(scale), _heightBounds(translation, translation + glm::vec3(scale, scale, scale)), _colorBounds(_heightBounds), - _textureBounds(_heightBounds), _height(height), _color(color), _texture(texture), + _textures(textures), _heightTextureID(0), _colorTextureID(0), _textureTextureID(0), _heightSize(glm::sqrt(height.size())), _heightIncrement(scale / (_heightSize - HEIGHT_EXTENSION)), _colorSize(glm::sqrt(color.size() / HeightfieldData::COLOR_BYTES)), - _colorIncrement(scale / (_colorSize - SHARED_EDGE)), - _textureSize(glm::sqrt(texture.size())), - _textureIncrement(scale / (_textureSize - SHARED_EDGE)) { + _colorIncrement(scale / (_colorSize - SHARED_EDGE)) { _heightBounds.minimum.x -= _heightIncrement * HEIGHT_BORDER; _heightBounds.minimum.z -= _heightIncrement * HEIGHT_BORDER; @@ -628,9 +627,6 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, _colorBounds.maximum.x += _colorIncrement * SHARED_EDGE; _colorBounds.maximum.z += _colorIncrement * SHARED_EDGE; - - _textureBounds.maximum.x += _textureIncrement * SHARED_EDGE; - _textureBounds.maximum.z += _textureIncrement * SHARED_EDGE; } HeightfieldBuffer::~HeightfieldBuffer() { @@ -712,6 +708,15 @@ void HeightfieldBuffer::render(bool cursor) { int textureSize = glm::sqrt(_texture.size()); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, textureSize, textureSize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, _texture.constData()); + + _networkTextures.resize(_textures.size()); + for (int i = 0; i < _textures.size(); i++) { + const SharedObjectPointer texture = _textures.at(i); + if (texture) { + _networkTextures[i] = Application::getInstance()->getTextureCache()->getTexture( + static_cast(texture.data())->getURL()); + } + } } } // create the buffer objects lazily @@ -780,7 +785,56 @@ void HeightfieldBuffer::render(bool cursor) { glBindTexture(GL_TEXTURE_2D, _heightTextureID); - if (!cursor) { + if (cursor) { + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + + } else if (!_textures.isEmpty()) { + DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getBaseHeightScaleLocation(), 1.0f / _heightSize); + DefaultMetavoxelRendererImplementation::getBaseHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getBaseColorScaleLocation(), (float)_heightSize / innerSize); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, _colorTextureID); + + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + + glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); + glBlendFunc(GL_DST_COLOR, GL_ZERO); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, -1.0f); + + glBindTexture(GL_TEXTURE_2D, 0); + + if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) { + DefaultMetavoxelRendererImplementation::getShadowLightHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getShadowLightHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getShadowLightHeightScaleLocation(), 1.0f / _heightSize); + + } else if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getCascadedShadowLightHeightScaleLocation(), 1.0f / _heightSize); + + } else { + DefaultMetavoxelRendererImplementation::getLightHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getLightHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getBaseHeightScaleLocation(), 1.0f / _heightSize); + } + + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + + DefaultMetavoxelRendererImplementation::getHeightfieldProgram().bind(); + + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glDepthFunc(GL_LESS); + + glActiveTexture(GL_TEXTURE0); + + } else { int heightScaleLocation = DefaultMetavoxelRendererImplementation::getHeightScaleLocation(); int colorScaleLocation = DefaultMetavoxelRendererImplementation::getColorScaleLocation(); ProgramObject* program = &DefaultMetavoxelRendererImplementation::getHeightfieldProgram(); @@ -798,11 +852,9 @@ void HeightfieldBuffer::render(bool cursor) { program->setUniformValue(colorScaleLocation, (float)_heightSize / innerSize); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, _colorTextureID); - } - - glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); - - if (!cursor) { + + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); } @@ -919,6 +971,68 @@ void DefaultMetavoxelRendererImplementation::init() { _shadowDistancesLocation = _cascadedShadowMapHeightfieldProgram.uniformLocation("shadowDistances"); _cascadedShadowMapHeightfieldProgram.release(); + _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_base.vert"); + _baseHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_base.frag"); + _baseHeightfieldProgram.link(); + + _baseHeightfieldProgram.bind(); + _baseHeightfieldProgram.setUniformValue("heightMap", 0); + _baseHeightfieldProgram.setUniformValue("diffuseMap", 1); + _baseHeightScaleLocation = _heightfieldProgram.uniformLocation("heightScale"); + _baseColorScaleLocation = _heightfieldProgram.uniformLocation("colorScale"); + _baseHeightfieldProgram.release(); + + _splatHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_splat.vert"); + _splatHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_splat.frag"); + _splatHeightfieldProgram.link(); + + _splatHeightfieldProgram.bind(); + _splatHeightfieldProgram.setUniformValue("heightMap", 0); + _splatHeightfieldProgram.setUniformValue("diffuseMap", 1); + _splatHeightScaleLocation = _splatHeightfieldProgram.uniformLocation("heightScale"); + _splatTextureScaleLocation = _splatHeightfieldProgram.uniformLocation("textureScale"); + _splatHeightfieldProgram.release(); + + _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.vert"); + _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.frag"); + _lightHeightfieldProgram.link(); + + _lightHeightfieldProgram.bind(); + _lightHeightfieldProgram.setUniformValue("heightMap", 0); + _lightHeightScaleLocation = _lightHeightfieldProgram.uniformLocation("heightScale"); + _lightHeightfieldProgram.release(); + + _shadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.vert"); + _shadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light_shadow_map.frag"); + _shadowLightHeightfieldProgram.link(); + + _shadowLightHeightfieldProgram.bind(); + _shadowLightHeightfieldProgram.setUniformValue("heightMap", 0); + _shadowLightHeightfieldProgram.setUniformValue("shadowMap", 2); + _shadowLightHeightScaleLocation = _shadowLightHeightfieldProgram.uniformLocation("heightScale"); + _shadowLightHeightfieldProgram.release(); + + _cascadedShadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light.vert"); + _cascadedShadowLightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/metavoxel_heightfield_light_cascaded_shadow_map.frag"); + _cascadedShadowLightHeightfieldProgram.link(); + + _cascadedShadowLightHeightfieldProgram.bind(); + _cascadedShadowLightHeightfieldProgram.setUniformValue("heightMap", 0); + _cascadedShadowLightHeightfieldProgram.setUniformValue("shadowMap", 2); + _cascadedShadowLightHeightScaleLocation = _cascadedShadowLightHeightfieldProgram.uniformLocation("heightScale"); + _shadowLightDistancesLocation = _cascadedShadowLightHeightfieldProgram.uniformLocation("shadowDistances"); + _cascadedShadowLightHeightfieldProgram.release(); + _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_heightfield_cursor.vert"); _heightfieldCursorProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + @@ -1019,8 +1133,7 @@ private: HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << - AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << - AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), QVector(), lod), + AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector(), lod), _intersections(intersections) { } @@ -1135,50 +1248,6 @@ int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { } } } - - int textureSize = _buffer->getTextureSize(); - if (textureSize == 0) { - continue; - } - HeightfieldTextureDataPointer texture = info.inputValues.at(2).getInlineValue(); - if (!texture) { - continue; - } - const Box& textureBounds = _buffer->getTextureBounds(); - overlap = textureBounds.getIntersection(overlap); - float textureIncrement = _buffer->getTextureIncrement(); - destX = (overlap.minimum.x - textureBounds.minimum.x) / textureIncrement; - destY = (overlap.minimum.z - textureBounds.minimum.z) / textureIncrement; - destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / textureIncrement); - destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / textureIncrement); - dest = _buffer->getTexture().data() + destY * textureSize + destX; - - const QByteArray& srcTexture = texture->getContents(); - srcSize = glm::sqrt(srcTexture.size()); - srcIncrement = info.size / srcSize; - - if (srcIncrement == textureIncrement) { - // easy case: same resolution - int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; - int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; - - const char* src = srcTexture.constData() + srcY * srcSize + srcX; - for (int y = 0; y < destHeight; y++, src += srcSize, dest += textureSize) { - memcpy(dest, src, destWidth); - } - } else { - // more difficult: different resolutions - float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; - float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; - float srcAdvance = textureIncrement / srcIncrement; - for (int y = 0; y < destHeight; y++, dest += textureSize, srcY += srcAdvance) { - const char* src = srcTexture.constData() + (int)srcY * srcSize; - float lineSrcX = srcX; - for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) { - *lineDest = src[(int)lineSrcX]; - } - } - } } return STOP_RECURSION; } @@ -1233,42 +1302,43 @@ int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { } HeightfieldTextureDataPointer texture = info.inputValues.at(2).getInlineValue(); - int textureContentsSize = 0; + QByteArray textureContents; + QVector textures; if (texture) { - const QByteArray& textureContents = texture->getContents(); - int textureSize = glm::sqrt(textureContents.size()); - int extendedTextureSize = textureSize + HeightfieldBuffer::SHARED_EDGE; - textureContentsSize = extendedTextureSize * extendedTextureSize; + textureContents = texture->getContents(); + textures = texture->getTextures(); } const HeightfieldBuffer* existingBuffer = static_cast( info.inputValues.at(3).getInlineValue().data()); Box bounds = info.getBounds(); if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize && - existingBuffer->getColor().size() == colorContentsSize && - existingBuffer->getTexture().size() == textureContentsSize) { + existingBuffer->getColor().size() == colorContentsSize) { // we already have a buffer of the correct resolution addRegion(bounds, existingBuffer->getHeightBounds()); - return STOP_RECURSION; + buffer = new HeightfieldBuffer(info.minimum, info.size, existingBuffer->getHeight(), + existingBuffer->getColor(), textureContents, textures); + + } else { + // we must create a new buffer and update its borders + buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), + QByteArray(colorContentsSize, 0), textureContents, textures); + const Box& heightBounds = buffer->getHeightBounds(); + addRegion(bounds, heightBounds); + + _intersections.clear(); + _intersections.append(Box(heightBounds.minimum, + glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); + _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), + glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); + _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), + heightBounds.maximum)); + _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), + glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); + + _fetchVisitor.init(buffer); + _data->guide(_fetchVisitor); } - // we must create a new buffer and update its borders - buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), - QByteArray(colorContentsSize, 0), QByteArray(textureContentsSize, 0)); - const Box& heightBounds = buffer->getHeightBounds(); - addRegion(bounds, heightBounds); - - _intersections.clear(); - _intersections.append(Box(heightBounds.minimum, - glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); - _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), - glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); - _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), - heightBounds.maximum)); - _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), - glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); - - _fetchVisitor.init(buffer); - _data->guide(_fetchVisitor); } info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(buffer))); return STOP_RECURSION; @@ -1326,7 +1396,7 @@ int HeightfieldUpdateVisitor::visit(MetavoxelInfo& info) { return STOP_RECURSION; } HeightfieldBuffer* newBuffer = new HeightfieldBuffer(info.minimum, info.size, - buffer->getHeight(), buffer->getColor(), buffer->getTexture()); + buffer->getHeight(), buffer->getColor(), buffer->getTexture(), buffer->getTextures()); _fetchVisitor.init(newBuffer); _data->guide(_fetchVisitor); info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(BufferDataPointer(newBuffer))); @@ -1514,6 +1584,9 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox ProgramObject* program = &_heightfieldProgram; if (Menu::getInstance()->getShadowsEnabled()) { if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + _cascadedShadowLightHeightfieldProgram.bind(); + _cascadedShadowLightHeightfieldProgram.setUniform(_shadowLightDistancesLocation, + Application::getInstance()->getShadowDistances()); program = &_cascadedShadowMapHeightfieldProgram; program->bind(); program->setUniform(_shadowDistancesLocation, Application::getInstance()->getShadowDistances()); @@ -1559,6 +1632,19 @@ ProgramObject DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightfi int DefaultMetavoxelRendererImplementation::_cascadedShadowMapHeightScaleLocation; int DefaultMetavoxelRendererImplementation::_cascadedShadowMapColorScaleLocation; int DefaultMetavoxelRendererImplementation::_shadowDistancesLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_baseHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_baseHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_baseColorScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_splatHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_splatHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_lightHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_lightHeightScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_shadowLightHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_shadowLightHeightScaleLocation; +ProgramObject DefaultMetavoxelRendererImplementation::_cascadedShadowLightHeightfieldProgram; +int DefaultMetavoxelRendererImplementation::_cascadedShadowLightHeightScaleLocation; +int DefaultMetavoxelRendererImplementation::_shadowLightDistancesLocation; ProgramObject DefaultMetavoxelRendererImplementation::_heightfieldCursorProgram; static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 206c378fd1..dc2ff8633a 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -140,7 +140,8 @@ public: static const int HEIGHT_EXTENSION; HeightfieldBuffer(const glm::vec3& translation, float scale, const QByteArray& height, - const QByteArray& color, const QByteArray& texture); + const QByteArray& color, const QByteArray& texture = QByteArray(), + const QVector& textures = QVector()); ~HeightfieldBuffer(); const glm::vec3& getTranslation() const { return _translation; } @@ -148,7 +149,6 @@ public: const Box& getHeightBounds() const { return _heightBounds; } const Box& getColorBounds() const { return _colorBounds; } - const Box& getTextureBounds() const { return _textureBounds; } QByteArray& getHeight() { return _height; } const QByteArray& getHeight() const { return _height; } @@ -159,6 +159,8 @@ public: QByteArray& getTexture() { return _texture; } const QByteArray& getTexture() const { return _texture; } + const QVector& getTextures() const { return _textures; } + QByteArray getUnextendedHeight() const; QByteArray getUnextendedColor() const; @@ -168,9 +170,6 @@ public: int getColorSize() const { return _colorSize; } float getColorIncrement() const { return _colorIncrement; } - int getTextureSize() const { return _textureSize; } - float getTextureIncrement() const { return _textureIncrement; } - virtual void render(bool cursor = false); private: @@ -179,19 +178,18 @@ private: float _scale; Box _heightBounds; Box _colorBounds; - Box _textureBounds; QByteArray _height; QByteArray _color; QByteArray _texture; + QVector _textures; GLuint _heightTextureID; GLuint _colorTextureID; GLuint _textureTextureID; + QVector _networkTextures; int _heightSize; float _heightIncrement; int _colorSize; float _colorIncrement; - int _textureSize; - float _textureIncrement; typedef QPair BufferPair; static QHash _bufferPairs; @@ -244,6 +242,23 @@ public: static int getCascadedShadowMapHeightScaleLocation() { return _cascadedShadowMapHeightScaleLocation; } static int getCascadedShadowMapColorScaleLocation() { return _cascadedShadowMapColorScaleLocation; } + static ProgramObject& getBaseHeightfieldProgram() { return _baseHeightfieldProgram; } + static int getBaseHeightScaleLocation() { return _baseHeightScaleLocation; } + static int getBaseColorScaleLocation() { return _baseColorScaleLocation; } + + static ProgramObject& getSplatHeightfieldProgram() { return _splatHeightfieldProgram; } + static int getSplatHeightScaleLocation() { return _splatHeightScaleLocation; } + static int getSplatTextureScaleLocation() { return _splatTextureScaleLocation; } + + static ProgramObject& getLightHeightfieldProgram() { return _lightHeightfieldProgram; } + static int getLightHeightScaleLocation() { return _lightHeightScaleLocation; } + + static ProgramObject& getShadowLightHeightfieldProgram() { return _shadowLightHeightfieldProgram; } + static int getShadowLightHeightScaleLocation() { return _shadowLightHeightScaleLocation; } + + static ProgramObject& getCascadedShadowLightHeightfieldProgram() { return _cascadedShadowLightHeightfieldProgram; } + static int getCascadedShadowLightHeightScaleLocation() { return _cascadedShadowLightHeightScaleLocation; } + static ProgramObject& getHeightfieldCursorProgram() { return _heightfieldCursorProgram; } Q_INVOKABLE DefaultMetavoxelRendererImplementation(); @@ -270,6 +285,24 @@ private: static int _cascadedShadowMapColorScaleLocation; static int _shadowDistancesLocation; + static ProgramObject _baseHeightfieldProgram; + static int _baseHeightScaleLocation; + static int _baseColorScaleLocation; + + static ProgramObject _splatHeightfieldProgram; + static int _splatHeightScaleLocation; + static int _splatTextureScaleLocation; + + static ProgramObject _lightHeightfieldProgram; + static int _lightHeightScaleLocation; + + static ProgramObject _shadowLightHeightfieldProgram; + static int _shadowLightHeightScaleLocation; + + static ProgramObject _cascadedShadowLightHeightfieldProgram; + static int _cascadedShadowLightHeightScaleLocation; + static int _shadowLightDistancesLocation; + static ProgramObject _heightfieldCursorProgram; }; diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 11c4778c19..ee10d67ee9 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -179,15 +179,14 @@ public: const QByteArray& content; }; -QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, - bool dilatable, const QByteArray& content) { +NetworkTexturePointer TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable, const QByteArray& content) { if (!dilatable) { TextureExtra extra = { normalMap, content }; return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast(); } - QSharedPointer texture = _dilatableNetworkTextures.value(url); + NetworkTexturePointer texture = _dilatableNetworkTextures.value(url); if (texture.isNull()) { - texture = QSharedPointer(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared); + texture = NetworkTexturePointer(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared); texture->setSelf(texture); texture->setCache(this); _dilatableNetworkTextures.insert(url, texture); diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 58f9345c4a..06f724d70d 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -23,6 +23,8 @@ class QOpenGLFramebufferObject; class NetworkTexture; +typedef QSharedPointer NetworkTexturePointer; + /// Stores cached textures, including render-to-texture targets. class TextureCache : public ResourceCache { Q_OBJECT @@ -47,7 +49,7 @@ public: GLuint getBlueTextureID(); /// Loads a texture from the specified URL. - QSharedPointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false, + NetworkTexturePointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false, const QByteArray& content = QByteArray()); /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 2c99caf7d2..3ca509cd95 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -974,7 +974,8 @@ void ImportHeightfieldTool::apply() { data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); - QByteArray texture(height.size(), 0); + int size = glm::sqrt(height.size()) + HeightfieldBuffer::SHARED_EDGE; + QByteArray texture(size * size, 0); HeightfieldTextureDataPointer texturePointer(new HeightfieldTextureData(texture)); data.setRoot(AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldTextureAttribute(), encodeInline(texturePointer)))); @@ -1049,8 +1050,7 @@ void ImportHeightfieldTool::updatePreview() { columns * HeightfieldData::COLOR_BYTES); } } - buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, - height, color, QByteArray()))); + buffers.append(BufferDataPointer(new HeightfieldBuffer(glm::vec3(x, 0.0f, z), 1.0f, height, color))); } } } diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 33aea9fcb1..1a8f64d935 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -593,7 +593,7 @@ int PaintHeightfieldTextureEditVisitor::visit(MetavoxelInfo& info) { } int size = glm::sqrt((float)contents.size()); int highest = size - 1; - float heightScale = size / info.size; + float heightScale = highest / info.size; glm::vec3 center = (_edit.position - info.minimum) * heightScale; float scaledRadius = _edit.radius * heightScale; From e3ab6476cac848a554ca4fdf580e2a90b6191cb8 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 20 Aug 2014 15:21:18 -0700 Subject: [PATCH 151/206] Recorder.js first draft --- examples/Recorder.js | 148 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 examples/Recorder.js diff --git a/examples/Recorder.js b/examples/Recorder.js new file mode 100644 index 0000000000..51cdf06211 --- /dev/null +++ b/examples/Recorder.js @@ -0,0 +1,148 @@ +// +// Recorder.js +// examples +// +// Created by ClĂ©ment Brisset on 8/20/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("toolBars.js"); + +var recordingFile = "recording.rec"; + +var windowDimensions = Controller.getViewportDimensions(); +var TOOL_ICON_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; +var TOOL_WIDTH = 50; +var TOOL_HEIGHT = 50; +var ALPHA_ON = 1.0; +var ALPHA_OFF = 0.5; + +var toolBar = null; +var recordIcon; +var playIcon; +var saveIcon; +var loadIcon; +var loadLastIcon; +setupToolBar(); + +function setupToolBar() { + if (toolBar != null) { + print("Multiple calls to Recorder.js:setupToolBar()"); + return; + } + + toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); + + recordIcon = toolBar.addTool({ + imageURL: TOOL_ICON_URL + "add-model-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: TOOL_WIDTH, + height: TOOL_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, true, MyAvatar.isRecording()); + + playIcon = toolBar.addTool({ + imageURL: TOOL_ICON_URL + "add-model-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: TOOL_WIDTH, + height: TOOL_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, false, false); + + saveIcon = toolBar.addTool({ + imageURL: TOOL_ICON_URL + "add-model-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: TOOL_WIDTH, + height: TOOL_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, false, false); + + loadIcon = toolBar.addTool({ + imageURL: TOOL_ICON_URL + "add-model-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: TOOL_WIDTH, + height: TOOL_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, false, false); + + loadLastIcon = toolBar.addTool({ + imageURL: TOOL_ICON_URL + "add-model-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: TOOL_WIDTH, + height: TOOL_HEIGHT, + alpha: ALPHA_ON, + visible: true + }, false, false); + + moveUI(); +} + +function moveUI() { + var relative = { x: 30, y: 90 }; + toolBar.move(relative.x, + windowDimensions.y - relative.y); +} + +function mousePressEvent(event) { + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (recordIcon === toolBar.clicked(clickedOverlay)) { + if (!MyAvatar.isRecording()) { + MyAvatar.startRecording(); + } else { + MyAvatar.stopRecording(); + } + } else if (playIcon === toolBar.clicked(clickedOverlay)) { + if (!MyAvatar.isRecording()) { + if (MyAvatar.isPlaying()) { + MyAvatar.stopPlaying(); + } else { + MyAvatar.startPlaying(); + } + } + } else if (saveIcon === toolBar.clicked(clickedOverlay)) { + recordingFile = Window.save("Save recording to file", ".", "*.rec"); + MyAvatar.saveRecording(recordingFile); + } else if (loadIcon === toolBar.clicked(clickedOverlay)) { + recordingFile = Window.browse("Load recorcding from file", ".", "*.rec"); + MyAvatar.loadRecording(recordingFile); + } else if (loadLastIcon === toolBar.clicked(clickedOverlay)) { + MyAvatar.loadLastRecording(); + } else { + + } +} + +function update() { + var newDimensions = Controller.getViewportDimensions(); + if (windowDimensions.x != newDimensions.x || + windowDimensions.y != newDimensions.y) { + windowDimensions = newDimensions; + moveUI(); + } +} + +function scriptEnding() { + if (MyAvatar.isRecording()) { + MyAvatar.stopRecording(); + } + if (MyAvatar.isPlaying()) { + MyAvatar.stopPlaying(); + } + toolBar.cleanup(); +} + +Controller.mousePressEvent.connect(mousePressEvent); +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + + + + From 4922592cfa86e197b8fbda0aca09fe2c19b0cc1b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 20 Aug 2014 15:21:33 -0700 Subject: [PATCH 152/206] Debug tweaks --- interface/src/Recorder.cpp | 15 ++++++++------- interface/src/avatar/MyAvatar.cpp | 1 + .../src/scripting/WindowScriptingInterface.cpp | 2 -- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 52d183332d..1d2ad3f059 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -223,7 +223,7 @@ void Player::startPlaying() { _audioThread = new QThread(); _options.setPosition(_avatar->getPosition()); _options.setOrientation(_avatar->getOrientation()); - _injector.reset(new AudioInjector(_recording->getAudio(), _options)); + _injector.reset(new AudioInjector(_recording->getAudio(), _options), &QObject::deleteLater); _injector->moveToThread(_audioThread); _audioThread->start(); QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); @@ -317,13 +317,13 @@ bool Player::computeCurrentFrame() { } void writeRecordingToFile(RecordingPointer recording, QString filename) { - qDebug() << "Writing recording to " << filename; + qDebug() << "Writing recording to " << filename << "."; QElapsedTimer timer; QFile file(filename); if (!file.open(QIODevice::WriteOnly)){ return; } - qDebug() << file.fileName(); + timer.start(); QDataStream fileStream(&file); @@ -443,20 +443,21 @@ void writeRecordingToFile(RecordingPointer recording, QString filename) { fileStream << recording->_audio->getByteArray(); - qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed(); + qDebug() << "Wrote " << file.size() << " bytes in " << timer.elapsed() << " ms."; } RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filename) { - qDebug() << "Reading recording from " << filename; + qDebug() << "Reading recording from " << filename << "."; if (!recording) { recording.reset(new Recording()); } + QElapsedTimer timer; QFile file(filename); if (!file.open(QIODevice::ReadOnly)){ return recording; } - + timer.start(); QDataStream fileStream(&file); fileStream >> recording->_timestamps; @@ -557,7 +558,7 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, QString filen recording->addAudioPacket(audioArray); - qDebug() << "Read " << file.size() << " bytes"; + qDebug() << "Read " << file.size() << " bytes in " << timer.elapsed() << " ms."; return recording; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b3805d15c7..41c0f3032b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -528,6 +528,7 @@ void MyAvatar::startRecording() { } Application::getInstance()->getAudio()->setRecorder(_recorder); _recorder->startRecording(); + } void MyAvatar::stopRecording() { diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 7cf37b4424..7a85fc7117 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -295,7 +295,6 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS // filename if the directory is valid. QString path = ""; QFileInfo fileInfo = QFileInfo(directory); - qDebug() << "File: " << directory << fileInfo.isFile(); if (fileInfo.isDir()) { fileInfo.setFile(directory, "__HIFI_INVALID_FILE__"); path = fileInfo.filePath(); @@ -303,7 +302,6 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter); fileDialog.setAcceptMode(acceptMode); - qDebug() << "Opening!"; QUrl fileUrl(directory); if (acceptMode == QFileDialog::AcceptSave) { fileDialog.setFileMode(QFileDialog::Directory); From 7e4c72445ee443886831df517de1942937c3f457 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 20 Aug 2014 17:14:59 -0700 Subject: [PATCH 153/206] JS scripts set joint animation priorities --- interface/src/Application.cpp | 8 +++++++ interface/src/avatar/FaceModel.cpp | 4 ++-- interface/src/avatar/MyAvatar.cpp | 31 ++++++++++++++------------ interface/src/avatar/MyAvatar.h | 2 ++ interface/src/avatar/SkeletonModel.cpp | 7 +++--- interface/src/renderer/JointState.cpp | 20 ++++++++++++----- interface/src/renderer/JointState.h | 5 ++++- interface/src/renderer/Model.cpp | 13 ++++------- 8 files changed, 56 insertions(+), 34 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 11729eef90..8421ee05b1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3821,6 +3821,10 @@ void Application::stopAllScripts(bool restart) { it.value()->stop(); qDebug() << "stopping script..." << it.key(); } + // HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities + // whenever a script stops in case it happened to have been setting joint rotations. + // TODO: expose animation priorities and provide a layered animation control system. + _myAvatar->clearJointAnimationPriorities(); } void Application::stopScript(const QString &scriptName) { @@ -3828,6 +3832,10 @@ void Application::stopScript(const QString &scriptName) { if (_scriptEnginesHash.contains(scriptURLString)) { _scriptEnginesHash.value(scriptURLString)->stop(); qDebug() << "stopping script..." << scriptName; + // HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities + // whenever a script stops in case it happened to have been setting joint rotations. + // TODO: expose animation priorities and provide a layered animation control system. + _myAvatar->clearJointAnimationPriorities(); } } diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 203dbf2283..521a4ddc57 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -54,7 +54,7 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX state.setRotationInConstrainedFrame(glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) * glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0])) - * joint.rotation); + * joint.rotation, DEFAULT_PRIORITY); } void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { @@ -69,7 +69,7 @@ void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJ glm::quat between = rotationBetween(front, lookAt); const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * - joint.rotation); + joint.rotation, DEFAULT_PRIORITY); } void FaceModel::updateJointState(int index) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b3805d15c7..357a39e17e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -992,36 +992,39 @@ glm::vec3 MyAvatar::getUprightHeadPosition() const { return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f); } -const float JOINT_PRIORITY = 2.0f; +const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f; +const float RECORDER_PRIORITY = SCRIPT_PRIORITY + 1.0f; void MyAvatar::setJointRotations(QVector jointRotations) { - for (int i = 0; i < jointRotations.size(); ++i) { - if (i < _jointData.size()) { - _skeletonModel.setJointState(i, true, jointRotations[i], JOINT_PRIORITY + 1.0f); - } + int numStates = glm::min(_skeletonModel.getJointStateCount(), jointRotations.size()); + for (int i = 0; i < numStates; ++i) { + // HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here + _skeletonModel.setJointState(i, true, jointRotations[i], RECORDER_PRIORITY); } } void MyAvatar::setJointData(int index, const glm::quat& rotation) { - Avatar::setJointData(index, rotation); if (QThread::currentThread() == thread()) { - _skeletonModel.setJointState(index, true, rotation, JOINT_PRIORITY); + // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority + _skeletonModel.setJointState(index, true, rotation, SCRIPT_PRIORITY); } } void MyAvatar::clearJointData(int index) { - Avatar::clearJointData(index); if (QThread::currentThread() == thread()) { - _skeletonModel.setJointState(index, false, glm::quat(), JOINT_PRIORITY); + // HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority + _skeletonModel.setJointState(index, false, glm::quat(), 0.0f); } } void MyAvatar::clearJointsData() { - for (int i = 0; i < _jointData.size(); ++i) { - Avatar::clearJointData(i); - if (QThread::currentThread() == thread()) { - _skeletonModel.clearJointAnimationPriority(i); - } + clearJointAnimationPriorities(); +} + +void MyAvatar::clearJointAnimationPriorities() { + int numStates = _skeletonModel.getJointStateCount(); + for (int i = 0; i < numStates; ++i) { + _skeletonModel.clearJointAnimationPriority(i); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 42deafd0fa..61708b9e79 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -128,6 +128,8 @@ public: virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setAttachmentData(const QVector& attachmentData); + void clearJointAnimationPriorities(); + virtual void attach(const QString& modelURL, const QString& jointName = QString(), const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool allowDuplicates = false, bool useSaved = true); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index a087693615..5a8eb5519d 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -51,7 +51,8 @@ void SkeletonModel::setJointStates(QVector states) { } } -const float PALM_PRIORITY = 3.0f; +const float PALM_PRIORITY = DEFAULT_PRIORITY; +const float LEAN_PRIORITY = DEFAULT_PRIORITY; void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { setTranslation(_owningAvatar->getPosition()); @@ -230,7 +231,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { JointState& parentState = _jointStates[parentJointIndex]; parentState.setRotationInBindFrame(palmRotation, PALM_PRIORITY); // lock hand to forearm by slamming its rotation (in parent-frame) to identity - _jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat()); + _jointStates[jointIndex].setRotationInConstrainedFrame(glm::quat(), PALM_PRIORITY); } else { inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY); } @@ -271,7 +272,7 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, Joint state.setRotationInConstrainedFrame( glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), inverse * zAxis) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), inverse * xAxis) - * state.getFBXJoint().rotation); + * state.getFBXJoint().rotation, LEAN_PRIORITY); } void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp index 9492413f54..0776891109 100644 --- a/interface/src/renderer/JointState.cpp +++ b/interface/src/renderer/JointState.cpp @@ -145,7 +145,7 @@ glm::quat JointState::getVisibleRotationInParentFrame() const { void JointState::restoreRotation(float fraction, float priority) { assert(_fbxJoint != NULL); if (priority == _animationPriority || _animationPriority == 0.0f) { - setRotationInConstrainedFrame(safeMix(_rotationInConstrainedFrame, _fbxJoint->rotation, fraction)); + setRotationInConstrainedFrameInternal(safeMix(_rotationInConstrainedFrame, _fbxJoint->rotation, fraction)); _animationPriority = 0.0f; } } @@ -158,7 +158,7 @@ void JointState::setRotationInBindFrame(const glm::quat& rotation, float priorit if (constrain && _constraint) { _constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f); } - setRotationInConstrainedFrame(targetRotation); + setRotationInConstrainedFrameInternal(targetRotation); _animationPriority = priority; } } @@ -189,7 +189,7 @@ void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, floa _rotation = delta * getRotation(); return; } - setRotationInConstrainedFrame(targetRotation); + setRotationInConstrainedFrameInternal(targetRotation); } /// Applies delta rotation to joint but mixes a little bit of the default pose as well. @@ -208,7 +208,7 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float if (_constraint) { _constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f); } - setRotationInConstrainedFrame(targetRotation); + setRotationInConstrainedFrameInternal(targetRotation); } void JointState::mixVisibleRotationDelta(const glm::quat& delta, float mixFactor) { @@ -232,7 +232,17 @@ glm::quat JointState::computeVisibleParentRotation() const { return _visibleRotation * glm::inverse(_fbxJoint->preRotation * _visibleRotationInConstrainedFrame * _fbxJoint->postRotation); } -void JointState::setRotationInConstrainedFrame(const glm::quat& targetRotation) { +void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain) { + if (priority >= _animationPriority || _animationPriority == 0.0f) { + if (constrain && _constraint) { + _constraint->softClamp(targetRotation, _rotationInConstrainedFrame, 0.5f); + } + setRotationInConstrainedFrameInternal(targetRotation); + _animationPriority = priority; + } +} + +void JointState::setRotationInConstrainedFrameInternal(const glm::quat& targetRotation) { glm::quat parentRotation = computeParentRotation(); _rotationInConstrainedFrame = targetRotation; _transformChanged = true; diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h index bc0c6dd51f..a2c5ee7801 100644 --- a/interface/src/renderer/JointState.h +++ b/interface/src/renderer/JointState.h @@ -19,6 +19,8 @@ #include #include +const float DEFAULT_PRIORITY = 3.0f; + class AngularConstraint; class JointState { @@ -81,7 +83,7 @@ public: /// NOTE: the JointState's model-frame transform/rotation are NOT updated! void setRotationInBindFrame(const glm::quat& rotation, float priority, bool constrain = false); - void setRotationInConstrainedFrame(const glm::quat& targetRotation); + void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, bool constrain = false); void setVisibleRotationInConstrainedFrame(const glm::quat& targetRotation); const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; } const glm::quat& getVisibleRotationInConstrainedFrame() const { return _visibleRotationInConstrainedFrame; } @@ -104,6 +106,7 @@ public: glm::quat computeVisibleParentRotation() const; private: + void setRotationInConstrainedFrameInternal(const glm::quat& targetRotation); /// debug helper function void loadBindRotation(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index c955b902c9..92c19dd839 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -438,7 +438,7 @@ void Model::reset() { } const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation); + _jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f); } } @@ -695,8 +695,7 @@ bool Model::getVisibleJointState(int index, glm::quat& rotation) const { void Model::clearJointState(int index) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; - state.setRotationInConstrainedFrame(glm::quat()); - state._animationPriority = 0.0f; + state.setRotationInConstrainedFrame(glm::quat(), 0.0f); } } @@ -711,8 +710,7 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa JointState& state = _jointStates[index]; if (priority >= state._animationPriority) { if (valid) { - state.setRotationInConstrainedFrame(rotation); - state._animationPriority = priority; + state.setRotationInConstrainedFrame(rotation, priority); } else { state.restoreRotation(1.0f, priority); } @@ -1745,10 +1743,7 @@ void AnimationHandle::applyFrame(float frameIndex) { int mapping = _jointMappings.at(i); if (mapping != -1) { JointState& state = _model->_jointStates[mapping]; - if (_priority >= state._animationPriority) { - state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction)); - state._animationPriority = _priority; - } + state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction), _priority); } } } From 8f9a32231838a47e88feb820fe7b24854429ea3e Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 20 Aug 2014 18:04:17 -0700 Subject: [PATCH 154/206] Recording UI --- examples/Recorder.js | 56 ++++++++++++++++++------------------------- examples/toolBars.js | 57 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 37 deletions(-) diff --git a/examples/Recorder.js b/examples/Recorder.js index 51cdf06211..d691b64481 100644 --- a/examples/Recorder.js +++ b/examples/Recorder.js @@ -15,17 +15,16 @@ var recordingFile = "recording.rec"; var windowDimensions = Controller.getViewportDimensions(); var TOOL_ICON_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; -var TOOL_WIDTH = 50; -var TOOL_HEIGHT = 50; var ALPHA_ON = 1.0; -var ALPHA_OFF = 0.5; +var ALPHA_OFF = 0.7; +Tool.IMAGE_WIDTH *= 0.7; +Tool.IMAGE_HEIGHT *= 0.7; var toolBar = null; var recordIcon; var playIcon; var saveIcon; var loadIcon; -var loadLastIcon; setupToolBar(); function setupToolBar() { @@ -35,48 +34,36 @@ function setupToolBar() { } toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); + toolBar.setBack({ red: 128, green: 128, blue: 128 }, 0.8); recordIcon = toolBar.addTool({ - imageURL: TOOL_ICON_URL + "add-model-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: TOOL_WIDTH, - height: TOOL_HEIGHT, - alpha: ALPHA_ON, + imageURL: "file:/Users/clement/Downloads/svg/camera8.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: (MyAvatar.isRecording()) ? ALPHA_ON : ALPHA_OFF, visible: true - }, true, MyAvatar.isRecording()); + }, false); playIcon = toolBar.addTool({ - imageURL: TOOL_ICON_URL + "add-model-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: TOOL_WIDTH, - height: TOOL_HEIGHT, + imageURL: "file:/Users/clement/Downloads/svg/media23.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, alpha: ALPHA_ON, visible: true }, false, false); saveIcon = toolBar.addTool({ - imageURL: TOOL_ICON_URL + "add-model-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: TOOL_WIDTH, - height: TOOL_HEIGHT, + imageURL: "file:/Users/clement/Downloads/svg/save15.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, alpha: ALPHA_ON, visible: true }, false, false); loadIcon = toolBar.addTool({ - imageURL: TOOL_ICON_URL + "add-model-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: TOOL_WIDTH, - height: TOOL_HEIGHT, - alpha: ALPHA_ON, - visible: true - }, false, false); - - loadLastIcon = toolBar.addTool({ - imageURL: TOOL_ICON_URL + "add-model-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: TOOL_WIDTH, - height: TOOL_HEIGHT, + imageURL: "file:/Users/clement/Downloads/svg/upload2.svg", + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, alpha: ALPHA_ON, visible: true }, false, false); @@ -96,8 +83,13 @@ function mousePressEvent(event) { if (recordIcon === toolBar.clicked(clickedOverlay)) { if (!MyAvatar.isRecording()) { MyAvatar.startRecording(); + toolBar.setAlpha(ALPHA_ON, recordIcon); + toolBar.setBack({ red: 128, green: 0, blue: 0 }, 0.9); } else { MyAvatar.stopRecording(); + MyAvatar.loadLastRecording(); + toolBar.setAlpha(ALPHA_OFF, recordIcon); + toolBar.setBack({ red: 128, green: 128, blue: 128 }, 0.8); } } else if (playIcon === toolBar.clicked(clickedOverlay)) { if (!MyAvatar.isRecording()) { @@ -113,8 +105,6 @@ function mousePressEvent(event) { } else if (loadIcon === toolBar.clicked(clickedOverlay)) { recordingFile = Window.browse("Load recorcding from file", ".", "*.rec"); MyAvatar.loadRecording(recordingFile); - } else if (loadLastIcon === toolBar.clicked(clickedOverlay)) { - MyAvatar.loadLastRecording(); } else { } diff --git a/examples/toolBars.js b/examples/toolBars.js index ede3b80062..064ae372fd 100644 --- a/examples/toolBars.js +++ b/examples/toolBars.js @@ -132,20 +132,34 @@ ToolBar = function(x, y, direction) { this.y = y; this.width = 0; this.height = 0; - + this.back = this.back = Overlays.addOverlay("text", { + backgroundColor: { red: 255, green: 255, blue: 255 }, + x: this.x, + y: this.y, + width: this.width, + height: this.height, + alpha: 1.0, + visible: false + }); this.addTool = function(properties, selectable, selected) { if (direction == ToolBar.HORIZONTAL) { properties.x = this.x + this.width; properties.y = this.y; this.width += properties.width + ToolBar.SPACING; - this.height += Math.max(properties.height, this.height); + this.height = Math.max(properties.height, this.height); } else { properties.x = this.x; properties.y = this.y + this.height; this.width = Math.max(properties.width, this.width); this.height += properties.height + ToolBar.SPACING; } + if (this.back != null) { + Overlays.editOverlay(this.back, { + width: this.width + 2 * ToolBar.SPACING, + height: this.height + 2 * ToolBar.SPACING + }); + } this.tools[this.tools.length] = new Tool(properties, selectable, selected); return ((this.tools.length) - 1); @@ -159,18 +173,48 @@ ToolBar = function(x, y, direction) { for(var tool in this.tools) { this.tools[tool].move(this.tools[tool].x() + dx, this.tools[tool].y() + dy); } + if (this.back != null) { + Overlays.editOverlay(this.back, { + x: x - ToolBar.SPACING, + y: y - ToolBar.SPACING + }); + } } - this.setAlpha = function(alpha) { - for(var tool in this.tools) { + this.setAlpha = function(alpha, tool) { + if(typeof(tool) === 'undefined') { + for(var tool in this.tools) { + this.tools[tool].setAlpha(alpha); + } + if (this.back != null) { + Overlays.editOverlay(this.back, { alpha: alpha}); + } + } else { this.tools[tool].setAlpha(alpha); } } + + this.setBack = function(color, alpha) { + if (color == null) { + Overlays.editOverlay(this.back, { + visible: false + }); + } else { + Overlays.editOverlay(this.back, { + visible: true, + backgroundColor: color, + alpha: alpha + }) + } + } this.show = function(doShow) { for(var tool in this.tools) { this.tools[tool].show(doShow); } + if (this.back != null) { + Overlays.editOverlay(this.back, { visible: doShow}); + } } this.clicked = function(clickedOverlay) { @@ -200,6 +244,11 @@ ToolBar = function(x, y, direction) { delete this.tools[tool]; } + if (this.back != null) { + Overlays.deleteOverlay(this.back); + this.back = null; + } + this.tools = []; this.x = x; this.y = y; From 56f7f88d75ebbddec2ab4ffcbf00c56af8b37030 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 20 Aug 2014 18:17:38 -0700 Subject: [PATCH 155/206] Changed icon URLs to the right ones --- examples/Recorder.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/Recorder.js b/examples/Recorder.js index d691b64481..e37b19b268 100644 --- a/examples/Recorder.js +++ b/examples/Recorder.js @@ -14,9 +14,11 @@ Script.include("toolBars.js"); var recordingFile = "recording.rec"; var windowDimensions = Controller.getViewportDimensions(); -var TOOL_ICON_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; +var TOOL_ICON_URL = "http://s3-us-west-1.amazonaws.com/highfidelity-public/images/tools/"; var ALPHA_ON = 1.0; var ALPHA_OFF = 0.7; +var COLOR_ON = { red: 128, green: 0, blue: 0 }; +var COLOR_OFF = { red: 128, green: 128, blue: 128 }; Tool.IMAGE_WIDTH *= 0.7; Tool.IMAGE_HEIGHT *= 0.7; @@ -34,18 +36,18 @@ function setupToolBar() { } toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); - toolBar.setBack({ red: 128, green: 128, blue: 128 }, 0.8); + toolBar.setBack(COLOR_OFF, ALPHA_OFF); recordIcon = toolBar.addTool({ - imageURL: "file:/Users/clement/Downloads/svg/camera8.svg", + imageURL: TOOL_ICON_URL + "record.svg", width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT, - alpha: (MyAvatar.isRecording()) ? ALPHA_ON : ALPHA_OFF, + alpha: ALPHA_ON, visible: true }, false); playIcon = toolBar.addTool({ - imageURL: "file:/Users/clement/Downloads/svg/media23.svg", + imageURL: TOOL_ICON_URL + "play.svg", width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT, alpha: ALPHA_ON, @@ -53,7 +55,7 @@ function setupToolBar() { }, false, false); saveIcon = toolBar.addTool({ - imageURL: "file:/Users/clement/Downloads/svg/save15.svg", + imageURL: TOOL_ICON_URL + "save.svg", width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT, alpha: ALPHA_ON, @@ -61,7 +63,7 @@ function setupToolBar() { }, false, false); loadIcon = toolBar.addTool({ - imageURL: "file:/Users/clement/Downloads/svg/upload2.svg", + imageURL: TOOL_ICON_URL + "load.svg", width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT, alpha: ALPHA_ON, @@ -83,13 +85,11 @@ function mousePressEvent(event) { if (recordIcon === toolBar.clicked(clickedOverlay)) { if (!MyAvatar.isRecording()) { MyAvatar.startRecording(); - toolBar.setAlpha(ALPHA_ON, recordIcon); - toolBar.setBack({ red: 128, green: 0, blue: 0 }, 0.9); + toolBar.setBack(COLOR_ON, ALPHA_ON); } else { MyAvatar.stopRecording(); MyAvatar.loadLastRecording(); - toolBar.setAlpha(ALPHA_OFF, recordIcon); - toolBar.setBack({ red: 128, green: 128, blue: 128 }, 0.8); + toolBar.setBack(COLOR_OFF, ALPHA_OFF); } } else if (playIcon === toolBar.clicked(clickedOverlay)) { if (!MyAvatar.isRecording()) { From 72500630b014f5f45131253d67d3b24ea002fd49 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 20 Aug 2014 19:26:06 -0700 Subject: [PATCH 156/206] Basic splatting. --- .../shaders/metavoxel_heightfield_splat.frag | 16 +++- .../shaders/metavoxel_heightfield_splat.vert | 19 ++++- interface/src/MetavoxelSystem.cpp | 63 ++++++++++++++-- interface/src/MetavoxelSystem.h | 4 + .../metavoxels/src/AttributeRegistry.cpp | 74 ++++++++++++------- libraries/metavoxels/src/AttributeRegistry.h | 1 - 6 files changed, 138 insertions(+), 39 deletions(-) diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.frag b/interface/resources/shaders/metavoxel_heightfield_splat.frag index 7b73cf331b..6a3c058b80 100644 --- a/interface/resources/shaders/metavoxel_heightfield_splat.frag +++ b/interface/resources/shaders/metavoxel_heightfield_splat.frag @@ -11,10 +11,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the diffuse texture -uniform sampler2D diffuseMap; +const int SPLAT_COUNT = 4; + +// the splat textures +uniform sampler2D diffuseMaps[SPLAT_COUNT]; + +// alpha values for the four splat textures +varying vec4 alphaValues; void main(void) { - // compute the base color based on OpenGL lighting model - gl_FragColor = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st); + // blend the splat textures + gl_FragColor = gl_Color * (texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x + + texture2D(diffuseMaps[1], gl_TexCoord[0].st) * alphaValues.y + + texture2D(diffuseMaps[2], gl_TexCoord[0].st) * alphaValues.z + + texture2D(diffuseMaps[3], gl_TexCoord[0].st) * alphaValues.w); } diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.vert b/interface/resources/shaders/metavoxel_heightfield_splat.vert index 0f36605454..950162073e 100644 --- a/interface/resources/shaders/metavoxel_heightfield_splat.vert +++ b/interface/resources/shaders/metavoxel_heightfield_splat.vert @@ -14,20 +14,37 @@ // the height texture uniform sampler2D heightMap; +// the texture that contains the texture indices +uniform sampler2D textureMap; + // the distance between height points in texture space uniform float heightScale; // the scale between height and texture textures uniform float textureScale; +// the lower bounds of the values corresponding to the splat textures +uniform vec4 textureValueMinima; + +// the upper bounds of the values corresponding to the splat textures +uniform vec4 textureValueMaxima; + +// alpha values for the four splat textures +varying vec4 alphaValues; + void main(void) { // add the height to the position float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); // the zero height should be invisible - gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0 - step(height, 0.0)); // pass along the scaled/offset texture coordinates gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * textureScale; + + // compute the alpha values for each texture + float value = texture2D(textureMap, gl_TexCoord[0].st).r; + vec4 valueVector = vec4(value, value, value, value); + alphaValues = step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima); } diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 41ae254c71..18d76b1543 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -673,13 +673,17 @@ public: glm::vec3 vertex; }; +const int SPLAT_COUNT = 4; +const GLint SPLAT_TEXTURE_UNITS[] = { 3, 4, 5, 6 }; + void HeightfieldBuffer::render(bool cursor) { // initialize textures, etc. on first render if (_heightTextureID == 0) { glGenTextures(1, &_heightTextureID); glBindTexture(GL_TEXTURE_2D, _heightTextureID); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, _heightSize, _heightSize, 0, @@ -702,7 +706,8 @@ void HeightfieldBuffer::render(bool cursor) { if (!_texture.isEmpty()) { glGenTextures(1, &_textureTextureID); glBindTexture(GL_TEXTURE_2D, _textureTextureID); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); int textureSize = glm::sqrt(_texture.size()); @@ -800,11 +805,53 @@ void HeightfieldBuffer::render(bool cursor) { glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); glDepthFunc(GL_LEQUAL); + glDepthMask(false); glEnable(GL_BLEND); - glBlendFunc(GL_DST_COLOR, GL_ZERO); + glDisable(GL_ALPHA_TEST); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(-1.0f, -1.0f); + + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatHeightScaleLocation(), 1.0f / _heightSize); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureScaleLocation(), (float)_heightSize / innerSize); + + glBindTexture(GL_TEXTURE_2D, _textureTextureID); + const int TEXTURES_PER_SPLAT = 4; + for (int i = 0; i < _textures.size(); i += TEXTURES_PER_SPLAT) { + for (int j = 0; j < SPLAT_COUNT; j++) { + glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[j]); + int index = i + j; + if (index < _networkTextures.size()) { + const NetworkTexturePointer& texture = _networkTextures.at(index); + glBindTexture(GL_TEXTURE_2D, texture ? texture->getID() : 0); + } else { + glBindTexture(GL_TEXTURE_2D, 0); + } + } + const float QUARTER_STEP = 0.25f * EIGHT_BIT_MAXIMUM_RECIPROCAL; + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureValueMinimaLocation(), + (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, + (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureValueMaximaLocation(), + (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, + (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP); + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + } + + glEnable(GL_ALPHA_TEST); + glBlendFunc(GL_DST_COLOR, GL_ZERO); + + for (int i = 0; i < SPLAT_COUNT; i++) { + glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[i]); + glBindTexture(GL_TEXTURE_2D, 0); + } + + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) { @@ -831,7 +878,8 @@ void HeightfieldBuffer::render(bool cursor) { glDisable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); glDepthFunc(GL_LESS); - + glDepthMask(true); + glActiveTexture(GL_TEXTURE0); } else { @@ -992,9 +1040,12 @@ void DefaultMetavoxelRendererImplementation::init() { _splatHeightfieldProgram.bind(); _splatHeightfieldProgram.setUniformValue("heightMap", 0); - _splatHeightfieldProgram.setUniformValue("diffuseMap", 1); + _splatHeightfieldProgram.setUniformValue("textureMap", 1); + _splatHeightfieldProgram.setUniformValueArray("diffuseMaps", SPLAT_TEXTURE_UNITS, SPLAT_COUNT); _splatHeightScaleLocation = _splatHeightfieldProgram.uniformLocation("heightScale"); _splatTextureScaleLocation = _splatHeightfieldProgram.uniformLocation("textureScale"); + _splatTextureValueMinimaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMinima"); + _splatTextureValueMaximaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMaxima"); _splatHeightfieldProgram.release(); _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + @@ -1638,6 +1689,8 @@ int DefaultMetavoxelRendererImplementation::_baseColorScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_splatHeightfieldProgram; int DefaultMetavoxelRendererImplementation::_splatHeightScaleLocation; int DefaultMetavoxelRendererImplementation::_splatTextureScaleLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureValueMinimaLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureValueMaximaLocation; ProgramObject DefaultMetavoxelRendererImplementation::_lightHeightfieldProgram; int DefaultMetavoxelRendererImplementation::_lightHeightScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_shadowLightHeightfieldProgram; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index dc2ff8633a..752a060b86 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -249,6 +249,8 @@ public: static ProgramObject& getSplatHeightfieldProgram() { return _splatHeightfieldProgram; } static int getSplatHeightScaleLocation() { return _splatHeightScaleLocation; } static int getSplatTextureScaleLocation() { return _splatTextureScaleLocation; } + static int getSplatTextureValueMinimaLocation() { return _splatTextureValueMinimaLocation; } + static int getSplatTextureValueMaximaLocation() { return _splatTextureValueMaximaLocation; } static ProgramObject& getLightHeightfieldProgram() { return _lightHeightfieldProgram; } static int getLightHeightScaleLocation() { return _lightHeightScaleLocation; } @@ -292,6 +294,8 @@ private: static ProgramObject _splatHeightfieldProgram; static int _splatHeightScaleLocation; static int _splatTextureScaleLocation; + static int _splatTextureValueMinimaLocation; + static int _splatTextureValueMaximaLocation; static ProgramObject _lightHeightfieldProgram; static int _lightHeightScaleLocation; diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index c09848c201..94f0abaee4 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -932,6 +932,29 @@ void HeightfieldColorData::set(const QImage& image) { memcpy(_contents.data(), image.constBits(), _contents.size()); } +const int TEXTURE_HEADER_SIZE = sizeof(qint32) * 4; + +static QByteArray encodeTexture(int offsetX, int offsetY, int width, int height, const QByteArray& contents) { + QByteArray inflated(TEXTURE_HEADER_SIZE, 0); + qint32* header = (qint32*)inflated.data(); + *header++ = offsetX; + *header++ = offsetY; + *header++ = width; + *header++ = height; + inflated.append(contents); + return qCompress(inflated); +} + +static QByteArray decodeTexture(const QByteArray& encoded, int& offsetX, int& offsetY, int& width, int& height) { + QByteArray inflated = qUncompress(encoded); + const qint32* header = (const qint32*)inflated.constData(); + offsetX = *header++; + offsetY = *header++; + width = *header++; + height = *header++; + return inflated.mid(TEXTURE_HEADER_SIZE); +} + HeightfieldTextureData::HeightfieldTextureData(const QByteArray& contents, const QVector& textures) : HeightfieldData(contents), _textures(textures) { @@ -951,32 +974,31 @@ HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes, const H in.readDelta(_textures, reference->getTextures()); reference->setDeltaData(HeightfieldDataPointer(this)); _contents = reference->getContents(); - QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); - if (image.isNull()) { + + int offsetX, offsetY, width, height; + QByteArray delta = decodeTexture(reference->getEncodedDelta(), offsetX, offsetY, width, height); + if (delta.isEmpty()) { return; } - QPoint offset = image.offset(); - if (offset.x() == 0) { - set(image); + if (offsetX == 0) { + _contents = delta; return; } - int minX = offset.x() - 1; - int minY = offset.y() - 1; + int minX = offsetX - 1; + int minY = offsetY - 1; int size = glm::sqrt((float)_contents.size()); + const char* src = delta.constData(); char* dest = _contents.data() + minY * size + minX; - for (int y = 0; y < image.height(); y++) { - memcpy(dest, image.constScanLine(y), image.width()); - dest += size; + for (int y = 0; y < height; y++, src += width, dest += size) { + memcpy(dest, src, width); } } void HeightfieldTextureData::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { - QImage image; int size = glm::sqrt((float)_contents.size()); - image = QImage((uchar*)_contents.data(), size, size, QImage::Format_Indexed8); - _encoded = encodeHeightfieldImage(image, true); + _encoded = encodeTexture(0, 0, size, size, _contents); } out << _encoded.size(); out.writeAligned(_encoded); @@ -990,7 +1012,6 @@ void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTexture } QMutexLocker locker(&reference->getEncodedDeltaMutex()); if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { - QImage image; int size = glm::sqrt((float)_contents.size()); int minX = size, minY = size; int maxX = -1, maxY = -1; @@ -1010,18 +1031,19 @@ void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTexture maxY = qMax(maxY, y); } } + QByteArray delta; + int width = 0, height = 0; if (maxX >= minX) { - int width = maxX - minX + 1; - int height = maxY - minY + 1; - image = QImage(width, height, QImage::Format_Indexed8); + width = maxX - minX + 1; + height = maxY - minY + 1; + delta = QByteArray(width * height, 0); + char* dest = delta.data(); src = _contents.constData() + minY * size + minX; - for (int y = 0; y < height; y++) { - memcpy(image.scanLine(y), src, width); - src += size; + for (int y = 0; y < height; y++, src += size, dest += width) { + memcpy(dest, src, width); } } - image.setOffset(QPoint(minX + 1, minY + 1)); - reference->setEncodedDelta(encodeHeightfieldImage(image, true)); + reference->setEncodedDelta(encodeTexture(minX + 1, minY + 1, width, height, delta)); reference->setDeltaData(HeightfieldDataPointer(this)); } out << reference->getEncodedDelta().size(); @@ -1030,15 +1052,11 @@ void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTexture } void HeightfieldTextureData::read(Bitstream& in, int bytes) { - set(decodeHeightfieldImage(_encoded = in.readAligned(bytes))); + int offsetX, offsetY, width, height; + _contents = decodeTexture(_encoded = in.readAligned(bytes), offsetX, offsetY, width, height); in >> _textures; } -void HeightfieldTextureData::set(const QImage& image) { - _contents.resize(image.width() * image.height()); - memcpy(_contents.data(), image.constBits(), _contents.size()); -} - HeightfieldTexture::HeightfieldTexture() { } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index f3e6dc0b97..fb6ff5097d 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -540,7 +540,6 @@ public: private: void read(Bitstream& in, int bytes); - void set(const QImage& image); QVector _textures; }; From 022109a249d5458cb8fefbf9c708eddb9438bcdb Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 20 Aug 2014 19:36:56 -0700 Subject: [PATCH 157/206] Added timestamps to the scripted UI --- examples/Recorder.js | 81 ++++++++++++++++++++++++++++--- interface/src/Recorder.cpp | 5 ++ interface/src/Recorder.h | 2 + interface/src/avatar/MyAvatar.cpp | 56 +++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 3 ++ 5 files changed, 139 insertions(+), 8 deletions(-) diff --git a/examples/Recorder.js b/examples/Recorder.js index e37b19b268..9a4375ab4f 100644 --- a/examples/Recorder.js +++ b/examples/Recorder.js @@ -29,6 +29,9 @@ var saveIcon; var loadIcon; setupToolBar(); +var timer = null; +setupTimer(); + function setupToolBar() { if (toolBar != null) { print("Multiple calls to Recorder.js:setupToolBar()"); @@ -69,14 +72,68 @@ function setupToolBar() { alpha: ALPHA_ON, visible: true }, false, false); - - moveUI(); } - + +function setupTimer() { + timer = Overlays.addOverlay("text", { + font: { size: 20 }, + text: (0.00).toFixed(3), + backgroundColor: COLOR_OFF, + x: 0, y: 0, + width: 100, + height: 100, + alpha: 1.0, + visible: true + }); +} + +function updateTimer() { + var text = ""; + if (MyAvatar.isRecording()) { + text = formatTime(MyAvatar.recorderElapsed()) + } else { + text = formatTime(MyAvatar.playerElapsed()) + " / " + + formatTime(MyAvatar.playerLength()); + } + + Overlays.editOverlay(timer, { + text: text + }) +} + +function formatTime(time) { + var MIN_PER_HOUR = 60; + var SEC_PER_MIN = 60; + var MSEC_PER_SEC = 1000; + + var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR)); + time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); + + var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN)); + time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN); + + var seconds = Math.floor(time / MSEC_PER_SEC); + seconds = time / MSEC_PER_SEC; + + var text = ""; + text += (hours > 0) ? hours + ":" : + ""; + text += (minutes > 0) ? ((minutes < 10 && text != "") ? "0" : "") + minutes + ":" : + ""; + text += ((seconds < 10 && text != "") ? "0" : "") + seconds.toFixed(3); + return text; +} + function moveUI() { var relative = { x: 30, y: 90 }; toolBar.move(relative.x, windowDimensions.y - relative.y); + Overlays.editOverlay(timer, { + x: relative.x - 10, + y: windowDimensions.y - relative.y - 35, + width: 0, + height: 0 + }); } function mousePressEvent(event) { @@ -100,11 +157,15 @@ function mousePressEvent(event) { } } } else if (saveIcon === toolBar.clicked(clickedOverlay)) { - recordingFile = Window.save("Save recording to file", ".", "*.rec"); - MyAvatar.saveRecording(recordingFile); + if (!MyAvatar.isRecording()) { + recordingFile = Window.save("Save recording to file", ".", "*.rec"); + MyAvatar.saveRecording(recordingFile); + } } else if (loadIcon === toolBar.clicked(clickedOverlay)) { - recordingFile = Window.browse("Load recorcding from file", ".", "*.rec"); - MyAvatar.loadRecording(recordingFile); + if (!MyAvatar.isRecording()) { + recordingFile = Window.browse("Load recorcding from file", ".", "*.rec"); + MyAvatar.loadRecording(recordingFile); + } } else { } @@ -117,6 +178,8 @@ function update() { windowDimensions = newDimensions; moveUI(); } + + updateTimer(); } function scriptEnding() { @@ -127,12 +190,14 @@ function scriptEnding() { MyAvatar.stopPlaying(); } toolBar.cleanup(); + Overlays.deleteOverlay(timer); } Controller.mousePressEvent.connect(mousePressEvent); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); - +// Should be called last to put everything into position +moveUI(); diff --git a/interface/src/Recorder.cpp b/interface/src/Recorder.cpp index 1d2ad3f059..fb5c92c849 100644 --- a/interface/src/Recorder.cpp +++ b/interface/src/Recorder.cpp @@ -317,6 +317,11 @@ bool Player::computeCurrentFrame() { } void writeRecordingToFile(RecordingPointer recording, QString filename) { + if (!recording || recording->getFrameNumber() < 1) { + qDebug() << "Can't save empty recording"; + return; + } + qDebug() << "Writing recording to " << filename << "."; QElapsedTimer timer; QFile file(filename); diff --git a/interface/src/Recorder.h b/interface/src/Recorder.h index 9f7eb66ec6..2670d78365 100644 --- a/interface/src/Recorder.h +++ b/interface/src/Recorder.h @@ -137,6 +137,8 @@ public: bool isPlaying() const; qint64 elapsed() const; + RecordingPointer getRecording() const { return _recording; } + // Those should only be called if isPlaying() returns true glm::quat getHeadRotation(); float getLeanSideways(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 41c0f3032b..1d271d5f40 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -509,6 +509,9 @@ bool MyAvatar::setJointReferential(int id, int jointIndex) { } bool MyAvatar::isRecording() { + if (!_recorder) { + return false; + } if (QThread::currentThread() != thread()) { bool result; QMetaObject::invokeMethod(this, "isRecording", Qt::BlockingQueuedConnection, @@ -518,6 +521,19 @@ bool MyAvatar::isRecording() { return _recorder && _recorder->isRecording(); } +qint64 MyAvatar::recorderElapsed() { + if (!_recorder) { + return 0; + } + if (QThread::currentThread() != thread()) { + qint64 result; + QMetaObject::invokeMethod(this, "recorderElapsed", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(qint64, result)); + return result; + } + return _recorder->elapsed(); +} + void MyAvatar::startRecording() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startRecording", Qt::BlockingQueuedConnection); @@ -532,6 +548,9 @@ void MyAvatar::startRecording() { } void MyAvatar::stopRecording() { + if (!_recorder) { + return; + } if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "stopRecording", Qt::BlockingQueuedConnection); return; @@ -542,6 +561,10 @@ void MyAvatar::stopRecording() { } void MyAvatar::saveRecording(QString filename) { + if (!_recorder) { + qDebug() << "There is no recording to save"; + return; + } if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "saveRecording", Qt::BlockingQueuedConnection, Q_ARG(QString, filename)); @@ -553,6 +576,9 @@ void MyAvatar::saveRecording(QString filename) { } bool MyAvatar::isPlaying() { + if (!_player) { + return false; + } if (QThread::currentThread() != thread()) { bool result; QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection, @@ -562,6 +588,32 @@ bool MyAvatar::isPlaying() { return _player && _player->isPlaying(); } +qint64 MyAvatar::playerElapsed() { + if (!_player) { + return 0; + } + if (QThread::currentThread() != thread()) { + qint64 result; + QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(qint64, result)); + return result; + } + return _player->elapsed(); +} + +qint64 MyAvatar::playerLength() { + if (!_player) { + return 0; + } + if (QThread::currentThread() != thread()) { + qint64 result; + QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(qint64, result)); + return result; + } + return _player->getRecording()->getLength(); +} + void MyAvatar::loadRecording(QString filename) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, @@ -581,6 +633,7 @@ void MyAvatar::loadLastRecording() { return; } if (!_recorder) { + qDebug() << "There is no recording to load"; return; } if (!_player) { @@ -604,6 +657,9 @@ void MyAvatar::startPlaying() { } void MyAvatar::stopPlaying() { + if (!_player) { + return; + } if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "stopPlaying", Qt::BlockingQueuedConnection); return; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0af105725b..b7abe96afa 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -162,11 +162,14 @@ public slots: bool setJointReferential(int id, int jointIndex); bool isRecording(); + qint64 recorderElapsed(); void startRecording(); void stopRecording(); void saveRecording(QString filename); bool isPlaying(); + qint64 playerElapsed(); + qint64 playerLength(); void loadRecording(QString filename); void loadLastRecording(); void startPlaying(); From 080c1917271b0594e9d5395d3cf4331fda85221b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 21 Aug 2014 08:20:31 -0700 Subject: [PATCH 158/206] namechange updateConstraint() -> buildConstraint() --- interface/src/renderer/JointState.cpp | 2 +- interface/src/renderer/JointState.h | 2 +- interface/src/renderer/Model.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/renderer/JointState.cpp b/interface/src/renderer/JointState.cpp index 94b4b37a3c..9ca1bf7492 100644 --- a/interface/src/renderer/JointState.cpp +++ b/interface/src/renderer/JointState.cpp @@ -73,7 +73,7 @@ void JointState::setFBXJoint(const FBXJoint* joint) { } } -void JointState::updateConstraint() { +void JointState::buildConstraint() { if (_constraint) { delete _constraint; _constraint = NULL; diff --git a/interface/src/renderer/JointState.h b/interface/src/renderer/JointState.h index 56044125f1..f98438e34d 100644 --- a/interface/src/renderer/JointState.h +++ b/interface/src/renderer/JointState.h @@ -30,7 +30,7 @@ public: void setFBXJoint(const FBXJoint* joint); const FBXJoint& getFBXJoint() const { return *_fbxJoint; } - void updateConstraint(); + void buildConstraint(); void copyState(const JointState& state); void initTransform(const glm::mat4& parentTransform); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 290f9b5c6f..d70ecc4e6c 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -547,7 +547,7 @@ void Model::setJointStates(QVector states) { if (distance > radius) { radius = distance; } - _jointStates[i].updateConstraint(); + _jointStates[i].buildConstraint(); } for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].slaveVisibleTransform(); @@ -1192,7 +1192,7 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm: } // Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose - // at in the process. This provides stability to the IK solution for most models. + // in the process. This provides stability to the IK solution for most models. glm::quat oldNextRotation = nextState.getRotation(); float mixFactor = 0.03f; nextState.mixRotationDelta(deltaRotation, mixFactor, priority); From 8760329323ee1cf8b07fb97ae44a908f1c953420 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 21 Aug 2014 11:08:12 -0700 Subject: [PATCH 159/206] Remove deprecated Audio class Player member --- interface/src/Audio.h | 2 -- interface/src/avatar/MyAvatar.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 59a6d942a6..ffa455908b 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -105,7 +105,6 @@ public: float getAudioOutputAverageMsecsUnplayed() const { return (float)_audioOutputMsecsUnplayedStats.getWindowAverage(); } void setRecorder(RecorderPointer recorder) { _recorder = recorder; } - void setPlayer(PlayerPointer player) { _player = player; } public slots: void start(); @@ -314,7 +313,6 @@ private: AudioOutputIODevice _audioOutputIODevice; WeakRecorderPointer _recorder; - WeakPlayerPointer _player; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1d271d5f40..ed764d7536 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -652,7 +652,6 @@ void MyAvatar::startPlaying() { _player = PlayerPointer(new Player(this)); } - Application::getInstance()->getAudio()->setPlayer(_player); _player->startPlaying(); } From 74b39773d601df830dc44959240377d471a35a01 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 21 Aug 2014 14:50:58 -0700 Subject: [PATCH 160/206] Don't merge after expansion; it breaks stuff. --- libraries/metavoxels/src/AttributeRegistry.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 94f0abaee4..e823e081cc 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -274,9 +274,7 @@ MetavoxelNode* Attribute::expandMetavoxelRoot(const MetavoxelNode& root) { MetavoxelNode* newGrandchild = new MetavoxelNode(attribute); newChild->setChild((index + j) % MetavoxelNode::CHILD_COUNT, newGrandchild); } - newChild->mergeChildren(attribute); } - newParent->mergeChildren(attribute); return newParent; } @@ -1501,9 +1499,7 @@ MetavoxelNode* SharedObjectSetAttribute::expandMetavoxelRoot(const MetavoxelNode MetavoxelNode* newGrandchild = new MetavoxelNode(attribute); newChild->setChild((index + j) % MetavoxelNode::CHILD_COUNT, newGrandchild); } - newChild->mergeChildren(attribute); } - newParent->mergeChildren(attribute); return newParent; } From e152e0c2bea918a7e117bf844640bed26ee5c9df Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 21 Aug 2014 15:22:34 -0700 Subject: [PATCH 161/206] Detachment fix. --- interface/src/ui/MetavoxelEditor.cpp | 4 +++- libraries/metavoxels/src/SharedObject.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 3ca509cd95..ce09e3657d 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1188,7 +1188,9 @@ QVariant HeightfieldTextureBrushTool::createEdit(bool alternate) { if (alternate) { return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), SharedObjectPointer(), QColor())); } else { - return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), _textureEditor->getObject(), + SharedObjectPointer texture = _textureEditor->getObject(); + _textureEditor->detachObject(); + return QVariant::fromValue(PaintHeightfieldTextureEdit(_position, _radius->value(), texture, _texture ? _texture->getAverageColor() : QColor())); } } diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index c125e1352b..bf9b123a36 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -193,7 +193,9 @@ void SharedObjectEditor::detachObject() { for (int i = 0; i < form->rowCount(); i++) { QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget(); QMetaProperty property = metaObject->property(widget->property("propertyIndex").toInt()); - connect(_object.data(), signal(property.notifySignal().methodSignature()), SLOT(updateProperty())); + if (property.hasNotifySignal()) { + connect(_object.data(), signal(property.notifySignal().methodSignature()), SLOT(updateProperty())); + } } } From f207abdd98ae6afa27ff033a962f5757762c7aef Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 21 Aug 2014 15:24:19 -0700 Subject: [PATCH 162/206] Move Player over to AvatarData for ACs to use --- interface/src/avatar/MyAvatar.cpp | 77 ---------------- interface/src/avatar/MyAvatar.h | 9 -- libraries/avatars/src/AvatarData.cpp | 88 +++++++++++++++++++ libraries/avatars/src/AvatarData.h | 11 +++ .../avatars}/src/Recorder.cpp | 10 ++- .../avatars}/src/Recorder.h | 2 +- 6 files changed, 108 insertions(+), 89 deletions(-) rename {interface => libraries/avatars}/src/Recorder.cpp (98%) rename {interface => libraries/avatars}/src/Recorder.h (99%) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ed764d7536..2202eafc00 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -575,58 +575,6 @@ void MyAvatar::saveRecording(QString filename) { } } -bool MyAvatar::isPlaying() { - if (!_player) { - return false; - } - if (QThread::currentThread() != thread()) { - bool result; - QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, result)); - return result; - } - return _player && _player->isPlaying(); -} - -qint64 MyAvatar::playerElapsed() { - if (!_player) { - return 0; - } - if (QThread::currentThread() != thread()) { - qint64 result; - QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(qint64, result)); - return result; - } - return _player->elapsed(); -} - -qint64 MyAvatar::playerLength() { - if (!_player) { - return 0; - } - if (QThread::currentThread() != thread()) { - qint64 result; - QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(qint64, result)); - return result; - } - return _player->getRecording()->getLength(); -} - -void MyAvatar::loadRecording(QString filename) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, - Q_ARG(QString, filename)); - return; - } - if (!_player) { - _player = PlayerPointer(new Player(this)); - } - - _player->loadFromFile(filename); -} - void MyAvatar::loadLastRecording() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadLastRecording", Qt::BlockingQueuedConnection); @@ -643,31 +591,6 @@ void MyAvatar::loadLastRecording() { _player->loadRecording(_recorder->getRecording()); } -void MyAvatar::startPlaying() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); - return; - } - if (!_player) { - _player = PlayerPointer(new Player(this)); - } - - _player->startPlaying(); -} - -void MyAvatar::stopPlaying() { - if (!_player) { - return; - } - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "stopPlaying", Qt::BlockingQueuedConnection); - return; - } - if (_player) { - _player->stopPlaying(); - } -} - void MyAvatar::setLocalGravity(glm::vec3 gravity) { _motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY; // Environmental and Local gravities are incompatible. Since Local is being set here diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 37daab3fa8..0ba2706e7d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -174,15 +174,7 @@ public slots: void startRecording(); void stopRecording(); void saveRecording(QString filename); - - bool isPlaying(); - qint64 playerElapsed(); - qint64 playerLength(); - void loadRecording(QString filename); void loadLastRecording(); - void startPlaying(); - void stopPlaying(); - signals: void transformChanged(); @@ -222,7 +214,6 @@ private: PhysicsSimulation _physicsSimulation; RecorderPointer _recorder; - PlayerPointer _player; // private methods float computeDistanceToFloor(const glm::vec3& startPoint); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 0e7d3228f9..0ec008d0cd 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -585,6 +585,94 @@ bool AvatarData::hasReferential() { return _referential != NULL; } +bool AvatarData::isPlaying() { + if (!_player) { + return false; + } + if (QThread::currentThread() != thread()) { + bool result; + QMetaObject::invokeMethod(this, "isPlaying", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result)); + return result; + } + return _player && _player->isPlaying(); +} + +qint64 AvatarData::playerElapsed() { + if (!_player) { + return 0; + } + if (QThread::currentThread() != thread()) { + qint64 result; + QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(qint64, result)); + return result; + } + return _player->elapsed(); +} + +qint64 AvatarData::playerLength() { + if (!_player) { + return 0; + } + if (QThread::currentThread() != thread()) { + qint64 result; + QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(qint64, result)); + return result; + } + return _player->getRecording()->getLength(); +} + +void AvatarData::loadRecording(QString filename) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "loadRecording", Qt::BlockingQueuedConnection, + Q_ARG(QString, filename)); + return; + } + if (!_player) { + _player = PlayerPointer(new Player(this)); + } + + _player->loadFromFile(filename); +} + +void AvatarData::startPlaying() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); + return; + } + if (!_player) { + _player = PlayerPointer(new Player(this)); + } + + _player->startPlaying(); +} + +void AvatarData::play() { + if (isPlaying()) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "play", Qt::BlockingQueuedConnection); + return; + } + + _player->play(); + } +} + +void AvatarData::stopPlaying() { + if (!_player) { + return; + } + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stopPlaying", Qt::BlockingQueuedConnection); + return; + } + if (_player) { + _player->stopPlaying(); + } +} + void AvatarData::changeReferential(Referential *ref) { delete _referential; _referential = ref; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index fa884c0229..12fe3adb6e 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -49,6 +49,7 @@ typedef unsigned long long quint64; #include +#include "Recorder.h" #include "Referential.h" #include "HeadData.h" #include "HandData.h" @@ -298,6 +299,14 @@ public slots: void setSessionUUID(const QUuid& sessionUUID) { _sessionUUID = sessionUUID; } bool hasReferential(); + bool isPlaying(); + qint64 playerElapsed(); + qint64 playerLength(); + void loadRecording(QString filename); + void startPlaying(); + void play(); + void stopPlaying(); + protected: QUuid _sessionUUID; glm::vec3 _position; @@ -351,6 +360,8 @@ protected: QWeakPointer _owningAvatarMixer; QElapsedTimer _lastUpdateTimer; + PlayerPointer _player; + /// Loads the joint indices, names from the FST file (if any) virtual void updateJointMappings(); void changeReferential(Referential* ref); diff --git a/interface/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp similarity index 98% rename from interface/src/Recorder.cpp rename to libraries/avatars/src/Recorder.cpp index fb5c92c849..09dea9fe22 100644 --- a/interface/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -15,6 +15,7 @@ #include #include +#include "AvatarData.h" #include "Recorder.h" void RecordingFrame::setBlendshapeCoefficients(QVector blendshapeCoefficients) { @@ -281,7 +282,9 @@ void Player::play() { _avatar->setTargetScale(_recording->getFrame(_currentFrame).getScale()); _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); HeadData* head = const_cast(_avatar->getHeadData()); - head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); + if (head) { + head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); + } } else { _avatar->setPosition(_recording->getFrame(0).getTranslation() + _recording->getFrame(_currentFrame).getTranslation()); @@ -291,7 +294,10 @@ void Player::play() { _recording->getFrame(_currentFrame).getScale()); _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); HeadData* head = const_cast(_avatar->getHeadData()); - head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); + if (head) { + + head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); + } } _options.setPosition(_avatar->getPosition()); diff --git a/interface/src/Recorder.h b/libraries/avatars/src/Recorder.h similarity index 99% rename from interface/src/Recorder.h rename to libraries/avatars/src/Recorder.h index 2670d78365..770027a21d 100644 --- a/interface/src/Recorder.h +++ b/libraries/avatars/src/Recorder.h @@ -23,10 +23,10 @@ #include #include -#include #include #include +class AvatarData; class Recorder; class Recording; class Player; From 6e06b637096fb60c3632e7c68123568bb5ca2fd4 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 21 Aug 2014 16:33:09 -0700 Subject: [PATCH 163/206] For splat textures, use transparent white as a default. --- interface/src/MetavoxelSystem.cpp | 2 +- interface/src/renderer/GeometryCache.cpp | 9 ++++--- interface/src/renderer/TextureCache.cpp | 34 ++++++++++++++++++------ interface/src/renderer/TextureCache.h | 6 +++-- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 18d76b1543..9a143706ed 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -719,7 +719,7 @@ void HeightfieldBuffer::render(bool cursor) { const SharedObjectPointer texture = _textures.at(i); if (texture) { _networkTextures[i] = Application::getInstance()->getTextureCache()->getTexture( - static_cast(texture.data())->getURL()); + static_cast(texture.data())->getURL(), SPLAT_TEXTURE); } } } diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index b5bd63ab87..3cfc5efd5f 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -593,17 +593,20 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { NetworkMeshPart networkPart; if (!part.diffuseTexture.filename.isEmpty()) { networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content); + _textureBase.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE, + mesh.isEye, part.diffuseTexture.content); networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); } if (!part.normalTexture.filename.isEmpty()) { networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content); + _textureBase.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE, + false, part.normalTexture.content); networkPart.normalTexture->setLoadPriorities(_loadPriorities); } if (!part.specularTexture.filename.isEmpty()) { networkPart.specularTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.specularTexture.filename)), true, false, part.specularTexture.content); + _textureBase.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE, + false, part.specularTexture.content); networkPart.specularTexture->setLoadPriorities(_loadPriorities); } networkMesh.parts.append(networkPart); diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index ee10d67ee9..c3e58d52bb 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -145,6 +145,8 @@ GLuint TextureCache::getPermutationNormalTextureID() { } const unsigned char OPAQUE_WHITE[] = { 0xFF, 0xFF, 0xFF, 0xFF }; +const unsigned char TRANSPARENT_WHITE[] = { 0xFF, 0xFF, 0xFF, 0x0 }; +const unsigned char OPAQUE_BLACK[] = { 0x0, 0x0, 0x0, 0xFF }; const unsigned char OPAQUE_BLUE[] = { 0x80, 0x80, 0xFF, 0xFF }; static void loadSingleColorTexture(const unsigned char* color) { @@ -175,13 +177,13 @@ GLuint TextureCache::getBlueTextureID() { /// Extra data for creating textures. class TextureExtra { public: - bool normalMap; + TextureType type; const QByteArray& content; }; -NetworkTexturePointer TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable, const QByteArray& content) { +NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type, bool dilatable, const QByteArray& content) { if (!dilatable) { - TextureExtra extra = { normalMap, content }; + TextureExtra extra = { type, content }; return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast(); } NetworkTexturePointer texture = _dilatableNetworkTextures.value(url); @@ -292,7 +294,7 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) { QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { const TextureExtra* textureExtra = static_cast(extra); - return QSharedPointer(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content), + return QSharedPointer(new NetworkTexture(url, textureExtra->type, textureExtra->content), &Resource::allReferencesCleared); } @@ -316,7 +318,7 @@ Texture::~Texture() { glDeleteTextures(1, &_id); } -NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) : +NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) : Resource(url, !content.isEmpty()), _translucent(false) { @@ -324,9 +326,25 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray _loaded = true; } - // default to white/blue + // default to white/blue/black glBindTexture(GL_TEXTURE_2D, getID()); - loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE); + switch (type) { + case NORMAL_TEXTURE: + loadSingleColorTexture(OPAQUE_BLUE); + break; + + case SPECULAR_TEXTURE: + loadSingleColorTexture(OPAQUE_BLACK); + break; + + case SPLAT_TEXTURE: + loadSingleColorTexture(TRANSPARENT_WHITE); + break; + + default: + loadSingleColorTexture(OPAQUE_WHITE); + break; + } glBindTexture(GL_TEXTURE_2D, 0); // if we have content, load it after we have our self pointer @@ -465,7 +483,7 @@ void NetworkTexture::imageLoaded(const QImage& image) { } DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) : - NetworkTexture(url, false, content), + NetworkTexture(url, DEFAULT_TEXTURE, content), _innerRadius(0), _outerRadius(0) { diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 06f724d70d..83d9b6cc74 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -25,6 +25,8 @@ class NetworkTexture; typedef QSharedPointer NetworkTexturePointer; +enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, SPECULAR_TEXTURE, SPLAT_TEXTURE }; + /// Stores cached textures, including render-to-texture targets. class TextureCache : public ResourceCache { Q_OBJECT @@ -49,7 +51,7 @@ public: GLuint getBlueTextureID(); /// Loads a texture from the specified URL. - NetworkTexturePointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false, + NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, bool dilatable = false, const QByteArray& content = QByteArray()); /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is @@ -123,7 +125,7 @@ class NetworkTexture : public Resource, public Texture { public: - NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content); + NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); /// Checks whether it "looks like" this texture is translucent /// (majority of pixels neither fully opaque or fully transparent). From 3ebd8c1969dada783945613ce7681144cc59b319 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 21 Aug 2014 16:40:25 -0700 Subject: [PATCH 164/206] use dispatchTable for shape-vs-shape collisions --- interface/src/avatar/MyAvatar.cpp | 1 + interface/src/avatar/SkeletonModel.cpp | 18 +- libraries/fbx/src/FBXReader.cpp | 8 +- libraries/shared/src/CapsuleShape.cpp | 8 +- libraries/shared/src/PhysicsEntity.cpp | 2 + libraries/shared/src/PhysicsSimulation.cpp | 3 +- libraries/shared/src/PlaneShape.cpp | 2 +- libraries/shared/src/Shape.h | 23 +- libraries/shared/src/ShapeCollider.cpp | 232 ++++++++++++--------- libraries/shared/src/ShapeCollider.h | 48 +++-- libraries/shared/src/SphereShape.h | 6 +- tests/physics/src/ShapeColliderTests.cpp | 37 ++++ tests/physics/src/ShapeColliderTests.h | 2 + tests/physics/src/VerletShapeTests.cpp | 2 + 14 files changed, 240 insertions(+), 152 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1d271d5f40..239ca16186 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -81,6 +81,7 @@ MyAvatar::MyAvatar() : _billboardValid(false), _physicsSimulation() { + ShapeCollider::initDispatchTable(); for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index dd398f2b2b..e5e91cb886 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -618,19 +618,19 @@ void SkeletonModel::buildShapes() { Shape::Type type = joint.shapeType; int parentIndex = joint.parentIndex; if (parentIndex == -1 || radius < EPSILON) { - type = Shape::UNKNOWN_SHAPE; - } else if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) { + type = UNKNOWN_SHAPE; + } else if (type == CAPSULE_SHAPE && halfHeight < EPSILON) { // this shape is forced to be a sphere - type = Shape::SPHERE_SHAPE; + type = SPHERE_SHAPE; } Shape* shape = NULL; - if (type == Shape::SPHERE_SHAPE) { + if (type == SPHERE_SHAPE) { shape = new VerletSphereShape(radius, &(points[i])); shape->setEntity(this); float mass = massScale * glm::max(MIN_JOINT_MASS, DENSITY_OF_WATER * shape->getVolume()); points[i].setMass(mass); totalMass += mass; - } else if (type == Shape::CAPSULE_SHAPE) { + } else if (type == CAPSULE_SHAPE) { assert(parentIndex != -1); shape = new VerletCapsuleShape(radius, &(points[parentIndex]), &(points[i])); shape->setEntity(this); @@ -729,7 +729,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { shapeExtents.reset(); glm::vec3 localPosition = shape->getTranslation(); int type = shape->getType(); - if (type == Shape::CAPSULE_SHAPE) { + if (type == CAPSULE_SHAPE) { // add the two furthest surface points of the capsule CapsuleShape* capsule = static_cast(shape); glm::vec3 axis; @@ -741,7 +741,7 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { shapeExtents.addPoint(localPosition + axis); shapeExtents.addPoint(localPosition - axis); totalExtents.addExtents(shapeExtents); - } else if (type == Shape::SPHERE_SHAPE) { + } else if (type == SPHERE_SHAPE) { float radius = shape->getBoundingRadius(); glm::vec3 axis = glm::vec3(radius); shapeExtents.addPoint(localPosition + axis); @@ -845,13 +845,13 @@ void SkeletonModel::renderJointCollisionShapes(float alpha) { glPushMatrix(); // shapes are stored in simulation-frame but we want position to be model-relative - if (shape->getType() == Shape::SPHERE_SHAPE) { + if (shape->getType() == SPHERE_SHAPE) { glm::vec3 position = shape->getTranslation() - simulationTranslation; glTranslatef(position.x, position.y, position.z); // draw a grey sphere at shape position glColor4f(0.75f, 0.75f, 0.75f, alpha); glutSolidSphere(shape->getBoundingRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); - } else if (shape->getType() == Shape::CAPSULE_SHAPE) { + } else if (shape->getType() == CAPSULE_SHAPE) { CapsuleShape* capsule = static_cast(shape); // draw a blue sphere at the capsule endpoint diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 1a152dc217..d8b52fb794 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1503,7 +1503,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) joint.inverseBindRotation = joint.inverseDefaultRotation; joint.name = model.name; joint.shapePosition = glm::vec3(0.f); - joint.shapeType = Shape::UNKNOWN_SHAPE; + joint.shapeType = UNKNOWN_SHAPE; geometry.joints.append(joint); geometry.jointIndices.insert(model.name, geometry.joints.size()); @@ -1848,10 +1848,10 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) if (collideLikeCapsule) { joint.shapeRotation = rotationBetween(defaultCapsuleAxis, jointShapeInfo.boneBegin); joint.shapePosition = 0.5f * jointShapeInfo.boneBegin; - joint.shapeType = Shape::CAPSULE_SHAPE; + joint.shapeType = CAPSULE_SHAPE; } else { // collide the joint like a sphere - joint.shapeType = Shape::SPHERE_SHAPE; + joint.shapeType = SPHERE_SHAPE; if (jointShapeInfo.numVertices > 0) { jointShapeInfo.averageVertex /= (float)jointShapeInfo.numVertices; joint.shapePosition = jointShapeInfo.averageVertex; @@ -1872,7 +1872,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) // The shape is further from both joint endpoints than the endpoints are from each other // which probably means the model has a bad transform somewhere. We disable this shape // by setting its type to UNKNOWN_SHAPE. - joint.shapeType = Shape::UNKNOWN_SHAPE; + joint.shapeType = UNKNOWN_SHAPE; } } } diff --git a/libraries/shared/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp index 03bc48bd94..09776a233f 100644 --- a/libraries/shared/src/CapsuleShape.cpp +++ b/libraries/shared/src/CapsuleShape.cpp @@ -18,20 +18,20 @@ #include "SharedUtil.h" -CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {} +CapsuleShape::CapsuleShape() : Shape(CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {} -CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(Shape::CAPSULE_SHAPE), +CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(CAPSULE_SHAPE), _radius(radius), _halfHeight(halfHeight) { updateBoundingRadius(); } CapsuleShape::CapsuleShape(float radius, float halfHeight, const glm::vec3& position, const glm::quat& rotation) : - Shape(Shape::CAPSULE_SHAPE, position, rotation), _radius(radius), _halfHeight(halfHeight) { + Shape(CAPSULE_SHAPE, position, rotation), _radius(radius), _halfHeight(halfHeight) { updateBoundingRadius(); } CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint) : - Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(0.0f) { + Shape(CAPSULE_SHAPE), _radius(radius), _halfHeight(0.0f) { setEndPoints(startPoint, endPoint); } diff --git a/libraries/shared/src/PhysicsEntity.cpp b/libraries/shared/src/PhysicsEntity.cpp index 09b00c201c..6be37a7528 100644 --- a/libraries/shared/src/PhysicsEntity.cpp +++ b/libraries/shared/src/PhysicsEntity.cpp @@ -12,8 +12,10 @@ #include "PhysicsEntity.h" #include "PhysicsSimulation.h" +#include "PlaneShape.h" #include "Shape.h" #include "ShapeCollider.h" +#include "SphereShape.h" PhysicsEntity::PhysicsEntity() : _translation(0.0f), diff --git a/libraries/shared/src/PhysicsSimulation.cpp b/libraries/shared/src/PhysicsSimulation.cpp index 6c4901bcd5..b58f62dfd4 100644 --- a/libraries/shared/src/PhysicsSimulation.cpp +++ b/libraries/shared/src/PhysicsSimulation.cpp @@ -16,8 +16,9 @@ #include "PerfStat.h" #include "PhysicsEntity.h" #include "Ragdoll.h" -#include "SharedUtil.h" +#include "Shape.h" #include "ShapeCollider.h" +#include "SharedUtil.h" int MAX_DOLLS_PER_SIMULATION = 16; int MAX_ENTITIES_PER_SIMULATION = 64; diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp index 15ea281510..72704c3116 100644 --- a/libraries/shared/src/PlaneShape.cpp +++ b/libraries/shared/src/PlaneShape.cpp @@ -15,7 +15,7 @@ const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f); PlaneShape::PlaneShape(const glm::vec4& coefficients) : - Shape(Shape::PLANE_SHAPE) { + Shape(PLANE_SHAPE) { glm::vec3 normal = glm::vec3(coefficients); _translation = -normal * coefficients.w; diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h index 2efa5b824f..9c30a0fdf4 100644 --- a/libraries/shared/src/Shape.h +++ b/libraries/shared/src/Shape.h @@ -22,17 +22,18 @@ class VerletPoint; const float MAX_SHAPE_MASS = 1.0e18f; // something less than sqrt(FLT_MAX) +const quint8 SPHERE_SHAPE = 0; +const quint8 CAPSULE_SHAPE = 1; +const quint8 PLANE_SHAPE = 2; +const quint8 LIST_SHAPE = 3; +const quint8 UNKNOWN_SHAPE = 4; + class Shape { public: - static quint32 getNextID() { static quint32 nextID = 0; return ++nextID; } - enum Type{ - UNKNOWN_SHAPE = 0, - SPHERE_SHAPE, - CAPSULE_SHAPE, - PLANE_SHAPE, - LIST_SHAPE - }; + typedef quint8 Type; + + static quint32 getNextID() { static quint32 nextID = 0; return ++nextID; } Shape() : _type(UNKNOWN_SHAPE), _owningEntity(NULL), _boundingRadius(0.f), _translation(0.f), _rotation(), _mass(MAX_SHAPE_MASS) { @@ -40,7 +41,7 @@ public: } virtual ~Shape() { } - int getType() const { return _type; } + Type getType() const { return _type; } quint32 getID() const { return _id; } void setEntity(PhysicsEntity* entity) { _owningEntity = entity; } @@ -95,8 +96,8 @@ protected: void setBoundingRadius(float radius) { _boundingRadius = radius; } - int _type; - unsigned int _id; + Type _type; + quint32 _id; PhysicsEntity* _owningEntity; float _boundingRadius; glm::vec3 _translation; diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 805e7f30f6..8d09c0408a 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -15,56 +15,66 @@ #include "GeometryUtil.h" #include "ShapeCollider.h" +#include "CapsuleShape.h" +#include "ListShape.h" +#include "PlaneShape.h" +#include "SphereShape.h" // NOTE: // // * Large ListShape's are inefficient keep the lists short. // * Collisions between lists of lists work in theory but are not recommended. +const Shape::Type NUM_SHAPE_TYPES = 5; +const quint8 NUM__DISPATCH_CELLS = NUM_SHAPE_TYPES * NUM_SHAPE_TYPES; + +Shape::Type getDispatchKey(Shape::Type typeA, Shape::Type typeB) { + return typeA + NUM_SHAPE_TYPES * typeB; +} + +// dummy dispatch for any non-implemented pairings +bool notImplemented(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + return false; +} + +// NOTE: hardcode the number of dispatchTable entries (NUM_SHAPE_TYPES ^2) +bool (*dispatchTable[NUM__DISPATCH_CELLS])(const Shape*, const Shape*, CollisionList&); + namespace ShapeCollider { -bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - // TODO: make a fast lookup for correct method - int typeA = shapeA->getType(); - int typeB = shapeB->getType(); - if (typeA == Shape::SPHERE_SHAPE) { - const SphereShape* sphereA = static_cast(shapeA); - if (typeB == Shape::SPHERE_SHAPE) { - return sphereSphere(sphereA, static_cast(shapeB), collisions); - } else if (typeB == Shape::CAPSULE_SHAPE) { - return sphereCapsule(sphereA, static_cast(shapeB), collisions); - } else if (typeB == Shape::PLANE_SHAPE) { - return spherePlane(sphereA, static_cast(shapeB), collisions); - } - } else if (typeA == Shape::CAPSULE_SHAPE) { - const CapsuleShape* capsuleA = static_cast(shapeA); - if (typeB == Shape::SPHERE_SHAPE) { - return capsuleSphere(capsuleA, static_cast(shapeB), collisions); - } else if (typeB == Shape::CAPSULE_SHAPE) { - return capsuleCapsule(capsuleA, static_cast(shapeB), collisions); - } else if (typeB == Shape::PLANE_SHAPE) { - return capsulePlane(capsuleA, static_cast(shapeB), collisions); - } - } else if (typeA == Shape::PLANE_SHAPE) { - const PlaneShape* planeA = static_cast(shapeA); - if (typeB == Shape::SPHERE_SHAPE) { - return planeSphere(planeA, static_cast(shapeB), collisions); - } else if (typeB == Shape::CAPSULE_SHAPE) { - return planeCapsule(planeA, static_cast(shapeB), collisions); - } else if (typeB == Shape::PLANE_SHAPE) { - return planePlane(planeA, static_cast(shapeB), collisions); - } - } else if (typeA == Shape::LIST_SHAPE) { - const ListShape* listA = static_cast(shapeA); - if (typeB == Shape::SPHERE_SHAPE) { - return listSphere(listA, static_cast(shapeB), collisions); - } else if (typeB == Shape::CAPSULE_SHAPE) { - return listCapsule(listA, static_cast(shapeB), collisions); - } else if (typeB == Shape::PLANE_SHAPE) { - return listPlane(listA, static_cast(shapeB), collisions); - } +// NOTE: the dispatch table must be initialized before the ShapeCollider is used. +void initDispatchTable() { + for (Shape::Type i = 0; i < NUM__DISPATCH_CELLS; ++i) { + dispatchTable[i] = ¬Implemented; } - return false; + + // NOTE: no need to update any that are notImplemented, but we leave them + // commented out in the code so that we remember that they exist. + dispatchTable[getDispatchKey(SPHERE_SHAPE, SPHERE_SHAPE)] = &sphereSphere; + dispatchTable[getDispatchKey(SPHERE_SHAPE, CAPSULE_SHAPE)] = &sphereCapsule; + dispatchTable[getDispatchKey(SPHERE_SHAPE, PLANE_SHAPE)] = &spherePlane; + //dispatchTable[getDispatchKey(SPHERE_SHAPE, LIST_SHAPE)] = ¬Implemented; + + dispatchTable[getDispatchKey(CAPSULE_SHAPE, SPHERE_SHAPE)] = &capsuleSphere; + dispatchTable[getDispatchKey(CAPSULE_SHAPE, CAPSULE_SHAPE)] = &capsuleCapsule; + dispatchTable[getDispatchKey(CAPSULE_SHAPE, PLANE_SHAPE)] = &capsulePlane; + //dispatchTable[getDispatchKey(CAPSULE_SHAPE, LIST_SHAPE)] = ¬Implemented; + + dispatchTable[getDispatchKey(PLANE_SHAPE, SPHERE_SHAPE)] = &planeSphere; + dispatchTable[getDispatchKey(PLANE_SHAPE, CAPSULE_SHAPE)] = &planeCapsule; + dispatchTable[getDispatchKey(PLANE_SHAPE, PLANE_SHAPE)] = &planePlane; + //dispatchTable[getDispatchKey(PLANE_SHAPE, LIST_SHAPE)] = ¬Implemented; + + //dispatchTable[getDispatchKey(LIST_SHAPE, SPHERE_SHAPE)] = ¬Implemented; + //dispatchTable[getDispatchKey(LIST_SHAPE, CAPSULE_SHAPE)] = ¬Implemented; + //dispatchTable[getDispatchKey(LIST_SHAPE, PLANE_SHAPE)] = ¬Implemented; + //dispatchTable[getDispatchKey(LIST_SHAPE, LIST_SHAPE)] = ¬Implemented; + + // all of the UNKNOWN_SHAPE pairings point at notImplemented +} + +bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + return (*dispatchTable[shapeA->getType() + NUM_SHAPE_TYPES * shapeB->getType()])(shapeA, shapeB, collisions); } static CollisionList tempCollisions(32); @@ -133,20 +143,20 @@ bool collideShapesWithShapes(const QVector& shapesA, const QVectorgetType(); - if (typeA == Shape::SPHERE_SHAPE) { + Shape::Type typeA = shapeA->getType(); + if (typeA == SPHERE_SHAPE) { return sphereAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); - } else if (typeA == Shape::CAPSULE_SHAPE) { + } else if (typeA == CAPSULE_SHAPE) { return capsuleAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); - } else if (typeA == Shape::LIST_SHAPE) { + } else if (typeA == LIST_SHAPE) { const ListShape* listA = static_cast(shapeA); bool touching = false; for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { const Shape* subShape = listA->getSubShape(i); int subType = subShape->getType(); - if (subType == Shape::SPHERE_SHAPE) { + if (subType == SPHERE_SHAPE) { touching = sphereAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; - } else if (subType == Shape::CAPSULE_SHAPE) { + } else if (subType == CAPSULE_SHAPE) { touching = capsuleAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; } } @@ -155,7 +165,9 @@ bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, fl return false; } -bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) { +bool sphereSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const SphereShape* sphereA = static_cast(shapeA); + const SphereShape* sphereB = static_cast(shapeB); glm::vec3 BA = sphereB->getTranslation() - sphereA->getTranslation(); float distanceSquared = glm::dot(BA, BA); float totalRadius = sphereA->getRadius() + sphereB->getRadius(); @@ -183,7 +195,9 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis return false; } -bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions) { +bool sphereCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const SphereShape* sphereA = static_cast(shapeA); + const CapsuleShape* capsuleB = static_cast(shapeB); // find sphereA's closest approach to axis of capsuleB glm::vec3 BA = capsuleB->getTranslation() - sphereA->getTranslation(); glm::vec3 capsuleAxis; @@ -252,7 +266,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col return false; } -bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions) { +bool spherePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const SphereShape* sphereA = static_cast(shapeA); + const PlaneShape* planeB = static_cast(shapeB); glm::vec3 penetration; if (findSpherePlanePenetration(sphereA->getTranslation(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) { CollisionInfo* collision = collisions.getNewCollision(); @@ -268,7 +284,9 @@ bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, Collision return false; } -bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) { +bool capsuleSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const CapsuleShape* capsuleA = static_cast(shapeA); + const SphereShape* sphereB = static_cast(shapeB); // find sphereB's closest approach to axis of capsuleA glm::vec3 AB = capsuleA->getTranslation() - sphereB->getTranslation(); glm::vec3 capsuleAxis; @@ -409,7 +427,9 @@ bool lineCylinder(const glm::vec3& lineP, const glm::vec3& lineDir, return true; } -bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions) { +bool capsuleCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const CapsuleShape* capsuleA = static_cast(shapeA); + const CapsuleShape* capsuleB = static_cast(shapeB); glm::vec3 axisA; capsuleA->computeNormalizedAxis(axisA); glm::vec3 axisB; @@ -568,7 +588,9 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, return false; } -bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions) { +bool capsulePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const CapsuleShape* capsuleA = static_cast(shapeA); + const PlaneShape* planeB = static_cast(shapeB); glm::vec3 start, end, penetration; capsuleA->getStartPoint(start); capsuleA->getEndPoint(end); @@ -588,7 +610,9 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis return false; } -bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions) { +bool planeSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const PlaneShape* planeA = static_cast(shapeA); + const SphereShape* sphereB = static_cast(shapeB); glm::vec3 penetration; if (findSpherePlanePenetration(sphereB->getTranslation(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) { CollisionInfo* collision = collisions.getNewCollision(); @@ -605,7 +629,9 @@ bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, Collision return false; } -bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions) { +bool planeCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { + const PlaneShape* planeA = static_cast(shapeA); + const CapsuleShape* capsuleB = static_cast(shapeB); glm::vec3 start, end, penetration; capsuleB->getStartPoint(start); capsuleB->getEndPoint(end); @@ -625,110 +651,118 @@ bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, Collis return false; } -bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions) { +bool planePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { // technically, planes always collide unless they're parallel and not coincident; however, that's // not going to give us any useful information return false; } -bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions) { +bool sphereList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; + const ListShape* listB = static_cast(shapeB); for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { const Shape* subShape = listB->getSubShape(i); int subType = subShape->getType(); - if (subType == Shape::SPHERE_SHAPE) { - touching = sphereSphere(sphereA, static_cast(subShape), collisions) || touching; - } else if (subType == Shape::CAPSULE_SHAPE) { - touching = sphereCapsule(sphereA, static_cast(subShape), collisions) || touching; - } else if (subType == Shape::PLANE_SHAPE) { - touching = spherePlane(sphereA, static_cast(subShape), collisions) || touching; + if (subType == SPHERE_SHAPE) { + touching = sphereSphere(shapeA, subShape, collisions) || touching; + } else if (subType == CAPSULE_SHAPE) { + touching = sphereCapsule(shapeA, subShape, collisions) || touching; + } else if (subType == PLANE_SHAPE) { + touching = spherePlane(shapeA, subShape, collisions) || touching; } } return touching; } -bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions) { +bool capsuleList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; + const ListShape* listB = static_cast(shapeB); for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { const Shape* subShape = listB->getSubShape(i); int subType = subShape->getType(); - if (subType == Shape::SPHERE_SHAPE) { - touching = capsuleSphere(capsuleA, static_cast(subShape), collisions) || touching; - } else if (subType == Shape::CAPSULE_SHAPE) { - touching = capsuleCapsule(capsuleA, static_cast(subShape), collisions) || touching; - } else if (subType == Shape::PLANE_SHAPE) { - touching = capsulePlane(capsuleA, static_cast(subShape), collisions) || touching; + if (subType == SPHERE_SHAPE) { + touching = capsuleSphere(shapeA, subShape, collisions) || touching; + } else if (subType == CAPSULE_SHAPE) { + touching = capsuleCapsule(shapeA, subShape, collisions) || touching; + } else if (subType == PLANE_SHAPE) { + touching = capsulePlane(shapeA, subShape, collisions) || touching; } } return touching; } -bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions) { +bool planeList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; + const ListShape* listB = static_cast(shapeB); for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { const Shape* subShape = listB->getSubShape(i); int subType = subShape->getType(); - if (subType == Shape::SPHERE_SHAPE) { - touching = planeSphere(planeA, static_cast(subShape), collisions) || touching; - } else if (subType == Shape::CAPSULE_SHAPE) { - touching = planeCapsule(planeA, static_cast(subShape), collisions) || touching; - } else if (subType == Shape::PLANE_SHAPE) { - touching = planePlane(planeA, static_cast(subShape), collisions) || touching; + if (subType == SPHERE_SHAPE) { + touching = planeSphere(shapeA, subShape, collisions) || touching; + } else if (subType == CAPSULE_SHAPE) { + touching = planeCapsule(shapeA, subShape, collisions) || touching; + } else if (subType == PLANE_SHAPE) { + touching = planePlane(shapeA, subShape, collisions) || touching; } } return touching; } -bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionList& collisions) { +bool listSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; + const ListShape* listA = static_cast(shapeA); for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { const Shape* subShape = listA->getSubShape(i); int subType = subShape->getType(); - if (subType == Shape::SPHERE_SHAPE) { - touching = sphereSphere(static_cast(subShape), sphereB, collisions) || touching; - } else if (subType == Shape::CAPSULE_SHAPE) { - touching = capsuleSphere(static_cast(subShape), sphereB, collisions) || touching; - } else if (subType == Shape::PLANE_SHAPE) { - touching = planeSphere(static_cast(subShape), sphereB, collisions) || touching; + if (subType == SPHERE_SHAPE) { + touching = sphereSphere(subShape, shapeB, collisions) || touching; + } else if (subType == CAPSULE_SHAPE) { + touching = capsuleSphere(subShape, shapeB, collisions) || touching; + } else if (subType == PLANE_SHAPE) { + touching = planeSphere(subShape, shapeB, collisions) || touching; } } return touching; } -bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions) { +bool listCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; + const ListShape* listA = static_cast(shapeA); for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { const Shape* subShape = listA->getSubShape(i); int subType = subShape->getType(); - if (subType == Shape::SPHERE_SHAPE) { - touching = sphereCapsule(static_cast(subShape), capsuleB, collisions) || touching; - } else if (subType == Shape::CAPSULE_SHAPE) { - touching = capsuleCapsule(static_cast(subShape), capsuleB, collisions) || touching; - } else if (subType == Shape::PLANE_SHAPE) { - touching = planeCapsule(static_cast(subShape), capsuleB, collisions) || touching; + if (subType == SPHERE_SHAPE) { + touching = sphereCapsule(subShape, shapeB, collisions) || touching; + } else if (subType == CAPSULE_SHAPE) { + touching = capsuleCapsule(subShape, shapeB, collisions) || touching; + } else if (subType == PLANE_SHAPE) { + touching = planeCapsule(subShape, shapeB, collisions) || touching; } } return touching; } -bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions) { +bool listPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; + const ListShape* listA = static_cast(shapeA); for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { const Shape* subShape = listA->getSubShape(i); int subType = subShape->getType(); - if (subType == Shape::SPHERE_SHAPE) { - touching = spherePlane(static_cast(subShape), planeB, collisions) || touching; - } else if (subType == Shape::CAPSULE_SHAPE) { - touching = capsulePlane(static_cast(subShape), planeB, collisions) || touching; - } else if (subType == Shape::PLANE_SHAPE) { - touching = planePlane(static_cast(subShape), planeB, collisions) || touching; + if (subType == SPHERE_SHAPE) { + touching = spherePlane(subShape, shapeB, collisions) || touching; + } else if (subType == CAPSULE_SHAPE) { + touching = capsulePlane(subShape, shapeB, collisions) || touching; + } else if (subType == PLANE_SHAPE) { + touching = planePlane(subShape, shapeB, collisions) || touching; } } return touching; } -bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions) { +bool listList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; + const ListShape* listA = static_cast(shapeA); + const ListShape* listB = static_cast(shapeB); for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { const Shape* subShape = listA->getSubShape(i); for (int j = 0; j < listB->size() && !collisions.isFull(); ++j) { diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index b1be75fa40..11768f0a48 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -14,20 +14,28 @@ #include -#include "CapsuleShape.h" #include "CollisionInfo.h" -#include "ListShape.h" -#include "PlaneShape.h" #include "SharedUtil.h" -#include "SphereShape.h" +//#include "CapsuleShape.h" +//#include "ListShape.h" +//#include "PlaneShape.h" +//#include "SphereShape.h" + +class Shape; +class SphereShape; +class CapsuleShape; namespace ShapeCollider { + /// MUST CALL this FIRST before using the ShapeCollider + void initDispatchTable(); + /// \param shapeA pointer to first shape (cannot be NULL) /// \param shapeB pointer to second shape (cannot be NULL) /// \param collisions[out] collision details /// \return true if shapes collide bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions); + bool collideShapesOld(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions); /// \param shapesA list of shapes /// \param shapeB list of shapes @@ -49,97 +57,97 @@ namespace ShapeCollider { /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions); + bool sphereSphere(const Shape* sphereA, const Shape* sphereB, CollisionList& collisions); /// \param sphereA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions); + bool sphereCapsule(const Shape* sphereA, const Shape* capsuleB, CollisionList& collisions); /// \param sphereA pointer to first shape (cannot be NULL) /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions); + bool spherePlane(const Shape* sphereA, const Shape* planeB, CollisionList& collisions); /// \param capsuleA pointer to first shape (cannot be NULL) /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions); + bool capsuleSphere(const Shape* capsuleA, const Shape* sphereB, CollisionList& collisions); /// \param capsuleA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions); + bool capsuleCapsule(const Shape* capsuleA, const Shape* capsuleB, CollisionList& collisions); /// \param capsuleA pointer to first shape (cannot be NULL) /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions); + bool capsulePlane(const Shape* capsuleA, const Shape* planeB, CollisionList& collisions); /// \param planeA pointer to first shape (cannot be NULL) /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions); + bool planeSphere(const Shape* planeA, const Shape* sphereB, CollisionList& collisions); /// \param planeA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions); + bool planeCapsule(const Shape* planeA, const Shape* capsuleB, CollisionList& collisions); /// \param planeA pointer to first shape (cannot be NULL) /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions); + bool planePlane(const Shape* planeA, const Shape* planeB, CollisionList& collisions); /// \param sphereA pointer to first shape (cannot be NULL) /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions); + bool sphereList(const Shape* sphereA, const Shape* listB, CollisionList& collisions); /// \param capuleA pointer to first shape (cannot be NULL) /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions); + bool capsuleList(const Shape* capsuleA, const Shape* listB, CollisionList& collisions); /// \param planeA pointer to first shape (cannot be NULL) /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions); + bool planeList(const Shape* planeA, const Shape* listB, CollisionList& collisions); /// \param listA pointer to first shape (cannot be NULL) /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionList& collisions); + bool listSphere(const Shape* listA, const Shape* sphereB, CollisionList& collisions); /// \param listA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions); + bool listCapsule(const Shape* listA, const Shape* capsuleB, CollisionList& collisions); /// \param listA pointer to first shape (cannot be NULL) /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions); + bool listPlane(const Shape* listA, const Shape* planeB, CollisionList& collisions); /// \param listA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions); + bool listList(const Shape* listA, const Shape* listB, CollisionList& collisions); /// \param sphereA pointer to sphere (cannot be NULL) /// \param cubeCenter center of cube diff --git a/libraries/shared/src/SphereShape.h b/libraries/shared/src/SphereShape.h index d2f2a8596f..0626927453 100644 --- a/libraries/shared/src/SphereShape.h +++ b/libraries/shared/src/SphereShape.h @@ -18,13 +18,13 @@ class SphereShape : public Shape { public: - SphereShape() : Shape(Shape::SPHERE_SHAPE) {} + SphereShape() : Shape(SPHERE_SHAPE) {} - SphereShape(float radius) : Shape(Shape::SPHERE_SHAPE) { + SphereShape(float radius) : Shape(SPHERE_SHAPE) { _boundingRadius = radius; } - SphereShape(float radius, const glm::vec3& position) : Shape(Shape::SPHERE_SHAPE, position) { + SphereShape(float radius, const glm::vec3& position) : Shape(SPHERE_SHAPE, position) { _boundingRadius = radius; } diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index bde29ea588..db4833b20a 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -16,7 +16,9 @@ #include #include +#include #include +#include #include #include #include @@ -112,6 +114,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (!collision) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: null collision" << std::endl; + return; } // penetration points from sphereA into sphereB @@ -1298,7 +1301,41 @@ void ShapeColliderTests::rayMissesPlane() { } } +void ShapeColliderTests::measureTimeOfCollisionDispatch() { + /* KEEP for future manual testing + // create two non-colliding spheres + float radiusA = 7.0f; + float radiusB = 3.0f; + float alpha = 1.2f; + float beta = 1.3f; + glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + float offsetDistance = alpha * radiusA + beta * radiusB; + + SphereShape sphereA(radiusA, origin); + SphereShape sphereB(radiusB, offsetDistance * offsetDirection); + CollisionList collisions(16); + + //int numTests = 1; + quint64 oldTime; + quint64 newTime; + int numTests = 100000000; + { + quint64 startTime = usecTimestampNow(); + for (int i = 0; i < numTests; ++i) { + ShapeCollider::collideShapes(&sphereA, &sphereB, collisions); + } + quint64 endTime = usecTimestampNow(); + std::cout << numTests << " non-colliding collisions in " << (endTime - startTime) << " usec" << std::endl; + newTime = endTime - startTime; + } + */ +} + void ShapeColliderTests::runAllTests() { + ShapeCollider::initDispatchTable(); + + //measureTimeOfCollisionDispatch(); + sphereMissesSphere(); sphereTouchesSphere(); diff --git a/tests/physics/src/ShapeColliderTests.h b/tests/physics/src/ShapeColliderTests.h index fd9f1f9706..4a51651cb8 100644 --- a/tests/physics/src/ShapeColliderTests.h +++ b/tests/physics/src/ShapeColliderTests.h @@ -35,6 +35,8 @@ namespace ShapeColliderTests { void rayHitsPlane(); void rayMissesPlane(); + void measureTimeOfCollisionDispatch(); + void runAllTests(); } diff --git a/tests/physics/src/VerletShapeTests.cpp b/tests/physics/src/VerletShapeTests.cpp index 3a3bd43278..705ddeeac3 100644 --- a/tests/physics/src/VerletShapeTests.cpp +++ b/tests/physics/src/VerletShapeTests.cpp @@ -757,6 +757,8 @@ void VerletShapeTests::capsuleTouchesCapsule() { } void VerletShapeTests::runAllTests() { + ShapeCollider::initDispatchTable(); + setSpherePosition(); sphereMissesSphere(); sphereTouchesSphere(); From 5c77317cfd6e6ce4508c5d870959d955b3352dce Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 21 Aug 2014 18:07:30 -0700 Subject: [PATCH 165/206] Fix for flashing when painting textures. --- interface/src/MetavoxelSystem.cpp | 12 +++++++++--- interface/src/MetavoxelSystem.h | 5 ++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 9a143706ed..3864b0537f 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -118,7 +118,7 @@ void MetavoxelSystem::render() { viewFrustum->getNearBottomLeft(), viewFrustum->getNearBottomRight()); RenderVisitor renderVisitor(getLOD()); - guideToAugmented(renderVisitor); + guideToAugmented(renderVisitor, true); } class RayHeightfieldIntersectionVisitor : public RayIntersectionVisitor { @@ -459,13 +459,19 @@ MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) { return new MetavoxelSystemClient(node, _updater); } -void MetavoxelSystem::guideToAugmented(MetavoxelVisitor& visitor) { +void MetavoxelSystem::guideToAugmented(MetavoxelVisitor& visitor, bool render) { foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { if (node->getType() == NodeType::MetavoxelServer) { QMutexLocker locker(&node->getMutex()); MetavoxelSystemClient* client = static_cast(node->getLinkedData()); if (client) { - client->getAugmentedData().guide(visitor); + MetavoxelData data = client->getAugmentedData(); + data.guide(visitor); + if (render) { + // save the rendered augmented data so that its cached texture references, etc., don't + // get collected when we replace it with more recent versions + client->setRenderedAugmentedData(data); + } } } } diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 752a060b86..ac4dd430fb 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -57,7 +57,7 @@ protected: private: - void guideToAugmented(MetavoxelVisitor& visitor); + void guideToAugmented(MetavoxelVisitor& visitor, bool render = false); AttributePointer _pointBufferAttribute; AttributePointer _heightfieldBufferAttribute; @@ -92,6 +92,8 @@ public: /// Returns a copy of the augmented data. This function is thread-safe. MetavoxelData getAugmentedData(); + void setRenderedAugmentedData(const MetavoxelData& data) { _renderedAugmentedData = data; } + virtual int parseData(const QByteArray& packet); protected: @@ -102,6 +104,7 @@ protected: private: MetavoxelData _augmentedData; + MetavoxelData _renderedAugmentedData; QReadWriteLock _augmentedDataLock; }; From e9e367171056e5ca7b22d8445b580f4d109b5822 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 21 Aug 2014 18:14:10 -0700 Subject: [PATCH 166/206] Increment the version number. --- libraries/networking/src/PacketHeaders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 1b48a2e333..619f652e1e 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 1; + return 2; default: return 0; } From cd81d6fc079417d84e0d8b2815c7a7f4d9912b8c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 21 Aug 2014 19:24:09 -0700 Subject: [PATCH 167/206] Bring libovr build instructions up to date --- interface/external/libovr/readme.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/external/libovr/readme.txt b/interface/external/libovr/readme.txt index f68818d1ee..f9db808d88 100644 --- a/interface/external/libovr/readme.txt +++ b/interface/external/libovr/readme.txt @@ -2,15 +2,15 @@ Instructions for adding the Oculus library (LibOVR) to Interface Stephen Birarda, March 6, 2014 -You can download the Oculus SDK from https://developer.oculusvr.com/ (account creation required). Interface has been tested with SDK version 0.3.2. +You can download the Oculus SDK from https://developer.oculusvr.com/ (account creation required). Interface has been tested with SDK version 0.4.1. -1. Copy the Oculus SDK folders from the LibOVR directory (Lib, Include, Src) into the interface/externals/oculus folder. +1. Copy the Oculus SDK folders from the LibOVR directory (Lib, Include, Src) into the interface/externals/libovr folder. This readme.txt should be there as well. You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects). If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'oculus' that contains the three folders mentioned above. - NOTE: For Windows users, you should copy libovr.lib and libovrd.lib from the \oculus\Lib\Win32\VS2010 directory to the \oculus\Lib\Win32\ directory. + NOTE: For Windows users, you should copy libovr.lib and libovrd.lib from the \oculus\Lib\Win32\VS2010 directory to the \libovr\Lib\Win32\ directory. 2. Clear your build directory, run cmake and build, and you should be all set. From c8ac5f584a6ff24387703d8047cc4553b8db1ed6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Aug 2014 09:29:08 -0700 Subject: [PATCH 168/206] Convert to vector maths --- examples/editVoxels.js | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 1ed3dcc0c3..b92c6c28b0 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -1299,26 +1299,19 @@ function mouseMoveEvent(event) { // Watch the drag direction to tell which way to 'extrude' this voxel if (!isExtruding) { var pickRay = Camera.computePickRay(event.x, event.y); - var lastVoxelDistance = { x: pickRay.origin.x - lastVoxelPosition.x, - y: pickRay.origin.y - lastVoxelPosition.y, - z: pickRay.origin.z - lastVoxelPosition.z }; - var distance = Vec3.length(lastVoxelDistance); - var mouseSpot = { x: pickRay.direction.x * distance, y: pickRay.direction.y * distance, z: pickRay.direction.z * distance }; - mouseSpot.x += pickRay.origin.x; - mouseSpot.y += pickRay.origin.y; - mouseSpot.z += pickRay.origin.z; - var dx = mouseSpot.x - lastVoxelPosition.x; - var dy = mouseSpot.y - lastVoxelPosition.y; - var dz = mouseSpot.z - lastVoxelPosition.z; + var distance = Vec3.length(Vec3.subtract(pickRay.origin, lastVoxelPosition)); + var mouseSpot = Vec3.sum(Vec3.multiply(pickRay.direction, distance), pickRay.origin); + var delta = Vec3.subtract(mouseSpot, lastVoxelPosition); + extrudeScale = lastVoxelScale; extrudeDirection = { x: 0, y: 0, z: 0 }; isExtruding = true; - if (dx > lastVoxelScale) extrudeDirection.x = extrudeScale; - else if (dx < -lastVoxelScale) extrudeDirection.x = -extrudeScale; - else if (dy > lastVoxelScale) extrudeDirection.y = extrudeScale; - else if (dy < -lastVoxelScale) extrudeDirection.y = -extrudeScale; - else if (dz > lastVoxelScale) extrudeDirection.z = extrudeScale; - else if (dz < -lastVoxelScale) extrudeDirection.z = -extrudeScale; + if (delta.x > lastVoxelScale) extrudeDirection.x = extrudeScale; + else if (delta.x < -lastVoxelScale) extrudeDirection.x = -extrudeScale; + else if (delta.y > lastVoxelScale) extrudeDirection.y = extrudeScale; + else if (delta.y < -lastVoxelScale) extrudeDirection.y = -extrudeScale; + else if (delta.z > lastVoxelScale) extrudeDirection.z = extrudeScale; + else if (delta.z < -lastVoxelScale) extrudeDirection.z = -extrudeScale; else isExtruding = false; } else { // We have got an extrusion direction, now look for mouse move beyond threshold to add new voxel From 761387854d2f3baef4a5d2a36cae5ed9f0092673 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 22 Aug 2014 10:09:10 -0700 Subject: [PATCH 169/206] Extrude per mouse movement in voxel space instead of screen space --- examples/editVoxels.js | 44 +++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/examples/editVoxels.js b/examples/editVoxels.js index b92c6c28b0..a85c04dd02 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -51,9 +51,6 @@ var lastVoxelScale = 0; var dragStart = { x: 0, y: 0 }; var wheelPixelsMoved = 0; -var mouseX = 0; -var mouseY = 0; - // Create a table of the different colors you can choose var colors = new Array(); colors[0] = { red: 120, green: 181, blue: 126 }; @@ -1041,8 +1038,6 @@ function mousePressEvent(event) { // TODO: does any of this stuff need to execute if we're panning or orbiting? trackMouseEvent(event); // used by preview support - mouseX = event.x; - mouseY = event.y; var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Voxels.findRayIntersection(pickRay); audioOptions.position = Vec3.sum(pickRay.origin, pickRay.direction); @@ -1296,33 +1291,30 @@ function mouseMoveEvent(event) { } if (isAdding) { - // Watch the drag direction to tell which way to 'extrude' this voxel - if (!isExtruding) { - var pickRay = Camera.computePickRay(event.x, event.y); - var distance = Vec3.length(Vec3.subtract(pickRay.origin, lastVoxelPosition)); - var mouseSpot = Vec3.sum(Vec3.multiply(pickRay.direction, distance), pickRay.origin); - var delta = Vec3.subtract(mouseSpot, lastVoxelPosition); + var pickRay = Camera.computePickRay(event.x, event.y); + var distance = Vec3.length(Vec3.subtract(pickRay.origin, lastVoxelPosition)); + var mouseSpot = Vec3.sum(Vec3.multiply(pickRay.direction, distance), pickRay.origin); + var delta = Vec3.subtract(mouseSpot, lastVoxelPosition); + if (!isExtruding) { + // Use the drag direction to tell which way to 'extrude' this voxel extrudeScale = lastVoxelScale; extrudeDirection = { x: 0, y: 0, z: 0 }; isExtruding = true; - if (delta.x > lastVoxelScale) extrudeDirection.x = extrudeScale; - else if (delta.x < -lastVoxelScale) extrudeDirection.x = -extrudeScale; - else if (delta.y > lastVoxelScale) extrudeDirection.y = extrudeScale; - else if (delta.y < -lastVoxelScale) extrudeDirection.y = -extrudeScale; - else if (delta.z > lastVoxelScale) extrudeDirection.z = extrudeScale; - else if (delta.z < -lastVoxelScale) extrudeDirection.z = -extrudeScale; + if (delta.x > lastVoxelScale) extrudeDirection.x = 1; + else if (delta.x < -lastVoxelScale) extrudeDirection.x = -1; + else if (delta.y > lastVoxelScale) extrudeDirection.y = 1; + else if (delta.y < -lastVoxelScale) extrudeDirection.y = -1; + else if (delta.z > lastVoxelScale) extrudeDirection.z = 1; + else if (delta.z < -lastVoxelScale) extrudeDirection.z = -1; else isExtruding = false; } else { - // We have got an extrusion direction, now look for mouse move beyond threshold to add new voxel - var dx = event.x - mouseX; - var dy = event.y - mouseY; - if (Math.sqrt(dx*dx + dy*dy) > PIXELS_PER_EXTRUDE_VOXEL) { - lastVoxelPosition = Vec3.sum(lastVoxelPosition, extrudeDirection); - Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z, - extrudeScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue); - mouseX = event.x; - mouseY = event.y; + // Extrude if mouse has moved by a voxel in the extrude direction + var distanceInDirection = Vec3.dot(delta, extrudeDirection); + if (distanceInDirection > extrudeScale) { + lastVoxelPosition = Vec3.sum(lastVoxelPosition, Vec3.multiply(extrudeDirection, extrudeScale)); + Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z, extrudeScale, + lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue); } } } From 1be922f986e0f55a5567861562a315cb28ce4a48 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 22 Aug 2014 11:20:53 -0700 Subject: [PATCH 170/206] add shapeVsList etc to dispatch table renamed some functions for more readability --- libraries/shared/src/ShapeCollider.cpp | 187 +++++------------------ libraries/shared/src/ShapeCollider.h | 73 +++------ tests/physics/src/ShapeColliderTests.cpp | 18 +-- 3 files changed, 70 insertions(+), 208 deletions(-) diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 8d09c0408a..536d9bdcde 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -50,27 +50,27 @@ void initDispatchTable() { // NOTE: no need to update any that are notImplemented, but we leave them // commented out in the code so that we remember that they exist. - dispatchTable[getDispatchKey(SPHERE_SHAPE, SPHERE_SHAPE)] = &sphereSphere; - dispatchTable[getDispatchKey(SPHERE_SHAPE, CAPSULE_SHAPE)] = &sphereCapsule; - dispatchTable[getDispatchKey(SPHERE_SHAPE, PLANE_SHAPE)] = &spherePlane; - //dispatchTable[getDispatchKey(SPHERE_SHAPE, LIST_SHAPE)] = ¬Implemented; + dispatchTable[getDispatchKey(SPHERE_SHAPE, SPHERE_SHAPE)] = &sphereVsSphere; + dispatchTable[getDispatchKey(SPHERE_SHAPE, CAPSULE_SHAPE)] = &sphereVsCapsule; + dispatchTable[getDispatchKey(SPHERE_SHAPE, PLANE_SHAPE)] = &sphereVsPlane; + dispatchTable[getDispatchKey(SPHERE_SHAPE, LIST_SHAPE)] = &shapeVsList; - dispatchTable[getDispatchKey(CAPSULE_SHAPE, SPHERE_SHAPE)] = &capsuleSphere; - dispatchTable[getDispatchKey(CAPSULE_SHAPE, CAPSULE_SHAPE)] = &capsuleCapsule; - dispatchTable[getDispatchKey(CAPSULE_SHAPE, PLANE_SHAPE)] = &capsulePlane; - //dispatchTable[getDispatchKey(CAPSULE_SHAPE, LIST_SHAPE)] = ¬Implemented; + dispatchTable[getDispatchKey(CAPSULE_SHAPE, SPHERE_SHAPE)] = &capsuleVsSphere; + dispatchTable[getDispatchKey(CAPSULE_SHAPE, CAPSULE_SHAPE)] = &capsuleVsCapsule; + dispatchTable[getDispatchKey(CAPSULE_SHAPE, PLANE_SHAPE)] = &capsuleVsPlane; + dispatchTable[getDispatchKey(CAPSULE_SHAPE, LIST_SHAPE)] = &shapeVsList; - dispatchTable[getDispatchKey(PLANE_SHAPE, SPHERE_SHAPE)] = &planeSphere; - dispatchTable[getDispatchKey(PLANE_SHAPE, CAPSULE_SHAPE)] = &planeCapsule; - dispatchTable[getDispatchKey(PLANE_SHAPE, PLANE_SHAPE)] = &planePlane; - //dispatchTable[getDispatchKey(PLANE_SHAPE, LIST_SHAPE)] = ¬Implemented; + dispatchTable[getDispatchKey(PLANE_SHAPE, SPHERE_SHAPE)] = &planeVsSphere; + dispatchTable[getDispatchKey(PLANE_SHAPE, CAPSULE_SHAPE)] = &planeVsCapsule; + dispatchTable[getDispatchKey(PLANE_SHAPE, PLANE_SHAPE)] = &planeVsPlane; + dispatchTable[getDispatchKey(PLANE_SHAPE, LIST_SHAPE)] = &shapeVsList; - //dispatchTable[getDispatchKey(LIST_SHAPE, SPHERE_SHAPE)] = ¬Implemented; - //dispatchTable[getDispatchKey(LIST_SHAPE, CAPSULE_SHAPE)] = ¬Implemented; - //dispatchTable[getDispatchKey(LIST_SHAPE, PLANE_SHAPE)] = ¬Implemented; - //dispatchTable[getDispatchKey(LIST_SHAPE, LIST_SHAPE)] = ¬Implemented; + dispatchTable[getDispatchKey(LIST_SHAPE, SPHERE_SHAPE)] = &listVsShape; + dispatchTable[getDispatchKey(LIST_SHAPE, CAPSULE_SHAPE)] = &listVsShape; + dispatchTable[getDispatchKey(LIST_SHAPE, PLANE_SHAPE)] = &listVsShape; + dispatchTable[getDispatchKey(LIST_SHAPE, LIST_SHAPE)] = &listVsList; - // all of the UNKNOWN_SHAPE pairings point at notImplemented + // all of the UNKNOWN_SHAPE pairings are notImplemented } bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { @@ -79,31 +79,6 @@ bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& coll static CollisionList tempCollisions(32); -bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision) { - tempCollisions.clear(); - foreach (const Shape* shapeA, shapesA) { - foreach (const Shape* shapeB, shapesB) { - collideShapes(shapeA, shapeB, tempCollisions); - } - } - if (tempCollisions.size() > 0) { - glm::vec3 totalPenetration(0.0f); - glm::vec3 averageContactPoint(0.0f); - for (int j = 0; j < tempCollisions.size(); ++j) { - CollisionInfo* c = tempCollisions.getCollision(j); - totalPenetration = addPenetrations(totalPenetration, c->_penetration); - averageContactPoint += c->_contactPoint; - } - collision._penetration = totalPenetration; - collision._contactPoint = averageContactPoint / (float)(tempCollisions.size()); - // there are no valid shape pointers for this collision so we set them NULL - collision._shapeA = NULL; - collision._shapeB = NULL; - return true; - } - return false; -} - bool collideShapeWithShapes(const Shape* shapeA, const QVector& shapes, int startIndex, CollisionList& collisions) { bool collided = false; if (shapeA) { @@ -145,9 +120,9 @@ bool collideShapesWithShapes(const QVector& shapesA, const QVectorgetType(); if (typeA == SPHERE_SHAPE) { - return sphereAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); + return sphereVsAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); } else if (typeA == CAPSULE_SHAPE) { - return capsuleAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); + return capsuleVsAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); } else if (typeA == LIST_SHAPE) { const ListShape* listA = static_cast(shapeA); bool touching = false; @@ -155,9 +130,9 @@ bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, fl const Shape* subShape = listA->getSubShape(i); int subType = subShape->getType(); if (subType == SPHERE_SHAPE) { - touching = sphereAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; + touching = sphereVsAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; } else if (subType == CAPSULE_SHAPE) { - touching = capsuleAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; + touching = capsuleVsAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; } } return touching; @@ -165,7 +140,7 @@ bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, fl return false; } -bool sphereSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool sphereVsSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const SphereShape* sphereA = static_cast(shapeA); const SphereShape* sphereB = static_cast(shapeB); glm::vec3 BA = sphereB->getTranslation() - sphereA->getTranslation(); @@ -195,7 +170,7 @@ bool sphereSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& colli return false; } -bool sphereCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool sphereVsCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const SphereShape* sphereA = static_cast(shapeA); const CapsuleShape* capsuleB = static_cast(shapeB); // find sphereA's closest approach to axis of capsuleB @@ -266,7 +241,7 @@ bool sphereCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& coll return false; } -bool spherePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool sphereVsPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const SphereShape* sphereA = static_cast(shapeA); const PlaneShape* planeB = static_cast(shapeB); glm::vec3 penetration; @@ -284,7 +259,7 @@ bool spherePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collis return false; } -bool capsuleSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool capsuleVsSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const CapsuleShape* capsuleA = static_cast(shapeA); const SphereShape* sphereB = static_cast(shapeB); // find sphereB's closest approach to axis of capsuleA @@ -427,7 +402,7 @@ bool lineCylinder(const glm::vec3& lineP, const glm::vec3& lineDir, return true; } -bool capsuleCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool capsuleVsCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const CapsuleShape* capsuleA = static_cast(shapeA); const CapsuleShape* capsuleB = static_cast(shapeB); glm::vec3 axisA; @@ -588,7 +563,7 @@ bool capsuleCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& col return false; } -bool capsulePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool capsuleVsPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const CapsuleShape* capsuleA = static_cast(shapeA); const PlaneShape* planeB = static_cast(shapeB); glm::vec3 start, end, penetration; @@ -610,7 +585,7 @@ bool capsulePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& colli return false; } -bool planeSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool planeVsSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const PlaneShape* planeA = static_cast(shapeA); const SphereShape* sphereB = static_cast(shapeB); glm::vec3 penetration; @@ -629,7 +604,7 @@ bool planeSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collis return false; } -bool planeCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool planeVsCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { const PlaneShape* planeA = static_cast(shapeA); const CapsuleShape* capsuleB = static_cast(shapeB); glm::vec3 start, end, penetration; @@ -651,115 +626,33 @@ bool planeCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& colli return false; } -bool planePlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool planeVsPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { // technically, planes always collide unless they're parallel and not coincident; however, that's // not going to give us any useful information return false; } -bool sphereList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool shapeVsList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; const ListShape* listB = static_cast(shapeB); for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { const Shape* subShape = listB->getSubShape(i); - int subType = subShape->getType(); - if (subType == SPHERE_SHAPE) { - touching = sphereSphere(shapeA, subShape, collisions) || touching; - } else if (subType == CAPSULE_SHAPE) { - touching = sphereCapsule(shapeA, subShape, collisions) || touching; - } else if (subType == PLANE_SHAPE) { - touching = spherePlane(shapeA, subShape, collisions) || touching; - } + touching = collideShapes(shapeA, subShape, collisions) || touching; } return touching; } -bool capsuleList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - bool touching = false; - const ListShape* listB = static_cast(shapeB); - for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { - const Shape* subShape = listB->getSubShape(i); - int subType = subShape->getType(); - if (subType == SPHERE_SHAPE) { - touching = capsuleSphere(shapeA, subShape, collisions) || touching; - } else if (subType == CAPSULE_SHAPE) { - touching = capsuleCapsule(shapeA, subShape, collisions) || touching; - } else if (subType == PLANE_SHAPE) { - touching = capsulePlane(shapeA, subShape, collisions) || touching; - } - } - return touching; -} - -bool planeList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - bool touching = false; - const ListShape* listB = static_cast(shapeB); - for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { - const Shape* subShape = listB->getSubShape(i); - int subType = subShape->getType(); - if (subType == SPHERE_SHAPE) { - touching = planeSphere(shapeA, subShape, collisions) || touching; - } else if (subType == CAPSULE_SHAPE) { - touching = planeCapsule(shapeA, subShape, collisions) || touching; - } else if (subType == PLANE_SHAPE) { - touching = planePlane(shapeA, subShape, collisions) || touching; - } - } - return touching; -} - -bool listSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool listVsShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; const ListShape* listA = static_cast(shapeA); for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { const Shape* subShape = listA->getSubShape(i); - int subType = subShape->getType(); - if (subType == SPHERE_SHAPE) { - touching = sphereSphere(subShape, shapeB, collisions) || touching; - } else if (subType == CAPSULE_SHAPE) { - touching = capsuleSphere(subShape, shapeB, collisions) || touching; - } else if (subType == PLANE_SHAPE) { - touching = planeSphere(subShape, shapeB, collisions) || touching; - } + touching = collideShapes(subShape, shapeB, collisions) || touching; } return touching; } -bool listCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - bool touching = false; - const ListShape* listA = static_cast(shapeA); - for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { - const Shape* subShape = listA->getSubShape(i); - int subType = subShape->getType(); - if (subType == SPHERE_SHAPE) { - touching = sphereCapsule(subShape, shapeB, collisions) || touching; - } else if (subType == CAPSULE_SHAPE) { - touching = capsuleCapsule(subShape, shapeB, collisions) || touching; - } else if (subType == PLANE_SHAPE) { - touching = planeCapsule(subShape, shapeB, collisions) || touching; - } - } - return touching; -} - -bool listPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - bool touching = false; - const ListShape* listA = static_cast(shapeA); - for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { - const Shape* subShape = listA->getSubShape(i); - int subType = subShape->getType(); - if (subType == SPHERE_SHAPE) { - touching = spherePlane(subShape, shapeB, collisions) || touching; - } else if (subType == CAPSULE_SHAPE) { - touching = capsulePlane(subShape, shapeB, collisions) || touching; - } else if (subType == PLANE_SHAPE) { - touching = planePlane(subShape, shapeB, collisions) || touching; - } - } - return touching; -} - -bool listList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool listVsList(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { bool touching = false; const ListShape* listA = static_cast(shapeA); const ListShape* listB = static_cast(shapeB); @@ -773,7 +666,7 @@ bool listList(const Shape* shapeA, const Shape* shapeB, CollisionList& collision } // helper function -bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter, +bool sphereVsAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { // sphere is A // cube is B @@ -921,11 +814,11 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius, } */ -bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { - return sphereAACube(sphereA->getTranslation(), sphereA->getRadius(), cubeCenter, cubeSide, collisions); +bool sphereVsAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + return sphereVsAACube(sphereA->getTranslation(), sphereA->getRadius(), cubeCenter, cubeSide, collisions); } -bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { +bool capsuleVsAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { // find nerest approach of capsule line segment to cube glm::vec3 capsuleAxis; capsuleA->computeNormalizedAxis(capsuleAxis); @@ -938,7 +831,7 @@ bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, fl } glm::vec3 nearestApproach = capsuleA->getTranslation() + offset * capsuleAxis; // collide nearest approach like a sphere at that point - return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions); + return sphereVsAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions); } bool findRayIntersectionWithShapes(const QVector shapes, const glm::vec3& rayStart, const glm::vec3& rayDirection, float& minDistance) { diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index 11768f0a48..3aa795e6fa 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -35,13 +35,6 @@ namespace ShapeCollider { /// \param collisions[out] collision details /// \return true if shapes collide bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions); - bool collideShapesOld(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions); - - /// \param shapesA list of shapes - /// \param shapeB list of shapes - /// \param collisions[out] average collision details - /// \return true if any shapes collide - bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision); bool collideShapeWithShapes(const Shape* shapeA, const QVector& shapes, int startIndex, CollisionList& collisions); bool collideShapesWithShapes(const QVector& shapesA, const QVector& shapesB, CollisionList& collisions); @@ -57,111 +50,87 @@ namespace ShapeCollider { /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool sphereSphere(const Shape* sphereA, const Shape* sphereB, CollisionList& collisions); + bool sphereVsSphere(const Shape* sphereA, const Shape* sphereB, CollisionList& collisions); /// \param sphereA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool sphereCapsule(const Shape* sphereA, const Shape* capsuleB, CollisionList& collisions); + bool sphereVsCapsule(const Shape* sphereA, const Shape* capsuleB, CollisionList& collisions); /// \param sphereA pointer to first shape (cannot be NULL) /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool spherePlane(const Shape* sphereA, const Shape* planeB, CollisionList& collisions); + bool sphereVsPlane(const Shape* sphereA, const Shape* planeB, CollisionList& collisions); /// \param capsuleA pointer to first shape (cannot be NULL) /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsuleSphere(const Shape* capsuleA, const Shape* sphereB, CollisionList& collisions); + bool capsuleVsSphere(const Shape* capsuleA, const Shape* sphereB, CollisionList& collisions); /// \param capsuleA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsuleCapsule(const Shape* capsuleA, const Shape* capsuleB, CollisionList& collisions); + bool capsuleVsCapsule(const Shape* capsuleA, const Shape* capsuleB, CollisionList& collisions); /// \param capsuleA pointer to first shape (cannot be NULL) /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsulePlane(const Shape* capsuleA, const Shape* planeB, CollisionList& collisions); + bool capsuleVsPlane(const Shape* capsuleA, const Shape* planeB, CollisionList& collisions); /// \param planeA pointer to first shape (cannot be NULL) /// \param sphereB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool planeSphere(const Shape* planeA, const Shape* sphereB, CollisionList& collisions); + bool planeVsSphere(const Shape* planeA, const Shape* sphereB, CollisionList& collisions); /// \param planeA pointer to first shape (cannot be NULL) /// \param capsuleB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool planeCapsule(const Shape* planeA, const Shape* capsuleB, CollisionList& collisions); + bool planeVsCapsule(const Shape* planeA, const Shape* capsuleB, CollisionList& collisions); /// \param planeA pointer to first shape (cannot be NULL) /// \param planeB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool planePlane(const Shape* planeA, const Shape* planeB, CollisionList& collisions); + bool planeVsPlane(const Shape* planeA, const Shape* planeB, CollisionList& collisions); - /// \param sphereA pointer to first shape (cannot be NULL) + /// \param shapeA pointer to first shape (cannot be NULL) /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool sphereList(const Shape* sphereA, const Shape* listB, CollisionList& collisions); + bool shapeVsList(const Shape* shapeA, const Shape* listB, CollisionList& collisions); - /// \param capuleA pointer to first shape (cannot be NULL) + /// \param listA pointer to first shape (cannot be NULL) + /// \param shapeB pointer to second shape (cannot be NULL) + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool listVsShape(const Shape* listA, const Shape* shapeB, CollisionList& collisions); + + /// \param listA pointer to first shape (cannot be NULL) /// \param listB pointer to second shape (cannot be NULL) /// \param[out] collisions where to append collision details /// \return true if shapes collide - bool capsuleList(const Shape* capsuleA, const Shape* listB, CollisionList& collisions); - - /// \param planeA pointer to first shape (cannot be NULL) - /// \param listB pointer to second shape (cannot be NULL) - /// \param[out] collisions where to append collision details - /// \return true if shapes collide - bool planeList(const Shape* planeA, const Shape* listB, CollisionList& collisions); - - /// \param listA pointer to first shape (cannot be NULL) - /// \param sphereB pointer to second shape (cannot be NULL) - /// \param[out] collisions where to append collision details - /// \return true if shapes collide - bool listSphere(const Shape* listA, const Shape* sphereB, CollisionList& collisions); - - /// \param listA pointer to first shape (cannot be NULL) - /// \param capsuleB pointer to second shape (cannot be NULL) - /// \param[out] collisions where to append collision details - /// \return true if shapes collide - bool listCapsule(const Shape* listA, const Shape* capsuleB, CollisionList& collisions); - - /// \param listA pointer to first shape (cannot be NULL) - /// \param planeB pointer to second shape (cannot be NULL) - /// \param[out] collisions where to append collision details - /// \return true if shapes collide - bool listPlane(const Shape* listA, const Shape* planeB, CollisionList& collisions); - - /// \param listA pointer to first shape (cannot be NULL) - /// \param capsuleB pointer to second shape (cannot be NULL) - /// \param[out] collisions where to append collision details - /// \return true if shapes collide - bool listList(const Shape* listA, const Shape* listB, CollisionList& collisions); + bool listVsList(const Shape* listA, const Shape* listB, CollisionList& collisions); /// \param sphereA pointer to sphere (cannot be NULL) /// \param cubeCenter center of cube /// \param cubeSide lenght of side of cube /// \param[out] collisions where to append collision details /// \return true if sphereA collides with axis aligned cube - bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + bool sphereVsAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); /// \param capsuleA pointer to capsule (cannot be NULL) /// \param cubeCenter center of cube /// \param cubeSide lenght of side of cube /// \param[out] collisions where to append collision details /// \return true if capsuleA collides with axis aligned cube - bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + bool capsuleVsAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); /// \param shapes list of pointers to shapes (shape pointers may be NULL) /// \param startPoint beginning of ray diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index db4833b20a..9473cc3452 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -713,7 +713,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() { sphereCenter = cubeCenter + sphereOffset * axis; sphere.setTranslation(sphereCenter); - if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (!ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl; } CollisionInfo* collision = collisions[0]; @@ -746,7 +746,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() { sphereCenter = cubeCenter + sphereOffset * axis; sphere.setTranslation(sphereCenter); - if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (!ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube." << " axis = " << axis << std::endl; @@ -820,7 +820,7 @@ void ShapeColliderTests::sphereTouchesAACubeEdges() { sphereCenter = cubeCenter + (lengthAxis * 0.5f * cubeSide + sphereRadius - overlap) * axis; sphere.setTranslation(sphereCenter); - if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (!ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl; } CollisionInfo* collision = collisions[i]; @@ -861,42 +861,42 @@ void ShapeColliderTests::sphereMissesAACube() { // top sphereCenter = cubeCenter + sphereOffset * yAxis; sphere.setTranslation(sphereCenter); - if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; } // bottom sphereCenter = cubeCenter - sphereOffset * yAxis; sphere.setTranslation(sphereCenter); - if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; } // left sphereCenter = cubeCenter + sphereOffset * xAxis; sphere.setTranslation(sphereCenter); - if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; } // right sphereCenter = cubeCenter - sphereOffset * xAxis; sphere.setTranslation(sphereCenter); - if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; } // forward sphereCenter = cubeCenter + sphereOffset * zAxis; sphere.setTranslation(sphereCenter); - if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; } // back sphereCenter = cubeCenter - sphereOffset * zAxis; sphere.setTranslation(sphereCenter); - if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + if (ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; } } From 2dfabdfe64710e34dfab6609af976486a43d17ab Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 22 Aug 2014 16:43:09 -0700 Subject: [PATCH 171/206] Support for scaling splat textures in S and T, use mipmaps for them. --- .../shaders/metavoxel_heightfield_splat.frag | 7 +++--- .../shaders/metavoxel_heightfield_splat.vert | 20 +++++++++++++--- interface/src/MetavoxelSystem.cpp | 24 ++++++++++++++++++- interface/src/MetavoxelSystem.h | 6 +++++ interface/src/renderer/TextureCache.cpp | 9 ++++++- interface/src/renderer/TextureCache.h | 1 + .../metavoxels/src/AttributeRegistry.cpp | 4 +++- libraries/metavoxels/src/AttributeRegistry.h | 9 ++++++- libraries/networking/src/PacketHeaders.cpp | 2 +- 9 files changed, 71 insertions(+), 11 deletions(-) diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.frag b/interface/resources/shaders/metavoxel_heightfield_splat.frag index 6a3c058b80..bb6b0d6536 100644 --- a/interface/resources/shaders/metavoxel_heightfield_splat.frag +++ b/interface/resources/shaders/metavoxel_heightfield_splat.frag @@ -11,6 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// the number of splats per pass const int SPLAT_COUNT = 4; // the splat textures @@ -22,7 +23,7 @@ varying vec4 alphaValues; void main(void) { // blend the splat textures gl_FragColor = gl_Color * (texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x + - texture2D(diffuseMaps[1], gl_TexCoord[0].st) * alphaValues.y + - texture2D(diffuseMaps[2], gl_TexCoord[0].st) * alphaValues.z + - texture2D(diffuseMaps[3], gl_TexCoord[0].st) * alphaValues.w); + texture2D(diffuseMaps[1], gl_TexCoord[1].st) * alphaValues.y + + texture2D(diffuseMaps[2], gl_TexCoord[2].st) * alphaValues.z + + texture2D(diffuseMaps[3], gl_TexCoord[3].st) * alphaValues.w); } diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.vert b/interface/resources/shaders/metavoxel_heightfield_splat.vert index 950162073e..926bcdd6c3 100644 --- a/interface/resources/shaders/metavoxel_heightfield_splat.vert +++ b/interface/resources/shaders/metavoxel_heightfield_splat.vert @@ -23,6 +23,15 @@ uniform float heightScale; // the scale between height and texture textures uniform float textureScale; +// the splat texture offset +uniform vec2 splatTextureOffset; + +// the splat textures scales on the S axis +uniform vec4 splatTextureScalesS; + +// the splat texture scales on the T axis +uniform vec4 splatTextureScalesT; + // the lower bounds of the values corresponding to the splat textures uniform vec4 textureValueMinima; @@ -35,16 +44,21 @@ varying vec4 alphaValues; void main(void) { // add the height to the position float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; - gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); + vec4 modelSpacePosition = gl_Vertex + vec4(0.0, height, 0.0, 0.0); + gl_Position = gl_ModelViewProjectionMatrix * modelSpacePosition; // the zero height should be invisible gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0 - step(height, 0.0)); // pass along the scaled/offset texture coordinates - gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * textureScale; + vec4 textureSpacePosition = vec4(modelSpacePosition.xz, 0.0, 1.0) + vec4(splatTextureOffset, 0.0, 0.0); + gl_TexCoord[0] = textureSpacePosition * vec4(splatTextureScalesS[0], splatTextureScalesT[0], 0.0, 1.0); + gl_TexCoord[1] = textureSpacePosition * vec4(splatTextureScalesS[1], splatTextureScalesT[1], 0.0, 1.0); + gl_TexCoord[2] = textureSpacePosition * vec4(splatTextureScalesS[2], splatTextureScalesT[2], 0.0, 1.0); + gl_TexCoord[3] = textureSpacePosition * vec4(splatTextureScalesS[3], splatTextureScalesT[3], 0.0, 1.0); // compute the alpha values for each texture - float value = texture2D(textureMap, gl_TexCoord[0].st).r; + float value = texture2D(textureMap, (gl_MultiTexCoord0.st - vec2(heightScale, heightScale)) * textureScale).r; vec4 valueVector = vec4(value, value, value, value); alphaValues = step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima); } diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 3864b0537f..1d97dc94fc 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -822,22 +822,38 @@ void HeightfieldBuffer::render(bool cursor) { DefaultMetavoxelRendererImplementation::getSplatHeightScaleLocation(), 1.0f / _heightSize); DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( DefaultMetavoxelRendererImplementation::getSplatTextureScaleLocation(), (float)_heightSize / innerSize); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureOffsetLocation(), + _translation.x / _scale, _translation.z / _scale); glBindTexture(GL_TEXTURE_2D, _textureTextureID); const int TEXTURES_PER_SPLAT = 4; for (int i = 0; i < _textures.size(); i += TEXTURES_PER_SPLAT) { + QVector4D scalesS, scalesT; + for (int j = 0; j < SPLAT_COUNT; j++) { glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[j]); int index = i + j; if (index < _networkTextures.size()) { const NetworkTexturePointer& texture = _networkTextures.at(index); - glBindTexture(GL_TEXTURE_2D, texture ? texture->getID() : 0); + if (texture) { + HeightfieldTexture* heightfieldTexture = static_cast(_textures.at(index).data()); + scalesS[j] = _scale / heightfieldTexture->getScaleS(); + scalesT[j] = _scale / heightfieldTexture->getScaleT(); + glBindTexture(GL_TEXTURE_2D, texture->getID()); + } else { + glBindTexture(GL_TEXTURE_2D, 0); + } } else { glBindTexture(GL_TEXTURE_2D, 0); } } const float QUARTER_STEP = 0.25f * EIGHT_BIT_MAXIMUM_RECIPROCAL; + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureScalesSLocation(), scalesS); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureScalesTLocation(), scalesT); DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( DefaultMetavoxelRendererImplementation::getSplatTextureValueMinimaLocation(), (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, @@ -1050,6 +1066,9 @@ void DefaultMetavoxelRendererImplementation::init() { _splatHeightfieldProgram.setUniformValueArray("diffuseMaps", SPLAT_TEXTURE_UNITS, SPLAT_COUNT); _splatHeightScaleLocation = _splatHeightfieldProgram.uniformLocation("heightScale"); _splatTextureScaleLocation = _splatHeightfieldProgram.uniformLocation("textureScale"); + _splatTextureOffsetLocation = _splatHeightfieldProgram.uniformLocation("splatTextureOffset"); + _splatTextureScalesSLocation = _splatHeightfieldProgram.uniformLocation("splatTextureScalesS"); + _splatTextureScalesTLocation = _splatHeightfieldProgram.uniformLocation("splatTextureScalesT"); _splatTextureValueMinimaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMinima"); _splatTextureValueMaximaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMaxima"); _splatHeightfieldProgram.release(); @@ -1695,6 +1714,9 @@ int DefaultMetavoxelRendererImplementation::_baseColorScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_splatHeightfieldProgram; int DefaultMetavoxelRendererImplementation::_splatHeightScaleLocation; int DefaultMetavoxelRendererImplementation::_splatTextureScaleLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureOffsetLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureScalesSLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureScalesTLocation; int DefaultMetavoxelRendererImplementation::_splatTextureValueMinimaLocation; int DefaultMetavoxelRendererImplementation::_splatTextureValueMaximaLocation; ProgramObject DefaultMetavoxelRendererImplementation::_lightHeightfieldProgram; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index ac4dd430fb..b1ddcf0bff 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -252,6 +252,9 @@ public: static ProgramObject& getSplatHeightfieldProgram() { return _splatHeightfieldProgram; } static int getSplatHeightScaleLocation() { return _splatHeightScaleLocation; } static int getSplatTextureScaleLocation() { return _splatTextureScaleLocation; } + static int getSplatTextureOffsetLocation() { return _splatTextureOffsetLocation; } + static int getSplatTextureScalesSLocation() { return _splatTextureScalesSLocation; } + static int getSplatTextureScalesTLocation() { return _splatTextureScalesTLocation; } static int getSplatTextureValueMinimaLocation() { return _splatTextureValueMinimaLocation; } static int getSplatTextureValueMaximaLocation() { return _splatTextureValueMaximaLocation; } @@ -297,6 +300,9 @@ private: static ProgramObject _splatHeightfieldProgram; static int _splatHeightScaleLocation; static int _splatTextureScaleLocation; + static int _splatTextureOffsetLocation; + static int _splatTextureScalesSLocation; + static int _splatTextureScalesTLocation; static int _splatTextureValueMinimaLocation; static int _splatTextureValueMaximaLocation; diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index c3e58d52bb..e13f97a1d2 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -320,6 +320,7 @@ Texture::~Texture() { NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) : Resource(url, !content.isEmpty()), + _type(type), _translucent(false) { if (!url.isValid()) { @@ -474,7 +475,13 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.constBits()); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (_type == SPLAT_TEXTURE) { + // generate mipmaps for splat textures + glGenerateMipmap(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index 83d9b6cc74..e1d69677f6 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -145,6 +145,7 @@ protected: private: + TextureType _type; bool _translucent; QColor _averageColor; }; diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index e823e081cc..425bf8ff4a 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -1055,7 +1055,9 @@ void HeightfieldTextureData::read(Bitstream& in, int bytes) { in >> _textures; } -HeightfieldTexture::HeightfieldTexture() { +HeightfieldTexture::HeightfieldTexture() : + _scaleS(1.0f), + _scaleT(1.0f) { } HeightfieldAttribute::HeightfieldAttribute(const QString& name) : diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index fb6ff5097d..66da7a9b6f 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -548,16 +548,23 @@ private: class HeightfieldTexture : public SharedObject { Q_OBJECT Q_PROPERTY(QUrl url MEMBER _url) - + Q_PROPERTY(float scaleS MEMBER _scaleS) + Q_PROPERTY(float scaleT MEMBER _scaleT) + public: Q_INVOKABLE HeightfieldTexture(); const QUrl& getURL() const { return _url; } + float getScaleS() const { return _scaleS; } + float getScaleT() const { return _scaleT; } + private: QUrl _url; + float _scaleS; + float _scaleT; }; /// An attribute that stores heightfield data. diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 619f652e1e..378d995793 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -81,7 +81,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 2; + return 3; default: return 0; } From 5d39efea827d02b83788b4c84a85f34cb5914513 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 22 Aug 2014 17:26:19 -0700 Subject: [PATCH 172/206] make sure each test error has a newline at end --- tests/physics/src/ShapeColliderTests.cpp | 213 ++++++++++------------- 1 file changed, 94 insertions(+), 119 deletions(-) diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index 9473cc3452..18882423fb 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -73,8 +73,7 @@ void ShapeColliderTests::sphereMissesSphere() { if (collisions.size() > 0) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: expected empty collision list but size is " << collisions.size() - << std::endl; + << " ERROR: expected empty collision list but size is " << collisions.size() << std::endl; } } @@ -122,7 +121,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -132,7 +131,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } @@ -152,7 +151,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -162,7 +161,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } } @@ -202,23 +201,20 @@ void ShapeColliderTests::sphereMissesCapsule() { if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should NOT touch" - << std::endl; + << " ERROR: sphere and capsule should NOT touch" << std::endl; } // capsuleB against sphereA if (ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should NOT touch" - << std::endl; + << " ERROR: sphere and capsule should NOT touch" << std::endl; } } if (collisions.size() > 0) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: expected empty collision list but size is " << collisions.size() - << std::endl; + << " ERROR: expected empty collision list but size is " << collisions.size() << std::endl; } } @@ -244,8 +240,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should touch" - << std::endl; + << " ERROR: sphere and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -257,7 +252,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -266,15 +261,14 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB collides with sphereA if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and sphere should touch" - << std::endl; + << " ERROR: capsule and sphere should touch" << std::endl; } else { ++numCollisions; } @@ -286,7 +280,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of capsuleB @@ -297,7 +291,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } { // sphereA hits end cap at axis @@ -307,8 +301,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should touch" - << std::endl; + << " ERROR: sphere and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -320,7 +313,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -329,15 +322,14 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB collides with sphereA if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and sphere should touch" - << std::endl; + << " ERROR: capsule and sphere should touch" << std::endl; } else { ++numCollisions; } @@ -349,7 +341,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of capsuleB @@ -360,7 +352,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } { // sphereA hits start cap at axis @@ -370,8 +362,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should touch" - << std::endl; + << " ERROR: sphere and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -383,7 +374,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -392,15 +383,14 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB collides with sphereA if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and sphere should touch" - << std::endl; + << " ERROR: capsule and sphere should touch" << std::endl; } else { ++numCollisions; } @@ -412,7 +402,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of capsuleB @@ -423,7 +413,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } if (collisions.size() != numCollisions) { @@ -453,14 +443,12 @@ void ShapeColliderTests::capsuleMissesCapsule() { if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } // end to end @@ -468,14 +456,12 @@ void ShapeColliderTests::capsuleMissesCapsule() { if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } // rotate B and move it to the side @@ -485,20 +471,17 @@ void ShapeColliderTests::capsuleMissesCapsule() { if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (collisions.size() > 0) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: expected empty collision list but size is " << collisions.size() - << std::endl; + << " ERROR: expected empty collision list but size is " << collisions.size() << std::endl; } } @@ -523,16 +506,14 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -544,16 +525,14 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -567,16 +546,14 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -593,8 +570,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -605,7 +581,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis; @@ -613,15 +589,14 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB vs capsuleA if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -632,8 +607,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration << std::endl; } expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis; @@ -641,8 +615,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint << std::endl; } } @@ -658,8 +631,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -670,8 +642,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration << std::endl; } glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis; @@ -679,8 +650,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint << std::endl; } } } @@ -714,7 +684,8 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() { sphere.setTranslation(sphereCenter); if (!ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube. axis = " << axis + << std::endl; } CollisionInfo* collision = collisions[0]; if (!collision) { @@ -724,17 +695,13 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() { glm::vec3 expectedPenetration = - overlap * axis; if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration - << " expected " << expectedPenetration - << " axis = " << axis - << std::endl; + << " expected " << expectedPenetration << " axis = " << axis << std::endl; } glm::vec3 expectedContact = sphereCenter - sphereRadius * axis; if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint - << " expected " << expectedContact - << " axis = " << axis - << std::endl; + << " expected " << expectedContact << " axis = " << axis << std::endl; } } @@ -748,30 +715,24 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() { if (!ShapeCollider::sphereVsAACube(&sphere, cubeCenter, cubeSide, collisions)){ std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube." - << " axis = " << axis - << std::endl; + << " axis = " << axis << std::endl; } CollisionInfo* collision = collisions[0]; if (!collision) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: no CollisionInfo on y-axis." - << " axis = " << axis - << std::endl; + << " axis = " << axis << std::endl; } glm::vec3 expectedPenetration = - overlap * axis; if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration - << " expected " << expectedPenetration - << " axis = " << axis - << std::endl; + << " expected " << expectedPenetration << " axis = " << axis << std::endl; } glm::vec3 expectedContact = sphereCenter - sphereRadius * axis; if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint - << " expected " << expectedContact - << " axis = " << axis - << std::endl; + << " expected " << expectedContact << " axis = " << axis << std::endl; } } } @@ -831,17 +792,13 @@ void ShapeColliderTests::sphereTouchesAACubeEdges() { glm::vec3 expectedPenetration = - overlap * axis; if (glm::distance(expectedPenetration, collision->_penetration) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: penetration = " << collision->_penetration - << " expected " << expectedPenetration - << " axis = " << axis - << std::endl; + << " expected " << expectedPenetration << " axis = " << axis << std::endl; } glm::vec3 expectedContact = sphereCenter - sphereRadius * axis; if (glm::distance(expectedContact, collision->_contactPoint) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: contactaPoint = " << collision->_contactPoint - << " expected " << expectedContact - << " axis = " << axis - << std::endl; + << " expected " << expectedContact << " axis = " << axis << std::endl; } } } @@ -968,7 +925,8 @@ void ShapeColliderTests::rayHitsSphere() { float expectedDistance = startDistance - radius; float relativeError = fabsf(distance - expectedDistance) / startDistance; if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray sphere intersection distance error = " + << relativeError << std::endl; } } } @@ -1025,7 +983,8 @@ void ShapeColliderTests::rayBarelyMissesSphere() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } // translate and rotate the whole system... @@ -1043,7 +1002,8 @@ void ShapeColliderTests::rayBarelyMissesSphere() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should just barely miss sphere" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } } @@ -1065,7 +1025,8 @@ void ShapeColliderTests::rayHitsCapsule() { float expectedDistance = startDistance - radius; float relativeError = fabsf(distance - expectedDistance) / startDistance; if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " + << relativeError << std::endl; } // toward top of cylindrical wall @@ -1076,7 +1037,8 @@ void ShapeColliderTests::rayHitsCapsule() { } relativeError = fabsf(distance - expectedDistance) / startDistance; if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " + << relativeError << std::endl; } // toward top cap @@ -1088,7 +1050,8 @@ void ShapeColliderTests::rayHitsCapsule() { } relativeError = fabsf(distance - expectedDistance) / startDistance; if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " + << relativeError << std::endl; } const float EDGE_CASE_SLOP_FACTOR = 20.0f; @@ -1103,7 +1066,8 @@ void ShapeColliderTests::rayHitsCapsule() { relativeError = fabsf(distance - expectedDistance) / startDistance; // for edge cases we allow a LOT of error if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " + << relativeError << std::endl; } // toward tip of bottom cap @@ -1116,7 +1080,8 @@ void ShapeColliderTests::rayHitsCapsule() { relativeError = fabsf(distance - expectedDistance) / startDistance; // for edge cases we allow a LOT of error if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " + << relativeError << std::endl; } // toward edge of capsule cylindrical face @@ -1130,7 +1095,8 @@ void ShapeColliderTests::rayHitsCapsule() { relativeError = fabsf(distance - expectedDistance) / startDistance; // for edge cases we allow a LOT of error if (relativeError > EDGE_CASE_SLOP_FACTOR * EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray capsule intersection distance error = " + << relativeError << std::endl; } } // TODO: test at steep angles near cylinder/cap junction @@ -1157,7 +1123,8 @@ void ShapeColliderTests::rayMissesCapsule() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } // below bottom cap @@ -1167,7 +1134,8 @@ void ShapeColliderTests::rayMissesCapsule() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } // past edge of capsule cylindrical face @@ -1178,7 +1146,8 @@ void ShapeColliderTests::rayMissesCapsule() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss capsule" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } } // TODO: test at steep angles near edge @@ -1204,7 +1173,8 @@ void ShapeColliderTests::rayHitsPlane() { float expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; float relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin; if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " + << relativeError << std::endl; } // rotate the whole system and try again @@ -1225,7 +1195,8 @@ void ShapeColliderTests::rayHitsPlane() { expectedDistance = SQUARE_ROOT_OF_3 * planeDistanceFromOrigin; relativeError = fabsf(distance - expectedDistance) / planeDistanceFromOrigin; if (relativeError > EPSILON) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " << relativeError << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray plane intersection distance error = " + << relativeError << std::endl; } } @@ -1246,7 +1217,8 @@ void ShapeColliderTests::rayMissesPlane() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } // rotate the whole system and try again @@ -1264,7 +1236,8 @@ void ShapeColliderTests::rayMissesPlane() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } } @@ -1278,7 +1251,8 @@ void ShapeColliderTests::rayMissesPlane() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } // rotate the whole system and try again @@ -1296,7 +1270,8 @@ void ShapeColliderTests::rayMissesPlane() { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: ray should miss plane" << std::endl; } if (distance != FLT_MAX) { - std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" << std::endl; + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: distance should be unchanged after intersection miss" + << std::endl; } } } From de0ebf2f575a0eea8d49849673dd0005e96f89ad Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 22 Aug 2014 17:33:31 -0700 Subject: [PATCH 173/206] make sure all test errors have newline at end --- tests/physics/src/VerletShapeTests.cpp | 126 ++++++++++--------------- 1 file changed, 48 insertions(+), 78 deletions(-) diff --git a/tests/physics/src/VerletShapeTests.cpp b/tests/physics/src/VerletShapeTests.cpp index 705ddeeac3..55ebbdc741 100644 --- a/tests/physics/src/VerletShapeTests.cpp +++ b/tests/physics/src/VerletShapeTests.cpp @@ -102,8 +102,7 @@ void VerletShapeTests::sphereMissesSphere() { if (collisions.size() > 0) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: expected empty collision list but size is " << collisions.size() - << std::endl; + << " ERROR: expected empty collision list but size is " << collisions.size() << std::endl; } } @@ -159,7 +158,7 @@ void VerletShapeTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -169,7 +168,7 @@ void VerletShapeTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } @@ -189,7 +188,7 @@ void VerletShapeTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -199,7 +198,7 @@ void VerletShapeTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } } @@ -247,23 +246,20 @@ void VerletShapeTests::sphereMissesCapsule() { if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should NOT touch" - << std::endl; + << " ERROR: sphere and capsule should NOT touch" << std::endl; } // capsuleB against sphereA if (ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should NOT touch" - << std::endl; + << " ERROR: sphere and capsule should NOT touch" << std::endl; } } if (collisions.size() > 0) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: expected empty collision list but size is " << collisions.size() - << std::endl; + << " ERROR: expected empty collision list but size is " << collisions.size() << std::endl; } } @@ -297,8 +293,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should touch" - << std::endl; + << " ERROR: sphere and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -310,7 +305,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -319,15 +314,14 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB collides with sphereA if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and sphere should touch" - << std::endl; + << " ERROR: capsule and sphere should touch" << std::endl; } else { ++numCollisions; } @@ -339,7 +333,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of capsuleB @@ -350,7 +344,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } { // sphereA hits end cap at axis @@ -360,8 +354,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should touch" - << std::endl; + << " ERROR: sphere and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -373,7 +366,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -382,15 +375,14 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB collides with sphereA if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and sphere should touch" - << std::endl; + << " ERROR: capsule and sphere should touch" << std::endl; } else { ++numCollisions; } @@ -402,7 +394,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of capsuleB @@ -413,7 +405,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } { // sphereA hits start cap at axis @@ -423,8 +415,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: sphere and capsule should touch" - << std::endl; + << " ERROR: sphere and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -436,7 +427,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of sphereA @@ -445,15 +436,14 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB collides with sphereA if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and sphere should touch" - << std::endl; + << " ERROR: capsule and sphere should touch" << std::endl; } else { ++numCollisions; } @@ -465,7 +455,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } // contactPoint is on surface of capsuleB @@ -476,7 +466,7 @@ void VerletShapeTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } } if (collisions.size() != numCollisions) { @@ -515,14 +505,12 @@ void VerletShapeTests::capsuleMissesCapsule() { if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } // end to end @@ -530,14 +518,12 @@ void VerletShapeTests::capsuleMissesCapsule() { if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } // rotate B and move it to the side @@ -547,20 +533,17 @@ void VerletShapeTests::capsuleMissesCapsule() { if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should NOT touch" - << std::endl; + << " ERROR: capsule and capsule should NOT touch" << std::endl; } if (collisions.size() > 0) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: expected empty collision list but size is " << collisions.size() - << std::endl; + << " ERROR: expected empty collision list but size is " << collisions.size() << std::endl; } } @@ -594,16 +577,14 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -615,16 +596,14 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -638,16 +617,14 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -664,8 +641,7 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -676,7 +652,7 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration; + << " actual = " << collision->_penetration << std::endl; } glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis; @@ -684,15 +660,14 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint; + << " actual = " << collision->_contactPoint << std::endl; } // capsuleB vs capsuleA if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -703,8 +678,7 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration << std::endl; } expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis; @@ -712,8 +686,7 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint << std::endl; } } @@ -729,8 +702,7 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ - << " ERROR: capsule and capsule should touch" - << std::endl; + << " ERROR: capsule and capsule should touch" << std::endl; } else { ++numCollisions; } @@ -741,8 +713,7 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration << std::endl; } glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis; @@ -750,8 +721,7 @@ void VerletShapeTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint << std::endl; } } } From 1bd7734ec1b376c7603da9f8d51729fe73688c83 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 22 Aug 2014 18:07:11 -0700 Subject: [PATCH 174/206] sometimes swap order of shapes for simpler code --- libraries/shared/src/ShapeCollider.cpp | 112 +---------------------- tests/physics/src/ShapeColliderTests.cpp | 29 +++++- tests/physics/src/VerletShapeTests.cpp | 29 +++++- 3 files changed, 57 insertions(+), 113 deletions(-) diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 536d9bdcde..ec0c88bd0f 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -260,80 +260,7 @@ bool sphereVsPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& coll } bool capsuleVsSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - const CapsuleShape* capsuleA = static_cast(shapeA); - const SphereShape* sphereB = static_cast(shapeB); - // find sphereB's closest approach to axis of capsuleA - glm::vec3 AB = capsuleA->getTranslation() - sphereB->getTranslation(); - glm::vec3 capsuleAxis; - capsuleA->computeNormalizedAxis(capsuleAxis); - float axialDistance = - glm::dot(AB, capsuleAxis); - float absAxialDistance = fabsf(axialDistance); - float totalRadius = sphereB->getRadius() + capsuleA->getRadius(); - if (absAxialDistance < totalRadius + capsuleA->getHalfHeight()) { - glm::vec3 radialAxis = AB + axialDistance * capsuleAxis; // from sphereB to axis of capsuleA - float radialDistance2 = glm::length2(radialAxis); - float totalRadius2 = totalRadius * totalRadius; - if (radialDistance2 > totalRadius2) { - // sphere is too far from capsule axis - return false; - } - - // closestApproach = point on capsuleA's axis that is closest to sphereB's center - glm::vec3 closestApproach = capsuleA->getTranslation() + axialDistance * capsuleAxis; - - if (absAxialDistance > capsuleA->getHalfHeight()) { - // sphere hits capsule on a cap - // --> recompute radialAxis and closestApproach - float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f; - closestApproach = capsuleA->getTranslation() + (sign * capsuleA->getHalfHeight()) * capsuleAxis; - radialAxis = closestApproach - sphereB->getTranslation(); - radialDistance2 = glm::length2(radialAxis); - if (radialDistance2 > totalRadius2) { - return false; - } - } - if (radialDistance2 > EPSILON * EPSILON) { - CollisionInfo* collision = collisions.getNewCollision(); - if (!collision) { - // collisions list is full - return false; - } - // normalize the radialAxis - float radialDistance = sqrtf(radialDistance2); - radialAxis /= radialDistance; - // penetration points from A into B - collision->_penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B - // contactPoint is on surface of capsuleA - collision->_contactPoint = closestApproach - capsuleA->getRadius() * radialAxis; - collision->_shapeA = capsuleA; - collision->_shapeB = sphereB; - } else { - // A is on B's axis, so the penetration is undefined... - if (absAxialDistance > capsuleA->getHalfHeight()) { - // ...for the cylinder case (for now we pretend the collision doesn't exist) - return false; - } else { - CollisionInfo* collision = collisions.getNewCollision(); - if (!collision) { - // collisions list is full - return false; - } - // ... but still defined for the cap case - if (axialDistance < 0.0f) { - // we're hitting the start cap, so we negate the capsuleAxis - capsuleAxis *= -1; - } - float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f; - collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis; - // contactPoint is on surface of sphereA - collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis; - collision->_shapeA = capsuleA; - collision->_shapeB = sphereB; - } - } - return true; - } - return false; + return sphereVsCapsule(shapeB, shapeA, collisions); } /// \param lineP point on line @@ -586,44 +513,11 @@ bool capsuleVsPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& col } bool planeVsSphere(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - const PlaneShape* planeA = static_cast(shapeA); - const SphereShape* sphereB = static_cast(shapeB); - glm::vec3 penetration; - if (findSpherePlanePenetration(sphereB->getTranslation(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) { - CollisionInfo* collision = collisions.getNewCollision(); - if (!collision) { - return false; // collision list is full - } - collision->_penetration = -penetration; - collision->_contactPoint = sphereB->getTranslation() + - (sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration; - collision->_shapeA = planeA; - collision->_shapeB = sphereB; - return true; - } - return false; + return sphereVsPlane(shapeB, shapeA, collisions); } bool planeVsCapsule(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { - const PlaneShape* planeA = static_cast(shapeA); - const CapsuleShape* capsuleB = static_cast(shapeB); - glm::vec3 start, end, penetration; - capsuleB->getStartPoint(start); - capsuleB->getEndPoint(end); - glm::vec4 plane = planeA->getCoefficients(); - if (findCapsulePlanePenetration(start, end, capsuleB->getRadius(), plane, penetration)) { - CollisionInfo* collision = collisions.getNewCollision(); - if (!collision) { - return false; // collision list is full - } - collision->_penetration = -penetration; - glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end; - collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration; - collision->_shapeA = planeA; - collision->_shapeB = capsuleB; - return true; - } - return false; + return capsuleVsPlane(shapeB, shapeA, collisions); } bool planeVsPlane(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index 18882423fb..45d3ed6508 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -276,6 +276,10 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); expectedPenetration = - (radialOffset - totalRadius) * xAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedPenetration *= -1.0f; + } inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -287,6 +291,11 @@ void ShapeColliderTests::sphereTouchesCapsule() { glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation(); glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis; expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach); + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + closestApproach = sphereA.getTranslation() - glm::dot(BtoA, yAxis) * yAxis; + expectedContactPoint = closestApproach - radiusB * glm::normalize(BtoA - closestApproach); + } inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -296,7 +305,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { } { // sphereA hits end cap at axis glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis; - sphereA.setTranslation(axialOffset * yAxis); + sphereA.setTranslation(axialOffset); if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { @@ -337,6 +346,10 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedPenetration *= -1.0f; + } inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -348,6 +361,10 @@ void ShapeColliderTests::sphereTouchesCapsule() { glm::vec3 endPoint; capsuleB.getEndPoint(endPoint); expectedContactPoint = endPoint + radiusB * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedContactPoint = axialOffset - radiusA * yAxis; + } inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -357,7 +374,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { } { // sphereA hits start cap at axis glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis; - sphereA.setTranslation(axialOffset * yAxis); + sphereA.setTranslation(axialOffset); if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { @@ -398,6 +415,10 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedPenetration *= -1.0f; + } inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -409,6 +430,10 @@ void ShapeColliderTests::sphereTouchesCapsule() { glm::vec3 startPoint; capsuleB.getStartPoint(startPoint); expectedContactPoint = startPoint - radiusB * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedContactPoint = axialOffset + radiusA * yAxis; + } inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ diff --git a/tests/physics/src/VerletShapeTests.cpp b/tests/physics/src/VerletShapeTests.cpp index 55ebbdc741..df5cdc5c6b 100644 --- a/tests/physics/src/VerletShapeTests.cpp +++ b/tests/physics/src/VerletShapeTests.cpp @@ -329,6 +329,10 @@ void VerletShapeTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); expectedPenetration = - (radialOffset - totalRadius) * xAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedPenetration *= -1.0f; + } inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -340,6 +344,11 @@ void VerletShapeTests::sphereTouchesCapsule() { glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation(); glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis; expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach); + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + closestApproach = sphereA.getTranslation() - glm::dot(BtoA, yAxis) * yAxis; + expectedContactPoint = closestApproach - radiusB * glm::normalize(BtoA - closestApproach); + } inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -349,7 +358,7 @@ void VerletShapeTests::sphereTouchesCapsule() { } { // sphereA hits end cap at axis glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis; - sphereA.setTranslation(axialOffset * yAxis); + sphereA.setTranslation(axialOffset); if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { @@ -390,6 +399,10 @@ void VerletShapeTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedPenetration *= -1.0f; + } inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -401,6 +414,10 @@ void VerletShapeTests::sphereTouchesCapsule() { glm::vec3 endPoint; capsuleB.getEndPoint(endPoint); expectedContactPoint = endPoint + radiusB * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedContactPoint = axialOffset - radiusA * yAxis; + } inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -410,7 +427,7 @@ void VerletShapeTests::sphereTouchesCapsule() { } { // sphereA hits start cap at axis glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis; - sphereA.setTranslation(axialOffset * yAxis); + sphereA.setTranslation(axialOffset); if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { @@ -451,6 +468,10 @@ void VerletShapeTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedPenetration *= -1.0f; + } inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ @@ -462,6 +483,10 @@ void VerletShapeTests::sphereTouchesCapsule() { glm::vec3 startPoint; capsuleB.getStartPoint(startPoint); expectedContactPoint = startPoint - radiusB * yAxis; + if (collision->_shapeA == &sphereA) { + // the ShapeCollider swapped the order of the shapes + expectedContactPoint = axialOffset + radiusA * yAxis; + } inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ From 4cd1f4afef126891f36a07e2547dbf22af3d858e Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Sun, 24 Aug 2014 22:17:07 -0700 Subject: [PATCH 175/206] add support for head-shadow/penumbra filter for positional audio streams --- assignment-client/src/audio/AudioMixer.cpp | 47 +++- assignment-client/src/audio/AudioMixer.h | 5 +- .../resources/web/settings/describe.json | 8 +- examples/playSound.js | 4 +- examples/playSoundOrbit.js | 42 ++++ interface/src/Audio.h | 5 +- libraries/audio/src/AudioFilter.cpp | 26 -- libraries/audio/src/AudioFilter.h | 233 ++++++------------ libraries/audio/src/AudioFilterBank.cpp | 45 ++++ libraries/audio/src/AudioFilterBank.h | 169 +++++++++++++ libraries/audio/src/PositionalAudioStream.cpp | 3 + libraries/audio/src/PositionalAudioStream.h | 6 + 12 files changed, 405 insertions(+), 188 deletions(-) create mode 100644 examples/playSoundOrbit.js delete mode 100644 libraries/audio/src/AudioFilter.cpp create mode 100644 libraries/audio/src/AudioFilterBank.cpp create mode 100644 libraries/audio/src/AudioFilterBank.h diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 5f4c3827f2..e1d118a11d 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -75,6 +75,8 @@ int AudioMixer::_maxFramesOverDesired = 0; bool AudioMixer::_printStreamStats = false; +bool AudioMixer::_enableFilter = false; + AudioMixer::AudioMixer(const QByteArray& packet) : ThreadedAssignment(packet), _trailingSleepRatio(1.0f), @@ -107,7 +109,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* float weakChannelAmplitudeRatio = 1.0f; bool shouldAttenuate = (streamToAdd != listeningNodeStream); - + if (shouldAttenuate) { // if the two stream pointers do not match then these are different streams @@ -267,6 +269,43 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); } } + + if ( _enableFilter && shouldAttenuate ) { + + glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream->getPosition(); + if ( relativePosition.z < 0 ) { // if the source is behind us + + AudioFilterPEQ1s& penumbraFilter = streamToAdd->getFilter(); + + // calculate penumbra angle + float headPenumbraAngle = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f), + glm::normalize(relativePosition)); + + // normalize penumbra angle + float normalizedHeadPenumbraAngle = headPenumbraAngle / PI_OVER_TWO; + + if ( normalizedHeadPenumbraAngle < EPSILON ) { + normalizedHeadPenumbraAngle = EPSILON; + } + + float penumbraFilterGain; + float penumbraFilterFrequency; + float penumbraFilterSlope; + + // calculate the updated gain + penumbraFilterGain = normalizedHeadPenumbraAngle; // Note this will be tuned - consider this only a crude-first pass at correlating gain with penumbra angle. + penumbraFilterFrequency = 2000.0f; + penumbraFilterSlope = 1.0f; // gentle slope + +// printf("gain=%f,angle=%f\n",penumbraFilterGain,headPenumbraAngle); + + // set the gain on both filter channels + penumbraFilter.setParameters(0,0,SAMPLE_RATE,penumbraFilterFrequency,penumbraFilterGain,penumbraFilterSlope); + penumbraFilter.setParameters(0,1,SAMPLE_RATE,penumbraFilterFrequency,penumbraFilterGain,penumbraFilterSlope); + + penumbraFilter.render( _clientSamples, _clientSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2); + } + } } void AudioMixer::prepareMixForListeningNode(Node* node) { @@ -462,6 +501,12 @@ void AudioMixer::run() { bool ok; + const QString FILTER_KEY = "E-enable-filter"; + _enableFilter = audioGroupObject[FILTER_KEY].toBool(); + if (_enableFilter) { + qDebug() << "Filter enabled"; + } + const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "B-desired-jitter-buffer-frames"; _staticDesiredJitterBufferFrames = audioGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok); if (!ok) { diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 2a4b93149c..47526adcfe 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -41,7 +41,7 @@ public slots: static bool getUseDynamicJitterBuffers() { return _useDynamicJitterBuffers; } static int getStaticDesiredJitterBufferFrames() { return _staticDesiredJitterBufferFrames; } static int getMaxFramesOverDesired() { return _maxFramesOverDesired; } - + private: /// adds one stream to the mix for a listening node void addStreamToMixForListeningNodeWithStream(PositionalAudioStream* streamToAdd, @@ -68,7 +68,8 @@ private: static int _maxFramesOverDesired; static bool _printStreamStats; - + static bool _enableFilter; + quint64 _lastSendAudioStreamStatsTime; }; diff --git a/domain-server/resources/web/settings/describe.json b/domain-server/resources/web/settings/describe.json index 788a3ad551..a9466eede9 100644 --- a/domain-server/resources/web/settings/describe.json +++ b/domain-server/resources/web/settings/describe.json @@ -32,7 +32,13 @@ "help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)", "placeholder": "no zone", "default": "" + }, + "E-enable-filter": { + "type": "checkbox", + "label": "Enable Positional Filter", + "help": "If enabled, positional audio stream uses lowpass filter", + "default": false } } } -} \ No newline at end of file +} diff --git a/examples/playSound.js b/examples/playSound.js index 189c24e86f..867768f807 100644 --- a/examples/playSound.js +++ b/examples/playSound.js @@ -12,14 +12,14 @@ var bird = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); function maybePlaySound(deltaTime) { - if (Math.random() < 0.01) { +// if (Math.random() < 0.01) { // Set the location and other info for the sound to play var options = new AudioInjectionOptions(); var position = MyAvatar.position; options.position = position; options.volume = 0.5; Audio.playSound(bird, options); - } +// } } // Connect a call back that happens every frame diff --git a/examples/playSoundOrbit.js b/examples/playSoundOrbit.js new file mode 100644 index 0000000000..92b8e0a96e --- /dev/null +++ b/examples/playSoundOrbit.js @@ -0,0 +1,42 @@ +// +// playSoundPath.js +// examples +// +// Created by Craig Hansen-Sturm on 05/27/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var soundClip = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel create 3.raw"); + +var currentTime = 1.570079; // pi/2 +var deltaTime = 0.05; +var distance = 1; +var debug = 0; + +function playSound() { + var options = new AudioInjectionOptions(); + currentTime += deltaTime; + + var s = distance * Math.sin(currentTime); + var c = distance * Math.cos(currentTime); + + var soundOffset = { x:s, y:0, z:c }; + + if( debug ) { + print("t=" + currentTime + "offset=" + soundOffset.x + "," + soundOffset.y + "," + soundOffset.z); + } + + var avatarPosition = MyAvatar.position; + var soundPosition = Vec3.sum(avatarPosition,soundOffset); + + options.position = soundPosition + options.volume = 1.0; + Audio.playSound(soundClip, options); +} + +Script.setInterval(playSound, 250); + + diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 4fb54218af..c7e9e6b2f0 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -20,6 +20,7 @@ #include "RingBufferHistory.h" #include "MovingMinMaxAvg.h" #include "AudioFilter.h" +#include "AudioFilterBank.h" #include #include @@ -278,8 +279,8 @@ private: int _samplesPerScope; // Multi-band parametric EQ - bool _peqEnabled; - AudioFilterPEQ3 _peq; + bool _peqEnabled; + AudioFilterPEQ3m _peq; QMutex _guard; QByteArray* _scopeInput; diff --git a/libraries/audio/src/AudioFilter.cpp b/libraries/audio/src/AudioFilter.cpp deleted file mode 100644 index 28e7716578..0000000000 --- a/libraries/audio/src/AudioFilter.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// -// AudioFilter.cpp -// hifi -// -// Created by Craig Hansen-Sturm on 8/10/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include -#include "AudioRingBuffer.h" -#include "AudioFilter.h" - -template<> -AudioFilterPEQ3::FilterParameter AudioFilterPEQ3::_profiles[ AudioFilterPEQ3::_profileCount ][ AudioFilterPEQ3::_filterCount ] = { - - // Freq Gain Q Freq Gain Q Freq Gain Q - { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // flat response (default) - { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 0.1f, 1.0f } }, // treble cut - { { 300.0f, 0.1f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // bass cut - { { 300.0f, 1.5f, 0.71f }, { 1000.0f, 0.5f, 1.0f }, { 4000.0f, 1.50f, 0.71f } } // smiley curve -}; diff --git a/libraries/audio/src/AudioFilter.h b/libraries/audio/src/AudioFilter.h index 0f3ec06f64..3907e3e378 100644 --- a/libraries/audio/src/AudioFilter.h +++ b/libraries/audio/src/AudioFilter.h @@ -90,209 +90,134 @@ public: } }; -//////////////////////////////////////////////////////////////////////////////////////////// -// Implements a single-band parametric EQ using a biquad "peaking EQ" configuration -// -// gain > 1.0 boosts the center frequency -// gain < 1.0 cuts the center frequency -// -class AudioParametricEQ { +//////////////////////////////////////////////////////////////////////////////////////////// +// Implements common base class interface for all Audio Filter Objects +// +template< class T > +class AudioFilterBase { + +protected: + // - // private data + // data // AudioBiquad _kernel; float _sampleRate; float _frequency; float _gain; float _slope; - + + // // helpers + // void updateKernel() { - - /* - a0 = 1 + alpha*A - a1 = -2*cos(w0) - a2 = 1 - alpha*A - b1 = -2*cos(w0) - b2 = 1 - alpha/A - */ - - const float a = _gain; - const float omega = TWO_PI * _frequency / _sampleRate; - const float alpha = 0.5f * sinf(omega) / _slope; - const float gamma = 1.0f / ( 1.0f + (alpha/a) ); - - const float a0 = 1.0f + (alpha*a); - const float a1 = -2.0f * cosf(omega); - const float a2 = 1.0f - (alpha*a); - const float b1 = a1; - const float b2 = 1.0f - (alpha/a); - - _kernel.setParameters( a0*gamma,a1*gamma,a2*gamma,b1*gamma,b2*gamma ); + static_cast(this)->updateKernel(); } - + public: // // ctor/dtor // - AudioParametricEQ() { - + AudioFilterBase() { setParameters(0.,0.,0.,0.); - updateKernel(); } - - ~AudioParametricEQ() { + + ~AudioFilterBase() { } - + // // public interface // void setParameters( const float sampleRate, const float frequency, const float gain, const float slope ) { - + _sampleRate = std::max(sampleRate,1.0f); _frequency = std::max(frequency,2.0f); _gain = std::max(gain,0.0f); _slope = std::max(slope,0.00001f); - + updateKernel(); } - + void getParameters( float& sampleRate, float& frequency, float& gain, float& slope ) { sampleRate = _sampleRate; frequency = _frequency; gain = _gain; slope = _slope; } - + void render(const float* in, float* out, const int frames ) { _kernel.render(in,out,frames); } - + void reset() { _kernel.reset(); } }; //////////////////////////////////////////////////////////////////////////////////////////// -// Helper/convenience class that implements a bank of EQ objects +// Implements a low-shelf filter using a biquad // -template< typename T, const int N> -class AudioFilterBank { - - // - // types - // - struct FilterParameter { - float _p1; - float _p2; - float _p3; - }; - - // - // private static data - // - static const int _filterCount = N; - static const int _profileCount = 4; - - static FilterParameter _profiles[_profileCount][_filterCount]; - - // - // private data - // - T _filters[ _filterCount ]; - float* _buffer; - float _sampleRate; - uint16_t _frameCount; - +class AudioFilterLSF : +public AudioFilterBase< AudioFilterLSF > +{ public: - + // - // ctor/dtor + // helpers // - AudioFilterBank() - : _buffer(NULL) - , _sampleRate(0.) - , _frameCount(0) { + void updateKernel() { + // TBD } - - ~AudioFilterBank() { - finalize(); - } - - // - // public interface - // - void initialize( const float sampleRate, const int frameCount ) { - finalize(); - - _buffer = (float*)malloc( frameCount * sizeof(float) ); - if(!_buffer) { - return; - } - - _sampleRate = sampleRate; - _frameCount = frameCount; - - reset(); - loadProfile(0); // load default profile "flat response" into the bank (see AudioFilter.cpp) - } - - void finalize() { - if (_buffer ) { - free (_buffer); - _buffer = NULL; - } - } - - void loadProfile( int profileIndex ) { - if (profileIndex >= 0 && profileIndex < _profileCount) { - - for (int i = 0; i < _filterCount; ++i) { - FilterParameter p = _profiles[profileIndex][i]; - - _filters[i].setParameters(_sampleRate,p._p1,p._p2,p._p3); - } - } - } - - void render( const float* in, float* out, const int frameCount ) { - for (int i = 0; i < _filterCount; ++i) { - _filters[i].render( in, out, frameCount ); - } - } - - void render( const int16_t* in, int16_t* out, const int frameCount ) { - if (!_buffer || ( frameCount > _frameCount )) - return; - - const int scale = (2 << ((8*sizeof(int16_t))-1)); - - // convert int16_t to float32 (normalized to -1. ... 1.) - for (int i = 0; i < frameCount; ++i) { - _buffer[i] = ((float)(*in++)) / scale; - } - // for this filter, we share input/output buffers at each stage, but our design does not mandate this - render( _buffer, _buffer, frameCount ); - - // convert float32 to int16_t - for (int i = 0; i < frameCount; ++i) { - *out++ = (int16_t)(_buffer[i] * scale); - } - } - - void reset() { - for (int i = 0; i < _filterCount; ++i ) { - _filters[i].reset(); - } - } - }; //////////////////////////////////////////////////////////////////////////////////////////// -// Specializations of AudioFilterBank +// Implements a hi-shelf filter using a biquad // -typedef AudioFilterBank< AudioParametricEQ, 1> AudioFilterPEQ1; // bank with one band of PEQ -typedef AudioFilterBank< AudioParametricEQ, 2> AudioFilterPEQ2; // bank with two bands of PEQ -typedef AudioFilterBank< AudioParametricEQ, 3> AudioFilterPEQ3; // bank with three bands of PEQ -// etc.... +class AudioFilterHSF : +public AudioFilterBase< AudioFilterHSF > +{ +public: + + // + // helpers + // + void updateKernel() { + // TBD + } +}; +//////////////////////////////////////////////////////////////////////////////////////////// +// Implements a single-band parametric EQ using a biquad "peaking EQ" configuration +// +class AudioFilterPEQ : + public AudioFilterBase< AudioFilterPEQ > +{ +public: + + // + // helpers + // + void updateKernel() { + + const float a = _gain; + const float omega = TWO_PI * _frequency / _sampleRate; + const float alpha = 0.5f * sinf(omega) / _slope; + + /* + a0 = 1 + alpha*A + a1 = -2*cos(w0) + a2 = 1 - alpha*A + b1 = -2*cos(w0) + b2 = 1 - alpha/A + */ + const float a0 = 1.0f + (alpha*a); + const float a1 = -2.0f * cosf(omega); + const float a2 = 1.0f - (alpha*a); + const float b1 = a1; + const float b2 = 1.0f - (alpha/a); + + const float scale = 1.0f / ( 1.0f + (alpha/a) ); + + _kernel.setParameters( a0*scale,a1*scale,a2*scale,b1*scale,b2*scale ); + } +}; #endif // hifi_AudioFilter_h diff --git a/libraries/audio/src/AudioFilterBank.cpp b/libraries/audio/src/AudioFilterBank.cpp new file mode 100644 index 0000000000..e46195b5b9 --- /dev/null +++ b/libraries/audio/src/AudioFilterBank.cpp @@ -0,0 +1,45 @@ +// +// AudioFilterBank.cpp +// hifi +// +// Created by Craig Hansen-Sturm on 8/10/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include "AudioRingBuffer.h" +#include "AudioFilter.h" +#include "AudioFilterBank.h" + +template<> +AudioFilterLSF1s::FilterParameter AudioFilterLSF1s::_profiles[ AudioFilterLSF1s::_profileCount ][ AudioFilterLSF1s::_filterCount ] = { + // Freq Gain Slope + { { 1000.0f, 1.0f, 1.0f } } // flat response (default) +}; + +template<> +AudioFilterHSF1s::FilterParameter AudioFilterHSF1s::_profiles[ AudioFilterHSF1s::_profileCount ][ AudioFilterHSF1s::_filterCount ] = { + // Freq Gain Slope + { { 1000.0f, 1.0f, 1.0f } } // flat response (default) +}; + +template<> +AudioFilterPEQ1s::FilterParameter AudioFilterPEQ1s::_profiles[ AudioFilterPEQ1s::_profileCount ][ AudioFilterPEQ1s::_filterCount ] = { + // Freq Gain Q + { { 1000.0f, 1.0f, 1.0f } } // flat response (default) +}; + +template<> +AudioFilterPEQ3m::FilterParameter AudioFilterPEQ3m::_profiles[ AudioFilterPEQ3m::_profileCount ][ AudioFilterPEQ3m::_filterCount ] = { + + // Freq Gain Q Freq Gain Q Freq Gain Q + { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // flat response (default) + { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 0.1f, 1.0f } }, // treble cut + { { 300.0f, 0.1f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // bass cut + { { 300.0f, 1.5f, 0.71f }, { 1000.0f, 0.5f, 1.0f }, { 4000.0f, 1.50f, 0.71f } } // smiley curve +}; diff --git a/libraries/audio/src/AudioFilterBank.h b/libraries/audio/src/AudioFilterBank.h new file mode 100644 index 0000000000..c995a87b63 --- /dev/null +++ b/libraries/audio/src/AudioFilterBank.h @@ -0,0 +1,169 @@ +// +// AudioFilterBank.h +// hifi +// +// Created by Craig Hansen-Sturm on 8/23/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AudioFilterBank_h +#define hifi_AudioFilterBank_h + +//////////////////////////////////////////////////////////////////////////////////////////// +// Helper/convenience class that implements a bank of Filter objects +// +template< typename T, const int N, const int C > +class AudioFilterBank { + + // + // types + // + struct FilterParameter { + float _p1; + float _p2; + float _p3; + }; + + // + // private static data + // + static const int _filterCount = N; + static const int _channelCount = C; + static const int _profileCount = 4; + + static FilterParameter _profiles[_profileCount][_filterCount]; + + // + // private data + // + T _filters[ _filterCount ][ _channelCount ]; + float* _buffer[ _channelCount ]; + float _sampleRate; + uint16_t _frameCount; + +public: + + // + // ctor/dtor + // + AudioFilterBank() + : _sampleRate(0.) + , _frameCount(0) { + for (int i = 0; i < _channelCount; ++i ) { + _buffer[ i ] = NULL; + } + } + + ~AudioFilterBank() { + finalize(); + } + + // + // public interface + // + void initialize( const float sampleRate, const int frameCount ) { + finalize(); + + for (int i = 0; i < _channelCount; ++i ) { + _buffer[i] = (float*)malloc( frameCount * sizeof( float ) ); + } + + _sampleRate = sampleRate; + _frameCount = frameCount; + + reset(); + loadProfile(0); // load default profile "flat response" into the bank (see AudioFilterBank.cpp) + } + + void finalize() { + for (int i = 0; i < _channelCount; ++i ) { + if (_buffer[i]) { + free (_buffer[i]); + _buffer[i] = NULL; + } + } + } + + void loadProfile( int profileIndex ) { + if (profileIndex >= 0 && profileIndex < _profileCount) { + + for (int i = 0; i < _filterCount; ++i) { + FilterParameter p = _profiles[profileIndex][i]; + + for (int j = 0; j < _channelCount; ++j) { + _filters[i][j].setParameters(_sampleRate,p._p1,p._p2,p._p3); + } + } + } + } + + void setParameters( int filterStage, int filterChannel, const float sampleRate, const float frequency, const float gain, const float slope ) { + if ( filterStage >= 0 && filterStage < _filterCount && filterChannel >= 0 && filterChannel < _channelCount ) { + _filters[filterStage][filterChannel].setParameters(sampleRate,frequency,gain,slope); + } + } + + void getParameters( int filterStage, int filterChannel, float& sampleRate, float& frequency, float& gain, float& slope ) { + if ( filterStage >= 0 && filterStage < _filterCount && filterChannel >= 0 && filterChannel < _channelCount ) { + _filters[filterStage][filterChannel].getParameters(sampleRate,frequency,gain,slope); + } + } + + void render( const int16_t* in, int16_t* out, const int frameCount ) { + if (!_buffer || ( frameCount > _frameCount )) + return; + + const int scale = (2 << ((8*sizeof(int16_t))-1)); + + // de-interleave and convert int16_t to float32 (normalized to -1. ... 1.) + for (int i = 0; i < frameCount; ++i) { + for (int j = 0; j < _channelCount; ++j) { + _buffer[j][i] = ((float)(*in++)) / scale; + } + } + + // now step through each filter + for (int i = 0; i < _channelCount; ++i) { + for (int j = 0; j < _filterCount; ++j ) { + _filters[j][i].render( &_buffer[i][0], &_buffer[i][0], frameCount ); + } + } + + // convert float32 to int16_t and interleave + for (int i = 0; i < frameCount; ++i) { + for (int j = 0; j < _channelCount; ++j) { + *out++ = (int16_t)(_buffer[j][i] * scale); + } + } + } + + void reset() { + for (int i = 0; i < _filterCount; ++i ) { + for (int j = 0; j < _channelCount; ++j ) { + _filters[i][j].reset(); + } + } + } + +}; + +//////////////////////////////////////////////////////////////////////////////////////////// +// Specializations of AudioFilterBank +// +typedef AudioFilterBank< AudioFilterLSF, 1, 1> AudioFilterLSF1m; // mono bank with one band of LSF +typedef AudioFilterBank< AudioFilterLSF, 1, 2> AudioFilterLSF1s; // stereo bank with one band of LSF +typedef AudioFilterBank< AudioFilterHSF, 1, 1> AudioFilterHSF1m; // mono bank with one band of HSF +typedef AudioFilterBank< AudioFilterHSF, 1, 2> AudioFilterHSF1s; // stereo bank with one band of HSF +typedef AudioFilterBank< AudioFilterPEQ, 1, 1> AudioFilterPEQ1m; // mono bank with one band of PEQ +typedef AudioFilterBank< AudioFilterPEQ, 2, 1> AudioFilterPEQ2m; // mono bank with two bands of PEQ +typedef AudioFilterBank< AudioFilterPEQ, 3, 1> AudioFilterPEQ3m; // mono bank with three bands of PEQ +typedef AudioFilterBank< AudioFilterPEQ, 1, 2> AudioFilterPEQ1s; // stereo bank with one band of PEQ +typedef AudioFilterBank< AudioFilterPEQ, 2, 2> AudioFilterPEQ2s; // stereo bank with two bands of PEQ +typedef AudioFilterBank< AudioFilterPEQ, 3, 2> AudioFilterPEQ3s; // stereo bank with three bands of PEQ +// etc.... + + +#endif // hifi_AudioFilter_h diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index 7b407ba62c..a57a78c9f2 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -33,6 +33,9 @@ PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, b _lastPopOutputTrailingLoudness(0.0f), _listenerUnattenuatedZone(NULL) { + // constant defined in AudioMixer.h. However, we don't want to include this here, since we will soon find a better common home for these audio-related constants + const int SAMPLE_PHASE_DELAY_AT_90 = 20; + _filter.initialize( SAMPLE_RATE, ( NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2) ) / 2); } void PositionalAudioStream::updateLastPopOutputTrailingLoudness() { diff --git a/libraries/audio/src/PositionalAudioStream.h b/libraries/audio/src/PositionalAudioStream.h index f99dc3a464..e31cd35ad4 100644 --- a/libraries/audio/src/PositionalAudioStream.h +++ b/libraries/audio/src/PositionalAudioStream.h @@ -16,6 +16,8 @@ #include #include "InboundAudioStream.h" +#include "AudioFilter.h" +#include "AudioFilterBank.h" const int AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY = 100; @@ -44,6 +46,8 @@ public: void setListenerUnattenuatedZone(AABox* listenerUnattenuatedZone) { _listenerUnattenuatedZone = listenerUnattenuatedZone; } + AudioFilterPEQ1s& getFilter() { return _filter; } + protected: // disallow copying of PositionalAudioStream objects PositionalAudioStream(const PositionalAudioStream&); @@ -61,6 +65,8 @@ protected: float _lastPopOutputTrailingLoudness; AABox* _listenerUnattenuatedZone; + + AudioFilterPEQ1s _filter; }; #endif // hifi_PositionalAudioStream_h From 6cdee21a50f0f905315b42474f82a45f558b10e1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Aug 2014 10:40:28 -0700 Subject: [PATCH 176/206] fix for clearJointData() in JS scripts let the JointState class verify animation priority --- interface/src/renderer/Model.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 92c19dd839..f53fddd394 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -708,12 +708,10 @@ void Model::clearJointAnimationPriority(int index) { void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; - if (priority >= state._animationPriority) { - if (valid) { - state.setRotationInConstrainedFrame(rotation, priority); - } else { - state.restoreRotation(1.0f, priority); - } + if (valid) { + state.setRotationInConstrainedFrame(rotation, priority); + } else { + state.restoreRotation(1.0f, priority); } } } From fd5e3d61dc5d9a9a78ece4fc9b976fcee3dc3b42 Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Mon, 25 Aug 2014 10:55:11 -0700 Subject: [PATCH 177/206] minor cleanup of playSoundOrbit.js + playSound.js --- examples/playSound.js | 4 ++-- examples/playSoundOrbit.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/playSound.js b/examples/playSound.js index 867768f807..189c24e86f 100644 --- a/examples/playSound.js +++ b/examples/playSound.js @@ -12,14 +12,14 @@ var bird = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); function maybePlaySound(deltaTime) { -// if (Math.random() < 0.01) { + if (Math.random() < 0.01) { // Set the location and other info for the sound to play var options = new AudioInjectionOptions(); var position = MyAvatar.position; options.position = position; options.volume = 0.5; Audio.playSound(bird, options); -// } + } } // Connect a call back that happens every frame diff --git a/examples/playSoundOrbit.js b/examples/playSoundOrbit.js index 92b8e0a96e..4349eac888 100644 --- a/examples/playSoundOrbit.js +++ b/examples/playSoundOrbit.js @@ -19,19 +19,19 @@ var debug = 0; function playSound() { var options = new AudioInjectionOptions(); currentTime += deltaTime; - + var s = distance * Math.sin(currentTime); var c = distance * Math.cos(currentTime); var soundOffset = { x:s, y:0, z:c }; - + if( debug ) { print("t=" + currentTime + "offset=" + soundOffset.x + "," + soundOffset.y + "," + soundOffset.z); } - + var avatarPosition = MyAvatar.position; var soundPosition = Vec3.sum(avatarPosition,soundOffset); - + options.position = soundPosition options.volume = 1.0; Audio.playSound(soundClip, options); From bf6d34b6f33ad9b8885a296df0cab39f0b9fef1a Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Mon, 25 Aug 2014 11:43:49 -0700 Subject: [PATCH 178/206] windows/linux build break + addresses zappoman review comments --- assignment-client/src/audio/AudioMixer.cpp | 14 +++++++------- libraries/audio/src/AudioFilter.h | 10 +++++----- libraries/audio/src/AudioFilterBank.cpp | 1 - libraries/audio/src/AudioFilterBank.h | 4 ++-- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index e1d118a11d..1fe3ddbbe9 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -270,10 +270,10 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* } } - if ( _enableFilter && shouldAttenuate ) { + if (_enableFilter && shouldAttenuate) { glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream->getPosition(); - if ( relativePosition.z < 0 ) { // if the source is behind us + if (relativePosition.z < 0) { // if the source is behind us AudioFilterPEQ1s& penumbraFilter = streamToAdd->getFilter(); @@ -297,13 +297,13 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* penumbraFilterFrequency = 2000.0f; penumbraFilterSlope = 1.0f; // gentle slope -// printf("gain=%f,angle=%f\n",penumbraFilterGain,headPenumbraAngle); - + qDebug() << "penumbra gain=" << penumbraFilterGain << ", penumbraAngle=" << normalizedHeadPenumbraAngle; + // set the gain on both filter channels - penumbraFilter.setParameters(0,0,SAMPLE_RATE,penumbraFilterFrequency,penumbraFilterGain,penumbraFilterSlope); - penumbraFilter.setParameters(0,1,SAMPLE_RATE,penumbraFilterFrequency,penumbraFilterGain,penumbraFilterSlope); + penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGain, penumbraFilterSlope); + penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGain, penumbraFilterSlope); - penumbraFilter.render( _clientSamples, _clientSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2); + penumbraFilter.render(_clientSamples, _clientSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2); } } } diff --git a/libraries/audio/src/AudioFilter.h b/libraries/audio/src/AudioFilter.h index 3907e3e378..2be88322af 100644 --- a/libraries/audio/src/AudioFilter.h +++ b/libraries/audio/src/AudioFilter.h @@ -12,7 +12,7 @@ #ifndef hifi_AudioFilter_h #define hifi_AudioFilter_h -//////////////////////////////////////////////////////////////////////////////////////////// +// // Implements a standard biquad filter in "Direct Form 1" // Reference http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt // @@ -91,7 +91,7 @@ public: }; -//////////////////////////////////////////////////////////////////////////////////////////// +// // Implements common base class interface for all Audio Filter Objects // template< class T > @@ -152,7 +152,7 @@ public: } }; -//////////////////////////////////////////////////////////////////////////////////////////// +// // Implements a low-shelf filter using a biquad // class AudioFilterLSF : @@ -168,7 +168,7 @@ public: } }; -//////////////////////////////////////////////////////////////////////////////////////////// +// // Implements a hi-shelf filter using a biquad // class AudioFilterHSF : @@ -184,7 +184,7 @@ public: } }; -//////////////////////////////////////////////////////////////////////////////////////////// +// // Implements a single-band parametric EQ using a biquad "peaking EQ" configuration // class AudioFilterPEQ : diff --git a/libraries/audio/src/AudioFilterBank.cpp b/libraries/audio/src/AudioFilterBank.cpp index e46195b5b9..a2cf008fc9 100644 --- a/libraries/audio/src/AudioFilterBank.cpp +++ b/libraries/audio/src/AudioFilterBank.cpp @@ -10,7 +10,6 @@ // #include -#include #include #include "AudioRingBuffer.h" #include "AudioFilter.h" diff --git a/libraries/audio/src/AudioFilterBank.h b/libraries/audio/src/AudioFilterBank.h index c995a87b63..6721d077f7 100644 --- a/libraries/audio/src/AudioFilterBank.h +++ b/libraries/audio/src/AudioFilterBank.h @@ -12,7 +12,7 @@ #ifndef hifi_AudioFilterBank_h #define hifi_AudioFilterBank_h -//////////////////////////////////////////////////////////////////////////////////////////// +// // Helper/convenience class that implements a bank of Filter objects // template< typename T, const int N, const int C > @@ -150,7 +150,7 @@ public: }; -//////////////////////////////////////////////////////////////////////////////////////////// +// // Specializations of AudioFilterBank // typedef AudioFilterBank< AudioFilterLSF, 1, 1> AudioFilterLSF1m; // mono bank with one band of LSF From 4f346bfe62ae7f737813d225e1575244d2d39ac9 Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Mon, 25 Aug 2014 13:07:52 -0700 Subject: [PATCH 179/206] addresses more coding-standard spacing issues --- assignment-client/src/audio/AudioMixer.cpp | 2 +- examples/playSoundOrbit.js | 2 +- libraries/audio/src/AudioFilter.h | 30 ++++++++-------- libraries/audio/src/AudioFilterBank.h | 35 ++++++++++--------- libraries/audio/src/PositionalAudioStream.cpp | 2 +- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 1fe3ddbbe9..85ce208548 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -284,7 +284,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* // normalize penumbra angle float normalizedHeadPenumbraAngle = headPenumbraAngle / PI_OVER_TWO; - if ( normalizedHeadPenumbraAngle < EPSILON ) { + if (normalizedHeadPenumbraAngle < EPSILON) { normalizedHeadPenumbraAngle = EPSILON; } diff --git a/examples/playSoundOrbit.js b/examples/playSoundOrbit.js index 4349eac888..da7c746e8e 100644 --- a/examples/playSoundOrbit.js +++ b/examples/playSoundOrbit.js @@ -25,7 +25,7 @@ function playSound() { var soundOffset = { x:s, y:0, z:c }; - if( debug ) { + if (debug) { print("t=" + currentTime + "offset=" + soundOffset.x + "," + soundOffset.y + "," + soundOffset.z); } diff --git a/libraries/audio/src/AudioFilter.h b/libraries/audio/src/AudioFilter.h index 2be88322af..408a713db7 100644 --- a/libraries/audio/src/AudioFilter.h +++ b/libraries/audio/src/AudioFilter.h @@ -51,15 +51,15 @@ public: // // public interface // - void setParameters( const float a0, const float a1, const float a2, const float b1, const float b2 ) { + void setParameters(const float a0, const float a1, const float a2, const float b1, const float b2) { _a0 = a0; _a1 = a1; _a2 = a2; _b1 = b1; _b2 = b2; } - void getParameters( float& a0, float& a1, float& a2, float& b1, float& b2 ) { + void getParameters(float& a0, float& a1, float& a2, float& b1, float& b2) { a0 = _a0; a1 = _a1; a2 = _a2; b1 = _b1; b2 = _b2; } - void render( const float* in, float* out, const int frames) { + void render(const float* in, float* out, const int frames) { float x; float y; @@ -129,21 +129,21 @@ public: // // public interface // - void setParameters( const float sampleRate, const float frequency, const float gain, const float slope ) { + void setParameters(const float sampleRate, const float frequency, const float gain, const float slope) { - _sampleRate = std::max(sampleRate,1.0f); - _frequency = std::max(frequency,2.0f); - _gain = std::max(gain,0.0f); - _slope = std::max(slope,0.00001f); + _sampleRate = std::max(sampleRate, 1.0f); + _frequency = std::max(frequency, 2.0f); + _gain = std::max(gain, 0.0f); + _slope = std::max(slope, 0.00001f); updateKernel(); } - void getParameters( float& sampleRate, float& frequency, float& gain, float& slope ) { + void getParameters(float& sampleRate, float& frequency, float& gain, float& slope) { sampleRate = _sampleRate; frequency = _frequency; gain = _gain; slope = _slope; } - void render(const float* in, float* out, const int frames ) { + void render(const float* in, float* out, const int frames) { _kernel.render(in,out,frames); } @@ -208,15 +208,15 @@ public: b1 = -2*cos(w0) b2 = 1 - alpha/A */ - const float a0 = 1.0f + (alpha*a); + const float a0 = 1.0f + (alpha * a); const float a1 = -2.0f * cosf(omega); - const float a2 = 1.0f - (alpha*a); + const float a2 = 1.0f - (alpha * a); const float b1 = a1; - const float b2 = 1.0f - (alpha/a); + const float b2 = 1.0f - (alpha / a); - const float scale = 1.0f / ( 1.0f + (alpha/a) ); + const float scale = 1.0f / (1.0f + (alpha / a)); - _kernel.setParameters( a0*scale,a1*scale,a2*scale,b1*scale,b2*scale ); + _kernel.setParameters(a0 * scale, a1 * scale, a2 * scale, b1 * scale, b2 * scale); } }; diff --git a/libraries/audio/src/AudioFilterBank.h b/libraries/audio/src/AudioFilterBank.h index 6721d077f7..c523736a57 100644 --- a/libraries/audio/src/AudioFilterBank.h +++ b/libraries/audio/src/AudioFilterBank.h @@ -34,7 +34,7 @@ class AudioFilterBank { static const int _channelCount = C; static const int _profileCount = 4; - static FilterParameter _profiles[_profileCount][_filterCount]; + static FilterParameter _profiles[ _profileCount ][ _filterCount ]; // // private data @@ -52,7 +52,7 @@ public: AudioFilterBank() : _sampleRate(0.) , _frameCount(0) { - for (int i = 0; i < _channelCount; ++i ) { + for (int i = 0; i < _channelCount; ++i) { _buffer[ i ] = NULL; } } @@ -64,11 +64,11 @@ public: // // public interface // - void initialize( const float sampleRate, const int frameCount ) { + void initialize(const float sampleRate, const int frameCount) { finalize(); - for (int i = 0; i < _channelCount; ++i ) { - _buffer[i] = (float*)malloc( frameCount * sizeof( float ) ); + for (int i = 0; i < _channelCount; ++i) { + _buffer[i] = (float*)malloc(frameCount * sizeof(float)); } _sampleRate = sampleRate; @@ -79,7 +79,7 @@ public: } void finalize() { - for (int i = 0; i < _channelCount; ++i ) { + for (int i = 0; i < _channelCount; ++i) { if (_buffer[i]) { free (_buffer[i]); _buffer[i] = NULL; @@ -87,7 +87,7 @@ public: } } - void loadProfile( int profileIndex ) { + void loadProfile(int profileIndex) { if (profileIndex >= 0 && profileIndex < _profileCount) { for (int i = 0; i < _filterCount; ++i) { @@ -100,23 +100,24 @@ public: } } - void setParameters( int filterStage, int filterChannel, const float sampleRate, const float frequency, const float gain, const float slope ) { - if ( filterStage >= 0 && filterStage < _filterCount && filterChannel >= 0 && filterChannel < _channelCount ) { + void setParameters(int filterStage, int filterChannel, const float sampleRate, const float frequency, const float gain, + const float slope) { + if (filterStage >= 0 && filterStage < _filterCount && filterChannel >= 0 && filterChannel < _channelCount) { _filters[filterStage][filterChannel].setParameters(sampleRate,frequency,gain,slope); } } - void getParameters( int filterStage, int filterChannel, float& sampleRate, float& frequency, float& gain, float& slope ) { - if ( filterStage >= 0 && filterStage < _filterCount && filterChannel >= 0 && filterChannel < _channelCount ) { + void getParameters(int filterStage, int filterChannel, float& sampleRate, float& frequency, float& gain, float& slope) { + if (filterStage >= 0 && filterStage < _filterCount && filterChannel >= 0 && filterChannel < _channelCount) { _filters[filterStage][filterChannel].getParameters(sampleRate,frequency,gain,slope); } } - void render( const int16_t* in, int16_t* out, const int frameCount ) { - if (!_buffer || ( frameCount > _frameCount )) + void render(const int16_t* in, int16_t* out, const int frameCount) { + if (!_buffer || (frameCount > _frameCount)) return; - const int scale = (2 << ((8*sizeof(int16_t))-1)); + const int scale = (2 << ((8 * sizeof(int16_t)) - 1)); // de-interleave and convert int16_t to float32 (normalized to -1. ... 1.) for (int i = 0; i < frameCount; ++i) { @@ -127,7 +128,7 @@ public: // now step through each filter for (int i = 0; i < _channelCount; ++i) { - for (int j = 0; j < _filterCount; ++j ) { + for (int j = 0; j < _filterCount; ++j) { _filters[j][i].render( &_buffer[i][0], &_buffer[i][0], frameCount ); } } @@ -141,8 +142,8 @@ public: } void reset() { - for (int i = 0; i < _filterCount; ++i ) { - for (int j = 0; j < _channelCount; ++j ) { + for (int i = 0; i < _filterCount; ++i) { + for (int j = 0; j < _channelCount; ++j) { _filters[i][j].reset(); } } diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index a57a78c9f2..46e83806bc 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -35,7 +35,7 @@ PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, b { // constant defined in AudioMixer.h. However, we don't want to include this here, since we will soon find a better common home for these audio-related constants const int SAMPLE_PHASE_DELAY_AT_90 = 20; - _filter.initialize( SAMPLE_RATE, ( NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2) ) / 2); + _filter.initialize(SAMPLE_RATE, (NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)) / 2); } void PositionalAudioStream::updateLastPopOutputTrailingLoudness() { From 3ad9797607dc5e636ef0043a86ed7f6bdd9c2b52 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Aug 2014 15:15:27 -0700 Subject: [PATCH 180/206] fix crash: divide by zero when computing avg color --- interface/src/renderer/TextureCache.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index e13f97a1d2..a2aba32594 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -404,6 +404,7 @@ void ImageReader::run() { const int EIGHT_BIT_MAXIMUM = 255; if (!image.hasAlphaChannel()) { + QColor averageColor(EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM); if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } @@ -416,8 +417,11 @@ void ImageReader::run() { blueTotal += qBlue(rgb); } } + if (imageArea > 0) { + averageColor.setRgb(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea); + } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false), - Q_ARG(const QColor&, QColor(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea))); + Q_ARG(const QColor&, averageColor)); return; } if (image.format() != QImage::Format_ARGB32) { From 8057071c126f8c5d9df35012089444b991cbabd1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Aug 2014 15:18:57 -0700 Subject: [PATCH 181/206] put averageColor init closer to where it is used --- interface/src/renderer/TextureCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index a2aba32594..d960525817 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -404,7 +404,6 @@ void ImageReader::run() { const int EIGHT_BIT_MAXIMUM = 255; if (!image.hasAlphaChannel()) { - QColor averageColor(EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM); if (image.format() != QImage::Format_RGB888) { image = image.convertToFormat(QImage::Format_RGB888); } @@ -417,6 +416,7 @@ void ImageReader::run() { blueTotal += qBlue(rgb); } } + QColor averageColor(EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM, EIGHT_BIT_MAXIMUM); if (imageArea > 0) { averageColor.setRgb(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea); } From 7a5c4fdb16e43a6c7523b0b5540231bfe2b686d7 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 25 Aug 2014 15:25:46 -0700 Subject: [PATCH 182/206] remove commented out #includes --- libraries/shared/src/ShapeCollider.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index 3aa795e6fa..279cbe3810 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -16,10 +16,6 @@ #include "CollisionInfo.h" #include "SharedUtil.h" -//#include "CapsuleShape.h" -//#include "ListShape.h" -//#include "PlaneShape.h" -//#include "SphereShape.h" class Shape; class SphereShape; From 1acce5c725e1059e3ce75d6f2ab917929ad77592 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 25 Aug 2014 18:02:30 -0700 Subject: [PATCH 183/206] fix sitting points on domain switch --- examples/sit.js | 3 +-- interface/src/scripting/LocationScriptingInterface.cpp | 4 ++++ interface/src/scripting/LocationScriptingInterface.h | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/sit.js b/examples/sit.js index 072471aa30..b5664b0d10 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -269,8 +269,7 @@ function update(deltaTime){ } var locationChanged = false; - if (location.hostname != oldHost) { - print("Changed domain"); + if (location.hostname != oldHost || !location.isConnected) { for (model in models) { removeIndicators(models[model]); } diff --git a/interface/src/scripting/LocationScriptingInterface.cpp b/interface/src/scripting/LocationScriptingInterface.cpp index 44ff94aa1f..9e68778942 100644 --- a/interface/src/scripting/LocationScriptingInterface.cpp +++ b/interface/src/scripting/LocationScriptingInterface.cpp @@ -20,6 +20,10 @@ LocationScriptingInterface* LocationScriptingInterface::getInstance() { return &sharedInstance; } +bool LocationScriptingInterface::isConnected() { + return NodeList::getInstance()->getDomainHandler().isConnected(); +} + QString LocationScriptingInterface::getHref() { return getProtocol() + "//" + getHostname() + getPathname(); } diff --git a/interface/src/scripting/LocationScriptingInterface.h b/interface/src/scripting/LocationScriptingInterface.h index 36b6d97561..20f63bceed 100644 --- a/interface/src/scripting/LocationScriptingInterface.h +++ b/interface/src/scripting/LocationScriptingInterface.h @@ -22,6 +22,7 @@ class LocationScriptingInterface : public QObject { Q_OBJECT + Q_PROPERTY(bool isConnected READ isConnected) Q_PROPERTY(QString href READ getHref) Q_PROPERTY(QString protocol READ getProtocol) Q_PROPERTY(QString hostname READ getHostname) @@ -30,6 +31,7 @@ class LocationScriptingInterface : public QObject { public: static LocationScriptingInterface* getInstance(); + bool isConnected(); QString getHref(); QString getProtocol() { return CUSTOM_URL_SCHEME; }; QString getPathname(); From 950862839e9b6b0fc14ddb536008357d2f2049fa Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Mon, 25 Aug 2014 18:17:32 -0700 Subject: [PATCH 184/206] added high-shelf and low-shelf filters, positional audio now uses high-shelf filter, significant reformatting/renaming of AudioFilter.h for coding standard --- assignment-client/src/audio/AudioMixer.cpp | 11 +- libraries/audio/src/AudioFilter.h | 102 ++++++++++++++---- libraries/audio/src/AudioFilterBank.cpp | 20 ++-- libraries/audio/src/PositionalAudioStream.cpp | 3 +- libraries/audio/src/PositionalAudioStream.h | 4 +- 5 files changed, 101 insertions(+), 39 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 85ce208548..ae8e7c6a39 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -275,7 +275,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream->getPosition(); if (relativePosition.z < 0) { // if the source is behind us - AudioFilterPEQ1s& penumbraFilter = streamToAdd->getFilter(); + AudioFilterHSF1s& penumbraFilter = streamToAdd->getFilter(); // calculate penumbra angle float headPenumbraAngle = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f), @@ -292,10 +292,11 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* float penumbraFilterFrequency; float penumbraFilterSlope; - // calculate the updated gain - penumbraFilterGain = normalizedHeadPenumbraAngle; // Note this will be tuned - consider this only a crude-first pass at correlating gain with penumbra angle. - penumbraFilterFrequency = 2000.0f; - penumbraFilterSlope = 1.0f; // gentle slope + // calculate the updated gain. this will be tuned over time. + // consider this only a crude-first pass at correlating gain, freq and slope with penumbra angle. + penumbraFilterGain = 0.71f * (normalizedHeadPenumbraAngle + 0.71f); // [1.0,0.71] + penumbraFilterFrequency = 4000.0f; // constant frequency + penumbraFilterSlope = 0.71f; // constant slope qDebug() << "penumbra gain=" << penumbraFilterGain << ", penumbraAngle=" << normalizedHeadPenumbraAngle; diff --git a/libraries/audio/src/AudioFilter.h b/libraries/audio/src/AudioFilter.h index 408a713db7..7c3c98582b 100644 --- a/libraries/audio/src/AudioFilter.h +++ b/libraries/audio/src/AudioFilter.h @@ -95,7 +95,7 @@ public: // Implements common base class interface for all Audio Filter Objects // template< class T > -class AudioFilterBase { +class AudioFilter { protected: @@ -119,11 +119,11 @@ public: // // ctor/dtor // - AudioFilterBase() { + AudioFilter() { setParameters(0.,0.,0.,0.); } - ~AudioFilterBase() { + ~AudioFilter() { } // @@ -156,7 +156,7 @@ public: // Implements a low-shelf filter using a biquad // class AudioFilterLSF : -public AudioFilterBase< AudioFilterLSF > +public AudioFilter< AudioFilterLSF > { public: @@ -164,7 +164,33 @@ public: // helpers // void updateKernel() { - // TBD + + const float a = _gain; + const float aAdd1 = a + 1.0f; + const float aSub1 = a - 1.0f; + const float omega = TWO_PI * _frequency / _sampleRate; + const float aAdd1TimesCosOmega = aAdd1 * cosf(omega); + const float aSub1TimesCosOmega = aSub1 * cosf(omega); + const float alpha = 0.5f * sinf(omega) / _slope; + const float zeta = 2.0f * sqrtf(a) * alpha; + /* + b0 = A*( (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha ) + b1 = 2*A*( (A-1) - (A+1)*cos(w0) ) + b2 = A*( (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha ) + a0 = (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha + a1 = -2*( (A-1) + (A+1)*cos(w0) ) + a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha + */ + const float b0 = +1.0f * (aAdd1 - aSub1TimesCosOmega + zeta) * a; + const float b1 = +2.0f * (aSub1 - aAdd1TimesCosOmega + ZERO) * a; + const float b2 = +1.0f * (aAdd1 - aSub1TimesCosOmega - zeta) * a; + const float a0 = +1.0f * (aAdd1 + aSub1TimesCosOmega + zeta); + const float a1 = -2.0f * (aSub1 + aAdd1TimesCosOmega + ZERO); + const float a2 = +1.0f * (aAdd1 + aSub1TimesCosOmega - zeta); + + const float normA0 = 1.0f / a0; + + _kernel.setParameters(b0 * normA0, b1 * normA0 , b2 * normA0, a1 * normA0, a2 * normA0); } }; @@ -172,7 +198,7 @@ public: // Implements a hi-shelf filter using a biquad // class AudioFilterHSF : -public AudioFilterBase< AudioFilterHSF > +public AudioFilter< AudioFilterHSF > { public: @@ -180,7 +206,33 @@ public: // helpers // void updateKernel() { - // TBD + + const float a = _gain; + const float aAdd1 = a + 1.0f; + const float aSub1 = a - 1.0f; + const float omega = TWO_PI * _frequency / _sampleRate; + const float aAdd1TimesCosOmega = aAdd1 * cosf(omega); + const float aSub1TimesCosOmega = aSub1 * cosf(omega); + const float alpha = 0.5f * sinf(omega) / _slope; + const float zeta = 2.0f * sqrtf(a) * alpha; + /* + b0 = A*( (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha ) + b1 = -2*A*( (A-1) + (A+1)*cos(w0) ) + b2 = A*( (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha ) + a0 = (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha + a1 = 2*( (A-1) - (A+1)*cos(w0) ) + a2 = (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha + */ + const float b0 = +1.0f * (aAdd1 + aSub1TimesCosOmega + zeta) * a; + const float b1 = -2.0f * (aSub1 + aAdd1TimesCosOmega + ZERO) * a; + const float b2 = +1.0f * (aAdd1 + aSub1TimesCosOmega - zeta) * a; + const float a0 = +1.0f * (aAdd1 - aSub1TimesCosOmega + zeta); + const float a1 = +2.0f * (aSub1 - aAdd1TimesCosOmega + ZERO); + const float a2 = +1.0f * (aAdd1 - aSub1TimesCosOmega - zeta); + + const float normA0 = 1.0f / a0; + + _kernel.setParameters(b0 * normA0, b1 * normA0 , b2 * normA0, a1 * normA0, a2 * normA0); } }; @@ -188,7 +240,7 @@ public: // Implements a single-band parametric EQ using a biquad "peaking EQ" configuration // class AudioFilterPEQ : - public AudioFilterBase< AudioFilterPEQ > + public AudioFilter< AudioFilterPEQ > { public: @@ -197,26 +249,30 @@ public: // void updateKernel() { - const float a = _gain; - const float omega = TWO_PI * _frequency / _sampleRate; - const float alpha = 0.5f * sinf(omega) / _slope; - + const float a = _gain; + const float omega = TWO_PI * _frequency / _sampleRate; + const float cosOmega = cosf(omega); + const float alpha = 0.5f * sinf(omega) / _slope; + const float alphaMulA = alpha * a; + const float alphaDivA = alpha / a; /* - a0 = 1 + alpha*A - a1 = -2*cos(w0) - a2 = 1 - alpha*A + b0 = 1 + alpha*A b1 = -2*cos(w0) - b2 = 1 - alpha/A + b2 = 1 - alpha*A + a0 = 1 + alpha/A + a1 = -2*cos(w0) + a2 = 1 - alpha/A */ - const float a0 = 1.0f + (alpha * a); - const float a1 = -2.0f * cosf(omega); - const float a2 = 1.0f - (alpha * a); - const float b1 = a1; - const float b2 = 1.0f - (alpha / a); + const float b0 = +1.0f + alphaMulA; + const float b1 = -2.0f * cosOmega; + const float b2 = +1.0f - alphaMulA; + const float a0 = +1.0f + alphaDivA; + const float a1 = -2.0f * cosOmega; + const float a2 = +1.0f - alphaDivA; - const float scale = 1.0f / (1.0f + (alpha / a)); + const float normA0 = 1.0f / a0; - _kernel.setParameters(a0 * scale, a1 * scale, a2 * scale, b1 * scale, b2 * scale); + _kernel.setParameters(b0 * normA0, b1 * normA0 , b2 * normA0, a1 * normA0, a2 * normA0); } }; diff --git a/libraries/audio/src/AudioFilterBank.cpp b/libraries/audio/src/AudioFilterBank.cpp index a2cf008fc9..a7b969540a 100644 --- a/libraries/audio/src/AudioFilterBank.cpp +++ b/libraries/audio/src/AudioFilterBank.cpp @@ -16,29 +16,33 @@ #include "AudioFilterBank.h" template<> -AudioFilterLSF1s::FilterParameter AudioFilterLSF1s::_profiles[ AudioFilterLSF1s::_profileCount ][ AudioFilterLSF1s::_filterCount ] = { +AudioFilterLSF1s::FilterParameter +AudioFilterLSF1s::_profiles[ AudioFilterLSF1s::_profileCount ][ AudioFilterLSF1s::_filterCount ] = { // Freq Gain Slope { { 1000.0f, 1.0f, 1.0f } } // flat response (default) }; template<> -AudioFilterHSF1s::FilterParameter AudioFilterHSF1s::_profiles[ AudioFilterHSF1s::_profileCount ][ AudioFilterHSF1s::_filterCount ] = { +AudioFilterHSF1s::FilterParameter +AudioFilterHSF1s::_profiles[ AudioFilterHSF1s::_profileCount ][ AudioFilterHSF1s::_filterCount ] = { // Freq Gain Slope { { 1000.0f, 1.0f, 1.0f } } // flat response (default) }; template<> -AudioFilterPEQ1s::FilterParameter AudioFilterPEQ1s::_profiles[ AudioFilterPEQ1s::_profileCount ][ AudioFilterPEQ1s::_filterCount ] = { +AudioFilterPEQ1s::FilterParameter +AudioFilterPEQ1s::_profiles[ AudioFilterPEQ1s::_profileCount ][ AudioFilterPEQ1s::_filterCount ] = { // Freq Gain Q { { 1000.0f, 1.0f, 1.0f } } // flat response (default) }; template<> -AudioFilterPEQ3m::FilterParameter AudioFilterPEQ3m::_profiles[ AudioFilterPEQ3m::_profileCount ][ AudioFilterPEQ3m::_filterCount ] = { +AudioFilterPEQ3m::FilterParameter +AudioFilterPEQ3m::_profiles[ AudioFilterPEQ3m::_profileCount ][ AudioFilterPEQ3m::_filterCount ] = { // Freq Gain Q Freq Gain Q Freq Gain Q - { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // flat response (default) - { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 0.1f, 1.0f } }, // treble cut - { { 300.0f, 0.1f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // bass cut - { { 300.0f, 1.5f, 0.71f }, { 1000.0f, 0.5f, 1.0f }, { 4000.0f, 1.50f, 0.71f } } // smiley curve + { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // flat response (default) + { { 300.0f, 1.0f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 0.1f, 1.0f } }, // treble cut + { { 300.0f, 0.1f, 1.0f }, { 1000.0f, 1.0f, 1.0f }, { 4000.0f, 1.0f, 1.0f } }, // bass cut + { { 300.0f, 1.5f, 0.71f }, { 1000.0f, 0.5f, 1.0f }, { 4000.0f, 1.50f, 0.71f } } // smiley curve }; diff --git a/libraries/audio/src/PositionalAudioStream.cpp b/libraries/audio/src/PositionalAudioStream.cpp index 46e83806bc..ee592a344e 100644 --- a/libraries/audio/src/PositionalAudioStream.cpp +++ b/libraries/audio/src/PositionalAudioStream.cpp @@ -33,7 +33,8 @@ PositionalAudioStream::PositionalAudioStream(PositionalAudioStream::Type type, b _lastPopOutputTrailingLoudness(0.0f), _listenerUnattenuatedZone(NULL) { - // constant defined in AudioMixer.h. However, we don't want to include this here, since we will soon find a better common home for these audio-related constants + // constant defined in AudioMixer.h. However, we don't want to include this here + // we will soon find a better common home for these audio-related constants const int SAMPLE_PHASE_DELAY_AT_90 = 20; _filter.initialize(SAMPLE_RATE, (NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (SAMPLE_PHASE_DELAY_AT_90 * 2)) / 2); } diff --git a/libraries/audio/src/PositionalAudioStream.h b/libraries/audio/src/PositionalAudioStream.h index e31cd35ad4..13cdd6b736 100644 --- a/libraries/audio/src/PositionalAudioStream.h +++ b/libraries/audio/src/PositionalAudioStream.h @@ -46,7 +46,7 @@ public: void setListenerUnattenuatedZone(AABox* listenerUnattenuatedZone) { _listenerUnattenuatedZone = listenerUnattenuatedZone; } - AudioFilterPEQ1s& getFilter() { return _filter; } + AudioFilterHSF1s& getFilter() { return _filter; } protected: // disallow copying of PositionalAudioStream objects @@ -66,7 +66,7 @@ protected: float _lastPopOutputTrailingLoudness; AABox* _listenerUnattenuatedZone; - AudioFilterPEQ1s _filter; + AudioFilterHSF1s _filter; }; #endif // hifi_PositionalAudioStream_h From 74585bc593d6c1b9a1274f93743f08e9cab94477 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 26 Aug 2014 10:14:28 -0700 Subject: [PATCH 185/206] fix bug in referentials --- libraries/avatars/src/Referential.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/Referential.cpp b/libraries/avatars/src/Referential.cpp index 784ad3963c..a37aaae1b8 100644 --- a/libraries/avatars/src/Referential.cpp +++ b/libraries/avatars/src/Referential.cpp @@ -107,7 +107,7 @@ int Referential::packExtraData(unsigned char *destinationBuffer) const { int Referential::unpackExtraData(const unsigned char* sourceBuffer, int size) { _extraDataBuffer.clear(); - _extraDataBuffer.setRawData(reinterpret_cast(sourceBuffer), size); + _extraDataBuffer.append(reinterpret_cast(sourceBuffer), size); return size; } From 5f695664a6dae38bf21679aab5f4a30da86cb46b Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Tue, 26 Aug 2014 11:26:39 -0700 Subject: [PATCH 186/206] add explicit constants inline for documebntation purposes --- assignment-client/src/audio/AudioMixer.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index ae8e7c6a39..ef44e7cd81 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -288,15 +288,17 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* normalizedHeadPenumbraAngle = EPSILON; } + const float SQUARE_ROOT_OF_TWO = 0.71f; + const float FILTER_CUTOFF_FREQUENCY_HZ = 4000.0f; float penumbraFilterGain; float penumbraFilterFrequency; float penumbraFilterSlope; - + // calculate the updated gain. this will be tuned over time. // consider this only a crude-first pass at correlating gain, freq and slope with penumbra angle. - penumbraFilterGain = 0.71f * (normalizedHeadPenumbraAngle + 0.71f); // [1.0,0.71] - penumbraFilterFrequency = 4000.0f; // constant frequency - penumbraFilterSlope = 0.71f; // constant slope + penumbraFilterGain = SQUARE_ROOT_OF_TWO * (normalizedHeadPenumbraAngle + SQUARE_ROOT_OF_TWO); // [1.0,0.71] + penumbraFilterFrequency = FILTER_CUTOFF_FREQUENCY_HZ; // constant frequency + penumbraFilterSlope = SQUARE_ROOT_OF_TWO; // constant slope qDebug() << "penumbra gain=" << penumbraFilterGain << ", penumbraAngle=" << normalizedHeadPenumbraAngle; From 47bc1834397c75d93547b54eee7ab87729d53f51 Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Tue, 26 Aug 2014 11:32:05 -0700 Subject: [PATCH 187/206] SQUARE_ROOT_OF_TWO renamed SQUARE_ROOT_OF_TWO_OVER_TWO --- assignment-client/src/audio/AudioMixer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index ef44e7cd81..0e59f7b702 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -288,7 +288,7 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* normalizedHeadPenumbraAngle = EPSILON; } - const float SQUARE_ROOT_OF_TWO = 0.71f; + const float SQUARE_ROOT_OF_TWO_OVER_TWO = 0.71f; const float FILTER_CUTOFF_FREQUENCY_HZ = 4000.0f; float penumbraFilterGain; float penumbraFilterFrequency; @@ -296,9 +296,9 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* // calculate the updated gain. this will be tuned over time. // consider this only a crude-first pass at correlating gain, freq and slope with penumbra angle. - penumbraFilterGain = SQUARE_ROOT_OF_TWO * (normalizedHeadPenumbraAngle + SQUARE_ROOT_OF_TWO); // [1.0,0.71] + penumbraFilterGain = SQUARE_ROOT_OF_TWO_OVER_TWO * (normalizedHeadPenumbraAngle + SQUARE_ROOT_OF_TWO_OVER_TWO); penumbraFilterFrequency = FILTER_CUTOFF_FREQUENCY_HZ; // constant frequency - penumbraFilterSlope = SQUARE_ROOT_OF_TWO; // constant slope + penumbraFilterSlope = SQUARE_ROOT_OF_TWO_OVER_TWO; // constant slope qDebug() << "penumbra gain=" << penumbraFilterGain << ", penumbraAngle=" << normalizedHeadPenumbraAngle; From 90d86266152ada661a8d48de7fd9a5992996a234 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 14:35:35 -0700 Subject: [PATCH 188/206] Fix for audio on ACs --- libraries/audio/src/AudioInjector.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 114ab1c95c..88251808a9 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -57,8 +57,6 @@ void AudioInjector::injectAudio() { } - NodeList* nodeList = NodeList::getInstance(); - // setup the packet for injected audio QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio); QDataStream packetStream(&injectAudioPacket, QIODevice::Append); @@ -122,6 +120,7 @@ void AudioInjector::injectAudio() { memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy); // grab our audio mixer from the NodeList, if it exists + NodeList* nodeList = NodeList::getInstance(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); // send off this audio packet From 9e686b40960770b8e52249ecc05874af760aede9 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 14:36:46 -0700 Subject: [PATCH 189/206] Moved lean and head rotation inside Player class --- interface/src/avatar/MyAvatar.cpp | 15 ++++++--------- libraries/avatars/src/Recorder.cpp | 30 +++++++++++++++++++++--------- libraries/avatars/src/Recorder.h | 2 ++ 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index dfedf2eb74..a4ebe85611 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -272,10 +272,12 @@ void MyAvatar::simulate(float deltaTime) { // Update avatar head rotation with sensor data void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; - - if (isPlaying()) { - estimatedRotation = glm::degrees(safeEulerAngles(_player->getHeadRotation())); - } else if (Application::getInstance()->getPrioVR()->hasHeadRotation()) { + + if (isPlaying() && !OculusManager::isConnected()) { + return; + } + + if (Application::getInstance()->getPrioVR()->hasHeadRotation()) { estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation())); estimatedRotation.x *= -1.0f; estimatedRotation.z *= -1.0f; @@ -327,11 +329,6 @@ void MyAvatar::updateFromTrackers(float deltaTime) { } head->setDeltaRoll(estimatedRotation.z); - if (isPlaying()) { - head->setLeanSideways(_player->getLeanSideways()); - head->setLeanForward(_player->getLeanForward()); - return; - } // the priovr can give us exact lean if (Application::getInstance()->getPrioVR()->isActive()) { glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation())); diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index 09dea9fe22..502b10a460 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -229,6 +229,11 @@ void Player::startPlaying() { _audioThread->start(); QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); + // Save head orientation + if (_avatar->getHeadData()) { + _originalHeadOrientation = _avatar->getHeadOrientation(); + } + _timer.start(); } } @@ -252,6 +257,12 @@ void Player::stopPlaying() { _audioThread, &QThread::deleteLater); _injector.clear(); _audioThread = NULL; + + // Restore head orientation + if (_avatar->getHeadData()) { + _avatar->setHeadOrientation(_originalHeadOrientation); + } + qDebug() << "Recorder::stopPlaying()"; } @@ -281,10 +292,6 @@ void Player::play() { _avatar->setOrientation(_recording->getFrame(_currentFrame).getRotation()); _avatar->setTargetScale(_recording->getFrame(_currentFrame).getScale()); _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); - HeadData* head = const_cast(_avatar->getHeadData()); - if (head) { - head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); - } } else { _avatar->setPosition(_recording->getFrame(0).getTranslation() + _recording->getFrame(_currentFrame).getTranslation()); @@ -293,11 +300,16 @@ void Player::play() { _avatar->setTargetScale(_recording->getFrame(0).getScale() * _recording->getFrame(_currentFrame).getScale()); _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); - HeadData* head = const_cast(_avatar->getHeadData()); - if (head) { - - head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); - } + } + HeadData* head = const_cast(_avatar->getHeadData()); + if (head) { + head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); + head->setLeanSideways(_recording->getFrame(_currentFrame).getLeanSideways()); + head->setLeanForward(_recording->getFrame(_currentFrame).getLeanForward()); + glm::vec3 eulers = glm::degrees(safeEulerAngles(_recording->getFrame(_currentFrame).getHeadRotation())); + head->setBasePitch(eulers.x); + head->setBaseYaw(eulers.y); + head->setBaseRoll(eulers.z); } _options.setPosition(_avatar->getPosition()); diff --git a/libraries/avatars/src/Recorder.h b/libraries/avatars/src/Recorder.h index 770027a21d..caf6cb665c 100644 --- a/libraries/avatars/src/Recorder.h +++ b/libraries/avatars/src/Recorder.h @@ -162,6 +162,8 @@ private: QSharedPointer _injector; AudioInjectorOptions _options; + glm::quat _originalHeadOrientation; + AvatarData* _avatar; QThread* _audioThread; }; From 3826ed0d6901e6812bcb7a7005c6c780803053f5 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 15:36:08 -0700 Subject: [PATCH 190/206] Added setFinalPitch/Yaw/Roll to HeadData --- interface/src/avatar/Head.cpp | 12 ++++++++++++ interface/src/avatar/Head.h | 3 +++ libraries/avatars/src/AvatarData.cpp | 6 +++++- libraries/avatars/src/HeadData.h | 4 ++++ libraries/avatars/src/Recorder.cpp | 16 +++------------- libraries/avatars/src/Recorder.h | 2 -- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 9bf8158ba7..b226b8ed31 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -222,6 +222,18 @@ glm::vec3 Head::getScalePivot() const { return _faceModel.isActive() ? _faceModel.getTranslation() : _position; } +void Head::setFinalPitch(float finalPitch) { + _deltaPitch = glm::clamp(finalPitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH) - _basePitch; +} + +void Head::setFinalYaw(float finalYaw) { + _deltaYaw = glm::clamp(finalYaw, MIN_HEAD_YAW, MAX_HEAD_YAW) - _baseYaw; +} + +void Head::setFinalRoll(float finalRoll) { + _deltaRoll = glm::clamp(finalRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL) - _baseRoll; +} + float Head::getFinalYaw() const { return glm::clamp(_baseYaw + _deltaYaw, MIN_HEAD_YAW, MAX_HEAD_YAW); } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 0409ee3295..1cdfdaf5a3 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -95,6 +95,9 @@ public: void setDeltaRoll(float roll) { _deltaRoll = roll; } float getDeltaRoll() const { return _deltaRoll; } + virtual void setFinalYaw(float finalYaw); + virtual void setFinalPitch(float finalPitch); + virtual void setFinalRoll(float finalRoll); virtual float getFinalPitch() const; virtual float getFinalYaw() const; virtual float getFinalRoll() const; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 0ec008d0cd..ddd032e91e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -153,7 +153,8 @@ QByteArray AvatarData::toByteArray() { destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyYaw); destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyPitch); destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyRoll); - + + // Body scale destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); @@ -793,6 +794,9 @@ void AvatarData::setJointRotations(QVector jointRotations) { "setJointRotations", Qt::BlockingQueuedConnection, Q_ARG(QVector, jointRotations)); } + if (_jointData.size() < jointRotations.size()) { + _jointData.resize(jointRotations.size()); + } for (int i = 0; i < jointRotations.size(); ++i) { if (i < _jointData.size()) { setJointData(i, jointRotations[i]); diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 310437689c..7e27365387 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -41,6 +41,10 @@ public: void setBasePitch(float pitch) { _basePitch = glm::clamp(pitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH); } float getBaseRoll() const { return _baseRoll; } void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } + + virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; } + virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; } + virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; } virtual float getFinalYaw() const { return _baseYaw; } virtual float getFinalPitch() const { return _basePitch; } virtual float getFinalRoll() const { return _baseRoll; } diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index 502b10a460..2c35790b2e 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -229,11 +229,6 @@ void Player::startPlaying() { _audioThread->start(); QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); - // Save head orientation - if (_avatar->getHeadData()) { - _originalHeadOrientation = _avatar->getHeadOrientation(); - } - _timer.start(); } } @@ -258,11 +253,6 @@ void Player::stopPlaying() { _injector.clear(); _audioThread = NULL; - // Restore head orientation - if (_avatar->getHeadData()) { - _avatar->setHeadOrientation(_originalHeadOrientation); - } - qDebug() << "Recorder::stopPlaying()"; } @@ -307,9 +297,9 @@ void Player::play() { head->setLeanSideways(_recording->getFrame(_currentFrame).getLeanSideways()); head->setLeanForward(_recording->getFrame(_currentFrame).getLeanForward()); glm::vec3 eulers = glm::degrees(safeEulerAngles(_recording->getFrame(_currentFrame).getHeadRotation())); - head->setBasePitch(eulers.x); - head->setBaseYaw(eulers.y); - head->setBaseRoll(eulers.z); + head->setFinalPitch(eulers.x); + head->setFinalYaw(eulers.y); + head->setFinalRoll(eulers.z); } _options.setPosition(_avatar->getPosition()); diff --git a/libraries/avatars/src/Recorder.h b/libraries/avatars/src/Recorder.h index caf6cb665c..770027a21d 100644 --- a/libraries/avatars/src/Recorder.h +++ b/libraries/avatars/src/Recorder.h @@ -162,8 +162,6 @@ private: QSharedPointer _injector; AudioInjectorOptions _options; - glm::quat _originalHeadOrientation; - AvatarData* _avatar; QThread* _audioThread; }; From dda02b5dcf83d92375f2445a790a92eafc22f3dc Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 15:39:57 -0700 Subject: [PATCH 191/206] Fake faceshift connection while playing back --- libraries/avatars/src/AvatarData.cpp | 6 +++--- libraries/avatars/src/Recorder.cpp | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ddd032e91e..b3b8ba3ab1 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -135,9 +135,9 @@ QByteArray AvatarData::toByteArray() { // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { _headData = new HeadData(this); - if (_forceFaceshiftConnected) { - _headData->_isFaceshiftConnected = true; - } + } + if (_forceFaceshiftConnected) { + _headData->_isFaceshiftConnected = true; } QByteArray avatarDataByteArray; diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index 2c35790b2e..5e9db60675 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -229,6 +229,9 @@ void Player::startPlaying() { _audioThread->start(); QMetaObject::invokeMethod(_injector.data(), "injectAudio", Qt::QueuedConnection); + // Fake faceshift connection + _avatar->setForceFaceshiftConnected(true); + _timer.start(); } } @@ -253,6 +256,9 @@ void Player::stopPlaying() { _injector.clear(); _audioThread = NULL; + // Turn off fake faceshift connection + _avatar->setForceFaceshiftConnected(false); + qDebug() << "Recorder::stopPlaying()"; } From 41461cd322966842891caf21011a1a0f7a39ee3c Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 17:23:50 -0700 Subject: [PATCH 192/206] Added option to playback from current location --- examples/Recorder.js | 2 +- libraries/avatars/src/AvatarData.cpp | 9 +++--- libraries/avatars/src/AvatarData.h | 2 +- libraries/avatars/src/Recorder.cpp | 46 +++++++++++++++++++--------- libraries/avatars/src/Recorder.h | 6 +++- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/examples/Recorder.js b/examples/Recorder.js index 9a4375ab4f..0a42eff576 100644 --- a/examples/Recorder.js +++ b/examples/Recorder.js @@ -153,7 +153,7 @@ function mousePressEvent(event) { if (MyAvatar.isPlaying()) { MyAvatar.stopPlaying(); } else { - MyAvatar.startPlaying(); + MyAvatar.startPlaying(true); } } } else if (saveIcon === toolBar.clicked(clickedOverlay)) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b3b8ba3ab1..e590a7b5ca 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -638,16 +638,17 @@ void AvatarData::loadRecording(QString filename) { _player->loadFromFile(filename); } -void AvatarData::startPlaying() { +void AvatarData::startPlaying(bool fromCurrentPosition) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection, + Q_ARG(bool, fromCurrentPosition)); return; } if (!_player) { _player = PlayerPointer(new Player(this)); } - - _player->startPlaying(); + qDebug() << "AvatarData::startPlaying():" << fromCurrentPosition; + _player->startPlaying(fromCurrentPosition); } void AvatarData::play() { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 12fe3adb6e..26ce5b3b8b 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -303,7 +303,7 @@ public slots: qint64 playerElapsed(); qint64 playerLength(); void loadRecording(QString filename); - void startPlaying(); + void startPlaying(bool fromCurrentPosition = false); void play(); void stopPlaying(); diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index 5e9db60675..56ea39358e 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -165,7 +165,8 @@ void Recorder::record(char* samples, int size) { Player::Player(AvatarData* avatar) : _recording(new Recording()), _avatar(avatar), - _audioThread(NULL) + _audioThread(NULL), + _startingScale(1.0f) { _timer.invalidate(); _options.setLoop(false); @@ -215,7 +216,7 @@ float Player::getLeanForward() { return _recording->getFrame(_currentFrame).getLeanForward(); } -void Player::startPlaying() { +void Player::startPlaying(bool fromCurrentPosition) { if (_recording && _recording->getFrameNumber() > 0) { qDebug() << "Recorder::startPlaying()"; _currentFrame = 0; @@ -232,6 +233,16 @@ void Player::startPlaying() { // Fake faceshift connection _avatar->setForceFaceshiftConnected(true); + if (fromCurrentPosition) { + _startingPosition = _avatar->getPosition(); + _startingRotation = _avatar->getOrientation(); + _startingScale = _avatar->getTargetScale(); + } else { + _startingPosition = _recording->getFrame(0).getTranslation(); + _startingRotation = _recording->getFrame(0).getRotation(); + _startingScale = _recording->getFrame(0).getScale(); + } + _timer.start(); } } @@ -283,20 +294,25 @@ void Player::play() { return; } - if (_currentFrame == 0) { - _avatar->setPosition(_recording->getFrame(_currentFrame).getTranslation()); - _avatar->setOrientation(_recording->getFrame(_currentFrame).getRotation()); - _avatar->setTargetScale(_recording->getFrame(_currentFrame).getScale()); - _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); - } else { - _avatar->setPosition(_recording->getFrame(0).getTranslation() + - _recording->getFrame(_currentFrame).getTranslation()); - _avatar->setOrientation(_recording->getFrame(0).getRotation() * - _recording->getFrame(_currentFrame).getRotation()); - _avatar->setTargetScale(_recording->getFrame(0).getScale() * - _recording->getFrame(_currentFrame).getScale()); - _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); + glm::vec3 positionOffset; + glm::quat rotationOffset; + float scaleOffset = 1.0f; + + if (_currentFrame > 0) { + positionOffset = _startingPosition; + rotationOffset = _startingRotation; + scaleOffset = _startingScale; } + + _avatar->setPosition(positionOffset + + glm::inverse(_recording->getFrame(0).getRotation()) * rotationOffset * + _recording->getFrame(_currentFrame).getTranslation()); + _avatar->setOrientation(rotationOffset * + _recording->getFrame(_currentFrame).getRotation()); + _avatar->setTargetScale(scaleOffset * + _recording->getFrame(_currentFrame).getScale()); + _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); + HeadData* head = const_cast(_avatar->getHeadData()); if (head) { head->setBlendshapeCoefficients(_recording->getFrame(_currentFrame).getBlendshapeCoefficients()); diff --git a/libraries/avatars/src/Recorder.h b/libraries/avatars/src/Recorder.h index 770027a21d..2e4814f9f7 100644 --- a/libraries/avatars/src/Recorder.h +++ b/libraries/avatars/src/Recorder.h @@ -146,7 +146,7 @@ public: public slots: - void startPlaying(); + void startPlaying(bool fromCurrentPosition = false); void stopPlaying(); void loadFromFile(QString file); void loadRecording(RecordingPointer recording); @@ -164,6 +164,10 @@ private: AvatarData* _avatar; QThread* _audioThread; + + glm::vec3 _startingPosition; + glm::quat _startingRotation; + float _startingScale; }; void writeRecordingToFile(RecordingPointer recording, QString file); From 056cd0a4d1f990a20dae55fced4120735bafe17a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 18:06:10 -0700 Subject: [PATCH 193/206] extra line --- libraries/avatars/src/AvatarData.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e590a7b5ca..ea1be03c55 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -154,7 +154,6 @@ QByteArray AvatarData::toByteArray() { destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyPitch); destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _bodyRoll); - // Body scale destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); From 465b076998961cb22f949c59c3dba4ee60559a99 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 18:19:26 -0700 Subject: [PATCH 194/206] Removed debug + ignore frame 0 --- libraries/avatars/src/AvatarData.cpp | 1 - libraries/avatars/src/Recorder.cpp | 20 ++++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ea1be03c55..67da20cf70 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -646,7 +646,6 @@ void AvatarData::startPlaying(bool fromCurrentPosition) { if (!_player) { _player = PlayerPointer(new Player(this)); } - qDebug() << "AvatarData::startPlaying():" << fromCurrentPosition; _player->startPlaying(fromCurrentPosition); } diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index 56ea39358e..68fff737c5 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -294,22 +294,18 @@ void Player::play() { return; } - glm::vec3 positionOffset; - glm::quat rotationOffset; - float scaleOffset = 1.0f; - - if (_currentFrame > 0) { - positionOffset = _startingPosition; - rotationOffset = _startingRotation; - scaleOffset = _startingScale; + if (_currentFrame == 0) { + // Don't play frame 0 + // only meant to store absolute values + return; } - _avatar->setPosition(positionOffset + - glm::inverse(_recording->getFrame(0).getRotation()) * rotationOffset * + _avatar->setPosition(_startingPosition + + glm::inverse(_recording->getFrame(0).getRotation()) * _startingRotation * _recording->getFrame(_currentFrame).getTranslation()); - _avatar->setOrientation(rotationOffset * + _avatar->setOrientation(_startingRotation * _recording->getFrame(_currentFrame).getRotation()); - _avatar->setTargetScale(scaleOffset * + _avatar->setTargetScale(_startingScale * _recording->getFrame(_currentFrame).getScale()); _avatar->setJointRotations(_recording->getFrame(_currentFrame).getJointRotations()); From b1ca1e98400326ffd5e0ba6fd8f751217452acce Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 Aug 2014 18:24:09 -0700 Subject: [PATCH 195/206] Script to play recording on ACs --- examples/PlayRecordingOnAC.js | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/PlayRecordingOnAC.js diff --git a/examples/PlayRecordingOnAC.js b/examples/PlayRecordingOnAC.js new file mode 100644 index 0000000000..a68e60a6fa --- /dev/null +++ b/examples/PlayRecordingOnAC.js @@ -0,0 +1,47 @@ +// +// PlayRecordingOnAC.js +// examples +// +// Created by Clément Brisset on 8/24/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +var filename = "http://your.recording.url"; +var playFromCurrentLocation = true; + +Avatar.faceModelURL = "http://public.highfidelity.io/models/heads/EvilPhilip_v7.fst"; +Avatar.skeletonModelURL = "http://public.highfidelity.io/models/skeletons/Philip_Carl_Body_A-Pose.fst"; + +// Set position here if playFromCurrentLocation is true +Avatar.position = { x:1, y: 1, z: 1 }; + +Agent.isAvatar = true; + +Avatar.loadRecording(filename); + +count = 300; // This is necessary to wait for the audio mixer to connect +function update(event) { + if (count > 0) { + count--; + return; + } + if (count == 0) { + Avatar.startPlaying(playFromCurrentLocation); + Avatar.play(); + Vec3.print("Playing from ", Avatar.position); + + count--; + } + + if (Avatar.isPlaying()) { + Avatar.play(); + } else { + Script.update.disconnect(update); + } +} + +Script.update.connect(update); From 40a5ff6b9e2eb9e0eabb55a478d640eef7e8aa44 Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Wed, 27 Aug 2014 10:56:44 -0700 Subject: [PATCH 196/206] ZappoMan deadcode analysis applied --- assignment-client/src/audio/AudioMixer.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index f617d847ca..83ce6195cc 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -39,18 +39,7 @@ public slots: void sendStatsPacket(); -#if 0 -<<<<<<< HEAD - static bool getUseDynamicJitterBuffers() { return _useDynamicJitterBuffers; } - static int getStaticDesiredJitterBufferFrames() { return _staticDesiredJitterBufferFrames; } - static int getMaxFramesOverDesired() { return _maxFramesOverDesired; } - -======= -#endif static const InboundAudioStream::Settings& getStreamSettings() { return _streamSettings; } -#if 0 ->>>>>>> 7a8a8684d6f8c9956ca7e4f81eb8064b8dece58e -#endif private: /// adds one stream to the mix for a listening node @@ -83,10 +72,8 @@ private: static InboundAudioStream::Settings _streamSettings; static bool _printStreamStats; - static bool _enableFilter; - quint64 _lastSendAudioStreamStatsTime; quint64 _lastPerSecondCallbackTime; bool _sendAudioStreamStats; From 2448229b46299c0f2d2d9f37801c729bc9fdc8ae Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 27 Aug 2014 16:03:53 -0700 Subject: [PATCH 197/206] fix for crash in Audio --- interface/src/Audio.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 4fdf048756..8a788df831 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1313,7 +1313,10 @@ void Audio::freeScope() { int Audio::addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamplesPerChannel, unsigned int sourceChannel, unsigned int sourceNumberOfChannels, float fade) { - + if (!_scopeEnabled || _scopeEnabledPause) { + return 0; + } + // Constant multiplier to map sample value to vertical size of scope float multiplier = (float)MULTIPLIER_SCOPE_HEIGHT / logf(2.0f); From 08819597500b1f807f5005844e321364abcbccdd Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Wed, 27 Aug 2014 17:30:11 -0700 Subject: [PATCH 198/206] left/right channels of head shadow filter now have independent gain + implemented all-pass filter --- assignment-client/src/audio/AudioMixer.cpp | 39 +++++++++++----------- libraries/audio/src/AudioFilter.h | 37 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 52a922b4e3..b0d9cae8d9 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -308,38 +308,37 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* if (_enableFilter && shouldAttenuate) { glm::vec3 relativePosition = streamToAdd->getPosition() - listeningNodeStream->getPosition(); - if (relativePosition.z < 0) { // if the source is behind us - + if (relativePosition.z < 0) { // if the source is behind us AudioFilterHSF1s& penumbraFilter = streamToAdd->getFilter(); // calculate penumbra angle float headPenumbraAngle = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(relativePosition)); - // normalize penumbra angle - float normalizedHeadPenumbraAngle = headPenumbraAngle / PI_OVER_TWO; - - if (normalizedHeadPenumbraAngle < EPSILON) { - normalizedHeadPenumbraAngle = EPSILON; + if (relativePosition.x < 0) { + headPenumbraAngle *= -1.0f; // [-pi/2,+pi/2] } - const float SQUARE_ROOT_OF_TWO_OVER_TWO = 0.71f; + const float SQUARE_ROOT_OF_TWO_OVER_TWO = 0.71f; // half power + const float ONE_OVER_TWO_PI = 1.0f / TWO_PI; const float FILTER_CUTOFF_FREQUENCY_HZ = 4000.0f; - float penumbraFilterGain; - float penumbraFilterFrequency; - float penumbraFilterSlope; - - // calculate the updated gain. this will be tuned over time. - // consider this only a crude-first pass at correlating gain, freq and slope with penumbra angle. - penumbraFilterGain = SQUARE_ROOT_OF_TWO_OVER_TWO * (normalizedHeadPenumbraAngle + SQUARE_ROOT_OF_TWO_OVER_TWO); - penumbraFilterFrequency = FILTER_CUTOFF_FREQUENCY_HZ; // constant frequency - penumbraFilterSlope = SQUARE_ROOT_OF_TWO_OVER_TWO; // constant slope - qDebug() << "penumbra gain=" << penumbraFilterGain << ", penumbraAngle=" << normalizedHeadPenumbraAngle; + // calculate the updated gain, frequency and slope. this will be tuned over time. + const float penumbraFilterGainL = (-1.0f * ONE_OVER_TWO_PI * headPenumbraAngle ) + SQUARE_ROOT_OF_TWO_OVER_TWO; + const float penumbraFilterGainR = (+1.0f * ONE_OVER_TWO_PI * headPenumbraAngle ) + SQUARE_ROOT_OF_TWO_OVER_TWO; + const float penumbraFilterFrequency = FILTER_CUTOFF_FREQUENCY_HZ; // constant frequency + const float penumbraFilterSlope = SQUARE_ROOT_OF_TWO_OVER_TWO; // constant slope + + qDebug() << "penumbra gainL=" + << penumbraFilterGainL + << "penumbra gainR=" + << penumbraFilterGainR + << "penumbraAngle=" + << headPenumbraAngle; // set the gain on both filter channels - penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGain, penumbraFilterSlope); - penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGain, penumbraFilterSlope); + penumbraFilter.setParameters(0, 0, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainL, penumbraFilterSlope); + penumbraFilter.setParameters(0, 1, SAMPLE_RATE, penumbraFilterFrequency, penumbraFilterGainR, penumbraFilterSlope); penumbraFilter.render(_clientSamples, _clientSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / 2); } diff --git a/libraries/audio/src/AudioFilter.h b/libraries/audio/src/AudioFilter.h index 7c3c98582b..eb65593e51 100644 --- a/libraries/audio/src/AudioFilter.h +++ b/libraries/audio/src/AudioFilter.h @@ -236,6 +236,43 @@ public: } }; +// +// Implements a all-pass filter using a biquad +// +class AudioFilterALL : +public AudioFilter< AudioFilterALL > +{ +public: + + // + // helpers + // + void updateKernel() { + + const float omega = TWO_PI * _frequency / _sampleRate; + const float cosOmega = cosf(omega); + const float alpha = 0.5f * sinf(omega) / _slope; + /* + b0 = 1 - alpha + b1 = -2*cos(w0) + b2 = 1 + alpha + a0 = 1 + alpha + a1 = -2*cos(w0) + a2 = 1 - alpha + */ + const float b0 = +1.0f - alpha; + const float b1 = -2.0f * cosOmega; + const float b2 = +1.0f + alpha; + const float a0 = +1.0f + alpha; + const float a1 = -2.0f * cosOmega; + const float a2 = +1.0f - alpha; + + const float normA0 = 1.0f / a0; + + _kernel.setParameters(b0 * normA0, b1 * normA0 , b2 * normA0, a1 * normA0, a2 * normA0); + } +}; + // // Implements a single-band parametric EQ using a biquad "peaking EQ" configuration // From 691ace7cc6a9f9806f3fffb53d3a585eff1f0ad0 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 27 Aug 2014 19:05:39 -0700 Subject: [PATCH 199/206] Looping option --- examples/Recorder.js | 20 +++++++++++--------- libraries/avatars/src/AvatarData.cpp | 15 +++++++++++---- libraries/avatars/src/AvatarData.h | 4 +++- libraries/avatars/src/Recorder.cpp | 20 +++++++++++++++++--- libraries/avatars/src/Recorder.h | 9 ++++++++- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/examples/Recorder.js b/examples/Recorder.js index 0a42eff576..cf4b422926 100644 --- a/examples/Recorder.js +++ b/examples/Recorder.js @@ -139,7 +139,9 @@ function moveUI() { function mousePressEvent(event) { clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - if (recordIcon === toolBar.clicked(clickedOverlay)) { + print("Status: isPlaying=" + MyAvatar.isPlaying() + ", isRecording=" + MyAvatar.isRecording()); + + if (recordIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isPlaying()) { if (!MyAvatar.isRecording()) { MyAvatar.startRecording(); toolBar.setBack(COLOR_ON, ALPHA_ON); @@ -148,14 +150,14 @@ function mousePressEvent(event) { MyAvatar.loadLastRecording(); toolBar.setBack(COLOR_OFF, ALPHA_OFF); } - } else if (playIcon === toolBar.clicked(clickedOverlay)) { - if (!MyAvatar.isRecording()) { - if (MyAvatar.isPlaying()) { - MyAvatar.stopPlaying(); - } else { - MyAvatar.startPlaying(true); - } - } + } else if (playIcon === toolBar.clicked(clickedOverlay) && !MyAvatar.isRecording()) { + if (MyAvatar.isPlaying()) { + MyAvatar.stopPlaying(); + } else { + MyAvatar.setPlayFromCurrentLocation(true); + MyAvatar.setPlayerLoop(true); + MyAvatar.startPlaying(true); + } } else if (saveIcon === toolBar.clicked(clickedOverlay)) { if (!MyAvatar.isRecording()) { recordingFile = Window.save("Save recording to file", ".", "*.rec"); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 67da20cf70..17c5d6c259 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -637,16 +637,23 @@ void AvatarData::loadRecording(QString filename) { _player->loadFromFile(filename); } -void AvatarData::startPlaying(bool fromCurrentPosition) { +void AvatarData::startPlaying() { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection, - Q_ARG(bool, fromCurrentPosition)); + QMetaObject::invokeMethod(this, "startPlaying", Qt::BlockingQueuedConnection); return; } if (!_player) { _player = PlayerPointer(new Player(this)); } - _player->startPlaying(fromCurrentPosition); + _player->startPlaying(); +} + +void AvatarData::setPlayFromCurrentLocation(bool playFromCurrentLocation) { + _player->setPlayFromCurrentLocation(playFromCurrentLocation); +} + +void AvatarData::setPlayerLoop(bool loop) { + _player->setLoop(loop); } void AvatarData::play() { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 26ce5b3b8b..432b68a776 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -303,7 +303,9 @@ public slots: qint64 playerElapsed(); qint64 playerLength(); void loadRecording(QString filename); - void startPlaying(bool fromCurrentPosition = false); + void startPlaying(); + void setPlayFromCurrentLocation(bool playFromCurrentLocation); + void setPlayerLoop(bool loop); void play(); void stopPlaying(); diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index 68fff737c5..9c35668b17 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -166,7 +166,9 @@ Player::Player(AvatarData* avatar) : _recording(new Recording()), _avatar(avatar), _audioThread(NULL), - _startingScale(1.0f) + _startingScale(1.0f), + _playFromCurrentPosition(true), + _loop(false) { _timer.invalidate(); _options.setLoop(false); @@ -216,7 +218,7 @@ float Player::getLeanForward() { return _recording->getFrame(_currentFrame).getLeanForward(); } -void Player::startPlaying(bool fromCurrentPosition) { +void Player::startPlaying() { if (_recording && _recording->getFrameNumber() > 0) { qDebug() << "Recorder::startPlaying()"; _currentFrame = 0; @@ -233,7 +235,7 @@ void Player::startPlaying(bool fromCurrentPosition) { // Fake faceshift connection _avatar->setForceFaceshiftConnected(true); - if (fromCurrentPosition) { + if (_playFromCurrentPosition) { _startingPosition = _avatar->getPosition(); _startingRotation = _avatar->getOrientation(); _startingScale = _avatar->getTargetScale(); @@ -291,6 +293,10 @@ void Player::play() { if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber() - 1) { // If it's the end of the recording, stop playing stopPlaying(); + + if (_loop) { + startPlaying(); + } return; } @@ -325,6 +331,14 @@ void Player::play() { _injector->setOptions(_options); } +void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) { + _playFromCurrentPosition = playFromCurrentLocation; +} + +void Player::setLoop(bool loop) { + _loop = loop; +} + bool Player::computeCurrentFrame() { if (!isPlaying()) { _currentFrame = -1; diff --git a/libraries/avatars/src/Recorder.h b/libraries/avatars/src/Recorder.h index 2e4814f9f7..1f41672749 100644 --- a/libraries/avatars/src/Recorder.h +++ b/libraries/avatars/src/Recorder.h @@ -97,6 +97,7 @@ private: QVector _timestamps; QVector _frames; + bool _stereo; Sound* _audio; friend class Recorder; @@ -146,12 +147,15 @@ public: public slots: - void startPlaying(bool fromCurrentPosition = false); + void startPlaying(); void stopPlaying(); void loadFromFile(QString file); void loadRecording(RecordingPointer recording); void play(); + void setPlayFromCurrentLocation(bool playFromCurrentLocation); + void setLoop(bool loop); + private: bool computeCurrentFrame(); @@ -168,6 +172,9 @@ private: glm::vec3 _startingPosition; glm::quat _startingRotation; float _startingScale; + + bool _playFromCurrentPosition; + bool _loop; }; void writeRecordingToFile(RecordingPointer recording, QString file); From e7fe7bd021387d3df92d0898e0ec3273ab63e49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sleepy=20Daddy=20Software=E2=84=A2?= Date: Thu, 28 Aug 2014 01:23:19 -0400 Subject: [PATCH 200/206] Added the correct install order for VS2010 C++ Express and Windows 7.1 SDK. --- BUILD.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index be30d35f21..d6e1603f37 100644 --- a/BUILD.md +++ b/BUILD.md @@ -96,7 +96,9 @@ Currently building on Windows has been tested using the following compilers: #####Windows SDK 7.1 -Whichever version of Visual Studio you use, first install [Microsoft Windows SDK for Windows 7 and .NET Framework 4](http://www.microsoft.com/en-us/download/details.aspx?id=8279). +Whichever version of Visual Studio you use, you will need [Microsoft Windows SDK for Windows 7 and .NET Framework 4](http://www.microsoft.com/en-us/download/details.aspx?id=8279). + +NOTE: If using Visual Studio C++ 2010 Express, you need to follow a specific install order. See below before installing the Windows SDK. ######Windows 8.1 You may have already downloaded the Windows 8 SDK (e.g. if you have previously installed Visual Studio 2013). If so, change CMAKE_PREFIX_PATH in %HIFI_DIR%\CMakeLists.txt to point to the Windows 8 SDK binaries. The default path is `C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86` @@ -109,6 +111,14 @@ The following patches/service packs are also required: * [VS2010 SP1](http://www.microsoft.com/en-us/download/details.aspx?id=23691) * [VS2010 SP1 Compiler Update](http://www.microsoft.com/en-us/download/details.aspx?id=4422) +IMPORTANT: Use the following install order: +Visual Studio C++ 2010 Express +Windows SDK 7.1 +VS2010 SP1 +VS2010 SP1 Compiler Update + +If you get an error while installing the VS2010 SP1 Compiler update saying that you don't have the Windows SDK installed, then uninstall all of the above and start again in the correct order. + Some of the build instructions will ask you to start a Visual Studio Command Prompt. You should have a shortcut in your Start menu called "Open Visual Studio Command Prompt (2010)" which will do so. #####Visual Studio 2013 From cc2a35c2a83506e13eee8bdade44a06e455700a4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 Aug 2014 10:52:29 -0700 Subject: [PATCH 201/206] handle not found errors returned for addresses from data-web --- interface/src/avatar/MyAvatar.cpp | 8 +--- interface/src/location/LocationManager.cpp | 45 +++++++++++++++------- interface/src/location/LocationManager.h | 3 ++ 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a4ebe85611..e51390f9d0 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1896,12 +1896,8 @@ void MyAvatar::resetSize() { } void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { - if (jsonObject["status"].toString() == "success") { - QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject(); - goToLocationFromAddress(locationObject); - } else { - QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found."); - } + QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject(); + goToLocationFromAddress(locationObject); } void MyAvatar::goToLocationFromAddress(const QJsonObject& locationObject) { diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index cbf7d3dfd0..551e08a8bc 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -85,9 +85,13 @@ void LocationManager::goTo(QString destination) { if (!goToDestination(destination)) { destination = QString(QUrl::toPercentEncoding(destination)); UserActivityLogger::getInstance().wentTo(OTHER_DESTINATION_TYPE, destination); + JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "goToAddressFromResponse"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "handleAddressLookupError"; + AccountManager::getInstance().authenticatedRequest(GET_ADDRESSES.arg(destination), QNetworkAccessManager::GetOperation, callbackParams); @@ -96,21 +100,17 @@ void LocationManager::goTo(QString destination) { void LocationManager::goToAddressFromResponse(const QJsonObject& responseData) { QJsonValue status = responseData["status"]; - qDebug() << responseData; - if (!status.isUndefined() && status.toString() == "success") { - const QJsonObject& data = responseData["data"].toObject(); - const QJsonValue& userObject = data["user"]; - const QJsonValue& placeObject = data["place"]; - - if (!placeObject.isUndefined() && !userObject.isUndefined()) { - emit multipleDestinationsFound(userObject.toObject(), placeObject.toObject()); - } else if (placeObject.isUndefined()) { - Application::getInstance()->getAvatar()->goToLocationFromAddress(userObject.toObject()["address"].toObject()); - } else { - Application::getInstance()->getAvatar()->goToLocationFromAddress(placeObject.toObject()["address"].toObject()); - } + + const QJsonObject& data = responseData["data"].toObject(); + const QJsonValue& userObject = data["user"]; + const QJsonValue& placeObject = data["place"]; + + if (!placeObject.isUndefined() && !userObject.isUndefined()) { + emit multipleDestinationsFound(userObject.toObject(), placeObject.toObject()); + } else if (placeObject.isUndefined()) { + Application::getInstance()->getAvatar()->goToLocationFromAddress(userObject.toObject()["address"].toObject()); } else { - QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found."); + Application::getInstance()->getAvatar()->goToLocationFromAddress(placeObject.toObject()["address"].toObject()); } } @@ -118,6 +118,8 @@ void LocationManager::goToUser(QString userName) { JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar(); callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "handleAddressLookupError"; userName = QString(QUrl::toPercentEncoding(userName)); AccountManager::getInstance().authenticatedRequest(GET_USER_ADDRESS.arg(userName), @@ -129,6 +131,8 @@ void LocationManager::goToPlace(QString placeName) { JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar(); callbackParams.jsonCallbackMethod = "goToLocationFromResponse"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "handleAddressLookupError"; placeName = QString(QUrl::toPercentEncoding(placeName)); AccountManager::getInstance().authenticatedRequest(GET_PLACE_ADDRESS.arg(placeName), @@ -212,6 +216,19 @@ bool LocationManager::goToDestination(QString destination) { return false; } +void LocationManager::handleAddressLookupError(QNetworkReply::NetworkError networkError, + const QString& errorString) { + QString messageBoxString; + + if (networkError == QNetworkReply::ContentNotFoundError) { + messageBoxString = "That address could not be found."; + } else { + messageBoxString = errorString; + } + + QMessageBox::warning(Application::getInstance()->getWindow(), "", messageBoxString); +} + void LocationManager::replaceLastOccurrence(const QChar search, const QChar replace, QString& string) { int lastIndex; lastIndex = string.lastIndexOf(search); diff --git a/interface/src/location/LocationManager.h b/interface/src/location/LocationManager.h index b781f3f54e..30b4447ded 100644 --- a/interface/src/location/LocationManager.h +++ b/interface/src/location/LocationManager.h @@ -37,6 +37,9 @@ public: void goToPlace(QString placeName); void goToOrientation(QString orientation); bool goToDestination(QString destination); + +public slots: + void handleAddressLookupError(QNetworkReply::NetworkError networkError, const QString& errorString); private: void replaceLastOccurrence(const QChar search, const QChar replace, QString& string); From 9df68dc2f16ea83c4ac86ba2eec714eb51bd1f5a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 Aug 2014 10:58:07 -0700 Subject: [PATCH 202/206] CR --- libraries/avatars/src/Recorder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index 9c35668b17..69cde6560e 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -290,7 +290,7 @@ void Player::loadRecording(RecordingPointer recording) { void Player::play() { computeCurrentFrame(); - if (_currentFrame < 0 || _currentFrame >= _recording->getFrameNumber() - 1) { + if (_currentFrame < 0 || (_currentFrame >= _recording->getFrameNumber() - 1)) { // If it's the end of the recording, stop playing stopPlaying(); From 3e3090f1ea946f4cf2fbc1804c74ac718499ac7c Mon Sep 17 00:00:00 2001 From: Craig Hansen-Sturm Date: Thu, 28 Aug 2014 11:12:17 -0700 Subject: [PATCH 203/206] coding standard --- assignment-client/src/audio/AudioMixer.cpp | 4 ++-- libraries/audio/src/AudioFilter.h | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index b0d9cae8d9..51ef47b67d 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -324,8 +324,8 @@ int AudioMixer::addStreamToMixForListeningNodeWithStream(PositionalAudioStream* const float FILTER_CUTOFF_FREQUENCY_HZ = 4000.0f; // calculate the updated gain, frequency and slope. this will be tuned over time. - const float penumbraFilterGainL = (-1.0f * ONE_OVER_TWO_PI * headPenumbraAngle ) + SQUARE_ROOT_OF_TWO_OVER_TWO; - const float penumbraFilterGainR = (+1.0f * ONE_OVER_TWO_PI * headPenumbraAngle ) + SQUARE_ROOT_OF_TWO_OVER_TWO; + const float penumbraFilterGainL = (-1.0f * ONE_OVER_TWO_PI * headPenumbraAngle) + SQUARE_ROOT_OF_TWO_OVER_TWO; + const float penumbraFilterGainR = (+1.0f * ONE_OVER_TWO_PI * headPenumbraAngle) + SQUARE_ROOT_OF_TWO_OVER_TWO; const float penumbraFilterFrequency = FILTER_CUTOFF_FREQUENCY_HZ; // constant frequency const float penumbraFilterSlope = SQUARE_ROOT_OF_TWO_OVER_TWO; // constant slope diff --git a/libraries/audio/src/AudioFilter.h b/libraries/audio/src/AudioFilter.h index eb65593e51..c2de3860db 100644 --- a/libraries/audio/src/AudioFilter.h +++ b/libraries/audio/src/AudioFilter.h @@ -155,8 +155,7 @@ public: // // Implements a low-shelf filter using a biquad // -class AudioFilterLSF : -public AudioFilter< AudioFilterLSF > +class AudioFilterLSF : public AudioFilter< AudioFilterLSF > { public: @@ -197,8 +196,7 @@ public: // // Implements a hi-shelf filter using a biquad // -class AudioFilterHSF : -public AudioFilter< AudioFilterHSF > +class AudioFilterHSF : public AudioFilter< AudioFilterHSF > { public: @@ -239,8 +237,7 @@ public: // // Implements a all-pass filter using a biquad // -class AudioFilterALL : -public AudioFilter< AudioFilterALL > +class AudioFilterALL : public AudioFilter< AudioFilterALL > { public: @@ -276,8 +273,7 @@ public: // // Implements a single-band parametric EQ using a biquad "peaking EQ" configuration // -class AudioFilterPEQ : - public AudioFilter< AudioFilterPEQ > +class AudioFilterPEQ : public AudioFilter< AudioFilterPEQ > { public: From 46c1a9d59ac577a7cc0565c2ad90ebf6ccfd873f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Aug 2014 11:25:13 -0700 Subject: [PATCH 204/206] Make login dialog automatically show only once per domain visit --- interface/src/Application.cpp | 3 ++- interface/src/Menu.cpp | 19 +++++++++++++++++-- interface/src/Menu.h | 3 +++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 90da5e5046..f1c0014ef9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -246,7 +246,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived); - + connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDomain); + // hookup VoxelEditSender to PaymentManager so we can pay for octree edits const PaymentManager& paymentManager = PaymentManager::getInstance(); connect(&_voxelEditSender, &VoxelEditPacketSender::octreePaymentRequired, diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 5ef64741cf..15a674566b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -114,6 +114,7 @@ Menu::Menu() : _loginAction(NULL), _preferencesDialog(NULL), _loginDialog(NULL), + _loginDomain(), _snapshotsLocation(), _scriptsLocation(), _walletPrivateKey() @@ -1052,14 +1053,28 @@ void sendFakeEnterEvent() { const float DIALOG_RATIO_OF_WINDOW = 0.30f; +void Menu::clearLoginDomain() { + // Needed for domains that don't require login. + _loginDomain = QString(); +} + void Menu::loginForCurrentDomain() { - if (!_loginDialog) { + QString domain = NodeList::getInstance()->getDomainHandler().getHostname(); + bool hasShownForDomain = domain == _loginDomain; + + if (!_loginDialog && !hasShownForDomain) { + _loginDomain = domain; _loginDialog = new LoginDialog(Application::getInstance()->getWindow()); _loginDialog->show(); _loginDialog->resizeAndPosition(false); } } +void Menu::showLoginForCurrentDomain() { + _loginDomain = QString(); + loginForCurrentDomain(); +} + void Menu::editPreferences() { if (!_preferencesDialog) { _preferencesDialog = new PreferencesDialog(Application::getInstance()->getWindow()); @@ -1404,7 +1419,7 @@ void Menu::toggleLoginMenuItem() { // change the menu item to login _loginAction->setText("Login"); - connect(_loginAction, &QAction::triggered, this, &Menu::loginForCurrentDomain); + connect(_loginAction, &QAction::triggered, this, &Menu::showLoginForCurrentDomain); } } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 6c7a867584..8f6724af24 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -175,7 +175,9 @@ signals: public slots: + void clearLoginDomain(); void loginForCurrentDomain(); + void showLoginForCurrentDomain(); void bandwidthDetails(); void octreeStatsDetails(); void lodTools(); @@ -302,6 +304,7 @@ private: QPointer _attachmentsDialog; QPointer _animationsDialog; QPointer _loginDialog; + QString _loginDomain; QAction* _chatAction; QString _snapshotsLocation; QString _scriptsLocation; From 85e9f8064aec73a5f94813c518d8b6d11689c421 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Aug 2014 12:21:55 -0700 Subject: [PATCH 205/206] Simplify to use bool instead of storing domain name --- interface/src/Menu.cpp | 14 ++++++-------- interface/src/Menu.h | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 15a674566b..4dc10fa304 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -114,7 +114,7 @@ Menu::Menu() : _loginAction(NULL), _preferencesDialog(NULL), _loginDialog(NULL), - _loginDomain(), + _hasLoginDialogDisplayed(false), _snapshotsLocation(), _scriptsLocation(), _walletPrivateKey() @@ -1055,23 +1055,21 @@ const float DIALOG_RATIO_OF_WINDOW = 0.30f; void Menu::clearLoginDomain() { // Needed for domains that don't require login. - _loginDomain = QString(); + _hasLoginDialogDisplayed = false; } void Menu::loginForCurrentDomain() { - QString domain = NodeList::getInstance()->getDomainHandler().getHostname(); - bool hasShownForDomain = domain == _loginDomain; - - if (!_loginDialog && !hasShownForDomain) { - _loginDomain = domain; + if (!_loginDialog && !_hasLoginDialogDisplayed) { _loginDialog = new LoginDialog(Application::getInstance()->getWindow()); _loginDialog->show(); _loginDialog->resizeAndPosition(false); } + + _hasLoginDialogDisplayed = true; } void Menu::showLoginForCurrentDomain() { - _loginDomain = QString(); + _hasLoginDialogDisplayed = false; loginForCurrentDomain(); } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8f6724af24..8db118a471 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -304,7 +304,7 @@ private: QPointer _attachmentsDialog; QPointer _animationsDialog; QPointer _loginDialog; - QString _loginDomain; + bool _hasLoginDialogDisplayed; QAction* _chatAction; QString _snapshotsLocation; QString _scriptsLocation; From 1d3a176f5fb90f7dbe0c8bf2addc1ec6d9e4c3e6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Aug 2014 13:53:47 -0700 Subject: [PATCH 206/206] Rename method --- interface/src/Application.cpp | 2 +- interface/src/Menu.cpp | 2 +- interface/src/Menu.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f1c0014ef9..10ae4b0303 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -246,7 +246,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived); - connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDomain); + connect(&domainHandler, &DomainHandler::hostnameChanged, Menu::getInstance(), &Menu::clearLoginDialogDisplayedFlag); // hookup VoxelEditSender to PaymentManager so we can pay for octree edits const PaymentManager& paymentManager = PaymentManager::getInstance(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4dc10fa304..0dcfd60051 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1053,7 +1053,7 @@ void sendFakeEnterEvent() { const float DIALOG_RATIO_OF_WINDOW = 0.30f; -void Menu::clearLoginDomain() { +void Menu::clearLoginDialogDisplayedFlag() { // Needed for domains that don't require login. _hasLoginDialogDisplayed = false; } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8db118a471..1d57da2891 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -175,7 +175,7 @@ signals: public slots: - void clearLoginDomain(); + void clearLoginDialogDisplayedFlag(); void loginForCurrentDomain(); void showLoginForCurrentDomain(); void bandwidthDetails();