From 7d7e0129656959558a563f87ceabfd0d4c399c9b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:28:09 -0700 Subject: [PATCH 01/28] Add XMLHttpRequest --- .../script-engine/src/XMLHttpRequestClass.cpp | 233 ++++++++++++++++++ .../script-engine/src/XMLHttpRequestClass.h | 128 ++++++++++ 2 files changed, 361 insertions(+) create mode 100644 libraries/script-engine/src/XMLHttpRequestClass.cpp create mode 100644 libraries/script-engine/src/XMLHttpRequestClass.h diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp new file mode 100644 index 0000000000..77b03db999 --- /dev/null +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -0,0 +1,233 @@ +// +// XMLHttpRequestClass.cpp +// libraries/script-engine/src/ +// +// Created by Ryan Huffman on 5/2/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// This class is an implementation of the XMLHttpRequest object for scripting use. It provides a near-complete implementation +// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest +// +// 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 "XMLHttpRequestClass.h" + +XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : + _engine(engine), + _async(true), + _url(), + _method(""), + _responseType(""), + _manager(this), + _request(), + _reply(NULL), + _sendData(NULL), + _rawResponseData(), + _responseData(""), + _onTimeout(QScriptValue::NullValue), + _onReadyStateChange(QScriptValue::NullValue), + _readyState(XMLHttpRequestClass::UNSENT), + _errorCode(QNetworkReply::NoError), + _timeout(0), + _timer(this), + _numRedirects(0) { + + _timer.setSingleShot(true); +} + +XMLHttpRequestClass::~XMLHttpRequestClass() { + if (_reply) { delete _reply; } + if (_sendData) { delete _sendData; } +} + +QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEngine* engine) { + return engine->newQObject(new XMLHttpRequestClass(engine)); +} + +QScriptValue XMLHttpRequestClass::getStatus() const { + if (_reply) { + return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); + } + return QScriptValue(0); +} + +QString XMLHttpRequestClass::getStatusText() const { + if (_reply) { + return _reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + } + return ""; +} + +void XMLHttpRequestClass::abort() { + _abortRequest(); +} + +void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) { + _request.setRawHeader(QByteArray(name.toLatin1()), QByteArray(value.toLatin1())); +} + +void XMLHttpRequestClass::requestMetaDataChanged() { + QVariant redirect = _reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + + // If this is a redirect, abort the current request and start a new one + if (redirect.isValid() && _numRedirects < MAXIMUM_REDIRECTS) { + _numRedirects++; + _abortRequest(); + + QUrl newUrl = _url.resolved(redirect.toUrl().toString()); + _request.setUrl(newUrl); + _doSend(); + } +} + +void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (_readyState == OPENED && bytesReceived > 0) { + _setReadyState(HEADERS_RECEIVED); + _setReadyState(LOADING); + } +} + +QScriptValue XMLHttpRequestClass::getAllResponseHeaders() const { + if (_reply) { + QList headerList = _reply->rawHeaderPairs(); + QByteArray headers; + for (int i = 0; i < headerList.size(); i++) { + headers.append(headerList[i].first); + headers.append(": "); + headers.append(headerList[i].second); + headers.append("\n"); + } + return QString(headers.data()); + } + return QScriptValue(""); +} + +QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { + if (_reply && _reply->hasRawHeader(name.toLatin1())) { + return QScriptValue(QString(_reply->rawHeader(name.toLatin1()))); + } + return QScriptValue::NullValue; +} + +void XMLHttpRequestClass::_setReadyState(ReadyState readyState) { + if (readyState != _readyState) { + _readyState = readyState; + if (_onReadyStateChange.isFunction()) { + _onReadyStateChange.call(QScriptValue::NullValue); + } + } +} + +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); + } +} + +void XMLHttpRequestClass::send() { + send(QString::Null()); +} + +void XMLHttpRequestClass::send(const QString& data) { + if (_readyState == OPENED && !_reply) { + if (!data.isNull()) { + _sendData = new QBuffer(this); + _sendData->setData(data.toUtf8()); + } + + _doSend(); + + if (!_async) { + QEventLoop loop; + connect(this, SIGNAL(requestComplete()), &loop, SLOT(quit())); + loop.exec(); + } + } +} + +void XMLHttpRequestClass::_doSend() { + _reply = _manager.sendCustomRequest(_request, _method.toLatin1(), _sendData); + + _connectToReply(_reply); + + if (_timeout > 0) { + _timer.start(_timeout); + connect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); + } +} + +void XMLHttpRequestClass::requestTimeout() { + if (_onTimeout.isFunction()) { + _onTimeout.call(QScriptValue::NullValue); + } + _abortRequest(); + _errorCode = QNetworkReply::TimeoutError; + _setReadyState(DONE); + emit requestComplete(); +} + +void XMLHttpRequestClass::requestError(QNetworkReply::NetworkError code) { +} + +void XMLHttpRequestClass::requestFinished() { + disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); + + _errorCode = _reply->error(); + if (_errorCode == QNetworkReply::NoError) { + _rawResponseData.append(_reply->readAll()); + + if (_responseType == "json") { + _responseData = _engine->evaluate("(" + QString(_rawResponseData.data()) + ")"); + if (_responseData.isError()) { + _engine->clearExceptions(); + _responseData = QScriptValue::NullValue; + } + } else if (_responseType == "arraybuffer") { + _responseData = QScriptValue(_rawResponseData.data()); + } else { + _responseData = QScriptValue(QString(_rawResponseData.data())); + } + } + _setReadyState(DONE); + emit requestComplete(); +} + +void XMLHttpRequestClass::_abortRequest() { + // Disconnect from signals we don't want to receive any longer. + disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); + if (_reply) { + _disconnectFromReply(_reply); + _reply->abort(); + delete _reply; + _reply = NULL; + } +} + +void XMLHttpRequestClass::_connectToReply(QNetworkReply* reply) { + connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); + connect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); +} + +void XMLHttpRequestClass::_disconnectFromReply(QNetworkReply* reply) { + disconnect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); + disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); + disconnect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); +} diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h new file mode 100644 index 0000000000..b1b33e4cc6 --- /dev/null +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -0,0 +1,128 @@ +// +// XMLHttpRequestClass.h +// libraries/script-engine/src/ +// +// Created by Ryan Huffman on 5/2/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// 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_XMLHttpRequestClass_h +#define hifi_XMLHttpRequestClass_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class XMLHttpRequestClass : public QObject { + Q_OBJECT + Q_PROPERTY(QScriptValue response READ getResponse) + Q_PROPERTY(QScriptValue responseText READ getResponseText) + Q_PROPERTY(QString responseType READ getResponseType WRITE setResponseType) + Q_PROPERTY(QScriptValue status READ getStatus) + Q_PROPERTY(QString statusText READ getStatusText) + Q_PROPERTY(QScriptValue readyState READ getReadyState) + Q_PROPERTY(QScriptValue errorCode READ getError) + Q_PROPERTY(int timeout READ getTimeout WRITE setTimeout) + + Q_PROPERTY(int UNSENT READ getUnsent) + Q_PROPERTY(int OPENED READ getOpened) + Q_PROPERTY(int HEADERS_RECEIVED READ getHeadersReceived) + Q_PROPERTY(int LOADING READ getLoading) + Q_PROPERTY(int DONE READ getDone) + + // Callbacks + Q_PROPERTY(QScriptValue ontimeout WRITE setOnTimeout) + Q_PROPERTY(QScriptValue onreadystatechange WRITE setOnReadyStateChange) +public: + XMLHttpRequestClass(QScriptEngine* engine); + ~XMLHttpRequestClass(); + + static const int MAXIMUM_REDIRECTS = 5; + enum ReadyState { + UNSENT = 0, + OPENED, + HEADERS_RECEIVED, + LOADING, + DONE + }; + + int getUnsent() const { return UNSENT; }; + int getOpened() const { return OPENED; }; + int getHeadersReceived() const { return HEADERS_RECEIVED; }; + int getLoading() const { return LOADING; }; + int getDone() const { return DONE; }; + + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static QString escapeJavascriptString(const QString& jsString); + + int getTimeout() const { return _timeout; } + void setTimeout(int timeout) { _timeout = timeout; } + QScriptValue getResponse() const { return _responseData; } + QScriptValue getResponseText() const { return QScriptValue(QString(_rawResponseData.data())); } + QString getResponseType() const { return _responseType; } + void setResponseType(const QString& responseType) { _responseType = responseType; } + QScriptValue getReadyState() const { return QScriptValue(_readyState); } + QScriptValue getError() const { return QScriptValue(_errorCode); } + QScriptValue getStatus() const; + QString getStatusText() const; + + void setOnTimeout(QScriptValue function) { _onTimeout = function; } + void setOnReadyStateChange(QScriptValue function) { _onReadyStateChange = function; } + +public slots: + void abort(); + void setRequestHeader(const QString& name, const QString& value); + void open(const QString& method, const QString& url, bool async = true, const QString& username = "", + const QString& password = ""); + void send(); + void send(const QString& data); + QScriptValue getAllResponseHeaders() const; + QScriptValue getResponseHeader(const QString& name) const; + +signals: + void requestComplete(); + +private: + void _setReadyState(ReadyState readyState); + void _doSend(); + void _connectToReply(QNetworkReply* reply); + void _disconnectFromReply(QNetworkReply* reply); + void _abortRequest(); + + QScriptEngine* _engine; + bool _async; + QUrl _url; + QString _method; + QString _responseType; + QNetworkAccessManager _manager; + QNetworkRequest _request; + QNetworkReply* _reply; + QBuffer* _sendData; + QByteArray _rawResponseData; + QScriptValue _responseData; + QScriptValue _onTimeout; + QScriptValue _onReadyStateChange; + ReadyState _readyState; + QNetworkReply::NetworkError _errorCode; + int _timeout; + QTimer _timer; + int _numRedirects; + +private slots: + void requestFinished(); + void requestError(QNetworkReply::NetworkError code); + void requestMetaDataChanged(); + void requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void requestTimeout(); +}; + +#endif // hifi_XMLHttpRequestClass_h From 2dda87fe7f4c5d8d8ed78e0ea73c2794d6b26c97 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:28:35 -0700 Subject: [PATCH 02/28] Add XMLHttpRequest constructor to ScriptEngine --- libraries/script-engine/src/ScriptEngine.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 402f1a2885..41ed07cc23 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -34,6 +34,7 @@ #include "MenuItemProperties.h" #include "LocalVoxels.h" #include "ScriptEngine.h" +#include "XMLHttpRequestClass.h" VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface; ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface; @@ -214,6 +215,9 @@ void ScriptEngine::init() { qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); + QScriptValue xmlHttpRequestConstructorValue = _engine.newFunction(XMLHttpRequestClass::constructor); + _engine.globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); + QScriptValue printConstructorValue = _engine.newFunction(debugPrint); _engine.globalObject().setProperty("print", printConstructorValue); From 39e6e61a6d2026c3c57da3a425566551a6fca982 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:31:33 -0700 Subject: [PATCH 03/28] Add XMLHttpRequest tests and examples --- examples/Test.js | 68 +++++++++++++++ examples/streetAreaExample.js | 51 ++++++++++++ examples/testXMLHttpRequest.js | 147 +++++++++++++++++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 examples/Test.js create mode 100644 examples/streetAreaExample.js create mode 100644 examples/testXMLHttpRequest.js diff --git a/examples/Test.js b/examples/Test.js new file mode 100644 index 0000000000..c56c7ebdc1 --- /dev/null +++ b/examples/Test.js @@ -0,0 +1,68 @@ +// +// Test.js +// examples +// +// Created by Ryan Huffman on 5/4//14 +// Copyright 2014 High Fidelity, Inc. +// +// This provides very basic unit testing functionality. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +test = function(name, func) { + print("Running test: " + name); + + var unitTest = new UnitTest(name, func); + + try { + unitTest.run(); + print(" Success: " + unitTest.numAssertions + " assertions passed"); + } catch (error) { + print(" Failure: " + error.message); + } +}; + +AssertionException = function(expected, actual, message) { + print("Creating exception"); + this.message = message + "\n: " + actual + " != " + expected; + this.name = 'AssertionException'; +}; + +UnitTest = function(name, func) { + this.numAssertions = 0; + this.func = func; +}; + +UnitTest.prototype.run = function() { + this.func(); +}; + +UnitTest.prototype.assertNotEquals = function(expected, actual, message) { + this.numAssertions++; + if (expected == actual) { + throw new AssertionException(expected, actual, message); + } +}; + +UnitTest.prototype.assertEquals = function(expected, actual, message) { + this.numAssertions++; + if (expected != actual) { + throw new AssertionException(expected, actual, message); + } +}; + +UnitTest.prototype.assertHasProperty = function(property, actual, message) { + this.numAssertions++; + if (actual[property] === undefined) { + throw new AssertionException(property, actual, message); + } +}; + +UnitTest.prototype.assertNull = function(value, message) { + this.numAssertions++; + if (value !== null) { + throw new AssertionException(value, null, message); + } +}; diff --git a/examples/streetAreaExample.js b/examples/streetAreaExample.js new file mode 100644 index 0000000000..5e92753689 --- /dev/null +++ b/examples/streetAreaExample.js @@ -0,0 +1,51 @@ +// +// streetAreaExample.js +// examples +// +// Created by Ryan Huffman on 5/4//14 +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script showing how to load JSON data using XMLHttpRequest. +// +// URL Macro created by Thijs Wenker. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var url = "https://script.google.com/macros/s/AKfycbwIo4lmF-qUwX1Z-9eA_P-g2gse9oFhNcjVyyksGukyDDEFXgU/exec?action=listOwners&domain=alpha.highfidelity.io"; +print("Loading street data from " + url); + +var req = new XMLHttpRequest(); + +// Set response type to "json". This will tell XMLHttpRequest to parse the response data as json, so req.response can be used +// as a regular javascript object +req.responseType = 'json'; + +req.open("GET", url, false); +req.send(); + +if (req.status == 200) { + for (var domain in req.response) { + print("DOMAIN: " + domain); + var locations = req.response[domain]; + var userAreas = []; + for (var i = 0; i < locations.length; i++) { + var loc = locations[i]; + var x1 = loc[1], + y1 = loc[2], + x2 = loc[3], + y2 = loc[4]; + userAreas.push({ + username: loc[0], + area: Math.abs(x2 - x1) * Math.abs(y2 - y1), + }); + } + userAreas.sort(function(a, b) { return a.area > b.area ? -1 : (a.area < b.area ? 1 : 0) }); + for (var i = 0; i < userAreas.length; i++) { + print(userAreas[i].username + ": " + userAreas[i].area + " sq units"); + } + } +} else { + print("Error loading data: " + req.status + " " + req.statusText + ", " + req.errorCode); +} diff --git a/examples/testXMLHttpRequest.js b/examples/testXMLHttpRequest.js new file mode 100644 index 0000000000..6471b2fdeb --- /dev/null +++ b/examples/testXMLHttpRequest.js @@ -0,0 +1,147 @@ +// +// testXMLHttpRequest.js +// examples +// +// Created by Ryan Huffman on 5/4//14 +// Copyright 2014 High Fidelity, Inc. +// +// XMLHttpRequest Tests +// +// 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("Test.js"); + +test("Test default request values", function(finished) { + var req = new XMLHttpRequest(); + + this.assertEquals(req.UNSENT, req.readyState, "readyState should be UNSENT"); + this.assertEquals(0, req.status, "status should be `0` by default"); + this.assertEquals("", req.statusText, "statusText should be empty string by default"); + this.assertEquals("", req.getAllResponseHeaders(), "getAllResponseHeaders() should return empty string by default"); + this.assertEquals("", req.response, "response should be empty string by default"); + this.assertEquals("", req.responseText, "responseText should be empty string by default"); + this.assertEquals("", req.responseType, "responseType should be empty string by default"); + this.assertEquals(0, req.timeout, "timeout should be `0` by default"); + this.assertEquals(0, req.errorCode, "there should be no error by default"); +}); + + +test("Test readyStates", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + var statesVisited = [true, false, false, false, false] + + req.onreadystatechange = function() { + statesVisited[req.readyState] = true; + }; + + req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false); + req.send(); + for (var i = 0; i <= req.DONE; i++) { + this.assertEquals(true, statesVisited[i], i + " should be set"); + } + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); +}); + +test("Test TEXT request", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(200, req.status, "status should be `200`"); + this.assertEquals(0, req.errorCode); + this.assertEquals("OK", req.statusText, "statusText should be `OK`"); + this.assertNotEquals("", req.getAllResponseHeaders(), "headers should no longer be empty string"); + this.assertNull(req.getResponseHeader('invalidheader'), "invalid header should return `null`"); + this.assertEquals("GitHub.com", req.getResponseHeader('Server'), "Server header should be GitHub.com"); + this.assertEquals('{"id": 1}', req.response); + this.assertEquals('{"id": 1}', req.responseText); +}); + +test("Test JSON request", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.responseType = "json"; + req.open("GET", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(200, req.status, "status should be `200`"); + this.assertEquals(0, req.errorCode); + this.assertEquals("OK", req.statusText, "statusText should be `OK`"); + this.assertNotEquals("", req.getAllResponseHeaders(), "headers should no longer be empty string"); + this.assertNull(req.getResponseHeader('invalidheader'), "invalid header should return `null`"); + this.assertEquals("GitHub.com", req.getResponseHeader('Server'), "Server header should be GitHub.com"); + this.assertHasProperty('id', req.response); + this.assertEquals(1, req.response.id); + this.assertEquals('{"id": 1}', req.responseText); +}); + +test("Test Bad URL", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("POST", "hifi://domain/path", false); + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertNotEquals(0, req.errorCode); +}); + +test("Test Bad Method Error", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("POST", "https://www.google.com", false); + + req.send("randomdata"); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(405, req.status); + this.assertEquals(202, req.errorCode); + + req.open("POST", "https://www.google.com", false) + req.send(); + + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(405, req.status); + this.assertEquals(202, req.errorCode); +}); + +test("Test abort", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + + req.open("POST", "https://www.google.com", true) + req.send(); + req.abort(); + + this.assertEquals(0, req.status); + this.assertEquals(0, req.errorCode); +}); + +test("Test timeout", function() { + var req = new XMLHttpRequest(); + var state = req.readyState; + var timedOut = false; + + req.ontimeout = function() { + timedOut = true; + }; + + req.open("POST", "https://gist.githubusercontent.com/huffman/33cc618fec183d1bccd0/raw/test.json", false) + req.timeout = 1; + req.send(); + + this.assertEquals(true, timedOut, "request should have timed out"); + this.assertEquals(req.DONE, req.readyState, "readyState should be DONE"); + this.assertEquals(0, req.status, "status should be `0`"); + this.assertEquals(4, req.errorCode, "4 is the timeout error code for QNetworkReply::NetworkError"); +}); From 5a7b133d2556bae5b9620e67b219c3e692a707a8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 11:38:12 -0700 Subject: [PATCH 04/28] Fix dates in XMLHttpRequest js files --- examples/Test.js | 2 +- examples/streetAreaExample.js | 2 +- examples/testXMLHttpRequest.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/Test.js b/examples/Test.js index c56c7ebdc1..056ec3cbbf 100644 --- a/examples/Test.js +++ b/examples/Test.js @@ -2,7 +2,7 @@ // Test.js // examples // -// Created by Ryan Huffman on 5/4//14 +// Created by Ryan Huffman on 5/4/14 // Copyright 2014 High Fidelity, Inc. // // This provides very basic unit testing functionality. diff --git a/examples/streetAreaExample.js b/examples/streetAreaExample.js index 5e92753689..deb10dd65a 100644 --- a/examples/streetAreaExample.js +++ b/examples/streetAreaExample.js @@ -2,7 +2,7 @@ // streetAreaExample.js // examples // -// Created by Ryan Huffman on 5/4//14 +// Created by Ryan Huffman on 5/4/14 // Copyright 2014 High Fidelity, Inc. // // This is an example script showing how to load JSON data using XMLHttpRequest. diff --git a/examples/testXMLHttpRequest.js b/examples/testXMLHttpRequest.js index 6471b2fdeb..421eb458e4 100644 --- a/examples/testXMLHttpRequest.js +++ b/examples/testXMLHttpRequest.js @@ -2,7 +2,7 @@ // testXMLHttpRequest.js // examples // -// Created by Ryan Huffman on 5/4//14 +// Created by Ryan Huffman on 5/4/14 // Copyright 2014 High Fidelity, Inc. // // XMLHttpRequest Tests From f3305a51e28047feaad47b43bac1ea9735af6200 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 6 May 2014 13:09:49 -0700 Subject: [PATCH 05/28] Remove XMLHttpRequestClass::escapeJavascriptString --- libraries/script-engine/src/XMLHttpRequestClass.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index b1b33e4cc6..bff88d91a9 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -62,7 +62,6 @@ public: int getDone() const { return DONE; }; static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - static QString escapeJavascriptString(const QString& jsString); int getTimeout() const { return _timeout; } void setTimeout(int timeout) { _timeout = timeout; } From cd78be7d2500a2a4a00bd19f1c147302969aab50 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 8 May 2014 16:47:18 -0700 Subject: [PATCH 06/28] Some work on mouse tools for editModels.js --- examples/editModels.js | 34 +++++++++++++++++++++++++++++++++- examples/testScript.js | 0 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 examples/testScript.js diff --git a/examples/editModels.js b/examples/editModels.js index ecf398edfa..737b6a85a5 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -33,6 +33,7 @@ var modelURLs = [ "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx", ]; +var tools = []; var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; var numberOfTools = 1; var toolHeight = 50; @@ -44,13 +45,44 @@ var toolsY = (windowDimensions.y - toolsHeight) / 2; var firstModel = Overlays.addOverlay("image", { - x: 0, y: 0, width: toolWidth, height: toolHeight, subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, imageURL: toolIconUrl + "voxel-tool.svg", x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, visible: true, alpha: 0.9 }); +function Tool(iconURL) { + this.overlay = Overlays.addOverlay("image", { + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: iconURL, + x: toolsX, + y: toolsY + ((toolHeight + toolVerticalSpacing) * tools.length), + width: toolWidth, + height: toolHeight, + visible: true, + alpha: 0.9 + }); + + + this.cleanup = function() { + Ovelays.deleteOverlay(this.overlay); + } + tools[tools.length] = this; + return tools.length - 1; +} +Tool.ICON_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; +Tool.HEIGHT = 50; +Tool.WIDTH = 50; + +function ToolBar(direction, x, y) { + this.tools = []; + + this.numberOfTools = function() { + return this.tools.length; + } +} +ToolBar.SPACING = 4; + function controller(wichSide) { this.side = wichSide; diff --git a/examples/testScript.js b/examples/testScript.js new file mode 100644 index 0000000000..e69de29bb2 From 37700c6ad3a331aac0bd3b8b4c2896512ba0b3e7 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 12 May 2014 17:10:19 -0700 Subject: [PATCH 07/28] Fixed prompt size --- examples/editModels.js | 167 ++++++++------- examples/testScript.js | 0 examples/toolBars.js | 200 ++++++++++++++++++ .../scripting/WindowScriptingInterface.cpp | 1 + 4 files changed, 291 insertions(+), 77 deletions(-) delete mode 100644 examples/testScript.js create mode 100644 examples/toolBars.js diff --git a/examples/editModels.js b/examples/editModels.js index 9acbff4aa6..90ce87d259 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -9,7 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("toolBars.js"); + var windowDimensions = Controller.getViewportDimensions(); +var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; +var toolHeight = 50; +var toolWidth = 50; var LASER_WIDTH = 4; var LASER_COLOR = { red: 255, green: 0, blue: 0 }; @@ -33,56 +38,7 @@ var modelURLs = [ "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx", ]; -var tools = []; -var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; -var numberOfTools = 1; -var toolHeight = 50; -var toolWidth = 50; -var toolVerticalSpacing = 4; -var toolsHeight = toolHeight * numberOfTools + toolVerticalSpacing * (numberOfTools - 1); -var toolsX = windowDimensions.x - 8 - toolWidth; -var toolsY = (windowDimensions.y - toolsHeight) / 2; - - -var firstModel = Overlays.addOverlay("image", { - subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, - imageURL: toolIconUrl + "voxel-tool.svg", - x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }); -function Tool(iconURL) { - this.overlay = Overlays.addOverlay("image", { - subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, - imageURL: iconURL, - x: toolsX, - y: toolsY + ((toolHeight + toolVerticalSpacing) * tools.length), - width: toolWidth, - height: toolHeight, - visible: true, - alpha: 0.9 - }); - - - this.cleanup = function() { - Ovelays.deleteOverlay(this.overlay); - } - tools[tools.length] = this; - return tools.length - 1; -} -Tool.ICON_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; -Tool.HEIGHT = 50; -Tool.WIDTH = 50; - -function ToolBar(direction, x, y) { - this.tools = []; - - this.numberOfTools = function() { - return this.tools.length; - } -} -ToolBar.SPACING = 4; - +var toolBar; function controller(wichSide) { this.side = wichSide; @@ -238,6 +194,13 @@ function controller(wichSide) { }); } + this.hideLaser = function() { + Overlays.editOverlay(this.laser, { visible: false }); + Overlays.editOverlay(this.ball, { visible: false }); + Overlays.editOverlay(this.leftRight, { visible: false }); + Overlays.editOverlay(this.topDown, { visible: false }); + } + this.moveModel = function () { if (this.grabbing) { var newPosition = Vec3.sum(this.palmPosition, @@ -377,67 +340,117 @@ function moveModels() { rightController.moveModel(); } +var hydraConnected = false; function checkController(deltaTime) { var numberOfButtons = Controller.getNumberOfButtons(); var numberOfTriggers = Controller.getNumberOfTriggers(); var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - - moveOverlays(); // this is expected for hydras - if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) { - //print("no hydra connected?"); - return; // bail if no hydra + if (numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2) { + if (!hydraConnected) { + hydraConnected = true; + } + + leftController.update(); + rightController.update(); + moveModels(); + } else { + if (hydraConnected) { + hydraConnected = false; + + leftController.hideLaser(); + rightController.hideLaser(); + } } - leftController.update(); - rightController.update(); - moveModels(); + moveOverlays(); } function moveOverlays() { - windowDimensions = Controller.getViewportDimensions(); - - toolsX = windowDimensions.x - 8 - toolWidth; - toolsY = (windowDimensions.y - toolsHeight) / 2; - - Overlays.editOverlay(firstModel, { - x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * 0), width: toolWidth, height: toolHeight, + if (typeof(toolBar) === 'undefined') { + toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); + // New Model + toolBar.addTool({ + imageURL: toolIconUrl + "voxel-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 }); + // Move YZ + toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + // Move XZ + toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + // Move XY + toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + } else if (windowDimensions.x == Controller.getViewportDimensions().x && + windowDimensions.y == Controller.getViewportDimensions().y) { + return; + } + + + windowDimensions = Controller.getViewportDimensions(); + var toolsX = windowDimensions.x - 8 - toolBar.width; + var toolsY = (windowDimensions.y - toolBar.height) / 2; + + toolBar.move(toolsX, toolsY); } function mousePressEvent(event) { var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); var url; - - if (clickedOverlay == firstModel) { + var index = toolBar.clicked(clickedOverlay); + if (index == 0) { url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); if (url == null) { - return; } - } else { + return; + } + + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + Models.addModel({ position: position, + radius: radiusDefault, + modelURL: url + }); + } else if (index == -1) { print("Didn't click on anything"); - return; } +} + +function mouseMoveEvent(event) { - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - Models.addModel({ position: position, - radius: radiusDefault, - modelURL: url - }); } function scriptEnding() { leftController.cleanup(); rightController.cleanup(); - - Overlays.deleteOverlay(firstModel); + toolBar.cleanup(); } Script.scriptEnding.connect(scriptEnding); // register the call back so it fires before each data send Script.update.connect(checkController); Controller.mousePressEvent.connect(mousePressEvent); +Controller.mousePressEvent.connect(mouseMoveEvent); diff --git a/examples/testScript.js b/examples/testScript.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/toolBars.js b/examples/toolBars.js new file mode 100644 index 0000000000..62a01a9a15 --- /dev/null +++ b/examples/toolBars.js @@ -0,0 +1,200 @@ +// +// testScript.js +// examples +// +// Created by Clément Brisset on 5/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 +// + +Overlay2D = function(properties, overlay) { // overlay is an optionnal variable + if (!(typeof(properties) === 'undefined')) { + if(typeof(overlay) === 'undefined') { + overlay = Overlays.addOverlay("image", properties); + print("New overlay: " + overlay); + } else { + Overlays.editOverlay(overlay, properties); + } + } + + this.overlay = function() { + return overlay; + } + this.x = function() { + return properties.x; + } + this.y = function() { + return properties.y; + } + this.width = function() { + return properties.width; + } + this.height = function() { + return properties.height; + } + this.alpha = function() { + return properties.alpha; + } + this.visible = function() { + return properties.visible; + } + + + this.move = function(x, y) { + properties.x = x; + properties.y = y; + Overlays.editOverlay(overlay, { x: x, y: y }); + } + this.resize = function(width, height) { + properties.width = width; + properties.height = height; + Overlays.editOverlay(overlay, { width: width, height: height }); + } + this.setAlpha = function(alpha) { + properties.alpha = alpha; + Overlays.editOverlay(overlay, { alpha: alpha }); + } + this.show = function(doShow) { + properties.visible = doShow; + Overlays.editOverlay(overlay, { visible: doShow }); + } + + this.clicked = function(clickedOverlay) { + return (overlay == clickedOverlay ? true : false); + } + + this.cleanup = function() { + print("Cleanup"); + Overlays.deleteOverlay(overlay); + } +} + + +Tool = function(properties, selectable, selected) { // selectable and selected are optional variables. + Overlay2D.call(this, properties); + + if(typeof(selectable)==='undefined') { + selectable = false; + if(typeof(selected)==='undefined') { + selected = false; + + } + } + + this.selectable = function() { + return selectable; + } + + if (this.selectable()) { + this.selected = function() { + return selected; + } + this.select = function(doSelect) { + selected = doSelect; + properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; + Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); + } + this.toggle = function() { + selected = !selected; + properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; + Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); + + return selected; + } + + this.select(selected); + } + + this.baseClicked = this.clicked; + this.clicked = function(clickedOverlay) { + if (this.baseClicked(clickedOverlay)) { + if (selectable) { + this.toggle(); + } + return true; + } + return false; + } +} +Tool.prototype = new Overlay2D; +Tool.IMAGE_HEIGHT = 50; +Tool.IMAGE_WIDTH = 50; + +ToolBar = function(x, y, direction) { + this.tools = []; + this.x = x; + this.y = y; + this.width = 0; + this.height = 0; + + + 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); + } 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; + } + + this.tools[this.tools.length] = new Tool(properties, selectable, selected); + return this.tools.length - 1; + } + + this.move = function(x, y) { + var dx = x - this.x; + var dy = y - this.y; + this.x = x; + this.y = y; + for(var tool in this.tools) { + this.tools[tool].move(this.tools[tool].x() + dx, this.tools[tool].y() + dy); + } + } + + this.setAlpha = function(alpha) { + for(var tool in this.tools) { + this.tools[tool].setAlpha(alpha); + } + } + + this.show = function(doShow) { + for(var tool in this.tools) { + this.tools[tool].show(doShow); + } + } + + this.clicked = function(clickedOverlay) { + for(var tool in this.tools) { + if (this.tools[tool].visible() && this.tools[tool].clicked(clickedOverlay)) { + return tool; + } + } + return -1; + } + + this.numberOfTools = function() { + return this.tools.length; + } + + this.cleanup = function() { + for(var tool in this.tools) { + this.tools[tool].cleanup(); + delete this.tools[tool]; + } + + this.tools = []; + this.x = x; + this.y = y; + this.width = 0; + this.height = 0; + } +} +ToolBar.SPACING = 4; +ToolBar.VERTICAL = 0; +ToolBar.HORIZONTAL = 1; \ No newline at end of file diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 144415d6ee..e10197d488 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -78,6 +78,7 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const promptDialog.setWindowTitle(""); promptDialog.setLabelText(message); promptDialog.setTextValue(defaultText); + promptDialog.setFixedSize(600, 200); if (promptDialog.exec() == QDialog::Accepted) { return QScriptValue(promptDialog.textValue()); From debd76b2cbb41b1ffef4000d7c13945314baf0d5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 13 May 2014 10:38:43 -0700 Subject: [PATCH 08/28] use sheet window modality so OAuth window doesn't take over full screen --- interface/src/ui/OAuthWebViewHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/OAuthWebViewHandler.cpp b/interface/src/ui/OAuthWebViewHandler.cpp index b3b914bd64..83d900cd5c 100644 --- a/interface/src/ui/OAuthWebViewHandler.cpp +++ b/interface/src/ui/OAuthWebViewHandler.cpp @@ -59,7 +59,7 @@ void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authoriz _activeWebView = new QWebView; // keep the window on top and delete it when it closes - _activeWebView->setWindowFlags(Qt::WindowStaysOnTopHint); + _activeWebView->setWindowFlags(Qt::Sheet | Qt::WindowStaysOnTopHint); _activeWebView->setAttribute(Qt::WA_DeleteOnClose); qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString(); From 607094075ce050d3f504fcdfa296254e9e4fea3c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 13 May 2014 10:38:56 -0700 Subject: [PATCH 09/28] Revert "Merge pull request #2834 from andrew/thermonuclear" This reverts commit e088c3d1150aaee0401adc5e1fbe0bb4d9c9d6cc, reversing changes made to d124c0319d9aaefd9eb223db8db41f492a60ad0b. --- interface/src/BuckyBalls.cpp | 8 +- interface/src/avatar/Avatar.cpp | 16 ++- interface/src/avatar/Hand.cpp | 64 ++++++++-- interface/src/avatar/SkeletonModel.cpp | 30 +++-- interface/src/devices/SixenseManager.cpp | 45 ++++--- .../ControllerScriptingInterface.cpp | 4 +- libraries/avatars/src/HandData.cpp | 110 +++++++++++++++--- libraries/avatars/src/HandData.h | 87 +++++++++++--- libraries/shared/src/SharedUtil.h | 1 - 9 files changed, 288 insertions(+), 77 deletions(-) diff --git a/interface/src/BuckyBalls.cpp b/interface/src/BuckyBalls.cpp index 68d1167071..e1ec41dca1 100644 --- a/interface/src/BuckyBalls.cpp +++ b/interface/src/BuckyBalls.cpp @@ -60,13 +60,15 @@ BuckyBalls::BuckyBalls() { void BuckyBalls::grab(PalmData& palm, float deltaTime) { float penetration; - glm::vec3 fingerTipPosition = palm.getFingerTipPosition(); + glm::vec3 diff; + FingerData& finger = palm.getFingers()[0]; // Sixense has only one finger + glm::vec3 fingerTipPosition = finger.getTipPosition(); if (palm.getControllerButtons() & BUTTON_FWD) { if (!_bballIsGrabbed[palm.getSixenseID()]) { // Look for a ball to grab for (int i = 0; i < NUM_BBALLS; i++) { - glm::vec3 diff = _bballPosition[i] - fingerTipPosition; + diff = _bballPosition[i] - fingerTipPosition; penetration = glm::length(diff) - (_bballRadius[i] + COLLISION_RADIUS); if (penetration < 0.f) { _bballIsGrabbed[palm.getSixenseID()] = i; @@ -75,7 +77,7 @@ void BuckyBalls::grab(PalmData& palm, float deltaTime) { } if (_bballIsGrabbed[palm.getSixenseID()]) { // If ball being grabbed, move with finger - glm::vec3 diff = _bballPosition[_bballIsGrabbed[palm.getSixenseID()]] - fingerTipPosition; + diff = _bballPosition[_bballIsGrabbed[palm.getSixenseID()]] - fingerTipPosition; penetration = glm::length(diff) - (_bballRadius[_bballIsGrabbed[palm.getSixenseID()]] + COLLISION_RADIUS); _bballPosition[_bballIsGrabbed[palm.getSixenseID()]] -= glm::normalize(diff) * penetration; glm::vec3 fingerTipVelocity = palm.getTipVelocity(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 2bd0bbbc6d..41dc50b1fa 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -609,6 +609,18 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti const PalmData* palm = handData->getPalm(i); if (palm && palm->hasPaddle()) { // create a disk collision proxy where the hand is + glm::vec3 fingerAxis(0.0f); + for (size_t f = 0; f < palm->getNumFingers(); ++f) { + const FingerData& finger = (palm->getFingers())[f]; + if (finger.isActive()) { + // compute finger axis + glm::vec3 fingerTip = finger.getTipPosition(); + glm::vec3 fingerRoot = finger.getRootPosition(); + fingerAxis = glm::normalize(fingerTip - fingerRoot); + break; + } + } + int jointIndex = -1; glm::vec3 handPosition; if (i == 0) { @@ -619,10 +631,8 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti _skeletonModel.getRightHandPosition(handPosition); jointIndex = _skeletonModel.getRightHandJointIndex(); } - - glm::vec3 fingerAxis = palm->getFingerDirection(); glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis; - glm::vec3 diskNormal = palm->getPalmDirection(); + glm::vec3 diskNormal = palm->getNormal(); const float DISK_THICKNESS = 0.08f; // collide against the disk diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 35132af929..4774e23cb2 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -176,7 +176,8 @@ void Hand::renderHandTargets(bool isMine) { if (!palm.isActive()) { continue; } - glm::vec3 targetPosition = palm.getFingerTipPosition(); + glm::vec3 targetPosition; + palm.getBallHoldPosition(targetPosition); glPushMatrix(); glTranslatef(targetPosition.x, targetPosition.y, targetPosition.z); @@ -196,20 +197,59 @@ void Hand::renderHandTargets(bool isMine) { for (size_t i = 0; i < getNumPalms(); ++i) { PalmData& palm = getPalms()[i]; if (palm.isActive()) { - glColor4f(handColor.r, handColor.g, handColor.b, alpha); - glm::vec3 tip = palm.getFingerTipPosition(); - glm::vec3 root = palm.getPosition(); - Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); - // Render sphere at palm/finger root - glm::vec3 offsetFromPalm = root + palm.getPalmDirection() * PALM_DISK_THICKNESS; - Avatar::renderJointConnectingCone(root, offsetFromPalm, PALM_DISK_RADIUS, 0.0f); - glPushMatrix(); - glTranslatef(root.x, root.y, root.z); - glutSolidSphere(PALM_BALL_RADIUS, 20.0f, 20.0f); - glPopMatrix(); + for (size_t f = 0; f < palm.getNumFingers(); ++f) { + FingerData& finger = palm.getFingers()[f]; + if (finger.isActive()) { + glColor4f(handColor.r, handColor.g, handColor.b, alpha); + glm::vec3 tip = finger.getTipPosition(); + glm::vec3 root = finger.getRootPosition(); + Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); + // Render sphere at palm/finger root + glm::vec3 palmNormal = root + palm.getNormal() * PALM_DISK_THICKNESS; + Avatar::renderJointConnectingCone(root, palmNormal, PALM_DISK_RADIUS, 0.0f); + glPushMatrix(); + glTranslatef(root.x, root.y, root.z); + glutSolidSphere(PALM_BALL_RADIUS, 20.0f, 20.0f); + glPopMatrix(); + + } + } } } + /* + // Draw the hand paddles + int MAX_NUM_PADDLES = 2; // one for left and one for right + glColor4f(handColor.r, handColor.g, handColor.b, 0.3f); + for (int i = 0; i < MAX_NUM_PADDLES; i++) { + const PalmData* palm = getPalm(i); + if (palm) { + // compute finger axis + glm::vec3 fingerAxis(0.f); + for (size_t f = 0; f < palm->getNumFingers(); ++f) { + const FingerData& finger = (palm->getFingers())[f]; + if (finger.isActive()) { + glm::vec3 fingerTip = finger.getTipPosition(); + glm::vec3 fingerRoot = finger.getRootPosition(); + fingerAxis = glm::normalize(fingerTip - fingerRoot); + break; + } + } + // compute paddle position + glm::vec3 handPosition; + if (i == SIXENSE_CONTROLLER_ID_LEFT_HAND) { + _owningAvatar->getSkeletonModel().getLeftHandPosition(handPosition); + } else if (i == SIXENSE_CONTROLLER_ID_RIGHT_HAND) { + _owningAvatar->getSkeletonModel().getRightHandPosition(handPosition); + } + glm::vec3 tip = handPosition + HAND_PADDLE_OFFSET * fingerAxis; + glm::vec3 root = tip + palm->getNormal() * HAND_PADDLE_THICKNESS; + // render a very shallow cone as the paddle + Avatar::renderJointConnectingCone(root, tip, HAND_PADDLE_RADIUS, 0.f); + } + } + */ + glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index a2e637f4e7..8c21a3240f 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -33,7 +33,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { return; // only simulate for own avatar } - // find the left and rightmost active palms + // find the left and rightmost active Leap palms int leftPalmIndex, rightPalmIndex; Hand* hand = _owningAvatar->getHand(); hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); @@ -42,7 +42,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (leftPalmIndex == -1) { - // palms are not yet set, use mouse + // no Leap data; set hands from mouse if (_owningAvatar->getHandState() == HAND_STATE_NULL) { restoreRightHandPosition(HAND_RESTORATION_RATE); } else { @@ -159,13 +159,29 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJoin } else { getJointRotation(jointIndex, palmRotation, true); } - palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getPalmDirection()) * palmRotation; + palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()) * palmRotation; + // sort the finger indices by raw x, get the average direction + QVector fingerIndices; + glm::vec3 direction; + for (size_t i = 0; i < palm.getNumFingers(); i++) { + glm::vec3 fingerVector = palm.getFingers()[i].getTipPosition() - palm.getPosition(); + float length = glm::length(fingerVector); + if (length > EPSILON) { + direction += fingerVector / length; + } + fingerVector = glm::inverse(palmRotation) * fingerVector * -sign; + IndexValue indexValue = { (int)i, atan2f(fingerVector.z, fingerVector.x) }; + fingerIndices.append(indexValue); + } + qSort(fingerIndices.begin(), fingerIndices.end()); + // rotate forearm according to average finger direction - // NOTE: we're doing this in the avatar local frame, so we DON'T want to use Palm::getHandDirection() - // which returns the world-frame. - glm::vec3 direction = palm.getRawRotation() * glm::vec3(0.0f, 0.0f, 1.0f); - palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation; + float directionLength = glm::length(direction); + const unsigned int MIN_ROTATION_FINGERS = 3; + if (directionLength > EPSILON && palm.getNumFingers() >= MIN_ROTATION_FINGERS) { + palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation; + } // set hand position, rotation if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 1698cebcf9..0435519124 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -22,9 +22,9 @@ const int CALIBRATION_STATE_Z = 3; const int CALIBRATION_STATE_COMPLETE = 4; // default (expected) location of neck in sixense space -const float NECK_X = 0.25f; // meters -const float NECK_Y = 0.3f; // meters -const float NECK_Z = 0.3f; // meters +const float NECK_X = 250.f; // millimeters +const float NECK_Y = 300.f; // millimeters +const float NECK_Z = 300.f; // millimeters #endif SixenseManager::SixenseManager() { @@ -106,11 +106,8 @@ void SixenseManager::update(float deltaTime) { palm->setControllerButtons(data->buttons); palm->setTrigger(data->trigger); palm->setJoystick(data->joystick_x, data->joystick_y); - - // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. - glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]); - position *= METERS_PER_MILLIMETER; + glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]); // Transform the measured position into body frame. glm::vec3 neck = _neckBase; // Zeroing y component of the "neck" effectively raises the measured position a little bit. @@ -120,12 +117,15 @@ void SixenseManager::update(float deltaTime) { // Rotation of Palm glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]); rotation = glm::angleAxis(PI, glm::vec3(0.f, 1.f, 0.f)) * _orbRotation * rotation; + const glm::vec3 PALM_VECTOR(0.0f, -1.0f, 0.0f); + glm::vec3 newNormal = rotation * PALM_VECTOR; + palm->setRawNormal(newNormal); palm->setRawRotation(rotation); // Compute current velocity from position change glm::vec3 rawVelocity; if (deltaTime > 0.f) { - rawVelocity = (position - palm->getRawPosition()) / deltaTime; + rawVelocity = (position - palm->getRawPosition()) / deltaTime / 1000.f; } else { rawVelocity = glm::vec3(0.0f); } @@ -140,17 +140,29 @@ void SixenseManager::update(float deltaTime) { _amountMoved = glm::vec3(0.0f); } - // Store the one fingertip in the palm structure so we can track velocity - const float FINGER_LENGTH = 0.3f; // meters + // initialize the "finger" based on the direction + FingerData finger(palm, hand); + finger.setActive(true); + finger.setRawRootPosition(position); + const float FINGER_LENGTH = 300.0f; // Millimeters const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH); const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR; + finger.setRawTipPosition(position + rotation * FINGER_VECTOR); + + // Store the one fingertip in the palm structure so we can track velocity glm::vec3 oldTipPosition = palm->getTipRawPosition(); if (deltaTime > 0.f) { - palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime); + palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime / 1000.f); } else { palm->setTipVelocity(glm::vec3(0.f)); } palm->setTipPosition(newTipPosition); + + // three fingers indicates to the skeleton that we have enough data to determine direction + palm->getFingers().clear(); + palm->getFingers().push_back(finger); + palm->getFingers().push_back(finger); + palm->getFingers().push_back(finger); } if (numActiveControllers == 2) { @@ -159,7 +171,7 @@ void SixenseManager::update(float deltaTime) { // if the controllers haven't been moved in a while, disable const unsigned int MOVEMENT_DISABLE_SECONDS = 3; - if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * USECS_PER_SECOND)) { + if (usecTimestampNow() - _lastMovement > (MOVEMENT_DISABLE_SECONDS * 1000 * 1000)) { for (std::vector::iterator it = hand->getPalms().begin(); it != hand->getPalms().end(); it++) { it->setActive(false); } @@ -176,8 +188,8 @@ void SixenseManager::update(float deltaTime) { // (4) move arms a bit forward (Z) // (5) release BUTTON_FWD on both hands -const float MINIMUM_ARM_REACH = 0.3f; // meters -const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters +const float MINIMUM_ARM_REACH = 300.f; // millimeters +const float MAXIMUM_NOISE_LEVEL = 50.f; // millimeters const quint64 LOCK_DURATION = USECS_PER_SECOND / 4; // time for lock to be acquired void SixenseManager::updateCalibration(const sixenseControllerData* controllers) { @@ -217,17 +229,14 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers) return; } - // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. const float* pos = dataLeft->pos; glm::vec3 positionLeft(pos[0], pos[1], pos[2]); - positionLeft *= METERS_PER_MILLIMETER; pos = dataRight->pos; glm::vec3 positionRight(pos[0], pos[1], pos[2]); - positionRight *= METERS_PER_MILLIMETER; if (_calibrationState == CALIBRATION_STATE_IDLE) { float reach = glm::distance(positionLeft, positionRight); - if (reach > 2.0f * MINIMUM_ARM_REACH) { + if (reach > 2.f * MINIMUM_ARM_REACH) { qDebug("started: sixense calibration"); _averageLeft = positionLeft; _averageRight = positionRight; diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 5e58ac66ea..aa14f769de 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -198,9 +198,9 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex if (palmData) { switch (controlOfPalm) { case PALM_SPATIALCONTROL: - return palmData->getPalmDirection(); + return palmData->getNormal(); case TIP_SPATIALCONTROL: - return palmData->getFingerDirection(); + return palmData->getNormal(); // currently the tip doesn't have a unique normal, use the palm normal } } return glm::vec3(0); // bad index diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index bd366f020a..c2e3b51cb3 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -26,8 +26,8 @@ HandData::HandData(AvatarData* owningAvatar) : addNewPalm(); } -glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const { - return glm::inverse(getBaseOrientation()) * worldVector; +glm::vec3 HandData::worldVectorToLeapVector(const glm::vec3& worldVector) const { + return glm::inverse(getBaseOrientation()) * worldVector / LEAP_UNIT_SCALE; } PalmData& HandData::addNewPalm() { @@ -66,21 +66,69 @@ void HandData::getLeftRightPalmIndices(int& leftPalmIndex, int& rightPalmIndex) PalmData::PalmData(HandData* owningHandData) : _rawRotation(0.f, 0.f, 0.f, 1.f), _rawPosition(0.f), +_rawNormal(0.f, 1.f, 0.f), _rawVelocity(0.f), _rotationalVelocity(0.f), _totalPenetration(0.f), _controllerButtons(0), _isActive(false), +_leapID(LEAPID_INVALID), _sixenseID(SIXENSEID_INVALID), _numFramesWithoutData(0), _owningHandData(owningHandData), _isCollidingWithVoxel(false), _isCollidingWithPalm(false), -_collisionlessPaddleExpiry(0) { +_collisionlessPaddleExpiry(0) +{ + for (int i = 0; i < NUM_FINGERS_PER_HAND; ++i) { + _fingers.push_back(FingerData(this, owningHandData)); + } } void PalmData::addToPosition(const glm::vec3& delta) { - _rawPosition += _owningHandData->worldToLocalVector(delta); + // convert to Leap coordinates, then add to palm and finger positions + glm::vec3 leapDelta = _owningHandData->worldVectorToLeapVector(delta); + _rawPosition += leapDelta; + for (size_t i = 0; i < getNumFingers(); i++) { + FingerData& finger = _fingers[i]; + if (finger.isActive()) { + finger.setRawTipPosition(finger.getTipRawPosition() + leapDelta); + finger.setRawRootPosition(finger.getRootRawPosition() + leapDelta); + } + } +} + +FingerData::FingerData(PalmData* owningPalmData, HandData* owningHandData) : +_tipRawPosition(0, 0, 0), +_rootRawPosition(0, 0, 0), +_isActive(false), +_leapID(LEAPID_INVALID), +_numFramesWithoutData(0), +_owningPalmData(owningPalmData), +_owningHandData(owningHandData) +{ + const int standardTrailLength = 10; + setTrailLength(standardTrailLength); +} + +void HandData::setFingerTrailLength(unsigned int length) { + for (size_t i = 0; i < getNumPalms(); ++i) { + PalmData& palm = getPalms()[i]; + for (size_t f = 0; f < palm.getNumFingers(); ++f) { + FingerData& finger = palm.getFingers()[f]; + finger.setTrailLength(length); + } + } +} + +void HandData::updateFingerTrails() { + for (size_t i = 0; i < getNumPalms(); ++i) { + PalmData& palm = getPalms()[i]; + for (size_t f = 0; f < palm.getNumFingers(); ++f) { + FingerData& finger = palm.getFingers()[f]; + finger.updateTrail(); + } + } } bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration, @@ -109,20 +157,54 @@ glm::vec3 HandData::getBasePosition() const { return _owningAvatarData->getPosition(); } -glm::vec3 PalmData::getFingerTipPosition() const { - glm::vec3 fingerOffset(0.0f, 0.0f, 0.3f); - glm::vec3 palmOffset(0.0f, -0.08f, 0.0f); - return getPosition() + _owningHandData->localToWorldDirection(_rawRotation * (fingerOffset + palmOffset)); +void FingerData::setTrailLength(unsigned int length) { + _tipTrailPositions.resize(length); + _tipTrailCurrentStartIndex = 0; + _tipTrailCurrentValidLength = 0; } -glm::vec3 PalmData::getFingerDirection() const { - const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f); - return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION); +void FingerData::updateTrail() { + if (_tipTrailPositions.size() == 0) + return; + + if (_isActive) { + // Add the next point in the trail. + _tipTrailCurrentStartIndex--; + if (_tipTrailCurrentStartIndex < 0) + _tipTrailCurrentStartIndex = _tipTrailPositions.size() - 1; + + _tipTrailPositions[_tipTrailCurrentStartIndex] = getTipPosition(); + + if (_tipTrailCurrentValidLength < (int)_tipTrailPositions.size()) + _tipTrailCurrentValidLength++; + } + else { + // It's not active, so just kill the trail. + _tipTrailCurrentValidLength = 0; + } } -glm::vec3 PalmData::getPalmDirection() const { - const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f); - return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION); +int FingerData::getTrailNumPositions() { + return _tipTrailCurrentValidLength; +} + +const glm::vec3& FingerData::getTrailPosition(int index) { + if (index >= _tipTrailCurrentValidLength) { + static glm::vec3 zero(0,0,0); + return zero; + } + int posIndex = (index + _tipTrailCurrentStartIndex) % _tipTrailCurrentValidLength; + return _tipTrailPositions[posIndex]; +} + +void PalmData::getBallHoldPosition(glm::vec3& position) const { + const float BALL_FORWARD_OFFSET = 0.08f; // put the ball a bit forward of fingers + position = BALL_FORWARD_OFFSET * getNormal(); + if (_fingers.size() > 0) { + position += _fingers[0].getTipPosition(); + } else { + position += getPosition(); + } } diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 1f2d134c43..a37e3a5814 100755 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -21,6 +21,7 @@ #include "SharedUtil.h" class AvatarData; +class FingerData; class PalmData; const int NUM_HANDS = 2; @@ -30,6 +31,8 @@ const int NUM_FINGERS = NUM_HANDS * NUM_FINGERS_PER_HAND; const int LEAPID_INVALID = -1; const int SIXENSEID_INVALID = -1; +const float LEAP_UNIT_SCALE = 0.001f; ///< convert mm to meters + const int SIXENSE_CONTROLLER_ID_LEFT_HAND = 0; const int SIXENSE_CONTROLLER_ID_RIGHT_HAND = 1; @@ -38,16 +41,17 @@ public: HandData(AvatarData* owningAvatar); virtual ~HandData() {} + // These methods return the positions in Leap-relative space. + // To convert to world coordinates, use Hand::leapPositionToWorldPosition. + // position conversion - glm::vec3 localToWorldPosition(const glm::vec3& localPosition) { - return getBasePosition() + getBaseOrientation() * localPosition; + glm::vec3 leapPositionToWorldPosition(const glm::vec3& leapPosition) { + return getBasePosition() + getBaseOrientation() * (leapPosition * LEAP_UNIT_SCALE); } - - glm::vec3 localToWorldDirection(const glm::vec3& localVector) { - return getBaseOrientation() * localVector; - } - - glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const; + glm::vec3 leapDirectionToWorldDirection(const glm::vec3& leapDirection) { + return getBaseOrientation() * leapDirection; + } + glm::vec3 worldVectorToLeapVector(const glm::vec3& worldVector) const; std::vector& getPalms() { return _palms; } const std::vector& getPalms() const { return _palms; } @@ -59,6 +63,9 @@ public: /// both is not found. void getLeftRightPalmIndices(int& leftPalmIndex, int& rightPalmIndex) const; + void setFingerTrailLength(unsigned int length); + void updateFingerTrails(); + /// Checks for penetration between the described sphere and the hand. /// \param penetratorCenter the center of the penetration test sphere /// \param penetratorRadius the radius of the penetration test sphere @@ -82,23 +89,71 @@ private: HandData& operator= (const HandData&); }; +class FingerData { +public: + FingerData(PalmData* owningPalmData, HandData* owningHandData); + + glm::vec3 getTipPosition() const { return _owningHandData->leapPositionToWorldPosition(_tipRawPosition); } + glm::vec3 getRootPosition() const { return _owningHandData->leapPositionToWorldPosition(_rootRawPosition); } + const glm::vec3& getTipRawPosition() const { return _tipRawPosition; } + const glm::vec3& getRootRawPosition() const { return _rootRawPosition; } + bool isActive() const { return _isActive; } + int getLeapID() const { return _leapID; } + + void setActive(bool active) { _isActive = active; } + void setLeapID(int id) { _leapID = id; } + void setRawTipPosition(const glm::vec3& pos) { _tipRawPosition = pos; } + void setRawRootPosition(const glm::vec3& pos) { _rootRawPosition = pos; } + + void setTrailLength(unsigned int length); + void updateTrail(); + + int getTrailNumPositions(); + const glm::vec3& getTrailPosition(int index); + + void incrementFramesWithoutData() { _numFramesWithoutData++; } + void resetFramesWithoutData() { _numFramesWithoutData = 0; } + int getFramesWithoutData() const { return _numFramesWithoutData; } + +private: + glm::vec3 _tipRawPosition; + glm::vec3 _rootRawPosition; + bool _isActive; // This has current valid data + int _leapID; // the Leap's serial id for this tracked object + int _numFramesWithoutData; // after too many frames without data, this tracked object assumed lost. + std::vector _tipTrailPositions; + int _tipTrailCurrentStartIndex; + int _tipTrailCurrentValidLength; + PalmData* _owningPalmData; + HandData* _owningHandData; +}; class PalmData { public: PalmData(HandData* owningHandData); - glm::vec3 getPosition() const { return _owningHandData->localToWorldPosition(_rawPosition); } - glm::vec3 getVelocity() const { return _owningHandData->localToWorldDirection(_rawVelocity); } + glm::vec3 getPosition() const { return _owningHandData->leapPositionToWorldPosition(_rawPosition); } + glm::vec3 getNormal() const { return _owningHandData->leapDirectionToWorldDirection(_rawNormal); } + glm::vec3 getVelocity() const { return _owningHandData->leapDirectionToWorldDirection(_rawVelocity); } const glm::vec3& getRawPosition() const { return _rawPosition; } + const glm::vec3& getRawNormal() const { return _rawNormal; } bool isActive() const { return _isActive; } + int getLeapID() const { return _leapID; } int getSixenseID() const { return _sixenseID; } + + std::vector& getFingers() { return _fingers; } + const std::vector& getFingers() const { return _fingers; } + size_t getNumFingers() const { return _fingers.size(); } + void setActive(bool active) { _isActive = active; } + void setLeapID(int id) { _leapID = id; } void setSixenseID(int id) { _sixenseID = id; } void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; }; glm::quat getRawRotation() const { return _rawRotation; } void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; } + void setRawNormal(const glm::vec3& normal) { _rawNormal = normal; } void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; } const glm::vec3& getRawVelocity() const { return _rawVelocity; } void addToPosition(const glm::vec3& delta); @@ -107,11 +162,11 @@ public: void resolvePenetrations() { addToPosition(-_totalPenetration); _totalPenetration = glm::vec3(0.f); } void setTipPosition(const glm::vec3& position) { _tipPosition = position; } - const glm::vec3 getTipPosition() const { return _owningHandData->localToWorldPosition(_tipPosition); } + const glm::vec3 getTipPosition() const { return _owningHandData->leapPositionToWorldPosition(_tipPosition); } const glm::vec3& getTipRawPosition() const { return _tipPosition; } void setTipVelocity(const glm::vec3& velocity) { _tipVelocity = velocity; } - const glm::vec3 getTipVelocity() const { return _owningHandData->localToWorldDirection(_tipVelocity); } + const glm::vec3 getTipVelocity() const { return _owningHandData->leapDirectionToWorldDirection(_tipVelocity); } const glm::vec3& getTipRawVelocity() const { return _tipVelocity; } void incrementFramesWithoutData() { _numFramesWithoutData++; } @@ -143,14 +198,11 @@ public: /// Store position where the palm holds the ball. void getBallHoldPosition(glm::vec3& position) const; - // return world-frame: - glm::vec3 getFingerTipPosition() const; - glm::vec3 getFingerDirection() const; - glm::vec3 getPalmDirection() const; - private: + std::vector _fingers; glm::quat _rawRotation; glm::vec3 _rawPosition; + glm::vec3 _rawNormal; glm::vec3 _rawVelocity; glm::vec3 _rotationalVelocity; glm::quat _lastRotation; @@ -164,6 +216,7 @@ private: float _joystickX, _joystickY; bool _isActive; // This has current valid data + int _leapID; // the Leap's serial id for this tracked object int _sixenseID; // Sixense controller ID for this palm int _numFramesWithoutData; // after too many frames without data, this tracked object assumed lost. HandData* _owningHandData; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index d111439b7e..4a3fe2a129 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -54,7 +54,6 @@ static const float SQUARE_ROOT_OF_3 = (float)sqrt(3.f); static const float METERS_PER_DECIMETER = 0.1f; static const float METERS_PER_CENTIMETER = 0.01f; static const float METERS_PER_MILLIMETER = 0.001f; -static const float MILLIMETERS_PER_METER = 1000.0f; static const quint64 USECS_PER_MSEC = 1000; static const quint64 MSECS_PER_SECOND = 1000; static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; From c59358ac96446c6a5e8947f4688db93afe51c64b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 13 May 2014 14:10:01 -0700 Subject: [PATCH 10/28] First cut at editModels with the mouse --- examples/editModels.js | 273 ++++++++++++++++++++++++++++++++++------- examples/toolBars.js | 39 +++--- 2 files changed, 244 insertions(+), 68 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 90ce87d259..46e8248681 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -368,41 +368,100 @@ function checkController(deltaTime) { moveOverlays(); } +var clickEvent = false; +var newModel; +var modifierType = -1; +var moveYZ; +var moveXZ; +var moveXY; +var yaw; +var pitch; +var roll; +var scale; + +var modelSelected = false; +var selectedModelID; +var selectedModelProperties; + + +function initToolBar() { + toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); + // New Model + newModel = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-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 + }); + print("New Model: " + newModel) + + // Move YZ + moveYZ = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + // Move XZ + moveXZ = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + // Move XY + moveXY = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + + + // Yaw + yaw = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + // Pitch + pitch = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + // Roll + roll = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); + + + // Scale + scale = toolBar.addTool({ + imageURL: toolIconUrl + "voxel-tool.svg", + subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true); +} + function moveOverlays() { if (typeof(toolBar) === 'undefined') { - toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); - // New Model - toolBar.addTool({ - imageURL: toolIconUrl + "voxel-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 - }); - // Move YZ - toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - // Move XZ - toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - // Move XY - toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); + initToolBar(); + } else if (windowDimensions.x == Controller.getViewportDimensions().x && windowDimensions.y == Controller.getViewportDimensions().y) { return; @@ -417,27 +476,147 @@ function moveOverlays() { } function mousePressEvent(event) { + modelSelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - var url; - var index = toolBar.clicked(clickedOverlay); - if (index == 0) { - url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); - if (url == null) { - return; - } + + if (clickedOverlay != 0) { + var index = toolBar.clicked(clickedOverlay); - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - Models.addModel({ position: position, - radius: radiusDefault, - modelURL: url - }); - } else if (index == -1) { - print("Didn't click on anything"); + switch(index) { + case newModel: + var url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); + if (url == null) { + return; + } + + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + Models.addModel({ position: position, + radius: radiusDefault, + modelURL: url + }); + break; + case moveYZ: + print("Selected moveYZ"); + + break; + case moveXZ: + print("Selected moveXZ"); + + break; + case moveXY: + print("Selected moveXY"); + + break; + case yaw: + print("Selected yaw"); + + break; + case pitch: + print("Selected pitch"); + + break; + case roll: + print("Selected roll"); + + break; + case scale: + print("Selected scale"); + + break; + default: + clickEvent = false; + return; + } + clickEvent = true; + + if (modifierType != -1) { + toolBar.tools[modifierType].select(false); + } + modifierType = index; + } else { + var pickRay = Camera.computePickRay(event.x, event.y); + Vec3.print("Looking at: ", pickRay.origin); + var foundModels = Models.findModels(pickray.origin, LASER_LENGTH_FACTOR); + for (var i = 0; i < foundModels.length; i++) { + if (!foundModels[i].isKnownID) { + var identify = Models.identifyModel(foundModels[i]); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + "(update loop)"); + return; + } + foundModels[i] = identify; + } + + var properties = Models.getModelProperties(foundModels[i]); + print("Checking properties: " + properties.id + " " + properties.isKnownID); + + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, LASER_LENGTH_FACTOR)); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { + modelSelected = true; + selectedModelID = foundModels[i]; + selectedModelProperties = properties; + return; + } + } } } function mouseMoveEvent(event) { + if (clickEvent && !modelSelected) { + return; + } + print("Dragging"); + + switch(modifierType) { + case moveYZ: + print("Move " + moveYZ); + break; + case moveXZ: + print("Move " + moveXZ); + + break; + case moveXY: + print("Move " + moveXY); + + break; + case yaw: + print("Move " + yaw); + + break; + case pitch: + print("Move " + pitch); + + break; + case roll: + print("Move " + roll); + + break; + case scale: + print("Move " + scale); + + break; + } + + Model.editModel(selectedModelID, selectedModelProperties); } function scriptEnding() { diff --git a/examples/toolBars.js b/examples/toolBars.js index 62a01a9a15..7f8f09575b 100644 --- a/examples/toolBars.js +++ b/examples/toolBars.js @@ -13,7 +13,6 @@ Overlay2D = function(properties, overlay) { // overlay is an optionnal variable if (!(typeof(properties) === 'undefined')) { if(typeof(overlay) === 'undefined') { overlay = Overlays.addOverlay("image", properties); - print("New overlay: " + overlay); } else { Overlays.editOverlay(overlay, properties); } @@ -87,25 +86,23 @@ Tool = function(properties, selectable, selected) { // selectable and selected a return selectable; } - if (this.selectable()) { - this.selected = function() { - return selected; - } - this.select = function(doSelect) { - selected = doSelect; - properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; - Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); - } - this.toggle = function() { - selected = !selected; - properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; - Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); - - return selected; - } - - this.select(selected); + this.selected = function() { + return selected; } + this.select = function(doSelect) { + selected = doSelect; + properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; + Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); + } + this.toggle = function() { + selected = !selected; + properties.subImage.y = (selected ? 2 : 1) * properties.subImage.height; + Overlays.editOverlay(this.overlay(), { subImage: properties.subImage }); + + return selected; + } + + this.select(selected); this.baseClicked = this.clicked; this.clicked = function(clickedOverlay) { @@ -144,7 +141,7 @@ ToolBar = function(x, y, direction) { } this.tools[this.tools.length] = new Tool(properties, selectable, selected); - return this.tools.length - 1; + return ((this.tools.length) - 1); } this.move = function(x, y) { @@ -172,7 +169,7 @@ ToolBar = function(x, y, direction) { this.clicked = function(clickedOverlay) { for(var tool in this.tools) { if (this.tools[tool].visible() && this.tools[tool].clicked(clickedOverlay)) { - return tool; + return parseInt(tool); } } return -1; From 701bc63bd19b37907ad7272898f6b044ec42948e Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 13 May 2014 14:34:08 -0700 Subject: [PATCH 11/28] Removed old toolbar --- examples/editModels.js | 185 +++++------------------------------------ 1 file changed, 22 insertions(+), 163 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 46e8248681..07b31e77d2 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -18,7 +18,7 @@ var toolWidth = 50; var LASER_WIDTH = 4; var LASER_COLOR = { red: 255, green: 0, blue: 0 }; -var LASER_LENGTH_FACTOR = 1.5; +var LASER_LENGTH_FACTOR = 5; var LEFT = 0; var RIGHT = 1; @@ -368,20 +368,6 @@ function checkController(deltaTime) { moveOverlays(); } -var clickEvent = false; -var newModel; -var modifierType = -1; -var moveYZ; -var moveXZ; -var moveXY; -var yaw; -var pitch; -var roll; -var scale; - -var modelSelected = false; -var selectedModelID; -var selectedModelProperties; function initToolBar() { @@ -394,68 +380,6 @@ function initToolBar() { visible: true, alpha: 0.9 }); - print("New Model: " + newModel) - - // Move YZ - moveYZ = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - // Move XZ - moveXZ = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - // Move XY - moveXY = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - - - // Yaw - yaw = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - // Pitch - pitch = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - // Roll - roll = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); - - - // Scale - scale = toolBar.addTool({ - imageURL: toolIconUrl + "voxel-tool.svg", - subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, height: toolHeight, - visible: true, - alpha: 0.9 - }, true); } function moveOverlays() { @@ -475,74 +399,39 @@ function moveOverlays() { toolBar.move(toolsX, toolsY); } + + +var modelSelected = false; +var selectedModelID; +var selectedModelProperties; + function mousePressEvent(event) { - modelSelected = false; + var modelSelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - if (clickedOverlay != 0) { - var index = toolBar.clicked(clickedOverlay); - - switch(index) { - case newModel: - var url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); - if (url == null) { - return; - } - - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - Models.addModel({ position: position, - radius: radiusDefault, - modelURL: url - }); - break; - case moveYZ: - print("Selected moveYZ"); - - break; - case moveXZ: - print("Selected moveXZ"); - - break; - case moveXY: - print("Selected moveXY"); - - break; - case yaw: - print("Selected yaw"); - - break; - case pitch: - print("Selected pitch"); - - break; - case roll: - print("Selected roll"); - - break; - case scale: - print("Selected scale"); - - break; - default: - clickEvent = false; - return; + if (newModel == toolBar.clicked(clickedOverlay)) { + var url = Window.prompt("Model url", modelURLs[Math.floor(Math.random() * modelURLs.length)]); + if (url == null) { + return; } - clickEvent = true; - if (modifierType != -1) { - toolBar.tools[modifierType].select(false); - } - modifierType = index; + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); + Models.addModel({ position: position, + radius: radiusDefault, + modelURL: url + }); + } else { var pickRay = Camera.computePickRay(event.x, event.y); - Vec3.print("Looking at: ", pickRay.origin); + Vec3.print("[Mouse] Looking at: ", pickRay.origin); var foundModels = Models.findModels(pickray.origin, LASER_LENGTH_FACTOR); + print("Num: " + foundModels.length.toString()); for (var i = 0; i < foundModels.length; i++) { if (!foundModels[i].isKnownID) { var identify = Models.identifyModel(foundModels[i]); if (!identify.isKnownID) { print("Unknown ID " + identify.id + "(update loop)"); - return; + continue; } foundModels[i] = identify; } @@ -580,41 +469,11 @@ function mousePressEvent(event) { } function mouseMoveEvent(event) { - if (clickEvent && !modelSelected) { + if (!modelSelected) { return; } - print("Dragging"); - switch(modifierType) { - case moveYZ: - print("Move " + moveYZ); - break; - case moveXZ: - print("Move " + moveXZ); - - break; - case moveXY: - print("Move " + moveXY); - - break; - case yaw: - print("Move " + yaw); - - break; - case pitch: - print("Move " + pitch); - - break; - case roll: - print("Move " + roll); - - break; - case scale: - print("Move " + scale); - - break; - } Model.editModel(selectedModelID, selectedModelProperties); } From 624ae4732f8a11af7aabc9c1abab9282fb751acb Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 13 May 2014 14:58:34 -0700 Subject: [PATCH 12/28] Fixes to PickRay check --- examples/editModels.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 07b31e77d2..017e4ba740 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -406,7 +406,7 @@ var selectedModelID; var selectedModelProperties; function mousePressEvent(event) { - var modelSelected = false; + modelSelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); if (newModel == toolBar.clicked(clickedOverlay)) { @@ -424,8 +424,7 @@ function mousePressEvent(event) { } else { var pickRay = Camera.computePickRay(event.x, event.y); Vec3.print("[Mouse] Looking at: ", pickRay.origin); - var foundModels = Models.findModels(pickray.origin, LASER_LENGTH_FACTOR); - print("Num: " + foundModels.length.toString()); + var foundModels = Models.findModels(pickRay.origin, LASER_LENGTH_FACTOR); for (var i = 0; i < foundModels.length; i++) { if (!foundModels[i].isKnownID) { var identify = Models.identifyModel(foundModels[i]); @@ -451,7 +450,7 @@ function mousePressEvent(event) { // d = |P-X| var A = pickRay.origin; - var B = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, LASER_LENGTH_FACTOR)); + var B = Vec3.normalize(pickRay.direction); var P = properties.position; var x = Vec3.dot(Vec3.subtract(P, A), B); @@ -462,6 +461,7 @@ function mousePressEvent(event) { modelSelected = true; selectedModelID = foundModels[i]; selectedModelProperties = properties; + print("Clicked on " + selectedModelID.id + " " + modelSelected); return; } } @@ -475,7 +475,7 @@ function mouseMoveEvent(event) { print("Dragging"); - Model.editModel(selectedModelID, selectedModelProperties); + //Model.editModel(selectedModelID, selectedModelProperties); } function scriptEnding() { @@ -488,7 +488,7 @@ Script.scriptEnding.connect(scriptEnding); // register the call back so it fires before each data send Script.update.connect(checkController); Controller.mousePressEvent.connect(mousePressEvent); -Controller.mousePressEvent.connect(mouseMoveEvent); +Controller.mouseMoveEvent.connect(mouseMoveEvent); From da7081300291c6179d7325c5ad58606d861ad996 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 13 May 2014 15:45:24 -0700 Subject: [PATCH 13/28] On Windows, Visage takes the folder containing the license file, not the license file itself. --- interface/src/devices/Visage.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp index a467d2d4a8..8173519478 100644 --- a/interface/src/devices/Visage.cpp +++ b/interface/src/devices/Visage.cpp @@ -41,7 +41,11 @@ Visage::Visage() : _headOrigin(DEFAULT_HEAD_ORIGIN) { #ifdef HAVE_VISAGE +#ifdef WIN32 + QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage"; +#else QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage/license.vlc"; +#endif initializeLicenseManager(licensePath.data()); _tracker = new VisageTracker2(Application::resourcesPath().toLatin1() + "visage/tracker.cfg"); _data = new FaceData(); From 19404fe0e5238c6315c5e0d16097d29385381caf Mon Sep 17 00:00:00 2001 From: Kai Ludwig Date: Wed, 14 May 2014 07:10:31 +0200 Subject: [PATCH 14/28] Changed start domain to sandbox. Changed start location to xyz: 6270, 211, 6000. goHome behaviour is unchanged and will still just go to the start location keeping the currently select domain. --- interface/src/avatar/Avatar.h | 7 +++---- libraries/networking/src/DomainHandler.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 289a0500d0..c2a8af55b2 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -54,10 +54,9 @@ enum ScreenTintLayer { NUM_SCREEN_TINT_LAYERS }; -// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found) -// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by -// Grayson as he's building a street around here for demo dinner 2 -const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.0f, 0.5f * TREE_SCALE); +// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found). +// This is the start location in the Sandbox (xyz: 6270, 211, 6000). +const glm::vec3 START_LOCATION(0.38269043f * TREE_SCALE, 0.01287842f * TREE_SCALE, 0.36621094f * TREE_SCALE); class Texture; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index b78b8875c4..599f6d4a0f 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -21,7 +21,7 @@ #include "DTLSClientSession.h" #include "HifiSockAddr.h" -const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io"; +const QString DEFAULT_DOMAIN_HOSTNAME = "sandbox.highfidelity.io"; const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; From d59bedfa96a1efaab3011ad18697ae71788f5a0f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 13 May 2014 22:25:08 -0700 Subject: [PATCH 15/28] Fix running scripts not properly updated when done programmatically Add a slot to Application to receive ScriptEngine::finished signals. Notify the Running scripts widget of stopped scripts so it can properly move them to the recent scripts list. --- interface/src/Application.cpp | 16 ++++++++++------ interface/src/Application.h | 1 + interface/src/ui/RunningScriptsWidget.cpp | 9 ++++----- interface/src/ui/RunningScriptsWidget.h | 1 + 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8858b6d324..ab8d69c319 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3405,6 +3405,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater())); + connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(scriptFinished(const QString&))); + scriptEngine->registerGlobalObject("Overlays", &_overlays); QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); @@ -3447,6 +3449,14 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript return scriptEngine; } +void Application::scriptFinished(const QString &scriptName) { + if (_scriptEnginesHash.remove(scriptName)) { + _runningScriptsWidget->scriptStopped(scriptName); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + bumpSettings(); + } +} + void Application::stopAllScripts(bool restart) { // stops all current running scripts for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); @@ -3457,18 +3467,12 @@ void Application::stopAllScripts(bool restart) { it.value()->stop(); qDebug() << "stopping script..." << it.key(); } - _scriptEnginesHash.clear(); - _runningScriptsWidget->setRunningScripts(getRunningScripts()); - bumpSettings(); } void Application::stopScript(const QString &scriptName) { if (_scriptEnginesHash.contains(scriptName)) { _scriptEnginesHash.value(scriptName)->stop(); qDebug() << "stopping script..." << scriptName; - _scriptEnginesHash.remove(scriptName); - _runningScriptsWidget->setRunningScripts(getRunningScripts()); - bumpSettings(); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 67cf7dad44..174ac61c06 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -292,6 +292,7 @@ public slots: void toggleLogDialog(); void initAvatarAndViewFrustum(); ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false); + void scriptFinished(const QString& scriptName); void stopAllScripts(bool restart = false); void stopScript(const QString& scriptName); void reloadAllScripts(); diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 61241f71fb..328974dc9d 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -157,6 +157,10 @@ void RunningScriptsWidget::paintEvent(QPaintEvent* event) { painter.end(); } +void RunningScriptsWidget::scriptStopped(const QString& scriptName) { + _recentlyLoadedScripts.prepend(scriptName); +} + void RunningScriptsWidget::stopScript(int row, int column) { if (column == 1) { // make sure the user has clicked on the close icon _lastStoppedScript = _runningScriptsTable->item(row, 0)->toolTip(); @@ -169,11 +173,6 @@ void RunningScriptsWidget::loadScript(int row, int column) { } void RunningScriptsWidget::allScriptsStopped() { - QStringList list = Application::getInstance()->getRunningScripts(); - for (int i = 0; i < list.size(); ++i) { - _recentlyLoadedScripts.prepend(list.at(i)); - } - Application::getInstance()->stopAllScripts(); } diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index ad310c4ed4..14a1f4a58e 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -36,6 +36,7 @@ protected: virtual void paintEvent(QPaintEvent* event); public slots: + void scriptStopped(const QString& scriptName); void setBoundary(const QRect& rect); private slots: From b01b14485491fcb3962d75839657c9cfc24c4b15 Mon Sep 17 00:00:00 2001 From: Kai Ludwig Date: Wed, 14 May 2014 07:36:27 +0200 Subject: [PATCH 16/28] No more hand movement when clicking with the mouse. --- interface/src/avatar/MyAvatar.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 36c51dc9fd..20e4bcc44e 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -137,15 +137,8 @@ void MyAvatar::simulate(float deltaTime) { Application::getInstance()->getCamera()->setScale(scale); } - // update the movement of the hand and process handshaking with other avatars... - bool pointing = false; - if (_mousePressed) { - _handState = HAND_STATE_GRASPING; - } else if (pointing) { - _handState = HAND_STATE_POINTING; - } else { - _handState = HAND_STATE_NULL; - } + // no extra movement of the hand here any more ... + _handState = HAND_STATE_NULL; updateOrientation(deltaTime); From 8247e5a55277e4361af4fd3663dfb944f637033f Mon Sep 17 00:00:00 2001 From: Stojce Slavkovski Date: Wed, 14 May 2014 17:25:40 +0200 Subject: [PATCH 17/28] added discourse key in shift operators --- libraries/networking/src/DataServerAccountInfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index a9522148a8..0fdb5ff4b1 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -76,11 +76,11 @@ void DataServerAccountInfo::setDiscourseApiKey(const QString& discourseApiKey) { } QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) { - out << info._accessToken << info._username << info._xmppPassword; + out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey; return out; } QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) { - in >> info._accessToken >> info._username >> info._xmppPassword; + in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey; return in; } From d2d9ca878884c3e097aab36f34ea3e356c74b368 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 14 May 2014 09:04:59 -0700 Subject: [PATCH 18/28] optimization: only compute baseTransform once --- interface/src/renderer/Model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index f46fd48beb..0c688865f3 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -128,6 +128,7 @@ QVector Model::createJointStates(const FBXGeometry& geometry) jointIsSet.fill(false, numJoints); int numJointsSet = 0; int lastNumJointsSet = -1; + glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) { lastNumJointsSet = numJointsSet; for (int i = 0; i < numJoints; ++i) { @@ -138,7 +139,6 @@ QVector Model::createJointStates(const FBXGeometry& geometry) const FBXJoint& joint = geometry.joints[i]; int parentIndex = joint.parentIndex; if (parentIndex == -1) { - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; From 366e9c7d3452cbed0cc78edbd59463c8a615b64f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 14 May 2014 09:05:54 -0700 Subject: [PATCH 19/28] PalmDataA::getPalmDirection --> getNormal() --- interface/src/avatar/Avatar.cpp | 2 +- interface/src/avatar/Hand.cpp | 3 +-- interface/src/scripting/ControllerScriptingInterface.cpp | 2 +- libraries/avatars/src/HandData.cpp | 2 +- libraries/avatars/src/HandData.h | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 2bd0bbbc6d..20f6275441 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -622,7 +622,7 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti glm::vec3 fingerAxis = palm->getFingerDirection(); glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis; - glm::vec3 diskNormal = palm->getPalmDirection(); + glm::vec3 diskNormal = palm->getNormal(); const float DISK_THICKNESS = 0.08f; // collide against the disk diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 78eab424ab..ee7a633a54 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -23,7 +23,6 @@ using namespace std; -const float FINGERTIP_COLLISION_RADIUS = 0.01f; const float PALM_COLLISION_RADIUS = 0.03f; @@ -201,7 +200,7 @@ void Hand::renderHandTargets(bool isMine) { glm::vec3 root = palm.getPosition(); Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); // Render sphere at palm/finger root - glm::vec3 offsetFromPalm = root + palm.getPalmDirection() * PALM_DISK_THICKNESS; + glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS; Avatar::renderJointConnectingCone(root, offsetFromPalm, PALM_DISK_RADIUS, 0.0f); glPushMatrix(); glTranslatef(root.x, root.y, root.z); diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 5e58ac66ea..58a08066d6 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -198,7 +198,7 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex if (palmData) { switch (controlOfPalm) { case PALM_SPATIALCONTROL: - return palmData->getPalmDirection(); + return palmData->getNormal(); case TIP_SPATIALCONTROL: return palmData->getFingerDirection(); } diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index bd366f020a..0105145466 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -120,7 +120,7 @@ glm::vec3 PalmData::getFingerDirection() const { return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION); } -glm::vec3 PalmData::getPalmDirection() const { +glm::vec3 PalmData::getNormal() const { const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f); return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION); } diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 1f2d134c43..505b1b600a 100755 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -146,7 +146,7 @@ public: // return world-frame: glm::vec3 getFingerTipPosition() const; glm::vec3 getFingerDirection() const; - glm::vec3 getPalmDirection() const; + glm::vec3 getNormal() const; private: glm::quat _rawRotation; From 498f2843b06655028db150ea7870fde8beea6b9c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 14 May 2014 09:06:46 -0700 Subject: [PATCH 20/28] Fix for bad hand rotations --- interface/src/avatar/SkeletonModel.cpp | 22 ++++++++-------------- interface/src/avatar/SkeletonModel.h | 3 +-- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index a2e637f4e7..544f573eda 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -52,15 +52,12 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } else if (leftPalmIndex == rightPalmIndex) { // right hand only - applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices, - hand->getPalms()[leftPalmIndex]); + applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[leftPalmIndex]); restoreLeftHandPosition(HAND_RESTORATION_RATE); } else { - applyPalmData(geometry.leftHandJointIndex, geometry.leftFingerJointIndices, geometry.leftFingertipJointIndices, - hand->getPalms()[leftPalmIndex]); - applyPalmData(geometry.rightHandJointIndex, geometry.rightFingerJointIndices, geometry.rightFingertipJointIndices, - hand->getPalms()[rightPalmIndex]); + applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]); + applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]); } } @@ -140,8 +137,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) applyRotationDelta(jointIndex, rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector)); } -void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJointIndices, - const QVector& fingertipJointIndices, PalmData& palm) { +void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { if (jointIndex == -1) { return; } @@ -152,19 +148,17 @@ void SkeletonModel::applyPalmData(int jointIndex, const QVector& fingerJoin return; } - // rotate palm to align with palm direction + // rotate palm to align with its normal (normal points out of hand's palm) glm::quat palmRotation; if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { getJointRotation(parentJointIndex, palmRotation, true); } else { getJointRotation(jointIndex, palmRotation, true); } - palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getPalmDirection()) * palmRotation; + palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()) * palmRotation; - // rotate forearm according to average finger direction - // NOTE: we're doing this in the avatar local frame, so we DON'T want to use Palm::getHandDirection() - // which returns the world-frame. - glm::vec3 direction = palm.getRawRotation() * glm::vec3(0.0f, 0.0f, 1.0f); + // rotate palm to align with finger direction + glm::vec3 direction = palm.getFingerDirection(); palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation; // set hand position, rotation diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index ee6b3b9de3..20384829ea 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -39,8 +39,7 @@ protected: void applyHandPosition(int jointIndex, const glm::vec3& position); - void applyPalmData(int jointIndex, const QVector& fingerJointIndices, - const QVector& fingertipJointIndices, PalmData& palm); + void applyPalmData(int jointIndex, PalmData& palm); /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); From 146b9958cb0380a0cdefb7e928eb1ca7175cdf10 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 09:41:13 -0700 Subject: [PATCH 21/28] Fix style issue --- 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 ab8d69c319..c23804166c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3449,7 +3449,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript return scriptEngine; } -void Application::scriptFinished(const QString &scriptName) { +void Application::scriptFinished(const QString& scriptName) { if (_scriptEnginesHash.remove(scriptName)) { _runningScriptsWidget->scriptStopped(scriptName); _runningScriptsWidget->setRunningScripts(getRunningScripts()); From 310f184978b4b396d698e72916b21f3187465ea4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 09:53:44 -0700 Subject: [PATCH 22/28] Add getters for onTimeout and onReadyStateChange --- libraries/script-engine/src/XMLHttpRequestClass.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index bff88d91a9..e94c67562a 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -40,8 +40,8 @@ class XMLHttpRequestClass : public QObject { Q_PROPERTY(int DONE READ getDone) // Callbacks - Q_PROPERTY(QScriptValue ontimeout WRITE setOnTimeout) - Q_PROPERTY(QScriptValue onreadystatechange WRITE setOnReadyStateChange) + Q_PROPERTY(QScriptValue ontimeout READ getOnTimeout WRITE setOnTimeout) + Q_PROPERTY(QScriptValue onreadystatechange READ getOnReadyStateChange WRITE setOnReadyStateChange) public: XMLHttpRequestClass(QScriptEngine* engine); ~XMLHttpRequestClass(); @@ -74,7 +74,9 @@ public: QScriptValue getStatus() const; QString getStatusText() const; + QScriptValue getOnTimeout() const { return _onTimeout; } void setOnTimeout(QScriptValue function) { _onTimeout = function; } + QScriptValue getOnReadyStateChange() const { return _onReadyStateChange; } void setOnReadyStateChange(QScriptValue function) { _onReadyStateChange = function; } public slots: From 02676848c605643e46789767a489701e5b8286dd Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 09:58:04 -0700 Subject: [PATCH 23/28] Fix order of components in streetAreaExample.js --- examples/streetAreaExample.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/streetAreaExample.js b/examples/streetAreaExample.js index deb10dd65a..b4efd99b70 100644 --- a/examples/streetAreaExample.js +++ b/examples/streetAreaExample.js @@ -33,8 +33,8 @@ if (req.status == 200) { for (var i = 0; i < locations.length; i++) { var loc = locations[i]; var x1 = loc[1], - y1 = loc[2], - x2 = loc[3], + x2 = loc[2], + y1 = loc[3], y2 = loc[4]; userAreas.push({ username: loc[0], From bd56ab911dad4ad92cc1162b602c5e81353d8cba Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 10:05:04 -0700 Subject: [PATCH 24/28] Fix bug with js print() breaking on certain characters The message text needs to be escaped before beign evaluated. --- libraries/script-engine/src/ScriptEngine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index be97b37b46..7b09916a1a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -49,7 +49,11 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ qDebug() << "script:print()<<" << context->argument(0).toString(); - engine->evaluate("Script.print('" + context->argument(0).toString() + "')"); + QString message = context->argument(0).toString() + .replace("\\", "\\\\") + .replace("\n", "\\n") + .replace("'", "\\'"); + engine->evaluate("Script.print('" + message + "')"); return QScriptValue(); } From c16654628a5530f4051703f739eb95741deaf9a7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 10:08:54 -0700 Subject: [PATCH 25/28] Add carriage return to escaped string in print() --- libraries/script-engine/src/ScriptEngine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 7b09916a1a..3427d0b19d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -52,6 +52,7 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ QString message = context->argument(0).toString() .replace("\\", "\\\\") .replace("\n", "\\n") + .replace("\r", "\\r") .replace("'", "\\'"); engine->evaluate("Script.print('" + message + "')"); return QScriptValue(); From a5c10220e6d62ddcac541893f0209c9be446eb50 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 10:16:04 -0700 Subject: [PATCH 26/28] Remove _ prefix from private methods in XMLHttpRequest --- .../script-engine/src/XMLHttpRequestClass.cpp | 34 +++++++++---------- .../script-engine/src/XMLHttpRequestClass.h | 10 +++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 77b03db999..a81f8950fa 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -63,7 +63,7 @@ QString XMLHttpRequestClass::getStatusText() const { } void XMLHttpRequestClass::abort() { - _abortRequest(); + abortRequest(); } void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) { @@ -76,18 +76,18 @@ void XMLHttpRequestClass::requestMetaDataChanged() { // If this is a redirect, abort the current request and start a new one if (redirect.isValid() && _numRedirects < MAXIMUM_REDIRECTS) { _numRedirects++; - _abortRequest(); + abortRequest(); QUrl newUrl = _url.resolved(redirect.toUrl().toString()); _request.setUrl(newUrl); - _doSend(); + doSend(); } } void XMLHttpRequestClass::requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { if (_readyState == OPENED && bytesReceived > 0) { - _setReadyState(HEADERS_RECEIVED); - _setReadyState(LOADING); + setReadyState(HEADERS_RECEIVED); + setReadyState(LOADING); } } @@ -113,7 +113,7 @@ QScriptValue XMLHttpRequestClass::getResponseHeader(const QString& name) const { return QScriptValue::NullValue; } -void XMLHttpRequestClass::_setReadyState(ReadyState readyState) { +void XMLHttpRequestClass::setReadyState(ReadyState readyState) { if (readyState != _readyState) { _readyState = readyState; if (_onReadyStateChange.isFunction()) { @@ -135,7 +135,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a } _request.setUrl(_url); _method = method; - _setReadyState(OPENED); + setReadyState(OPENED); } } @@ -150,7 +150,7 @@ void XMLHttpRequestClass::send(const QString& data) { _sendData->setData(data.toUtf8()); } - _doSend(); + doSend(); if (!_async) { QEventLoop loop; @@ -160,10 +160,10 @@ void XMLHttpRequestClass::send(const QString& data) { } } -void XMLHttpRequestClass::_doSend() { +void XMLHttpRequestClass::doSend() { _reply = _manager.sendCustomRequest(_request, _method.toLatin1(), _sendData); - _connectToReply(_reply); + connectToReply(_reply); if (_timeout > 0) { _timer.start(_timeout); @@ -175,9 +175,9 @@ void XMLHttpRequestClass::requestTimeout() { if (_onTimeout.isFunction()) { _onTimeout.call(QScriptValue::NullValue); } - _abortRequest(); + abortRequest(); _errorCode = QNetworkReply::TimeoutError; - _setReadyState(DONE); + setReadyState(DONE); emit requestComplete(); } @@ -203,29 +203,29 @@ void XMLHttpRequestClass::requestFinished() { _responseData = QScriptValue(QString(_rawResponseData.data())); } } - _setReadyState(DONE); + setReadyState(DONE); emit requestComplete(); } -void XMLHttpRequestClass::_abortRequest() { +void XMLHttpRequestClass::abortRequest() { // Disconnect from signals we don't want to receive any longer. disconnect(&_timer, SIGNAL(timeout()), this, SLOT(requestTimeout())); if (_reply) { - _disconnectFromReply(_reply); + disconnectFromReply(_reply); _reply->abort(); delete _reply; _reply = NULL; } } -void XMLHttpRequestClass::_connectToReply(QNetworkReply* reply) { +void XMLHttpRequestClass::connectToReply(QNetworkReply* reply) { connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); connect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); } -void XMLHttpRequestClass::_disconnectFromReply(QNetworkReply* reply) { +void XMLHttpRequestClass::disconnectFromReply(QNetworkReply* reply) { disconnect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); diff --git a/libraries/script-engine/src/XMLHttpRequestClass.h b/libraries/script-engine/src/XMLHttpRequestClass.h index e94c67562a..49a952e638 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.h +++ b/libraries/script-engine/src/XMLHttpRequestClass.h @@ -93,11 +93,11 @@ signals: void requestComplete(); private: - void _setReadyState(ReadyState readyState); - void _doSend(); - void _connectToReply(QNetworkReply* reply); - void _disconnectFromReply(QNetworkReply* reply); - void _abortRequest(); + void setReadyState(ReadyState readyState); + void doSend(); + void connectToReply(QNetworkReply* reply); + void disconnectFromReply(QNetworkReply* reply); + void abortRequest(); QScriptEngine* _engine; bool _async; From 5ef71d6a3557715773d2dde964aedb08bb33a158 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 May 2014 10:40:50 -0700 Subject: [PATCH 27/28] Full mouse handling --- examples/editModels.js | 121 +++++++++++++++++- .../models/src/ModelsScriptingInterface.cpp | 1 - 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index 017e4ba740..70a2e178ae 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -404,8 +404,24 @@ function moveOverlays() { var modelSelected = false; var selectedModelID; var selectedModelProperties; +var mouseLastPosition; +var orientation; +var intersection; + + +var SCALE_FACTOR = 200.0; +var TRANSLATION_FACTOR = 100.0; +var ROTATION_FACTOR = 100.0; + +function rayPlaneIntersection(pickRay, point, normal) { + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / Vec3.dot(pickRay.direction, normal); + + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); +} function mousePressEvent(event) { + mouseLastPosition = { x: event.x, y: event.y }; modelSelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); @@ -461,21 +477,122 @@ function mousePressEvent(event) { modelSelected = true; selectedModelID = foundModels[i]; selectedModelProperties = properties; + + selectedModelProperties.oldRadius = selectedModelProperties.radius; + selectedModelProperties.oldPosition = { + x: selectedModelProperties.position.x, + y: selectedModelProperties.position.y, + z: selectedModelProperties.position.z, + }; + selectedModelProperties.oldRotation = { + x: selectedModelProperties.modelRotation.x, + y: selectedModelProperties.modelRotation.y, + z: selectedModelProperties.modelRotation.z, + w: selectedModelProperties.modelRotation.w, + }; + + + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); print("Clicked on " + selectedModelID.id + " " + modelSelected); + return; } } } } +var oldModifier = 0; +var modifier = 0; +var wasShifted = false; function mouseMoveEvent(event) { if (!modelSelected) { return; } - print("Dragging"); + + if (event.isLeftButton) { + if (event.isRightButton) { + modifier = 1; // Scale + } else { + modifier = 2; // Translate + } + } else if (event.isRightButton) { + modifier = 3; // rotate + } else { + modifier = 0; + } + + var pickRay = Camera.computePickRay(event.x, event.y); + if (wasShifted != event.isShifted || modifier != oldModifier) { + selectedModelProperties.oldRadius = selectedModelProperties.radius; + + selectedModelProperties.oldPosition = { + x: selectedModelProperties.position.x, + y: selectedModelProperties.position.y, + z: selectedModelProperties.position.z, + }; + selectedModelProperties.oldRotation = { + x: selectedModelProperties.modelRotation.x, + y: selectedModelProperties.modelRotation.y, + z: selectedModelProperties.modelRotation.z, + w: selectedModelProperties.modelRotation.w, + }; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, + selectedModelProperties.oldPosition, + Quat.getFront(orientation)); + + mouseLastPosition = { x: event.x, y: event.y }; + wasShifted = event.isShifted; + oldModifier = modifier; + return; + } - //Model.editModel(selectedModelID, selectedModelProperties); + switch (modifier) { + case 0: + return; + case 1: + // Let's Scale + selectedModelProperties.radius = (selectedModelProperties.oldRadius * + (1.0 + (mouseLastPosition.y - event.y) / SCALE_FACTOR)); + + if (selectedModelProperties.radius < 0.01) { + print("Scale too small ... bailling."); + return; + } + break; + + case 2: + // Let's translate + var newIntersection = rayPlaneIntersection(pickRay, + selectedModelProperties.oldPosition, + Quat.getFront(orientation)); + var vector = Vec3.subtract(newIntersection, intersection) + if (event.isShifted) { + var i = Vec3.dot(vector, Quat.getRight(orientation)); + var j = Vec3.dot(vector, Quat.getUp(orientation)); + vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i), + Vec3.multiply(Quat.getFront(orientation), j)); + } + + selectedModelProperties.position = Vec3.sum(selectedModelProperties.oldPosition, vector); + break; + case 3: + // Let's rotate + var rotation = Quat.fromVec3Degrees({ x: event.y - mouseLastPosition.y, y: event.x - mouseLastPosition.x, z: 0 }); + if (event.isShifted) { + rotation = Quat.fromVec3Degrees({ x: event.y - mouseLastPosition.y, y: 0, z: mouseLastPosition.x - event.x }); + } + + var newRotation = Quat.multiply(orientation, rotation); + newRotation = Quat.multiply(newRotation, Quat.inverse(orientation)); + + selectedModelProperties.modelRotation = Quat.multiply(newRotation, selectedModelProperties.oldRotation); + break; + } + + Models.editModel(selectedModelID, selectedModelProperties); } function scriptEnding() { diff --git a/libraries/models/src/ModelsScriptingInterface.cpp b/libraries/models/src/ModelsScriptingInterface.cpp index 446b0280a4..7625eef998 100644 --- a/libraries/models/src/ModelsScriptingInterface.cpp +++ b/libraries/models/src/ModelsScriptingInterface.cpp @@ -105,7 +105,6 @@ ModelItemID ModelsScriptingInterface::editModel(ModelItemID modelID, const Model _modelTree->updateModel(modelID, properties); _modelTree->unlock(); } - return modelID; } From 53d7aeab4f1b6a38d4820d1d055725522d0b9ea7 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 14 May 2014 12:02:38 -0700 Subject: [PATCH 28/28] Fixed typo --- examples/toolBars.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/toolBars.js b/examples/toolBars.js index 7f8f09575b..88b07276f0 100644 --- a/examples/toolBars.js +++ b/examples/toolBars.js @@ -1,5 +1,5 @@ // -// testScript.js +// toolBars.js // examples // // Created by Clément Brisset on 5/7/14.