From 38a5b5b8d14b6c155d4583b77505786b3e54d02e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 00:28:11 -0700 Subject: [PATCH 01/37] Add ScriptsLocation settings value --- interface/resources/styles/preferences.qss | 3 +- interface/src/Menu.cpp | 10 +- interface/src/Menu.h | 7 + interface/src/ui/PreferencesDialog.cpp | 26 ++++ interface/src/ui/PreferencesDialog.h | 1 + interface/src/ui/RunningScriptsWidget.cpp | 33 ++++- interface/ui/preferencesDialog.ui | 143 ++++++++++++++++++++- 7 files changed, 217 insertions(+), 6 deletions(-) diff --git a/interface/resources/styles/preferences.qss b/interface/resources/styles/preferences.qss index e678acd0c9..40e35c8e52 100644 --- a/interface/resources/styles/preferences.qss +++ b/interface/resources/styles/preferences.qss @@ -12,7 +12,8 @@ QLabel#advancedTuningLabel { QPushButton#buttonBrowseHead, QPushButton#buttonBrowseBody, -QPushButton#buttonBrowseLocation { +QPushButton#buttonBrowseLocation, +QPushButton#buttonBrowseScriptsLocation { background-image: url(styles/search.svg); background-repeat: no-repeat; background-position: center center; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 13b72bcdb3..a3d2a0cb56 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -93,8 +93,8 @@ Menu::Menu() : _fastFPSAverage(ONE_SECOND_OF_FRAMES), _loginAction(NULL), _preferencesDialog(NULL), - _snapshotsLocation() -{ + _snapshotsLocation(), + _scriptsLocation() { Application *appInstance = Application::getInstance(); QMenu* fileMenu = addMenu("File"); @@ -501,6 +501,7 @@ void Menu::loadSettings(QSettings* settings) { _boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0); _snapshotsLocation = settings->value("snapshotsLocation", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString(); + setScriptsLocation(settings->value("scriptsLocation", QString()).toString()); settings->beginGroup("View Frustum Offset Camera"); // in case settings is corrupt or missing loadSetting() will check for NaN @@ -545,6 +546,7 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("avatarLODDistanceMultiplier", _avatarLODDistanceMultiplier); settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust); settings->setValue("snapshotsLocation", _snapshotsLocation); + settings->setValue("scriptsLocation", _scriptsLocation); settings->beginGroup("View Frustum Offset Camera"); settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw); settings->setValue("viewFrustumOffsetPitch", _viewFrustumOffset.pitch); @@ -1604,3 +1606,7 @@ QString Menu::getSnapshotsLocation() const { return _snapshotsLocation; } +void Menu::setScriptsLocation(const QString& scriptsLocation) { + _scriptsLocation = scriptsLocation; + emit scriptLocationChanged(scriptsLocation); +} diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 723d320905..7d4b3f5f6a 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -93,6 +93,9 @@ public: QString getSnapshotsLocation() const; void setSnapshotsLocation(QString snapshotsLocation) { _snapshotsLocation = snapshotsLocation; } + const QString& getScriptsLocation() const { return _scriptsLocation; } + void setScriptsLocation(const QString& scriptsLocation); + BandwidthDialog* getBandwidthDialog() const { return _bandwidthDialog; } FrustumDrawMode getFrustumDrawMode() const { return _frustumDrawMode; } ViewFrustumOffset getViewFrustumOffset() const { return _viewFrustumOffset; } @@ -145,6 +148,9 @@ public: void static goToDomain(const QString newDomain); void static goTo(QString destination); +signals: + void scriptLocationChanged(const QString& newPath); + public slots: void loginForCurrentDomain(); @@ -261,6 +267,7 @@ private: QPointer _attachmentsDialog; QAction* _chatAction; QString _snapshotsLocation; + QString _scriptsLocation; }; namespace MenuOption { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index eed33fda23..3eb6f94195 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -29,6 +29,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : F connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser); connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser); connect(ui.buttonBrowseLocation, &QPushButton::clicked, this, &PreferencesDialog::openSnapshotLocationBrowser); + connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser); } void PreferencesDialog::accept() { @@ -70,13 +71,32 @@ void PreferencesDialog::openBodyModelBrowser() { void PreferencesDialog::openSnapshotLocationBrowser() { setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + show(); + QString dir = QFileDialog::getExistingDirectory(this, tr("Snapshots Location"), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!dir.isNull() && !dir.isEmpty()) { ui.snapshotLocationEdit->setText(dir); } + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + show(); +} + +void PreferencesDialog::openScriptsLocationBrowser() { + setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + show(); + + QString dir = QFileDialog::getExistingDirectory(this, tr("Scripts Location"), + ui.scriptsLocationEdit->text(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (!dir.isNull() && !dir.isEmpty()) { + ui.scriptsLocationEdit->setText(dir); + } + + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + show(); } void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) { @@ -116,6 +136,8 @@ void PreferencesDialog::loadPreferences() { ui.snapshotLocationEdit->setText(menuInstance->getSnapshotsLocation()); + ui.scriptsLocationEdit->setText(menuInstance->getScriptsLocation()); + ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() * ui.pupilDilationSlider->maximum()); @@ -169,6 +191,10 @@ void PreferencesDialog::savePreferences() { Menu::getInstance()->setSnapshotsLocation(ui.snapshotLocationEdit->text()); } + if (!ui.scriptsLocationEdit->text().isEmpty() && QDir(ui.scriptsLocationEdit->text()).exists()) { + Menu::getInstance()->setScriptsLocation(ui.scriptsLocationEdit->text()); + } + myAvatar->getHead()->setPupilDilation(ui.pupilDilationSlider->value() / (float)ui.pupilDilationSlider->maximum()); myAvatar->setLeanScale(ui.leanScaleSpin->value()); myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value()); diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index c52986cc5c..0573304eda 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -42,6 +42,7 @@ private slots: void setHeadUrl(QString modelUrl); void setSkeletonUrl(QString modelUrl); void openSnapshotLocationBrowser(); + void openScriptsLocationBrowser(); }; diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 61241f71fb..86db1c71fe 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -12,18 +12,27 @@ #include "ui_runningScriptsWidget.h" #include "RunningScriptsWidget.h" +#include +#include #include #include #include #include +#include +#include "ScriptListModel.h" #include "Application.h" + RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : FramelessDialog(parent, 0, POSITION_LEFT), - ui(new Ui::RunningScriptsWidget) { + ui(new Ui::RunningScriptsWidget), + _fsm(this), + _spm(this) { ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, false); + setAllowResize(false); ui->hideWidgetButton->setIcon(QIcon(Application::resourcesPath() + "images/close.svg")); @@ -31,6 +40,28 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : ui->stopAllButton->setIcon(QIcon(Application::resourcesPath() + "images/stop.svg")); ui->loadScriptButton->setIcon(QIcon(Application::resourcesPath() + "images/plus-white.svg")); + ui->recentlyLoadedScriptsArea->hide(); + + _fsm.setReadOnly(true); + _fsm.setRootPath(QDir("/Users/huffman/dev/hifi-19644/examples").absolutePath()); + _fsm.setFilter(QDir::NoDotAndDotDot | QDir::Files); + _fsm.setNameFilterDisables(false); + _fsm.setNameFilters(QStringList("*.js")); + _spm.setSourceModel(&_fsm); + _spm.sort(0, Qt::AscendingOrder); + _spm.setDynamicSortFilter(true); + ui->scriptListView->setModel(&_spm); + ui->scriptListView->setAttribute(Qt::WA_MacShowFocusRect, false); + ui->scriptListView->setRootIndex(_spm.mapFromSource(_fsm.index("/Users/huffman/dev/hifi-19644/examples"))); + connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter); + connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::scriptFileSelected); + + // QCompleter *completer = new QCompleter(this); + // completer->setModel(&_spm); + // completer->setCompletionMode(QCompleter::InlineCompletion); + // completer->setCaseSensitivity(Qt::CaseInsensitive); + // ui->filterLineEdit->setCompleter(completer); + _runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget); _runningScriptsTable->setColumnCount(2); _runningScriptsTable->setColumnWidth(0, 245); diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index 8a84956269..119b083d37 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -155,8 +155,8 @@ color: #0e7077 0 0 - 615 - 936 + 600 + 1039 @@ -612,6 +612,145 @@ color: #0e7077 + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + Scripts + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + Load scripts from this directory: + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + 0 + + + snapshotLocationEdit + + + + + + + + + + 0 + 0 + + + + + Arial + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + 30 + 30 + + + + + + + + + + + 30 + 30 + + + + + + From 35151b5dada5dace965f4163a6b6b1f5a23a26ed Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 01:18:08 -0700 Subject: [PATCH 02/37] Add ScripsModel --- interface/src/ScriptsModel.cpp | 209 +++++++++++++++++++++++++++++++++ interface/src/ScriptsModel.h | 62 ++++++++++ 2 files changed, 271 insertions(+) create mode 100644 interface/src/ScriptsModel.cpp create mode 100644 interface/src/ScriptsModel.h diff --git a/interface/src/ScriptsModel.cpp b/interface/src/ScriptsModel.cpp new file mode 100644 index 0000000000..2b819c04b1 --- /dev/null +++ b/interface/src/ScriptsModel.cpp @@ -0,0 +1,209 @@ +// +// ScriptsModel.cpp +// interface/src +// +// Created by Ryan Huffman on 05/12/14. +// Copyright 2014 High Fidelity, Inc. +// +// S3 request code written with ModelBrowser as a reference. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include + +#include "ScriptsModel.h" +#include "Menu.h" + + +static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; +static const QString PUBLIC_URL = "http://public.highfidelity.io"; +static const QString MODELS_LOCATION = "scripts/"; + +static const QString PREFIX_PARAMETER_NAME = "prefix"; +static const QString MARKER_PARAMETER_NAME = "marker"; +static const QString IS_TRUNCATED_NAME = "IsTruncated"; +static const QString CONTAINER_NAME = "Contents"; +static const QString KEY_NAME = "Key"; + +static const int SCRIPT_PATH = Qt::UserRole; + +ScriptItem::ScriptItem(const QString& filename, const QString& fullPath) : + _filename(filename), + _fullPath(fullPath) { +}; + +ScriptsModel::ScriptsModel(QObject* parent) : + QAbstractListModel(parent), + _loadingScripts(false), + _localDirectory(), + _fsWatcher(), + _localFiles(), + _remoteFiles() { + + QString scriptPath = Menu::getInstance()->getScriptsLocation(); + + _localDirectory.setPath(scriptPath); + _localDirectory.setFilter(QDir::Files | QDir::Readable); + _localDirectory.setNameFilters(QStringList("*.js")); + + _fsWatcher.addPath(_localDirectory.absolutePath()); + connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles); + + connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation); + + reloadLocalFiles(); + reloadRemoteFiles(); +} + +QVariant ScriptsModel::data(const QModelIndex& index, int role) const { + const QList* files = NULL; + int row = 0; + bool isLocal = index.row() < _localFiles.size(); + if (isLocal) { + files = &_localFiles; + row = index.row(); + } else { + files = &_remoteFiles; + row = index.row() - _localFiles.size(); + } + + if (role == Qt::DisplayRole) { + return QVariant((*files)[row]->getFilename() + (isLocal ? "" : " (remote)")); + } else if (role == ScriptPath) { + return QVariant((*files)[row]->getFullPath()); + } + return QVariant(); +} + +int ScriptsModel::rowCount(const QModelIndex& parent) const { + if (parent.isValid()) { + return 0; + } + return _localFiles.length() + _remoteFiles.length(); +} + +void ScriptsModel::updateScriptsLocation(const QString& newPath) { + _fsWatcher.removePath(_localDirectory.absolutePath()); + _localDirectory.setPath(newPath); + _fsWatcher.addPath(_localDirectory.absolutePath()); + reloadLocalFiles(); +} + +void ScriptsModel::reloadRemoteFiles() { + if (!_loadingScripts) { + _loadingScripts = true; + while (!_remoteFiles.isEmpty()) { + delete _remoteFiles.takeFirst(); + } + requestRemoteFiles(); + } +} + +void ScriptsModel::requestRemoteFiles(QString marker) { + QUrl url(S3_URL); + QUrlQuery query; + query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION); + if (!marker.isEmpty()) { + query.addQueryItem(MARKER_PARAMETER_NAME, marker); + } + url.setQuery(query); + + QNetworkAccessManager* accessManager = new QNetworkAccessManager(this); + connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*))); + + QNetworkRequest request(url); + accessManager->get(request); +} + +void ScriptsModel::downloadFinished(QNetworkReply* reply) { + bool finished = true; + + if (reply->error() == QNetworkReply::NoError) { + QByteArray data = reply->readAll(); + + if (!data.isEmpty()) { + finished = parseXML(data); + } else { + qDebug() << "Error: Received no data when loading remote scripts"; + } + } + + reply->deleteLater(); + sender()->deleteLater(); + + if (finished) { + _loadingScripts = false; + } +} + +bool ScriptsModel::parseXML(QByteArray xmlFile) { + beginResetModel(); + + QXmlStreamReader xml(xmlFile); + QRegExp jsRegex(".*\\.js"); + bool truncated = false; + QString lastKey; + + while (!xml.atEnd() && !xml.hasError()) { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { + while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { + xml.readNext(); + if (xml.text().toString() == "true") { + truncated = true; + } + } + } + + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { + while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { + if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { + xml.readNext(); + lastKey = xml.text().toString(); + if (jsRegex.exactMatch(xml.text().toString())) { + _remoteFiles.append(new ScriptItem(lastKey.mid(MODELS_LOCATION.length()), S3_URL + "/" + lastKey)); + } + } + xml.readNext(); + } + } + xml.readNext(); + } + + endResetModel(); + + // Error handling + if (xml.hasError()) { + qDebug() << "Error loading remote scripts: " << xml.errorString(); + return true; + } + + if (truncated) { + requestRemoteFiles(lastKey); + } + + // If this request was not truncated, we are done. + return !truncated; +} + +void ScriptsModel::reloadLocalFiles() { + beginResetModel(); + + while (!_localFiles.isEmpty()) { + delete _localFiles.takeFirst(); + } + + _localDirectory.refresh(); + + const QFileInfoList localFiles = _localDirectory.entryInfoList(); + for (int i = 0; i < localFiles.size(); i++) { + QFileInfo file = localFiles[i]; + _localFiles.append(new ScriptItem(file.fileName(), file.absoluteFilePath())); + } + + endResetModel(); +} diff --git a/interface/src/ScriptsModel.h b/interface/src/ScriptsModel.h new file mode 100644 index 0000000000..250c7eb9a8 --- /dev/null +++ b/interface/src/ScriptsModel.h @@ -0,0 +1,62 @@ +// +// ScriptsModel.h +// interface/src +// +// Created by Ryan Huffman on 05/12/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScriptsModel_h +#define hifi_ScriptsModel_h + +#include +#include +#include +#include + +class ScriptItem { +public: + ScriptItem(const QString& filename, const QString& fullPath); + + const QString& getFilename() { return _filename; }; + const QString& getFullPath() { return _fullPath; }; + +private: + QString _filename; + QString _fullPath; +}; + +class ScriptsModel : public QAbstractListModel { + Q_OBJECT +public: + ScriptsModel(QObject* parent = NULL); + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + + enum Role { + ScriptPath = Qt::UserRole, + }; + +protected slots: + void updateScriptsLocation(const QString& newPath); + void downloadFinished(QNetworkReply* reply); + void reloadLocalFiles(); + void reloadRemoteFiles(); + +protected: + void requestRemoteFiles(QString marker = QString()); + bool parseXML(QByteArray xmlFile); + +private: + bool _loadingScripts; + QDir _localDirectory; + QFileSystemWatcher _fsWatcher; + QList _localFiles; + QList _remoteFiles; +}; + +#endif // hifi_ScriptsModel_h From 03f9c0c7e23a4b0232fcd490576d3f1d6d1265c7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 01:42:24 -0700 Subject: [PATCH 03/37] Update Running Scripts Widget to use ScriptModel --- interface/src/ui/RunningScriptsWidget.cpp | 44 +- interface/src/ui/RunningScriptsWidget.h | 9 + interface/ui/runningScriptsWidget.ui | 792 +++++++++++++++------- 3 files changed, 598 insertions(+), 247 deletions(-) diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 86db1c71fe..5eac34731f 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -3,6 +3,7 @@ // interface/src/ui // // Created by Mohammed Nafees on 03/28/2014. +// Updated by Ryan Huffman on 05/13/2014. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -18,17 +19,17 @@ #include #include #include -#include -#include "ScriptListModel.h" #include "Application.h" +#include "Menu.h" +#include "ScriptsModel.h" RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : FramelessDialog(parent, 0, POSITION_LEFT), ui(new Ui::RunningScriptsWidget), - _fsm(this), - _spm(this) { + _scriptsModel(this), + _proxyModel(this) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, false); @@ -42,17 +43,13 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : ui->recentlyLoadedScriptsArea->hide(); - _fsm.setReadOnly(true); - _fsm.setRootPath(QDir("/Users/huffman/dev/hifi-19644/examples").absolutePath()); - _fsm.setFilter(QDir::NoDotAndDotDot | QDir::Files); - _fsm.setNameFilterDisables(false); - _fsm.setNameFilters(QStringList("*.js")); - _spm.setSourceModel(&_fsm); - _spm.sort(0, Qt::AscendingOrder); - _spm.setDynamicSortFilter(true); - ui->scriptListView->setModel(&_spm); + QString scriptPath = "/Users/huffman/dev/hifi-19644/examples";//Application::getInstance()->getPreviousScriptLocation(); + + _proxyModel.setSourceModel(&_scriptsModel); + _proxyModel.sort(0, Qt::AscendingOrder); + _proxyModel.setDynamicSortFilter(true); + ui->scriptListView->setModel(&_proxyModel); ui->scriptListView->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->scriptListView->setRootIndex(_spm.mapFromSource(_fsm.index("/Users/huffman/dev/hifi-19644/examples"))); connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter); connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::scriptFileSelected); @@ -88,6 +85,17 @@ RunningScriptsWidget::~RunningScriptsWidget() { delete ui; } +void RunningScriptsWidget::updateFileFilter(const QString& filter) { + QRegExp regex("^.*" + QRegExp::escape(filter) + ".*$", Qt::CaseInsensitive); + _proxyModel.setFilterRegExp(regex); +} + +void RunningScriptsWidget::scriptFileSelected(const QModelIndex& index) { + QVariant scriptFile = _proxyModel.data(index, ScriptsModel::ScriptPath); + qDebug() << "Loading: " << scriptFile.toString(); + Application::getInstance()->loadScript(scriptFile.toString()); +} + void RunningScriptsWidget::setBoundary(const QRect& rect) { _boundary = rect; } @@ -126,8 +134,6 @@ void RunningScriptsWidget::setRunningScripts(const QStringList& list) { ui->runningScriptsTableWidget->resize(ui->runningScriptsTableWidget->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN); _runningScriptsTable->resize(_runningScriptsTable->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN); - ui->reloadAllButton->move(ui->reloadAllButton->x(), y); - ui->stopAllButton->move(ui->stopAllButton->x(), y); ui->recentlyLoadedLabel->move(ui->recentlyLoadedLabel->x(), ui->stopAllButton->y() + ui->stopAllButton->height() + RECENTLY_LOADED_TOP_MARGIN); ui->recentlyLoadedScriptsTableWidget->move(ui->recentlyLoadedScriptsTableWidget->x(), @@ -173,10 +179,12 @@ void RunningScriptsWidget::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setPen(QColor::fromRgb(225, 225, 225)); // #e1e1e1 + const QPoint& labelPos = ui->runningScriptsArea->mapToParent(ui->currentlyRunningLabel->pos()); + if (ui->currentlyRunningLabel->isVisible()) { // line below the 'Currently Running' label - painter.drawLine(36, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height(), - 300, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height()); + painter.drawLine(36, labelPos.y() + ui->currentlyRunningLabel->height(), + 300, labelPos.y() + ui->currentlyRunningLabel->height()); } if (ui->recentlyLoadedLabel->isVisible()) { diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index ad310c4ed4..6b45ac2202 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -3,6 +3,7 @@ // interface/src/ui // // Created by Mohammed Nafees on 03/28/2014. +// Updated by Ryan Huffman on 05/13/2014. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -12,6 +13,10 @@ #ifndef hifi_RunningScriptsWidget_h #define hifi_RunningScriptsWidget_h +#include +#include + +#include "ScriptsModel.h" #include "FramelessDialog.h" #include "ScriptsTableWidget.h" @@ -42,9 +47,13 @@ private slots: void stopScript(int row, int column); void loadScript(int row, int column); void allScriptsStopped(); + void updateFileFilter(const QString& filter); + void scriptFileSelected(const QModelIndex& index); private: Ui::RunningScriptsWidget* ui; + QSortFilterProxyModel _proxyModel; + ScriptsModel _scriptsModel; ScriptsTableWidget* _runningScriptsTable; ScriptsTableWidget* _recentlyLoadedScriptsTable; QStringList _recentlyLoadedScripts; diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui index 6abd0f2d5a..324a460d1b 100644 --- a/interface/ui/runningScriptsWidget.ui +++ b/interface/ui/runningScriptsWidget.ui @@ -6,8 +6,8 @@ 0 0 - 323 - 894 + 324 + 971 @@ -21,252 +21,586 @@ QWidget { background: #f7f7f7; } - - - - 37 - 29 - 251 - 20 - + + + 20 - - color: #0e7077; + + 20 + + + 20 + + + 20 + + + + + + 0 + + + 0 + + + 0 + + + + + color: #0e7077; font-size: 20pt; background: transparent; - - - <html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html> - - - 0 - - - -1 - - - - - - 36 - 110 - 270 - 20 - - - - color: #0e7077; -font: bold 14pt; + + + <html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html> + + + 0 + + + -1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PointingHandCursor + + + border: 0 + + + + + + + 16 + 16 + + + + true + + + + + + + + + + + 0 + 1 + + + + + 0 + 141 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Helvetica,Arial,sans-serif + 16 + 75 + false + true + + + + color: #0e7077; +font: bold 16pt; background: transparent; - - - <html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html> - - - - - - 36 - 270 - 111 - 35 - - - - PointingHandCursor - - - false - - - background: #0e7077; + + + <html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html> + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 0 + 8 + + + + + + + + + 0 + 0 + + + + + 24 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 111 + 35 + + + + PointingHandCursor + + + false + + + background: #0e7077; color: #fff; border-radius: 4px; font: bold 14pt; padding-top: 3px; - - - Reload all - - - - ../resources/images/reload.svg../resources/images/reload.svg - - - - - - 160 - 270 - 93 - 35 - - - - PointingHandCursor - - - background: #0e7077; + + + Reload all + + + + ../../../../../../../../.designer/resources/images/reload.svg../../../../../../../../.designer/resources/images/reload.svg + + + + + + + + 0 + 0 + + + + + 111 + 35 + + + + PointingHandCursor + + + background: #0e7077; color: #fff; border-radius: 4px; font: bold 14pt; padding-top: 3px; - - - Stop all - - - - ../resources/images/stop.svg../resources/images/stop.svg - - - - - - 36 - 320 - 265 - 20 - - - - color: #0e7077; -font: bold 14pt; - - - <html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html> - - - - - - 36 - 630 - 211 - 41 - - - - color: #95a5a6; + + + Stop all + + + + ../../../../../../../../.designer/resources/images/stop.svg../../../../../../../../.designer/resources/images/stop.svg + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 8 + + + + + + + + font: 14pt; + + + There are no scripts currently running. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + 0 + 1 + + + + + 284 + 0 + + + + 0 + + + background: transparent; font-size: 14pt; - - - (click a script or use the 1-9 keys to load and run it) - - - true - - - - - - 285 - 29 - 16 - 16 - - - - PointingHandCursor - - - - - - - 16 - 16 - - - - true - - - - - - 36 - 110 - 271 - 51 - - - - font: 14pt; - - - There are no scripts currently running. - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - 30 - 340 - 272 - 280 - - - - background: transparent; + + + + + + + + + + true + + + + 0 + 100 + + + + + 16777215 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + color: #0e7077; +font: bold 16pt; + + + <html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html> + + + + + + + font: 14pt; + + + There are no recently loaded scripts. + + + + + + + + 0 + 1 + + + + + 284 + 0 + + + + background: transparent; font-size: 14pt; - - - - - - 30 - 128 - 272 - 161 - - - - background: transparent; + + + + + + + color: #95a5a6; font-size: 14pt; - - - - - - 36 - 70 - 111 - 35 - - - - PointingHandCursor - - - background: #0e7077; + + + (click a script or use the 1-9 keys to load and run it) + + + true + + + + + + + + + + + 0 + 1 + + + + + 0 + 300 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 15 + + + + + color: #0e7077; +font: bold 16pt; + + + Scripts + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 111 + 35 + + + + PointingHandCursor + + + background: #0e7077; color: #fff; border-radius: 4px; font: bold 14pt; padding-top: 3px; - - - Load script - - - - ../resources/images/plus.svg../resources/images/plus.svg - - - widgetTitle - currentlyRunningLabel - recentlyLoadedLabel - recentlyLoadedInstruction - hideWidgetButton - recentlyLoadedScriptsTableWidget - runningScriptsTableWidget - noRunningScriptsLabel - reloadAllButton - stopAllButton - loadScriptButton + + + Load script + + + + ../../../../../../../../.designer/resources/images/plus.svg../../../../../../../../.designer/resources/images/plus.svg + + + + + + + + + + border: 1px solid rgb(128, 128, 128); +border-radius: 2px; +padding: 4px; +background-color: white; + + + filter + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + border: 1px solid rgb(128, 128, 128); +border-radius: 2px; + + + QFrame::Box + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAsNeeded + + + + + + + From 90d3dd3ceb980c0ef582c1adac87f1e58bc596f0 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 01:43:37 -0700 Subject: [PATCH 04/37] Remove unused lines --- interface/src/ui/RunningScriptsWidget.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 5eac34731f..5a7c2e7925 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -43,8 +43,6 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : ui->recentlyLoadedScriptsArea->hide(); - QString scriptPath = "/Users/huffman/dev/hifi-19644/examples";//Application::getInstance()->getPreviousScriptLocation(); - _proxyModel.setSourceModel(&_scriptsModel); _proxyModel.sort(0, Qt::AscendingOrder); _proxyModel.setDynamicSortFilter(true); @@ -53,12 +51,6 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter); connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::scriptFileSelected); - // QCompleter *completer = new QCompleter(this); - // completer->setModel(&_spm); - // completer->setCompletionMode(QCompleter::InlineCompletion); - // completer->setCaseSensitivity(Qt::CaseInsensitive); - // ui->filterLineEdit->setCompleter(completer); - _runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget); _runningScriptsTable->setColumnCount(2); _runningScriptsTable->setColumnWidth(0, 245); From 6e80b97efe3774c3690d1ce4711db1be1537d17d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 01:44:35 -0700 Subject: [PATCH 05/37] Disable hiding of running script area when none are running --- interface/src/ui/RunningScriptsWidget.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 5a7c2e7925..866297f92d 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -96,7 +96,6 @@ void RunningScriptsWidget::setRunningScripts(const QStringList& list) { _runningScriptsTable->setRowCount(list.size()); ui->noRunningScriptsLabel->setVisible(list.isEmpty()); - ui->currentlyRunningLabel->setVisible(!list.isEmpty()); ui->runningScriptsTableWidget->setVisible(!list.isEmpty()); ui->reloadAllButton->setVisible(!list.isEmpty()); ui->stopAllButton->setVisible(!list.isEmpty()); @@ -220,7 +219,7 @@ void RunningScriptsWidget::createRecentlyLoadedScriptsTable() { } } - ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty()); + ui->noRecentlyLoadedLabel->setVisible(_recentlyLoadedScripts.isEmpty()); ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty()); ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty()); From a1d5d44bc12630e80e6314788a21a5b7efd85b8f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 14 May 2014 01:45:07 -0700 Subject: [PATCH 06/37] Update RunningScripts to be created after Menu has been created --- interface/src/Application.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b50e9ef1dc..ec6f0d9eff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -168,7 +168,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _bytesPerSecond(0), _previousScriptLocation(), _logger(new FileLogger(this)), - _runningScriptsWidget(new RunningScriptsWidget(_window)), + _runningScriptsWidget(NULL), _runningScriptsWidgetWasVisible(false) { // init GnuTLS for DTLS with domain-servers @@ -201,6 +201,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // call Menu getInstance static method to set up the menu _window->setMenuBar(Menu::getInstance()); + _runningScriptsWidget = new RunningScriptsWidget(_window); + unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast(argv); const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); From 2eeb3f5f2f078e90fae0d808258011d3c0cd8125 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 May 2014 16:56:51 -0700 Subject: [PATCH 07/37] Update loadScript to optionally activate main window --- interface/src/Application.cpp | 4 ++-- interface/src/Application.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8509bd96d1..e89c3d9853 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3412,7 +3412,7 @@ void Application::saveScripts() { _settings->endArray(); } -ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor) { +ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor, bool activateMainWindow) { if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptName) && !_scriptEnginesHash[scriptName]->isFinished()){ return _scriptEnginesHash[scriptName]; } @@ -3486,7 +3486,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript workerThread->start(); // restore the main window's active state - if (!loadScriptFromEditor) { + if (activateMainWindow && !loadScriptFromEditor) { _window->activateWindow(); } bumpSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 1968ef4fee..17200a9419 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -303,7 +303,7 @@ public slots: void loadScriptURLDialog(); void toggleLogDialog(); void initAvatarAndViewFrustum(); - ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false); + ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false, bool activateMainWindow = false); void scriptFinished(const QString& scriptName); void stopAllScripts(bool restart = false); void stopScript(const QString& scriptName); From b108ea31403cfdd032da606f611e725a76d7aeb1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 May 2014 16:57:13 -0700 Subject: [PATCH 08/37] Update script list to only label local files --- interface/src/ScriptsModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ScriptsModel.cpp b/interface/src/ScriptsModel.cpp index 2b819c04b1..f9ed94f3fa 100644 --- a/interface/src/ScriptsModel.cpp +++ b/interface/src/ScriptsModel.cpp @@ -73,7 +73,7 @@ QVariant ScriptsModel::data(const QModelIndex& index, int role) const { } if (role == Qt::DisplayRole) { - return QVariant((*files)[row]->getFilename() + (isLocal ? "" : " (remote)")); + return QVariant((*files)[row]->getFilename() + (isLocal ? " (local)" : "")); } else if (role == ScriptPath) { return QVariant((*files)[row]->getFullPath()); } From c428e6896241752a423c06b9740b394fe573883a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 May 2014 17:03:07 -0700 Subject: [PATCH 09/37] Update script window to load script when pressing enter --- interface/src/ui/RunningScriptsWidget.cpp | 68 ++++++++++++++++++----- interface/src/ui/RunningScriptsWidget.h | 7 ++- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index b897c96c36..46989200cc 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -13,7 +13,6 @@ #include "ui_runningScriptsWidget.h" #include "RunningScriptsWidget.h" -#include #include #include #include @@ -43,13 +42,18 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : ui->recentlyLoadedScriptsArea->hide(); + ui->filterLineEdit->installEventFilter(this); + + connect(&_proxyModel, &QSortFilterProxyModel::modelReset, + this, &RunningScriptsWidget::selectFirstInList); + _proxyModel.setSourceModel(&_scriptsModel); _proxyModel.sort(0, Qt::AscendingOrder); _proxyModel.setDynamicSortFilter(true); ui->scriptListView->setModel(&_proxyModel); - ui->scriptListView->setAttribute(Qt::WA_MacShowFocusRect, false); + connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter); - connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::scriptFileSelected); + connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList); _runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget); _runningScriptsTable->setColumnCount(2); @@ -80,12 +84,19 @@ RunningScriptsWidget::~RunningScriptsWidget() { void RunningScriptsWidget::updateFileFilter(const QString& filter) { QRegExp regex("^.*" + QRegExp::escape(filter) + ".*$", Qt::CaseInsensitive); _proxyModel.setFilterRegExp(regex); + selectFirstInList(); } -void RunningScriptsWidget::scriptFileSelected(const QModelIndex& index) { +void RunningScriptsWidget::loadScriptFromList(const QModelIndex& index) { QVariant scriptFile = _proxyModel.data(index, ScriptsModel::ScriptPath); - qDebug() << "Loading: " << scriptFile.toString(); - Application::getInstance()->loadScript(scriptFile.toString()); + Application::getInstance()->loadScript(scriptFile.toString(), false, false); +} + +void RunningScriptsWidget::loadSelectedScript() { + QModelIndex selectedIndex = ui->scriptListView->currentIndex(); + if (selectedIndex.isValid()) { + loadScriptFromList(selectedIndex); + } } void RunningScriptsWidget::setBoundary(const QRect& rect) { @@ -134,14 +145,43 @@ void RunningScriptsWidget::setRunningScripts(const QStringList& list) { createRecentlyLoadedScriptsTable(); } -void RunningScriptsWidget::keyPressEvent(QKeyEvent* event) -{ +void RunningScriptsWidget::showEvent(QShowEvent* event) { + if (!event->spontaneous()) { + ui->filterLineEdit->setFocus(); + } + + FramelessDialog::showEvent(event); +} + +void RunningScriptsWidget::selectFirstInList() { + if (_proxyModel.rowCount() > 0) { + ui->scriptListView->setCurrentIndex(_proxyModel.index(0, 0)); + } +} + +bool RunningScriptsWidget::eventFilter(QObject* sender, QEvent* event) { + if (sender == ui->filterLineEdit) { + if (event->type() != QEvent::KeyPress) { + return false; + } + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + QModelIndex selectedIndex = ui->scriptListView->currentIndex(); + if (selectedIndex.isValid()) { + loadScriptFromList(selectedIndex); + } + event->accept(); + return true; + } + return false; + } + + return FramelessDialog::eventFilter(sender, event); +} + +void RunningScriptsWidget::keyPressEvent(QKeyEvent* event) { int loadScriptNumber = -1; switch(event->key()) { - case Qt::Key_Escape: - Application::getInstance()->toggleRunningScriptsWidget(); - break; - case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: @@ -159,7 +199,7 @@ void RunningScriptsWidget::keyPressEvent(QKeyEvent* event) } if (loadScriptNumber >= 0) { if (_recentlyLoadedScripts.size() > loadScriptNumber) { - Application::getInstance()->loadScript(_recentlyLoadedScripts.at(loadScriptNumber)); + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(loadScriptNumber), false, false); } } @@ -199,7 +239,7 @@ void RunningScriptsWidget::stopScript(int row, int column) { } void RunningScriptsWidget::loadScript(int row, int column) { - Application::getInstance()->loadScript(_recentlyLoadedScriptsTable->item(row, column)->toolTip()); + Application::getInstance()->loadScript(_recentlyLoadedScriptsTable->item(row, column)->toolTip(), false, false); } void RunningScriptsWidget::allScriptsStopped() { diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index 8170cb65b8..7b03898ece 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -37,8 +37,11 @@ signals: void stopScriptName(const QString& name); protected: + virtual bool eventFilter(QObject* sender, QEvent* event); + virtual void keyPressEvent(QKeyEvent* event); virtual void paintEvent(QPaintEvent* event); + virtual void showEvent(QShowEvent* event); public slots: void scriptStopped(const QString& scriptName); @@ -49,7 +52,9 @@ private slots: void loadScript(int row, int column); void allScriptsStopped(); void updateFileFilter(const QString& filter); - void scriptFileSelected(const QModelIndex& index); + void loadScriptFromList(const QModelIndex& index); + void loadSelectedScript(); + void selectFirstInList(); private: Ui::RunningScriptsWidget* ui; From e21a7524c6b1a206910b74ce0cfc453added7ae9 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 May 2014 17:03:19 -0700 Subject: [PATCH 10/37] Update runningscripts style --- interface/ui/runningScriptsWidget.ui | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui index 747b29abf2..063b2cd3b5 100644 --- a/interface/ui/runningScriptsWidget.ui +++ b/interface/ui/runningScriptsWidget.ui @@ -434,7 +434,7 @@ font-size: 14pt; 0 - 1 + 2 @@ -544,9 +544,15 @@ border-radius: 2px; padding: 4px; background-color: white; + + + filter + + true + @@ -568,8 +574,14 @@ background-color: white; - border: 1px solid rgb(128, 128, 128); -border-radius: 2px; + QListView { + border: 1px solid rgb(128, 128, 128); + border-radius: 2px; +} +QListView::item { + padding-top: 2px; + padding-bottom: 2px; +} QFrame::Box From 0877f9f15930e6fcd531b69e61fb85f40e93ee4a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 Jun 2014 20:19:49 -0700 Subject: [PATCH 11/37] Fix issues with running scripts window on Windows Replace running scripts table with a new widget that uses a proper layout that doesn't rely on hard-coded sizing. Custom painted lines were replaced with a horizontal QFrame line. --- interface/src/ui/RunningScriptsWidget.cpp | 161 +++++++--------------- interface/src/ui/RunningScriptsWidget.h | 8 +- interface/ui/runningScriptsWidget.ui | 97 +++++++++---- 3 files changed, 116 insertions(+), 150 deletions(-) diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 449b11c89c..b2ee36d1fe 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -27,6 +27,7 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : FramelessDialog(parent, 0, POSITION_LEFT), ui(new Ui::RunningScriptsWidget), + _signalMapper(this), _scriptsModel(this), _proxyModel(this) { ui->setupUi(this); @@ -55,17 +56,9 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter); connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList); - _runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget); - _runningScriptsTable->setColumnCount(2); - _runningScriptsTable->setColumnWidth(0, 245); - _runningScriptsTable->setColumnWidth(1, 22); - connect(_runningScriptsTable, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript); - _recentlyLoadedScriptsTable = new ScriptsTableWidget(ui->recentlyLoadedScriptsTableWidget); _recentlyLoadedScriptsTable->setColumnCount(1); _recentlyLoadedScriptsTable->setColumnWidth(0, 265); - connect(_recentlyLoadedScriptsTable, &QTableWidget::cellClicked, - this, &RunningScriptsWidget::loadScript); connect(ui->hideWidgetButton, &QPushButton::clicked, Application::getInstance(), &Application::toggleRunningScriptsWidget); @@ -75,6 +68,7 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) : this, &RunningScriptsWidget::allScriptsStopped); connect(ui->loadScriptButton, &QPushButton::clicked, Application::getInstance(), &Application::loadDialog); + connect(&_signalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(stopScript(const QString&))); } RunningScriptsWidget::~RunningScriptsWidget() { @@ -104,45 +98,55 @@ void RunningScriptsWidget::setBoundary(const QRect& rect) { } void RunningScriptsWidget::setRunningScripts(const QStringList& list) { - _runningScriptsTable->setRowCount(list.size()); + setUpdatesEnabled(false); + QLayoutItem* widget; + while ((widget = ui->scrollAreaWidgetContents->layout()->takeAt(0)) != NULL) { + delete widget->widget(); + delete widget; + } + const int CLOSE_ICON_HEIGHT = 12; + for (int i = 0; i < list.size(); i++) { + QWidget* row = new QWidget(ui->scrollAreaWidgetContents); + row->setLayout(new QHBoxLayout(row)); + + QUrl url = QUrl(list.at(i)); + QLabel* name = new QLabel(url.fileName(), row); + QPushButton* closeButton = new QPushButton(row); + closeButton->setFlat(true); + closeButton->setIcon( + QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(CLOSE_ICON_HEIGHT))); + closeButton->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred)); + closeButton->setStyleSheet("border: 0;"); + closeButton->setCursor(Qt::PointingHandCursor); + + connect(closeButton, SIGNAL(clicked()), &_signalMapper, SLOT(map())); + _signalMapper.setMapping(closeButton, url.toString()); + + row->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + row->layout()->setContentsMargins(4, 4, 4, 4); + row->layout()->setSpacing(0); + + row->layout()->addWidget(name); + row->layout()->addWidget(closeButton); + + QFrame* line = new QFrame(row); + line->setFrameShape(QFrame::HLine); + line->setStyleSheet("color: #E1E1E1; margin-left: 6px; margin-right: 6px;"); + + ui->scrollAreaWidgetContents->layout()->addWidget(row); + ui->scrollAreaWidgetContents->layout()->addWidget(line); + } + ui->noRunningScriptsLabel->setVisible(list.isEmpty()); - ui->runningScriptsTableWidget->setVisible(!list.isEmpty()); ui->reloadAllButton->setVisible(!list.isEmpty()); ui->stopAllButton->setVisible(!list.isEmpty()); - const int CLOSE_ICON_HEIGHT = 12; - - for (int i = 0; i < list.size(); ++i) { - QTableWidgetItem *scriptName = new QTableWidgetItem; - scriptName->setText(QFileInfo(list.at(i)).fileName()); - scriptName->setToolTip(list.at(i)); - scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); - QTableWidgetItem *closeIcon = new QTableWidgetItem; - closeIcon->setIcon(QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(CLOSE_ICON_HEIGHT))); - - _runningScriptsTable->setItem(i, 0, scriptName); - _runningScriptsTable->setItem(i, 1, closeIcon); - } - - const int RUNNING_SCRIPTS_TABLE_LEFT_MARGIN = 12; - const int RECENTLY_LOADED_TOP_MARGIN = 61; - const int RECENTLY_LOADED_LABEL_TOP_MARGIN = 19; - - int y = ui->runningScriptsTableWidget->y() + RUNNING_SCRIPTS_TABLE_LEFT_MARGIN; - for (int i = 0; i < _runningScriptsTable->rowCount(); ++i) { - y += _runningScriptsTable->rowHeight(i); - } - - ui->runningScriptsTableWidget->resize(ui->runningScriptsTableWidget->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN); - _runningScriptsTable->resize(_runningScriptsTable->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN); - ui->recentlyLoadedLabel->move(ui->recentlyLoadedLabel->x(), - ui->stopAllButton->y() + ui->stopAllButton->height() + RECENTLY_LOADED_TOP_MARGIN); - ui->recentlyLoadedScriptsTableWidget->move(ui->recentlyLoadedScriptsTableWidget->x(), - ui->recentlyLoadedLabel->y() + RECENTLY_LOADED_LABEL_TOP_MARGIN); - - - createRecentlyLoadedScriptsTable(); + ui->scrollAreaWidgetContents->updateGeometry(); + setUpdatesEnabled(true); + Application::processEvents(); + repaint(); } void RunningScriptsWidget::showEvent(QShowEvent* event) { @@ -187,81 +191,10 @@ void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) { } } -void RunningScriptsWidget::paintEvent(QPaintEvent* event) { - QPainter painter(this); - painter.setPen(QColor::fromRgb(225, 225, 225)); // #e1e1e1 - - const QPoint& labelPos = ui->runningScriptsArea->mapToParent(ui->currentlyRunningLabel->pos()); - - if (ui->currentlyRunningLabel->isVisible()) { - // line below the 'Currently Running' label - painter.drawLine(36, labelPos.y() + ui->currentlyRunningLabel->height(), - 300, labelPos.y() + ui->currentlyRunningLabel->height()); - } - - if (ui->recentlyLoadedLabel->isVisible()) { - // line below the 'Recently loaded' label - painter.drawLine(36, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height(), - 300, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height()); - } - - 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(); - emit stopScriptName(_runningScriptsTable->item(row, 0)->toolTip()); - } -} - -void RunningScriptsWidget::loadScript(int row, int column) { - Application::getInstance()->loadScript(_recentlyLoadedScriptsTable->item(row, column)->toolTip(), false, false); + // _recentlyLoadedScripts.prepend(scriptName); } void RunningScriptsWidget::allScriptsStopped() { Application::getInstance()->stopAllScripts(); } - -void RunningScriptsWidget::createRecentlyLoadedScriptsTable() { - if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) { - _recentlyLoadedScripts.prepend(_lastStoppedScript); - _lastStoppedScript = ""; - } - - for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) { - if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) { - _recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i)); - } - } - - ui->noRecentlyLoadedLabel->setVisible(_recentlyLoadedScripts.isEmpty()); - ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty()); - ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty()); - - int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size(); - _recentlyLoadedScriptsTable->setRowCount(limit); - for (int i = 0; i < limit; i++) { - QTableWidgetItem *scriptName = new QTableWidgetItem; - scriptName->setText(QString::number(i + 1) + ". " + QFileInfo(_recentlyLoadedScripts.at(i)).fileName()); - scriptName->setToolTip(_recentlyLoadedScripts.at(i)); - scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); - - _recentlyLoadedScriptsTable->setItem(i, 0, scriptName); - } - - int y = ui->recentlyLoadedScriptsTableWidget->y() + 15; - for (int i = 0; i < _recentlyLoadedScriptsTable->rowCount(); ++i) { - y += _recentlyLoadedScriptsTable->rowHeight(i); - } - - ui->recentlyLoadedInstruction->setGeometry(36, y, - ui->recentlyLoadedInstruction->width(), - ui->recentlyLoadedInstruction->height()); - - repaint(); -} diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index 7b03898ece..6810aca487 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -14,6 +14,7 @@ #define hifi_RunningScriptsWidget_h #include +#include #include #include "ScriptsModel.h" @@ -40,7 +41,6 @@ protected: virtual bool eventFilter(QObject* sender, QEvent* event); virtual void keyPressEvent(QKeyEvent* event); - virtual void paintEvent(QPaintEvent* event); virtual void showEvent(QShowEvent* event); public slots: @@ -48,8 +48,6 @@ public slots: void setBoundary(const QRect& rect); private slots: - void stopScript(int row, int column); - void loadScript(int row, int column); void allScriptsStopped(); void updateFileFilter(const QString& filter); void loadScriptFromList(const QModelIndex& index); @@ -58,15 +56,13 @@ private slots: private: Ui::RunningScriptsWidget* ui; + QSignalMapper _signalMapper; QSortFilterProxyModel _proxyModel; ScriptsModel _scriptsModel; - ScriptsTableWidget* _runningScriptsTable; ScriptsTableWidget* _recentlyLoadedScriptsTable; QStringList _recentlyLoadedScripts; QString _lastStoppedScript; QRect _boundary; - - void createRecentlyLoadedScriptsTable(); }; #endif // hifi_RunningScriptsWidget_h diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui index 063b2cd3b5..2f51ab7a94 100644 --- a/interface/ui/runningScriptsWidget.ui +++ b/interface/ui/runningScriptsWidget.ui @@ -304,6 +304,9 @@ padding-top: 3px; Qt::Vertical + + QSizePolicy::Fixed + 20 @@ -313,26 +316,73 @@ padding-top: 3px; - - - - 0 - 1 - + + + + Helvetica,Arial,sans-serif + 14 + - - - 284 - 0 - - - - 0 + + Qt::LeftToRight - background: transparent; -font-size: 14pt; + margin: 0; + + QFrame::NoFrame + + + 0 + + + Qt::ScrollBarAlwaysOn + + + Qt::ScrollBarAlwaysOff + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 269 + 16 + + + + + 0 + 0 + + + + font-size: 14pt; + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + @@ -410,20 +460,7 @@ font: bold 16pt; background: transparent; font-size: 14pt; - - - - - - color: #95a5a6; -font-size: 14pt; - - - (click a script or use the 1-9 keys to load and run it) - - - true - + runningScriptsList From 108fa36d2461777eacfc1c284173424ae8543f7b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 20 Jun 2014 16:32:42 -0700 Subject: [PATCH 12/37] Disable the ability to load a script multiple times It won't show up correctly in running scripts, and can cause it to end up running in the background unnoticed. Also added an error message for when a script doesn't load. --- interface/src/Application.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 869fdc2770..72b0c79724 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3523,8 +3523,13 @@ void Application::saveScripts() { ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor, bool activateMainWindow) { QUrl scriptUrl(scriptName); const QString& scriptURLString = scriptUrl.toString(); - if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptURLString) && !_scriptEnginesHash[scriptURLString]->isFinished()){ - return _scriptEnginesHash[scriptURLString]; + if (_scriptEnginesHash.contains(scriptURLString)) { + if(loadScriptFromEditor && !_scriptEnginesHash[scriptURLString]->isFinished()) { + return _scriptEnginesHash[scriptURLString]; + } else { + QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " is already running."); + return NULL; + } } ScriptEngine* scriptEngine; @@ -3536,6 +3541,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; + QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load."); return NULL; } From e72429171ff86589fc0384bc23420562ff1b7cb8 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 23 Jun 2014 12:01:49 -0700 Subject: [PATCH 13/37] Working on congestion tests. --- tests/metavoxels/src/MetavoxelTests.cpp | 77 +++++++++++++++++++++---- tests/metavoxels/src/MetavoxelTests.h | 2 +- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 287d3a648c..609d36c085 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -335,7 +335,7 @@ bool MetavoxelTests::run() { QByteArray datagramHeader("testheader"); const int SIMULATION_ITERATIONS = 10000; if (test == 0 || test == 1) { - qDebug() << "Running transmission tests..."; + qDebug() << "Running transmission test..."; qDebug(); // create two endpoints with the same header @@ -365,7 +365,37 @@ bool MetavoxelTests::run() { } if (test == 0 || test == 2) { - qDebug() << "Running serialization tests..."; + qDebug() << "Running congestion control test..."; + qDebug(); + + // clear the stats + streamedBytesSent = streamedBytesReceived = datagramsSent = bytesSent = 0; + datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0; + + // create two endpoints with the same header + Endpoint alice(datagramHeader, Endpoint::CONGESTION_MODE), bob(datagramHeader, Endpoint::CONGESTION_MODE); + + alice.setOther(&bob); + bob.setOther(&alice); + + // perform a large number of simulation iterations + for (int i = 0; i < SIMULATION_ITERATIONS; i++) { + if (alice.simulate(i) || bob.simulate(i)) { + return true; + } + } + + qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; + qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << + datagramsReceived << "with" << bytesReceived << "bytes"; + qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet"; + qDebug() << "Average" << (bytesReceived / datagramsReceived) << "bytes per datagram"; + qDebug() << "Speed:" << (bytesReceived / SIMULATION_ITERATIONS) << "bytes per iteration"; + qDebug() << "Efficiency:" << ((float)streamedBytesReceived / bytesReceived); + } + + if (test == 0 || test == 3) { + qDebug() << "Running serialization test..."; qDebug(); if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) { @@ -373,8 +403,8 @@ bool MetavoxelTests::run() { } } - if (test == 0 || test == 3) { - qDebug() << "Running metavoxel data tests..."; + if (test == 0 || test == 4) { + qDebug() << "Running metavoxel data test..."; qDebug(); // clear the stats @@ -498,9 +528,15 @@ Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) : ReliableChannel* output = _sequencer->getReliableOutputChannel(1); output->setPriority(0.25f); output->setMessagesEnabled(false); - const int MIN_STREAM_BYTES = 100000; - const int MAX_STREAM_BYTES = 200000; - QByteArray bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES); + QByteArray bytes; + if (mode == CONGESTION_MODE) { + const int HUGE_STREAM_BYTES = 50 * 1024 * 1024; + bytes = createRandomBytes(HUGE_STREAM_BYTES, HUGE_STREAM_BYTES); + } else { + const int MIN_STREAM_BYTES = 100000; + const int MAX_STREAM_BYTES = 200000; + bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES); + } _dataStreamed.append(bytes); output->getBuffer().write(bytes); streamedBytesSent += bytes.size(); @@ -646,7 +682,16 @@ bool Endpoint::simulate(int iterationNumber) { int oldDatagramsSent = datagramsSent; int oldBytesSent = bytesSent; - if (_mode == METAVOXEL_CLIENT_MODE) { + if (_mode == CONGESTION_MODE) { + Bitstream& out = _sequencer->startPacket(); + out << QVariant(); + _sequencer->endPacket(); + + // record the send + SendRecord record = { _sequencer->getOutgoingPacketNumber() }; + _sendRecords.append(record); + + } else if (_mode == METAVOXEL_CLIENT_MODE) { Bitstream& out = _sequencer->startPacket(); ClientStateMessage state = { _lod }; @@ -748,13 +793,14 @@ void Endpoint::sendDatagram(const QByteArray& datagram) { // some datagrams are dropped const float DROP_PROBABILITY = 0.1f; - if (randFloat() < DROP_PROBABILITY) { + float probabilityMultiplier = (_mode == CONGESTION_MODE) ? 0.01f : 1.0f; + if (randFloat() < DROP_PROBABILITY * probabilityMultiplier) { return; } // some are received out of order const float REORDER_PROBABILITY = 0.1f; - if (randFloat() < REORDER_PROBABILITY) { + if (randFloat() < REORDER_PROBABILITY * probabilityMultiplier) { const int MIN_DELAY = 1; const int MAX_DELAY = 5; // have to copy the datagram; the one we're passed is a reference to a shared buffer @@ -763,7 +809,7 @@ void Endpoint::sendDatagram(const QByteArray& datagram) { // and some are duplicated const float DUPLICATE_PROBABILITY = 0.01f; - if (randFloat() > DUPLICATE_PROBABILITY) { + if (randFloat() > DUPLICATE_PROBABILITY * probabilityMultiplier) { return; } } @@ -788,6 +834,15 @@ void Endpoint::handleHighPriorityMessage(const QVariant& message) { } void Endpoint::readMessage(Bitstream& in) { + if (_mode == CONGESTION_MODE) { + QVariant message; + in >> message; + + // record the receipt + ReceiveRecord record = { _sequencer->getIncomingPacketNumber() }; + _receiveRecords.append(record); + return; + } if (_mode == METAVOXEL_CLIENT_MODE) { QVariant message; in >> message; diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index c340d78963..46bac319fa 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -40,7 +40,7 @@ class Endpoint : public QObject { public: - enum Mode { BASIC_PEER_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE }; + enum Mode { BASIC_PEER_MODE, CONGESTION_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE }; Endpoint(const QByteArray& datagramHeader, Mode mode = BASIC_PEER_MODE); From 556578b8dc83ef130935630410f3994b2fe361a0 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 24 Jun 2014 09:30:11 -0700 Subject: [PATCH 14/37] first pass - hair as vertlet strands --- examples/sit.js | 8 +- examples/squeezeHands.js | 4 +- interface/src/Application.cpp | 4 +- interface/src/Util.cpp | 1 + interface/src/avatar/Avatar.cpp | 192 ++++++++++++++++++++++++++++++ interface/src/avatar/Avatar.h | 24 ++++ interface/src/avatar/MyAvatar.cpp | 4 + 7 files changed, 229 insertions(+), 8 deletions(-) diff --git a/examples/sit.js b/examples/sit.js index d10c08c95a..87b4f232ca 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -49,12 +49,12 @@ var pose = [ {joint:"RightFoot", rotation: {x:30, y:15.0, z:0.0}}, {joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}}, {joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}}, - {joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}}, + {joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}} - {joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}}, + //{joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}}, - {joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}}, - {joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}} + //{joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}}, + //{joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}} ]; diff --git a/examples/squeezeHands.js b/examples/squeezeHands.js index e53dd9569c..da720734e1 100644 --- a/examples/squeezeHands.js +++ b/examples/squeezeHands.js @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnim.fbx"; -var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnim.fbx"; +var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnimPhilip.fbx"; +var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnimPhilip.fbx"; var LEFT = 0; var RIGHT = 1; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6b44503af4..9c257a7658 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -133,7 +133,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _nodeThread(new QThread(this)), _datagramProcessor(), _frameCount(0), - _fps(120.0f), + _fps(60.0f), _justStarted(true), _voxelImporter(NULL), _importSucceded(false), @@ -550,7 +550,7 @@ void Application::initializeGL() { } // update before the first render - update(0.0f); + update(1.f / _fps); InfoView::showFirstTime(); } diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 07ca65b286..30fda645ca 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -66,6 +66,7 @@ void printVector(glm::vec3 vec) { qDebug("%4.2f, %4.2f, %4.2f", vec.x, vec.y, vec.z); } + // Return the azimuth angle (in radians) between two points. float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos) { return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index baf46605fd..95399062ae 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -48,6 +48,10 @@ Avatar::Avatar() : _skeletonModel(this), _bodyYawDelta(0.0f), _velocity(0.0f, 0.0f, 0.0f), + _lastVelocity(0.0f, 0.0f, 0.0f), + _acceleration(0.0f, 0.0f, 0.0f), + _angularVelocity(0.0f, 0.0f, 0.0f), + _lastOrientation(), _leanScale(0.5f), _scale(1.0f), _worldUpDirection(DEFAULT_UP_DIRECTION), @@ -76,6 +80,7 @@ void Avatar::init() { _skeletonModel.init(); _initialized = true; _shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE); + initializeHair(); } glm::vec3 Avatar::getChestPosition() const { @@ -134,10 +139,13 @@ void Avatar::simulate(float deltaTime) { head->setPosition(headPosition); head->setScale(_scale); head->simulate(deltaTime, false, _shouldRenderBillboard); + + simulateHair(deltaTime); } // update position by velocity, and subtract the change added earlier for gravity _position += _velocity * deltaTime; + updateAcceleration(deltaTime); // update animation for display name fade in/out if ( _displayNameTargetAlpha != _displayNameAlpha) { @@ -157,6 +165,17 @@ void Avatar::simulate(float deltaTime) { } } +void Avatar::updateAcceleration(float deltaTime) { + // Linear Component of Acceleration + _acceleration = (_velocity - _lastVelocity) * (1.f / deltaTime); + _lastVelocity = _velocity; + // Angular Component of Acceleration + glm::quat orientation = getOrientation(); + glm::quat delta = glm::inverse(_lastOrientation) * orientation; + _angularVelocity = safeEulerAngles(delta) * (1.f / deltaTime); + _lastOrientation = getOrientation(); +} + void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) { _mouseRayOrigin = origin; _mouseRayDirection = direction; @@ -361,6 +380,179 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { getHand()->render(false, modelRenderMode); } getHead()->render(1.0f, modelRenderMode); + renderHair(); +} + +const float HAIR_LENGTH = 0.5f; +const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS; +const float HAIR_DAMPING = 0.99f; +const float HEAD_RADIUS = 0.20f; +const float COLLISION_RELAXATION = 10.f; +const float CONSTRAINT_RELAXATION = 10.0f; +const float NOISE = 0.0f; // 0.1f; +const float NOISE_MAGNITUDE = 0.02f; +const glm::vec3 HAIR_GRAVITY(0.f, -0.05f, 0.f); +const float HAIR_ACCELERATION_COUPLING = 0.025f; +const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.15f; +const float HAIR_MAX_LINEAR_ACCELERATION = 5.f; +const float HAIR_THICKNESS = 0.015f; +const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f); +const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f); + +void Avatar::renderHair() { + // + // Render the avatar's moveable hair + // + glm::vec3 headPosition = getHead()->getPosition(); + + glPushMatrix(); + glTranslatef(headPosition.x, headPosition.y, headPosition.z); + const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame(); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + glBegin(GL_QUADS); + for (int strand = 0; strand < HAIR_STRANDS; strand++) { + for (int link = 0; link < HAIR_LINKS - 1; link++) { + int vertexIndex = strand * HAIR_LINKS + link; + glColor3fv(&_hairColors[vertexIndex].x); + glNormal3fv(&_hairNormals[vertexIndex].x); + glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x, + _hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y, + _hairPosition[vertexIndex].z - _hairQuadDelta[vertexIndex].z); + glVertex3f(_hairPosition[vertexIndex].x + _hairQuadDelta[vertexIndex].x, + _hairPosition[vertexIndex].y + _hairQuadDelta[vertexIndex].y, + _hairPosition[vertexIndex].z + _hairQuadDelta[vertexIndex].z); + + glVertex3f(_hairPosition[vertexIndex + 1].x + _hairQuadDelta[vertexIndex].x, + _hairPosition[vertexIndex + 1].y + _hairQuadDelta[vertexIndex].y, + _hairPosition[vertexIndex + 1].z + _hairQuadDelta[vertexIndex].z); + glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x, + _hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y, + _hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z); + } + } + glEnd(); + + //glColor4f(1.0f, 0.0f, 0.0f, 0.5f); + //glutSolidSphere(HEAD_RADIUS, 20, 20); + glPopMatrix(); + +} + +void Avatar::simulateHair(float deltaTime) { + deltaTime = glm::clamp(deltaTime, 0.f, 1.f / 30.f); + glm::vec3 acceleration = getAcceleration(); + if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) { + acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION; + } + const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame(); + acceleration = acceleration * rotation; + glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity(); + + for (int strand = 0; strand < HAIR_STRANDS; strand++) { + for (int link = 0; link < HAIR_LINKS; link++) { + int vertexIndex = strand * HAIR_LINKS + link; + if (vertexIndex % HAIR_LINKS == 0) { + // Base Joint - no integration + if (randFloat() < NOISE) { + // Move base of hair + _hairPosition[vertexIndex] += randVector() * NOISE_MAGNITUDE; + } + } else { + // + // Vertlet Integration + // + // Add velocity from last position, with damping + glm::vec3 thisPosition = _hairPosition[vertexIndex]; + glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex]; + _hairPosition[vertexIndex] += diff * HAIR_DAMPING; + // Attempt to resolve collision with head sphere + if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) { + _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) * + (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex])) * COLLISION_RELAXATION * deltaTime; + } + // Add a little gravity + _hairPosition[vertexIndex] += HAIR_GRAVITY * rotation * deltaTime; + + // Add linear acceleration of the avatar body + _hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime; + + const float ANGULAR_VELOCITY_MIN = 0.001f; + // Add angular acceleration of the avatar body + if (glm::length(angularVelocity) > ANGULAR_VELOCITY_MIN) { + glm::vec3 yawVector = _hairPosition[vertexIndex]; + yawVector.y = 0.f; + if (glm::length(yawVector) > EPSILON) { + float radius = glm::length(yawVector); + yawVector = glm::normalize(yawVector); + float angle = atan2f(yawVector.x, -yawVector.z) + PI; + glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0)); + _hairPosition[vertexIndex] -= delta * radius * angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime; + } + } + + // Iterate length constraints to other links + for (int link = 0; link < HAIR_MAX_CONSTRAINTS; link++) { + if (_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] > -1) { + // If there is a constraint, try to enforce it + glm::vec3 vectorBetween = _hairPosition[_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link]] - _hairPosition[vertexIndex]; + _hairPosition[vertexIndex] += glm::normalize(vectorBetween) * (glm::length(vectorBetween) - HAIR_LINK_LENGTH) * CONSTRAINT_RELAXATION * deltaTime; + } + } + // Store start position for next vertlet pass + _hairLastPosition[vertexIndex] = thisPosition; + } + } + } +} + +void Avatar::initializeHair() { + const float FACE_WIDTH = 0.25f * PI; + glm::vec3 thisVertex; + for (int strand = 0; strand < HAIR_STRANDS; strand++) { + float strandAngle = randFloat() * PI; + float azimuth = randFloat() * 2.f * PI; + float elevation; + if ((azimuth > FACE_WIDTH) || (azimuth < -FACE_WIDTH)) { + elevation = randFloat() * PI_OVER_TWO; + } else { + elevation = (PI_OVER_TWO / 2.f) + randFloat() * (PI_OVER_TWO / 2.f); + } + glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation)); + thisStrand *= HEAD_RADIUS + 0.01f; + + for (int link = 0; link < HAIR_LINKS; link++) { + int vertexIndex = strand * HAIR_LINKS + link; + // Clear constraints + for (int link = 0; link < HAIR_MAX_CONSTRAINTS; link++) { + _hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] = -1; + } + if (vertexIndex % HAIR_LINKS == 0) { + // start of strand + thisVertex = thisStrand; + } else { + thisVertex+= glm::normalize(thisStrand) * HAIR_LINK_LENGTH; + // Set constraints to vertex before and maybe vertex after in strand + _hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS] = vertexIndex - 1; + if (link < (HAIR_LINKS - 1)) { + _hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + 1] = vertexIndex + 1; + } + } + _hairPosition[vertexIndex] = thisVertex; + _hairLastPosition[vertexIndex] = _hairPosition[vertexIndex]; + + _hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * HAIR_THICKNESS, 0.f, sin(strandAngle) * HAIR_THICKNESS); + _hairNormals[vertexIndex] = glm::normalize(randVector()); + if (randFloat() < elevation / PI_OVER_TWO) { + _hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)HAIR_LINKS); + } else { + _hairColors[vertexIndex] = HAIR_COLOR2 * ((float)(link + 1) / (float)HAIR_LINKS); + } + + } + } + qDebug() << "Initialize Hair"; } bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index f928881068..cf45cdf07f 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -32,6 +32,10 @@ static const float RESCALING_TOLERANCE = .02f; extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; +const int HAIR_STRANDS = 100; // Number of strands of hair +const int HAIR_LINKS = 10; // Number of links in a hair strand +const int HAIR_MAX_CONSTRAINTS = 2; + enum DriveKeys { FWD = 0, BACK, @@ -158,6 +162,9 @@ public: Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const; Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const; + glm::vec3 getAcceleration() const { return _acceleration; } + glm::vec3 getAngularVelocity() const { return _angularVelocity; } + public slots: void updateCollisionGroups(); @@ -169,6 +176,10 @@ protected: QVector _attachmentModels; float _bodyYawDelta; glm::vec3 _velocity; + glm::vec3 _lastVelocity; + glm::vec3 _acceleration; + glm::vec3 _angularVelocity; + glm::quat _lastOrientation; float _leanScale; float _scale; glm::vec3 _worldUpDirection; @@ -185,6 +196,7 @@ protected: glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; } glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const; void setScale(float scale); + void updateAcceleration(float deltaTime); float getSkeletonHeight() const; float getHeadHeight() const; @@ -200,6 +212,17 @@ protected: virtual void renderAttachments(RenderMode renderMode); virtual void updateJointMappings(); + + glm::vec3 _hairPosition[HAIR_STRANDS * HAIR_LINKS]; + glm::vec3 _hairLastPosition[HAIR_STRANDS * HAIR_LINKS]; + glm::vec3 _hairQuadDelta[HAIR_STRANDS * HAIR_LINKS]; + glm::vec3 _hairNormals[HAIR_STRANDS * HAIR_LINKS]; + glm::vec3 _hairColors[HAIR_STRANDS * HAIR_LINKS]; + int _hairIsMoveable[HAIR_STRANDS * HAIR_LINKS]; + int _hairConstraints[HAIR_STRANDS * HAIR_LINKS * 2]; // Hair can link to two others + void renderHair(); + void simulateHair(float deltaTime); + void initializeHair(); private: @@ -211,6 +234,7 @@ private: void renderBillboard(); float getBillboardSize() const; + }; #endif // hifi_Avatar_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 54ed641d72..66f10e68c6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -188,6 +188,8 @@ void MyAvatar::simulate(float deltaTime) { head->setScale(_scale); head->simulate(deltaTime, true); } + + simulateHair(deltaTime); // now that we're done stepping the avatar forward in time, compute new collisions if (_collisionGroups != 0) { @@ -814,6 +816,7 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { getHead()->render(1.0f, modelRenderMode); } getHand()->render(true, modelRenderMode); + renderHair(); } const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; @@ -955,6 +958,7 @@ void MyAvatar::updatePosition(float deltaTime) { } else { _position += _velocity * deltaTime; } + updateAcceleration(deltaTime); } // update moving flag based on speed From 652543cdca28a00de94dff8a848e54e62091311d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 24 Jun 2014 11:46:35 -0700 Subject: [PATCH 15/37] Update _scriptEngineHash to work with duplicate running scripts --- interface/src/Application.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 72b0c79724..c3e8876a82 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3524,11 +3524,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript QUrl scriptUrl(scriptName); const QString& scriptURLString = scriptUrl.toString(); if (_scriptEnginesHash.contains(scriptURLString)) { - if(loadScriptFromEditor && !_scriptEnginesHash[scriptURLString]->isFinished()) { + if (loadScriptFromEditor && !_scriptEnginesHash[scriptURLString]->isFinished()) { return _scriptEnginesHash[scriptURLString]; - } else { - QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " is already running."); - return NULL; } } @@ -3545,7 +3542,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript return NULL; } - _scriptEnginesHash.insert(scriptURLString, scriptEngine); + _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine); _runningScriptsWidget->setRunningScripts(getRunningScripts()); } @@ -3617,7 +3614,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript } void Application::scriptFinished(const QString& scriptName) { - if (_scriptEnginesHash.remove(scriptName)) { + QHash::iterator it = _scriptEnginesHash.find(scriptName); + if (it != _scriptEnginesHash.end()) { + _scriptEnginesHash.erase(it); _runningScriptsWidget->scriptStopped(scriptName); _runningScriptsWidget->setRunningScripts(getRunningScripts()); bumpSettings(); From 89f43acb7faf16f12f166f3cc548eac3ccaac061 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 24 Jun 2014 11:47:14 -0700 Subject: [PATCH 16/37] Add a tooltip with the full path to running scripts --- interface/src/ui/RunningScriptsWidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index b2ee36d1fe..136d0a2d38 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -130,6 +130,8 @@ void RunningScriptsWidget::setRunningScripts(const QStringList& list) { row->layout()->addWidget(name); row->layout()->addWidget(closeButton); + row->setToolTip(url.toString()); + QFrame* line = new QFrame(row); line->setFrameShape(QFrame::HLine); line->setStyleSheet("color: #E1E1E1; margin-left: 6px; margin-right: 6px;"); From f249020d6d028f35bc004e1d6a890be19fcb6491 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 24 Jun 2014 11:50:54 -0700 Subject: [PATCH 17/37] Clean up redundant if statement --- interface/src/Application.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c3e8876a82..e74b5fd58a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3523,10 +3523,10 @@ void Application::saveScripts() { ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor, bool activateMainWindow) { QUrl scriptUrl(scriptName); const QString& scriptURLString = scriptUrl.toString(); - if (_scriptEnginesHash.contains(scriptURLString)) { - if (loadScriptFromEditor && !_scriptEnginesHash[scriptURLString]->isFinished()) { - return _scriptEnginesHash[scriptURLString]; - } + if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor + && !_scriptEnginesHash[scriptURLString]->isFinished()) { + + return _scriptEnginesHash[scriptURLString]; } ScriptEngine* scriptEngine; From 5bb2c3c62fdc96ff1104fff0ed00206b7716f900 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 24 Jun 2014 16:14:40 -0700 Subject: [PATCH 18/37] Added in-world test tone JS file --- examples/inWorldTestTone.js | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 examples/inWorldTestTone.js diff --git a/examples/inWorldTestTone.js b/examples/inWorldTestTone.js new file mode 100644 index 0000000000..e4f34d87cd --- /dev/null +++ b/examples/inWorldTestTone.js @@ -0,0 +1,38 @@ +// +// inWorldTestTone.js +// +// +// Created by Philip Rosedale on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// This example script plays a test tone that is useful for debugging audio dropout. 220Hz test tone played at the domain origin. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/220Sine.wav"); + +var soundPlaying = false; + +function update(deltaTime) { + if (!Audio.isInjectorPlaying(soundPlaying)) { + var options = new AudioInjectionOptions(); + options.position = { x:0, y:0, z:0 }; + options.volume = 1.0; + options.loop = true; + soundPlaying = Audio.playSound(sound, options); + print("Started sound loop"); + } +} + +function scriptEnding() { + if (Audio.isInjectorPlaying(soundPlaying)) { + Audio.stopInjector(soundPlaying); + print("Stopped sound loop"); + } +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + From 9eb91e2b4498ad0d07e37cf387258df82e4398a0 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 24 Jun 2014 16:15:16 -0700 Subject: [PATCH 19/37] tweaking params --- interface/src/avatar/Avatar.cpp | 31 ++++++++++++++++++++++++++++--- interface/src/avatar/Avatar.h | 2 +- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 95399062ae..14e01536e2 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -383,10 +383,10 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { renderHair(); } -const float HAIR_LENGTH = 0.5f; +const float HAIR_LENGTH = 0.4f; const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS; const float HAIR_DAMPING = 0.99f; -const float HEAD_RADIUS = 0.20f; +const float HEAD_RADIUS = 0.25f; const float COLLISION_RELAXATION = 10.f; const float CONSTRAINT_RELAXATION = 10.0f; const float NOISE = 0.0f; // 0.1f; @@ -395,9 +395,10 @@ const glm::vec3 HAIR_GRAVITY(0.f, -0.05f, 0.f); const float HAIR_ACCELERATION_COUPLING = 0.025f; const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.15f; const float HAIR_MAX_LINEAR_ACCELERATION = 5.f; -const float HAIR_THICKNESS = 0.015f; +const float HAIR_THICKNESS = 0.025f; const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f); const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f); +const glm::vec3 WIND_DIRECTION(1.0f, -1.0f, 0.f); void Avatar::renderHair() { // @@ -450,6 +451,8 @@ void Avatar::simulateHair(float deltaTime) { acceleration = acceleration * rotation; glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity(); + float windIntensity = randFloat() * 0.02f; + for (int strand = 0; strand < HAIR_STRANDS; strand++) { for (int link = 0; link < HAIR_LINKS; link++) { int vertexIndex = strand * HAIR_LINKS + link; @@ -478,6 +481,10 @@ void Avatar::simulateHair(float deltaTime) { // Add linear acceleration of the avatar body _hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime; + // Add some wind + glm::vec3 wind = WIND_DIRECTION * windIntensity; + _hairPosition[vertexIndex] += wind * deltaTime; + const float ANGULAR_VELOCITY_MIN = 0.001f; // Add angular acceleration of the avatar body if (glm::length(angularVelocity) > ANGULAR_VELOCITY_MIN) { @@ -490,6 +497,24 @@ void Avatar::simulateHair(float deltaTime) { glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0)); _hairPosition[vertexIndex] -= delta * radius * angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime; } + glm::vec3 pitchVector = _hairPosition[vertexIndex]; + pitchVector.x = 0.f; + if (glm::length(pitchVector) > EPSILON) { + float radius = glm::length(pitchVector); + pitchVector = glm::normalize(pitchVector); + float angle = atan2f(pitchVector.y, -pitchVector.z) + PI; + glm::vec3 delta = glm::vec3(0.0f, 1.0f, 0.f) * glm::angleAxis(angle, glm::vec3(1, 0, 0)); + _hairPosition[vertexIndex] -= delta * radius * angularVelocity.x * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime; + } + glm::vec3 rollVector = _hairPosition[vertexIndex]; + rollVector.z = 0.f; + if (glm::length(rollVector) > EPSILON) { + float radius = glm::length(rollVector); + pitchVector = glm::normalize(rollVector); + float angle = atan2f(rollVector.x, rollVector.y) + PI; + glm::vec3 delta = glm::vec3(-1.0f, 0.0f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 0, 1)); + _hairPosition[vertexIndex] -= delta * radius * angularVelocity.z * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime; + } } // Iterate length constraints to other links diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index cf45cdf07f..bd4416f70d 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -32,7 +32,7 @@ static const float RESCALING_TOLERANCE = .02f; extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; -const int HAIR_STRANDS = 100; // Number of strands of hair +const int HAIR_STRANDS = 200; // Number of strands of hair const int HAIR_LINKS = 10; // Number of links in a hair strand const int HAIR_MAX_CONSTRAINTS = 2; From 65e50f32e489249dc5f54146f973da3e9107f072 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 24 Jun 2014 19:10:52 -0700 Subject: [PATCH 20/37] Tests, fixes for SpanList. --- .../metavoxels/src/DatagramSequencer.cpp | 143 ++++++++------- libraries/metavoxels/src/DatagramSequencer.h | 5 +- tests/metavoxels/src/MetavoxelTests.cpp | 172 ++++++++++++++++-- tests/metavoxels/src/MetavoxelTests.h | 11 +- 4 files changed, 253 insertions(+), 78 deletions(-) diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index f1f60e4d87..babbff6f2b 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -520,7 +520,9 @@ int SpanList::set(int offset, int length) { // look for an intersection within the list int position = 0; - for (QList::iterator it = _spans.begin(); it != _spans.end(); it++) { + for (int i = 0; i < _spans.size(); i++) { + QList::iterator it = _spans.begin() + i; + // if we intersect the unset portion, contract it position += it->unset; if (offset <= position) { @@ -530,16 +532,20 @@ int SpanList::set(int offset, int length) { // if we continue into the set portion, expand it and consume following spans int extra = offset + length - position; if (extra >= 0) { - int amount = setSpans(it + 1, extra); - it->set += amount; - _totalSet += amount; - + extra -= it->set; + it->set += remove; + _totalSet += remove; + if (extra > 0) { + int amount = setSpans(it + 1, extra); + _spans[i].set += amount; + _totalSet += amount; + } // otherwise, insert a new span } else { - Span span = { it->unset, length + extra }; - _spans.insert(it, span); + Span span = { it->unset, length }; it->unset = -extra; - _totalSet += span.set; + _spans.insert(it, span); + _totalSet += length; } return 0; } @@ -548,9 +554,11 @@ int SpanList::set(int offset, int length) { position += it->set; if (offset <= position) { int extra = offset + length - position; - int amount = setSpans(it + 1, extra); - it->set += amount; - _totalSet += amount; + if (extra > 0) { + int amount = setSpans(it + 1, extra); + _spans[i].set += amount; + _totalSet += amount; + } return 0; } } @@ -629,67 +637,71 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o } void ReliableChannel::writeData(QDataStream& out, int bytes, QVector& spans) { - // find out how many spans we want to write - int spanCount = 0; - int remainingBytes = bytes; - bool first = true; - while (remainingBytes > 0) { + if (bytes > 0) { + _writePosition %= _buffer.pos(); + int position = 0; - foreach (const SpanList::Span& span, _acknowledged.getSpans()) { - if (remainingBytes <= 0) { - break; + for (int i = 0; i < _acknowledged.getSpans().size(); i++) { + const SpanList::Span& span = _acknowledged.getSpans().at(i); + position += span.unset; + if (_writePosition < position) { + int start = qMax(position - span.unset, _writePosition); + int length = qMin(bytes, position - start); + writeSpan(out, start, length, spans); + writeFullSpans(out, bytes - length, i + 1, position + span.set, spans); + out << (quint32)0; + return; } - spanCount++; - remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, span.unset)); - position += (span.unset + span.set); + position += span.set; } int leftover = _buffer.pos() - position; - if (remainingBytes > 0 && leftover > 0) { - spanCount++; - remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, leftover)); + position = _buffer.pos(); + + if (_writePosition < position && leftover > 0) { + int start = qMax(position - leftover, _writePosition); + int length = qMin(bytes, position - start); + writeSpan(out, start, length, spans); + writeFullSpans(out, bytes - length, 0, 0, spans); } } - - // write the count and the spans - out << (quint32)spanCount; - remainingBytes = bytes; - first = true; - while (remainingBytes > 0) { - int position = 0; - foreach (const SpanList::Span& span, _acknowledged.getSpans()) { - if (remainingBytes <= 0) { - break; - } - remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, span.unset), spans); - position += (span.unset + span.set); + out << (quint32)0; +} + +void ReliableChannel::writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position, + QVector& spans) { + int expandedSize = _acknowledged.getSpans().size() + 1; + for (int i = 0; i < expandedSize; i++) { + if (bytes == 0) { + return; } - int leftover = _buffer.pos() - position; - if (remainingBytes > 0 && leftover > 0) { - remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, leftover), spans); + int index = (startingIndex + i) % expandedSize; + if (index == _acknowledged.getSpans().size()) { + int leftover = _buffer.pos() - position; + if (leftover > 0) { + int length = qMin(leftover, bytes); + writeSpan(out, position, length, spans); + bytes -= length; + } + position = 0; + + } else { + const SpanList::Span& span = _acknowledged.getSpans().at(index); + int length = qMin(span.unset, bytes); + writeSpan(out, position, length, spans); + bytes -= length; + position += (span.unset + span.set); } } } -int ReliableChannel::getBytesToWrite(bool& first, int length) const { - if (first) { - first = false; - return length - (_writePosition % length); - } - return length; -} - -int ReliableChannel::writeSpan(QDataStream& out, bool& first, int position, int length, QVector& spans) { - if (first) { - first = false; - position = _writePosition % length; - length -= position; - _writePosition += length; - } +int ReliableChannel::writeSpan(QDataStream& out, int position, int length, QVector& spans) { DatagramSequencer::ChannelSpan span = { _index, _offset + position, length }; spans.append(span); - out << (quint32)span.offset; out << (quint32)length; + out << (quint32)span.offset; _buffer.writeToStream(position, length, out); + _writePosition = position + length; + return length; } @@ -700,17 +712,20 @@ void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& spa _buffer.seek(_buffer.size()); _offset += advancement; - _writePosition = qMax(_writePosition - advancement, 0); - } + _writePosition = qMax(_writePosition - advancement, 0); + } } void ReliableChannel::readData(QDataStream& in) { - quint32 segments; - in >> segments; bool readSome = false; - for (quint32 i = 0; i < segments; i++) { - quint32 offset, size; - in >> offset >> size; + forever { + quint32 size; + in >> size; + if (size == 0) { + break; + } + quint32 offset; + in >> offset; int position = offset - _offset; int end = position + size; diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 5ac88556f0..47fef8e645 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -343,8 +343,9 @@ private: ReliableChannel(DatagramSequencer* sequencer, int index, bool output); void writeData(QDataStream& out, int bytes, QVector& spans); - int getBytesToWrite(bool& first, int length) const; - int writeSpan(QDataStream& out, bool& first, int position, int length, QVector& spans); + void writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position, + QVector& spans); + int writeSpan(QDataStream& out, int position, int length, QVector& spans); void spanAcknowledged(const DatagramSequencer::ChannelSpan& span); diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 609d36c085..688749f39b 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -28,6 +28,119 @@ MetavoxelTests::MetavoxelTests(int& argc, char** argv) : QCoreApplication(argc, argv) { } +static bool testSpanList() { + SpanList list; + + if (list.getTotalSet() != 0 || !list.getSpans().isEmpty()) { + qDebug() << "Failed empty state test."; + return true; + } + + if (list.set(-5, 15) != 10 || list.getTotalSet() != 0 || !list.getSpans().isEmpty()) { + qDebug() << "Failed initial front set."; + return true; + } + + if (list.set(5, 15) != 0 || list.getTotalSet() != 15 || list.getSpans().size() != 1 || + list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 15) { + qDebug() << "Failed initial middle set."; + return true; + } + + if (list.set(25, 5) != 0 || list.getTotalSet() != 20 || list.getSpans().size() != 2 || + list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 15 || + list.getSpans().at(1).unset != 5 || list.getSpans().at(1).set != 5) { + qDebug() << "Failed initial end set."; + return true; + } + + if (list.set(1, 3) != 0 || list.getTotalSet() != 23 || list.getSpans().size() != 3 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 || + list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 15 || + list.getSpans().at(2).unset != 5 || list.getSpans().at(2).set != 5) { + qDebug() << "Failed second front set."; + return true; + } + SpanList threeSet = list; + + if (list.set(20, 5) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 || + list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) { + qDebug() << "Failed minimal join last two."; + return true; + } + + list = threeSet; + if (list.set(5, 25) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 || + list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) { + qDebug() << "Failed maximal join last two."; + return true; + } + + list = threeSet; + if (list.set(10, 18) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 || + list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) { + qDebug() << "Failed middle join last two."; + return true; + } + + list = threeSet; + if (list.set(10, 18) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 || + list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) { + qDebug() << "Failed middle join last two."; + return true; + } + + list = threeSet; + if (list.set(2, 26) != 0 || list.getTotalSet() != 29 || list.getSpans().size() != 1 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 29) { + qDebug() << "Failed middle join three."; + return true; + } + + list = threeSet; + if (list.set(0, 2) != 4 || list.getTotalSet() != 20 || list.getSpans().size() != 2 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 15 || + list.getSpans().at(1).unset != 5 || list.getSpans().at(1).set != 5) { + qDebug() << "Failed front advance."; + return true; + } + + list = threeSet; + if (list.set(-10, 15) != 20 || list.getTotalSet() != 5 || list.getSpans().size() != 1 || + list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 5) { + qDebug() << "Failed middle advance."; + return true; + } + + list = threeSet; + if (list.set(-10, 38) != 30 || list.getTotalSet() != 0 || list.getSpans().size() != 0) { + qDebug() << "Failed end advance."; + return true; + } + + list = threeSet; + if (list.set(-10, 100) != 90 || list.getTotalSet() != 0 || list.getSpans().size() != 0) { + qDebug() << "Failed clobber advance."; + return true; + } + + list = threeSet; + if (list.set(21, 3) != 0 || list.getTotalSet() != 26 || list.getSpans().size() != 4 || + list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 || + list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 15 || + list.getSpans().at(2).unset != 1 || list.getSpans().at(2).set != 3 || + list.getSpans().at(3).unset != 1 || list.getSpans().at(3).set != 5) { + qDebug() << "Failed adding fourth."; + return true; + } + + return false; +} + static int datagramsSent = 0; static int datagramsReceived = 0; static int bytesSent = 0; @@ -332,9 +445,18 @@ bool MetavoxelTests::run() { QStringList arguments = this->arguments(); int test = (arguments.size() > 1) ? arguments.at(1).toInt() : 0; + if (test == 0 || test == 1) { + qDebug() << "Running SpanList test..."; + qDebug(); + + if (testSpanList()) { + return true; + } + } + QByteArray datagramHeader("testheader"); const int SIMULATION_ITERATIONS = 10000; - if (test == 0 || test == 1) { + if (test == 0 || test == 2) { qDebug() << "Running transmission test..."; qDebug(); @@ -364,7 +486,7 @@ bool MetavoxelTests::run() { qDebug(); } - if (test == 0 || test == 2) { + if (test == 0 || test == 3) { qDebug() << "Running congestion control test..."; qDebug(); @@ -394,7 +516,7 @@ bool MetavoxelTests::run() { qDebug() << "Efficiency:" << ((float)streamedBytesReceived / bytesReceived); } - if (test == 0 || test == 3) { + if (test == 0 || test == 4) { qDebug() << "Running serialization test..."; qDebug(); @@ -403,7 +525,7 @@ bool MetavoxelTests::run() { } } - if (test == 0 || test == 4) { + if (test == 0 || test == 5) { qDebug() << "Running metavoxel data test..."; qDebug(); @@ -532,6 +654,13 @@ Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) : if (mode == CONGESTION_MODE) { const int HUGE_STREAM_BYTES = 50 * 1024 * 1024; bytes = createRandomBytes(HUGE_STREAM_BYTES, HUGE_STREAM_BYTES); + + // initialize the pipeline + for (int i = 0; i < 10; i++) { + _pipeline.append(ByteArrayVector()); + } + _remainingPipelineCapacity = 100 * 1024; + } else { const int MIN_STREAM_BYTES = 100000; const int MAX_STREAM_BYTES = 200000; @@ -669,10 +798,9 @@ int MutateVisitor::visit(MetavoxelInfo& info) { bool Endpoint::simulate(int iterationNumber) { // update/send our delayed datagrams - for (QList >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) { + for (QList::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) { if (it->second-- == 1) { - _other->_sequencer->receivedDatagram(it->first); - datagramsReceived++; + _other->receiveDatagram(it->first); it = _delayedDatagrams.erase(it); } else { @@ -683,6 +811,16 @@ bool Endpoint::simulate(int iterationNumber) { int oldDatagramsSent = datagramsSent; int oldBytesSent = bytesSent; if (_mode == CONGESTION_MODE) { + // cycle our pipeline + ByteArrayVector datagrams = _pipeline.takeLast(); + _pipeline.prepend(ByteArrayVector()); + foreach (const QByteArray& datagram, datagrams) { + _sequencer->receivedDatagram(datagram); + datagramsReceived++; + bytesReceived += datagram.size(); + _remainingPipelineCapacity += datagram.size(); + } + Bitstream& out = _sequencer->startPacket(); out << QVariant(); _sequencer->endPacket(); @@ -804,7 +942,7 @@ void Endpoint::sendDatagram(const QByteArray& datagram) { const int MIN_DELAY = 1; const int MAX_DELAY = 5; // have to copy the datagram; the one we're passed is a reference to a shared buffer - _delayedDatagrams.append(QPair(QByteArray(datagram.constData(), datagram.size()), + _delayedDatagrams.append(ByteArrayIntPair(QByteArray(datagram.constData(), datagram.size()), randIntInRange(MIN_DELAY, MAX_DELAY))); // and some are duplicated @@ -814,9 +952,7 @@ void Endpoint::sendDatagram(const QByteArray& datagram) { } } - _other->_sequencer->receivedDatagram(datagram); - datagramsReceived++; - bytesReceived += datagram.size(); + _other->receiveDatagram(datagram); } void Endpoint::handleHighPriorityMessage(const QVariant& message) { @@ -942,6 +1078,20 @@ void Endpoint::clearReceiveRecordsBefore(int index) { _receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1); } +void Endpoint::receiveDatagram(const QByteArray& datagram) { + if (_mode == CONGESTION_MODE) { + if (datagram.size() <= _remainingPipelineCapacity) { + // have to copy the datagram; the one we're passed is a reference to a shared buffer + _pipeline[0].append(QByteArray(datagram.constData(), datagram.size())); + _remainingPipelineCapacity -= datagram.size(); + } + } else { + _sequencer->receivedDatagram(datagram); + datagramsReceived++; + bytesReceived += datagram.size(); + } +} + void Endpoint::handleMessage(const QVariant& message, Bitstream& in) { int userType = message.userType(); if (userType == ClientStateMessage::Type) { diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 46bac319fa..f9a314dcd7 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -63,6 +63,8 @@ private slots: private: + void receiveDatagram(const QByteArray& datagram); + void handleMessage(const QVariant& message, Bitstream& in); class SendRecord { @@ -96,7 +98,14 @@ private: SharedObjectPointer _sphere; Endpoint* _other; - QList > _delayedDatagrams; + + typedef QPair ByteArrayIntPair; + QList _delayedDatagrams; + + typedef QVector ByteArrayVector; + QList _pipeline; + int _remainingPipelineCapacity; + float _highPriorityMessagesToSend; QVariantList _highPriorityMessagesSent; QList _unreliableMessagesSent; From d913ac4486b50a9bd20f36b10f0d01427a1631e0 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Tue, 24 Jun 2014 19:34:19 -0700 Subject: [PATCH 21/37] Fix for streams' getting stuck on the final part. --- libraries/metavoxels/src/DatagramSequencer.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index babbff6f2b..35d0876391 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -637,9 +637,12 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o } void ReliableChannel::writeData(QDataStream& out, int bytes, QVector& spans) { - if (bytes > 0) { - _writePosition %= _buffer.pos(); - + if (bytes == 0) { + out << (quint32)0; + return; + } + _writePosition %= _buffer.pos(); + while (bytes > 0) { int position = 0; for (int i = 0; i < _acknowledged.getSpans().size(); i++) { const SpanList::Span& span = _acknowledged.getSpans().at(i); @@ -662,9 +665,11 @@ void ReliableChannel::writeData(QDataStream& out, int bytes, QVector Date: Tue, 24 Jun 2014 20:32:41 -0700 Subject: [PATCH 22/37] Expose mode shift period to JS, remove default value --- examples/inspect.js | 2 ++ interface/src/Camera.cpp | 2 -- interface/src/Camera.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/inspect.js b/examples/inspect.js index b292d5f609..a4ff405c3f 100644 --- a/examples/inspect.js +++ b/examples/inspect.js @@ -195,6 +195,8 @@ function keyReleaseEvent(event) { } } + + function mousePressEvent(event) { if (alt && !isActive) { mouseLastX = event.x; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 0e33e14f32..4490b60fc9 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -317,8 +317,6 @@ void CameraScriptableObject::setMode(const QString& mode) { } if (currentMode != targetMode) { _camera->setMode(targetMode); - const float DEFAULT_MODE_SHIFT_PERIOD = 0.5f; // half second - _camera->setModeShiftPeriod(DEFAULT_MODE_SHIFT_PERIOD); } } diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 5e189c1111..2bbbf0e751 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -42,9 +42,8 @@ public: void setTargetPosition(const glm::vec3& t); void setTightness(float t) { _tightness = t; } void setTargetRotation(const glm::quat& rotation); - - void setMode(CameraMode m); void setModeShiftPeriod(float r); + void setMode(CameraMode m); void setFieldOfView(float f); void setAspectRatio(float a); void setNearClip(float n); @@ -130,6 +129,7 @@ public: public slots: QString getMode() const; void setMode(const QString& mode); + void setModeShiftPeriod(float r) {_camera->setModeShiftPeriod(r); } void setPosition(const glm::vec3& value) { _camera->setTargetPosition(value);} glm::vec3 getPosition() const { return _camera->getPosition(); } From 3bec59fd3397a7e5e873d5bfbb4b263aafd175bc Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 24 Jun 2014 20:38:46 -0700 Subject: [PATCH 23/37] Fix unindented line --- 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 ec095a4f59..67de203fa2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1286,7 +1286,7 @@ void Application::dropEvent(QDropEvent *event) { void Application::sendPingPackets() { QByteArray pingPacket = NodeList::getInstance()->constructPingPacket(); -controlledBroadcastToNodes(pingPacket, NodeSet() + controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::MetavoxelServer); From 88c01266abd1c9190e34e6a7a1d26322c2f6ab8b Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 25 Jun 2014 03:29:08 -0700 Subject: [PATCH 24/37] =?UTF-8?q?more=20hair=20tweaks,=20don=E2=80=99t=20r?= =?UTF-8?q?ender=20in=201P,=20improved=20toy=20ball?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/toyball.js | 41 +++++++++++++++++++++---------- interface/src/avatar/Avatar.cpp | 39 +++++++++++++++-------------- interface/src/avatar/Avatar.h | 1 + interface/src/avatar/MyAvatar.cpp | 2 +- 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/examples/toyball.js b/examples/toyball.js index d312c1bc94..5fd522ef4a 100644 --- a/examples/toyball.js +++ b/examples/toyball.js @@ -26,14 +26,21 @@ var RIGHT_TIP = 3; var RIGHT_BUTTON_FWD = 11; var RIGHT_BUTTON_3 = 9; +var BALL_RADIUS = 0.08; +var GRAVITY_STRENGTH = 0.5; + +var HELD_COLOR = { red: 240, green: 0, blue: 0 }; +var THROWN_COLOR = { red: 128, green: 0, blue: 0 }; + var leftBallAlreadyInHand = false; var rightBallAlreadyInHand = false; var leftHandParticle; var rightHandParticle; -var throwSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw"); +var newSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw"); var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw"); -var targetRadius = 0.25; +var throwSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw"); +var targetRadius = 1.0; var wantDebugging = false; @@ -54,7 +61,7 @@ function getBallHoldPosition(whichSide) { tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP); } - var BALL_FORWARD_OFFSET = 0.08; // put the ball a bit forward of fingers + var BALL_FORWARD_OFFSET = 0.15; // put the ball a bit forward of fingers position = { x: BALL_FORWARD_OFFSET * normal.x, y: BALL_FORWARD_OFFSET * normal.y, z: BALL_FORWARD_OFFSET * normal.z }; @@ -69,6 +76,7 @@ function getBallHoldPosition(whichSide) { function checkControllerSide(whichSide) { var BUTTON_FWD; var BUTTON_3; + var TRIGGER; var palmPosition; var ballAlreadyInHand; var handMessage; @@ -76,18 +84,20 @@ function checkControllerSide(whichSide) { if (whichSide == LEFT_PALM) { BUTTON_FWD = LEFT_BUTTON_FWD; BUTTON_3 = LEFT_BUTTON_3; + TRIGGER = 0; palmPosition = Controller.getSpatialControlPosition(LEFT_PALM); ballAlreadyInHand = leftBallAlreadyInHand; handMessage = "LEFT"; } else { BUTTON_FWD = RIGHT_BUTTON_FWD; BUTTON_3 = RIGHT_BUTTON_3; + TRIGGER = 1; palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM); ballAlreadyInHand = rightBallAlreadyInHand; handMessage = "RIGHT"; } - - var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3)); + + var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3) || (Controller.getTriggerValue(TRIGGER) > 0.5)); // If I don't currently have a ball in my hand, then try to catch closest one if (!ballAlreadyInHand && grabButtonPressed) { @@ -107,8 +117,11 @@ function checkControllerSide(whichSide) { var ballPosition = getBallHoldPosition(whichSide); var properties = { position: { x: ballPosition.x, y: ballPosition.y, - z: ballPosition.z }, - velocity : { x: 0, y: 0, z: 0}, inHand: true }; + z: ballPosition.z }, + color: HELD_COLOR, + velocity : { x: 0, y: 0, z: 0}, + lifetime : 600, + inHand: true }; Particles.editParticle(closestParticle, properties); var options = new AudioInjectionOptions(); @@ -127,7 +140,7 @@ function checkControllerSide(whichSide) { //} // If '3' is pressed, and not holding a ball, make a new one - if (Controller.isButtonPressed(BUTTON_3) && !ballAlreadyInHand) { + if (grabButtonPressed && !ballAlreadyInHand) { var ballPosition = getBallHoldPosition(whichSide); var properties = { position: { x: ballPosition.x, y: ballPosition.y, @@ -135,11 +148,11 @@ function checkControllerSide(whichSide) { velocity: { x: 0, y: 0, z: 0}, gravity: { x: 0, y: 0, z: 0}, inHand: true, - radius: 0.05, + radius: BALL_RADIUS, damping: 0.999, - color: { red: 255, green: 0, blue: 0 }, + color: HELD_COLOR, - lifetime: 10 // 10 seconds - same as default, not needed but here as an example + lifetime: 600 // 10 seconds - same as default, not needed but here as an example }; newParticle = Particles.addParticle(properties); @@ -155,7 +168,7 @@ function checkControllerSide(whichSide) { var options = new AudioInjectionOptions(); options.position = ballPosition; options.volume = 1.0; - Audio.playSound(catchSound, options); + Audio.playSound(newSound, options); return; // exit early } @@ -188,7 +201,9 @@ function checkControllerSide(whichSide) { y: tipVelocity.y * THROWN_VELOCITY_SCALING, z: tipVelocity.z * THROWN_VELOCITY_SCALING } , inHand: false, - gravity: { x: 0, y: -2, z: 0}, + color: THROWN_COLOR, + lifetime: 10, + gravity: { x: 0, y: -GRAVITY_STRENGTH, z: 0}, }; Particles.editParticle(handParticle, properties); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 2f4717dd3b..ee65b9c170 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -379,22 +379,22 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { renderHair(); } -const float HAIR_LENGTH = 0.4f; +const float HAIR_LENGTH = 0.2f; const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS; const float HAIR_DAMPING = 0.99f; -const float HEAD_RADIUS = 0.25f; +const float HEAD_RADIUS = 0.15f; const float COLLISION_RELAXATION = 10.f; const float CONSTRAINT_RELAXATION = 10.0f; -const float NOISE = 0.0f; // 0.1f; -const float NOISE_MAGNITUDE = 0.02f; -const glm::vec3 HAIR_GRAVITY(0.f, -0.05f, 0.f); +const glm::vec3 HAIR_GRAVITY(0.f, -0.005f, 0.f); const float HAIR_ACCELERATION_COUPLING = 0.025f; -const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.15f; -const float HAIR_MAX_LINEAR_ACCELERATION = 5.f; +const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f; +const float HAIR_MAX_LINEAR_ACCELERATION = 4.f; const float HAIR_THICKNESS = 0.025f; +const float HAIR_STIFFNESS = 0.002f; const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f); const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f); -const glm::vec3 WIND_DIRECTION(1.0f, -1.0f, 0.f); +const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.f); +const float MAX_WIND_STRENGTH = 0.01f; void Avatar::renderHair() { // @@ -447,18 +447,14 @@ void Avatar::simulateHair(float deltaTime) { acceleration = acceleration * rotation; glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity(); - float windIntensity = randFloat() * 0.02f; + float windIntensity = randFloat() * MAX_WIND_STRENGTH; for (int strand = 0; strand < HAIR_STRANDS; strand++) { for (int link = 0; link < HAIR_LINKS; link++) { int vertexIndex = strand * HAIR_LINKS + link; if (vertexIndex % HAIR_LINKS == 0) { // Base Joint - no integration - if (randFloat() < NOISE) { - // Move base of hair - _hairPosition[vertexIndex] += randVector() * NOISE_MAGNITUDE; - } - } else { + } else { // // Vertlet Integration // @@ -477,6 +473,10 @@ void Avatar::simulateHair(float deltaTime) { // Add linear acceleration of the avatar body _hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime; + // Add stiffness (product) + _hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex]) + * powf(1.f - link / HAIR_LINKS, 2.f) * HAIR_STIFFNESS; + // Add some wind glm::vec3 wind = WIND_DIRECTION * windIntensity; _hairPosition[vertexIndex] += wind * deltaTime; @@ -529,17 +529,17 @@ void Avatar::simulateHair(float deltaTime) { } void Avatar::initializeHair() { - const float FACE_WIDTH = 0.25f * PI; + const float FACE_WIDTH = PI / 4.0f; glm::vec3 thisVertex; for (int strand = 0; strand < HAIR_STRANDS; strand++) { float strandAngle = randFloat() * PI; - float azimuth = randFloat() * 2.f * PI; - float elevation; - if ((azimuth > FACE_WIDTH) || (azimuth < -FACE_WIDTH)) { + float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH)); + float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI); + /*if ((azimuth > FACE_WIDTH) || (azimuth < -FACE_WIDTH)) { elevation = randFloat() * PI_OVER_TWO; } else { elevation = (PI_OVER_TWO / 2.f) + randFloat() * (PI_OVER_TWO / 2.f); - } + }*/ glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation)); thisStrand *= HEAD_RADIUS + 0.01f; @@ -562,6 +562,7 @@ void Avatar::initializeHair() { } _hairPosition[vertexIndex] = thisVertex; _hairLastPosition[vertexIndex] = _hairPosition[vertexIndex]; + _hairOriginalPosition[vertexIndex] = _hairPosition[vertexIndex]; _hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * HAIR_THICKNESS, 0.f, sin(strandAngle) * HAIR_THICKNESS); _hairNormals[vertexIndex] = glm::normalize(randVector()); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index bb38cab5c5..40f92b468c 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -201,6 +201,7 @@ protected: virtual void updateJointMappings(); glm::vec3 _hairPosition[HAIR_STRANDS * HAIR_LINKS]; + glm::vec3 _hairOriginalPosition[HAIR_STRANDS * HAIR_LINKS]; glm::vec3 _hairLastPosition[HAIR_STRANDS * HAIR_LINKS]; glm::vec3 _hairQuadDelta[HAIR_STRANDS * HAIR_LINKS]; glm::vec3 _hairNormals[HAIR_STRANDS * HAIR_LINKS]; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6938e20313..bbdf041341 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -833,9 +833,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { // Render head so long as the camera isn't inside it if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) { getHead()->render(1.0f, modelRenderMode); + renderHair(); } getHand()->render(true, modelRenderMode); - renderHair(); } const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; From 40dee3b39eb01b1adf69b8d56f3eae1212ff8c21 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 25 Jun 2014 05:24:22 -0700 Subject: [PATCH 25/37] Added JS calls for left/right estimated palm position on skeleton model, improved toy ball to use them --- examples/toyball.js | 17 +------- interface/src/avatar/Avatar.cpp | 65 ++++++++++++++++++++++++++----- interface/src/avatar/MyAvatar.cpp | 19 +++++++++ interface/src/avatar/MyAvatar.h | 5 ++- 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/examples/toyball.js b/examples/toyball.js index 5fd522ef4a..e03fd67a5d 100644 --- a/examples/toyball.js +++ b/examples/toyball.js @@ -51,25 +51,12 @@ function debugPrint(message) { } function getBallHoldPosition(whichSide) { - var normal; - var tipPosition; if (whichSide == LEFT_PALM) { - normal = Controller.getSpatialControlNormal(LEFT_PALM); - tipPosition = Controller.getSpatialControlPosition(LEFT_TIP); + position = MyAvatar.getLeftPalmPosition(); } else { - normal = Controller.getSpatialControlNormal(RIGHT_PALM); - tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP); + position = MyAvatar.getRightPalmPosition(); } - var BALL_FORWARD_OFFSET = 0.15; // put the ball a bit forward of fingers - position = { x: BALL_FORWARD_OFFSET * normal.x, - y: BALL_FORWARD_OFFSET * normal.y, - z: BALL_FORWARD_OFFSET * normal.z }; - - position.x += tipPosition.x; - position.y += tipPosition.y; - position.z += tipPosition.z; - return position; } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ee65b9c170..ee8e7266cb 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -382,25 +382,46 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { const float HAIR_LENGTH = 0.2f; const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS; const float HAIR_DAMPING = 0.99f; -const float HEAD_RADIUS = 0.15f; -const float COLLISION_RELAXATION = 10.f; -const float CONSTRAINT_RELAXATION = 10.0f; -const glm::vec3 HAIR_GRAVITY(0.f, -0.005f, 0.f); +const float HEAD_RADIUS = 0.18f; +const float CONSTRAINT_RELAXATION = 20.0f; +const glm::vec3 HAIR_GRAVITY(0.f, -0.015f, 0.f); const float HAIR_ACCELERATION_COUPLING = 0.025f; const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f; const float HAIR_MAX_LINEAR_ACCELERATION = 4.f; -const float HAIR_THICKNESS = 0.025f; -const float HAIR_STIFFNESS = 0.002f; +const float HAIR_THICKNESS = 0.020f; +const float HAIR_STIFFNESS = 0.0006f; const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f); const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f); const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.f); const float MAX_WIND_STRENGTH = 0.01f; +const float FINGER_LENGTH = 0.25; +const float FINGER_RADIUS = 0.10; void Avatar::renderHair() { // // Render the avatar's moveable hair // + glm::vec3 headPosition = getHead()->getPosition(); + /* + glm::vec3 leftHandPosition, rightHandPosition; + getSkeletonModel().getLeftHandPosition(leftHandPosition); + getSkeletonModel().getRightHandPosition(rightHandPosition); + glm::quat leftRotation, rightRotation; + getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); + rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(rightRotation); + + glPushMatrix(); + glTranslatef(leftHandPosition.x, leftHandPosition.y, leftHandPosition.z); + glColor4f(1.0f, 0.0f, 0.0f, 0.5f); + glutSolidSphere(FINGER_RADIUS, 20, 20); + glPopMatrix(); + glPushMatrix(); + glTranslatef(rightHandPosition.x, rightHandPosition.y, rightHandPosition.z); + glColor4f(1.0f, 0.0f, 0.0f, 0.5f); + glutSolidSphere(FINGER_RADIUS, 20, 20); + glPopMatrix(); + */ glPushMatrix(); glTranslatef(headPosition.x, headPosition.y, headPosition.z); @@ -431,13 +452,12 @@ void Avatar::renderHair() { } glEnd(); - //glColor4f(1.0f, 0.0f, 0.0f, 0.5f); - //glutSolidSphere(HEAD_RADIUS, 20, 20); glPopMatrix(); } void Avatar::simulateHair(float deltaTime) { + deltaTime = glm::clamp(deltaTime, 0.f, 1.f / 30.f); glm::vec3 acceleration = getAcceleration(); if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) { @@ -447,6 +467,20 @@ void Avatar::simulateHair(float deltaTime) { acceleration = acceleration * rotation; glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity(); + // Get hand positions to allow touching hair + glm::vec3 leftHandPosition, rightHandPosition; + getSkeletonModel().getLeftHandPosition(leftHandPosition); + getSkeletonModel().getRightHandPosition(rightHandPosition); + leftHandPosition -= getHead()->getPosition(); + rightHandPosition -= getHead()->getPosition(); + glm::quat leftRotation, rightRotation; + getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); + getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); + leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(leftRotation); + rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(rightRotation); + leftHandPosition = leftHandPosition * rotation; + rightHandPosition = rightHandPosition * rotation; + float windIntensity = randFloat() * MAX_WIND_STRENGTH; for (int strand = 0; strand < HAIR_STRANDS; strand++) { @@ -462,11 +496,22 @@ void Avatar::simulateHair(float deltaTime) { glm::vec3 thisPosition = _hairPosition[vertexIndex]; glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex]; _hairPosition[vertexIndex] += diff * HAIR_DAMPING; - // Attempt to resolve collision with head sphere + // Resolve collision with head sphere if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) { _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) * - (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex])) * COLLISION_RELAXATION * deltaTime; + (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex])); // * COLLISION_RELAXATION * deltaTime; } + // Collide with hands + if (glm::length(_hairPosition[vertexIndex] - leftHandPosition) < FINGER_RADIUS) { + _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - leftHandPosition) * + (FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - leftHandPosition)); + } + if (glm::length(_hairPosition[vertexIndex] - rightHandPosition) < FINGER_RADIUS) { + _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - rightHandPosition) * + (FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - rightHandPosition)); + } + + // Add a little gravity _hairPosition[vertexIndex] += HAIR_GRAVITY * rotation * deltaTime; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bbdf041341..5d6b381296 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -422,6 +422,25 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const { } } +const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); + +glm::vec3 MyAvatar::getLeftPalmPosition() { + glm::vec3 leftHandPosition; + getSkeletonModel().getLeftHandPosition(leftHandPosition); + glm::quat leftRotation; + getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); + leftHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftRotation); + return leftHandPosition; +} +glm::vec3 MyAvatar::getRightPalmPosition() { + glm::vec3 rightHandPosition; + getSkeletonModel().getRightHandPosition(rightHandPosition); + glm::quat rightRotation; + getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); + rightHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(rightRotation); + return rightHandPosition; +} + void MyAvatar::setLocalGravity(glm::vec3 gravity) { _motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY; // Environmental and Local gravities are incompatible. Since Local is being set here diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index e7023b45a1..0ee76c6b45 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -139,7 +139,10 @@ public slots: void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } void updateMotionBehaviorsFromMenu(); - + + glm::vec3 getLeftPalmPosition(); + glm::vec3 getRightPalmPosition(); + signals: void transformChanged(); From e92ad862824240068238ca8cf29466b4d63175d0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 17 Jun 2014 10:54:55 -0700 Subject: [PATCH 26/37] Clear script engine errors once they have been reported So that an error is not repeatedly reported to the console and log file. Also consistently report filename. And scripts included after one in error will now be run. --- libraries/script-engine/src/ScriptEngine.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b0cce114a9..350473cc87 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -314,8 +314,9 @@ void ScriptEngine::evaluate() { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); - qDebug() << "Uncaught exception at line" << line << ":" << result.toString(); - emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString()); + qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString()); + _engine.clearExceptions(); } } @@ -324,7 +325,7 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN bool hasUncaughtException = _engine.hasUncaughtException(); if (hasUncaughtException) { int line = _engine.uncaughtExceptionLineNumber(); - qDebug() << "Uncaught exception at line" << line << ": " << result.toString(); + qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ": " << result.toString(); } emit evaluationFinished(result, hasUncaughtException); _engine.clearExceptions(); @@ -353,9 +354,9 @@ void ScriptEngine::run() { QScriptValue result = _engine.evaluate(_scriptContents); if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); - - qDebug() << "Uncaught exception at line" << line << ":" << result.toString(); - emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString()); + qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString()); + _engine.clearExceptions(); } QElapsedTimer startTime; @@ -495,8 +496,9 @@ void ScriptEngine::run() { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); - qDebug() << "Uncaught exception at line" << line << ":" << _engine.uncaughtException().toString(); - emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + _engine.uncaughtException().toString()); + qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << _engine.uncaughtException().toString(); + emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + _engine.uncaughtException().toString()); + _engine.clearExceptions(); } } emit scriptEnding(); @@ -656,5 +658,6 @@ void ScriptEngine::include(const QString& includeFile) { int line = _engine.uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at (" << includeFile << ") line" << line << ":" << result.toString(); emit errorMessage("Uncaught exception at (" + includeFile + ") line" + QString::number(line) + ":" + result.toString()); + _engine.clearExceptions(); } } From ae1422e0cbf692239ea8b8a6da077984ac4f0d88 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 25 Jun 2014 11:42:02 -0700 Subject: [PATCH 27/37] Reset the write position when we hear of packet loss (up to once per round trip time). --- .../metavoxels/src/DatagramSequencer.cpp | 21 ++++++++++++++++++- libraries/metavoxels/src/DatagramSequencer.h | 5 +++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 35d0876391..bb3e8723ac 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -172,7 +172,10 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) { if (index < 0 || index >= _sendRecords.size()) { continue; } - QList::iterator it = _sendRecords.begin() + index; + QList::iterator it = _sendRecords.begin(); + for (int i = 0; i < index; i++) { + sendRecordLost(*it++); + } sendRecordAcknowledged(*it); emit sendAcknowledged(index); _sendRecords.erase(_sendRecords.begin(), it + 1); @@ -255,6 +258,13 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { } } +void DatagramSequencer::sendRecordLost(const SendRecord& record) { + // notify the channels of their lost spans + foreach (const ChannelSpan& span, record.spans) { + getReliableOutputChannel(span.channel)->spanLost(record.packetNumber, _outgoingPacketNumber + 1); + } +} + void DatagramSequencer::appendReliableData(int bytes, QVector& spans) { // gather total number of bytes to write, priority int totalBytes = 0; @@ -627,6 +637,7 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o _priority(1.0f), _offset(0), _writePosition(0), + _writePositionResetPacketNumber(0), _messagesEnabled(true) { _buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly); @@ -721,6 +732,14 @@ void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& spa } } +void ReliableChannel::spanLost(int packetNumber, int nextOutgoingPacketNumber) { + // reset the write position up to once each round trip time + if (packetNumber >= _writePositionResetPacketNumber) { + _writePosition = 0; + _writePositionResetPacketNumber = nextOutgoingPacketNumber; + } +} + void ReliableChannel::readData(QDataStream& in) { bool readSome = false; forever { diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 47fef8e645..4ab9f7667b 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -165,6 +165,9 @@ private: /// Notes that the described send was acknowledged by the other party. void sendRecordAcknowledged(const SendRecord& record); + /// Notes that the described send was lost in transit. + void sendRecordLost(const SendRecord& record); + /// Appends some reliable data to the outgoing packet. void appendReliableData(int bytes, QVector& spans); @@ -348,6 +351,7 @@ private: int writeSpan(QDataStream& out, int position, int length, QVector& spans); void spanAcknowledged(const DatagramSequencer::ChannelSpan& span); + void spanLost(int packetNumber, int nextOutgoingPacketNumber); void readData(QDataStream& in); @@ -360,6 +364,7 @@ private: int _offset; int _writePosition; + int _writePositionResetPacketNumber; SpanList _acknowledged; bool _messagesEnabled; }; From 41b01dbaed4dbf181ae12fa6534a84f916cf9332 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 25 Jun 2014 13:00:17 -0700 Subject: [PATCH 28/37] Fix warning and simplify encodeRandomOrder. --- libraries/metavoxels/src/MetavoxelData.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 3e70e0f09b..2d61ede796 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -1094,14 +1094,14 @@ const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1; int MetavoxelVisitor::encodeRandomOrder() { // see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_.22inside-out.22_algorithm - int order; + int order = 0; int randomValues = rand(); for (int i = 0, iShift = 0; i < MetavoxelNode::CHILD_COUNT; i++, iShift += ORDER_ELEMENT_BITS) { int j = (randomValues >> iShift) % (i + 1); int jShift = j * ORDER_ELEMENT_BITS; if (j != i) { int jValue = (order >> jShift) & ORDER_ELEMENT_MASK; - order = (order & ~(ORDER_ELEMENT_MASK << iShift)) | (jValue << iShift); + order |= (jValue << iShift); } order = (order & ~(ORDER_ELEMENT_MASK << jShift)) | (i << jShift); } From 6480c678d2b9f0485fdf0d693c63f330cfbd6393 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 25 Jun 2014 13:46:13 -0700 Subject: [PATCH 29/37] Disable strict aliasing for safe type punning, fix potentially uninitialized variable warning. --- CMakeLists.txt | 2 +- interface/src/ui/MetavoxelEditor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a399e11168..b7fa55d4a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ if (WIN32) elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic") #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-strict-aliasing") endif(WIN32) if (NOT QT_CMAKE_PREFIX_PATH) diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index a42c84470b..c1deb20111 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -771,7 +771,7 @@ int VoxelizationVisitor::visit(MetavoxelInfo& info) { } return DEFAULT_ORDER; } - QRgb closestColor; + QRgb closestColor = QRgb(); float closestDistance = FLT_MAX; for (unsigned int i = 0; i < sizeof(DIRECTION_ROTATIONS) / sizeof(DIRECTION_ROTATIONS[0]); i++) { glm::vec3 rotated = DIRECTION_ROTATIONS[i] * center; From 58d460da0927c9b7a2f2012619e047fb32943c59 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 25 Jun 2014 15:14:17 -0700 Subject: [PATCH 30/37] option to turn on/off string hair --- examples/concertCamera.js | 67 ++++++++++++++++++++++++++++ interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/avatar/Avatar.cpp | 72 ++++++++++++------------------- interface/src/avatar/Avatar.h | 6 +-- interface/src/avatar/MyAvatar.cpp | 12 +++++- 6 files changed, 110 insertions(+), 49 deletions(-) create mode 100644 examples/concertCamera.js diff --git a/examples/concertCamera.js b/examples/concertCamera.js new file mode 100644 index 0000000000..0d26fd8ae0 --- /dev/null +++ b/examples/concertCamera.js @@ -0,0 +1,67 @@ +// +// concertCamera.js +// +// Created by Philip Rosedale on June 24, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Move a camera through a series of pre-set locations by pressing number keys +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var oldMode; +var avatarPosition; + +var cameraNumber = 0; +var freeCamera = false; + +var cameraLocations = [ {x: 7972.0, y: 241.6, z: 7304.1}, {x: 7973.7, y: 241.6, z: 7304.1}, {x: 7975.5, y: 241.6, z: 7304.1}, {x: 7972.3, y: 241.6, z: 7303.3}, {x: 7971.4, y: 241.6, z: 7304.3} ]; +var cameraLookAts = [ {x: 7971.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1} ]; + +function saveCameraState() { + oldMode = Camera.getMode(); + avatarPosition = MyAvatar.position; + Camera.setModeShiftPeriod(0.0); + Camera.setMode("independent"); +} + +function restoreCameraState() { + Camera.stopLooking(); + Camera.setMode(oldMode); +} + +function update(deltaTime) { + if (freeCamera) { + var delta = Vec3.subtract(MyAvatar.position, avatarPosition); + if (Vec3.length(delta) > 0.05) { + cameraNumber = 0; + freeCamera = false; + restoreCameraState(); + } + } +} + +function keyPressEvent(event) { + + var choice = parseInt(event.text); + + if ((choice > 0) && (choice <= cameraLocations.length)) { + print("camera " + choice); + if (!freeCamera) { + saveCameraState(); + freeCamera = true; + } + Camera.setMode("independent"); + Camera.setPosition(cameraLocations[choice - 1]); + Camera.keepLookingAt(cameraLookAts[choice - 1]); + } + if (event.text == "0") { + // Show camera location in log + var cameraLocation = Camera.getPosition(); + print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z); + } +} + +Script.update.connect(update); +Controller.keyPressEvent.connect(keyPressEvent); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7d4c7d109d..c0c798799a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -345,6 +345,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::StringHair, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 5b479773b6..3665f5838f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -319,6 +319,7 @@ namespace MenuOption { const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; const QString BuckyBalls = "Bucky Balls"; + const QString StringHair = "String Hair"; const QString CascadedShadows = "Cascaded"; const QString Chat = "Chat..."; const QString ChatCircling = "Chat Circling"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ee8e7266cb..5a294bb2a5 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -140,7 +140,9 @@ void Avatar::simulate(float deltaTime) { head->setScale(_scale); head->simulate(deltaTime, false, _shouldRenderBillboard); - simulateHair(deltaTime); + if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + simulateHair(deltaTime); + } } // update position by velocity, and subtract the change added earlier for gravity @@ -376,26 +378,32 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { getHand()->render(false, modelRenderMode); } getHead()->render(1.0f, modelRenderMode); - renderHair(); + if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + renderHair(); + } } +// +// Constants for the Hair Simulation +// + const float HAIR_LENGTH = 0.2f; const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS; const float HAIR_DAMPING = 0.99f; -const float HEAD_RADIUS = 0.18f; -const float CONSTRAINT_RELAXATION = 20.0f; -const glm::vec3 HAIR_GRAVITY(0.f, -0.015f, 0.f); +const float HEAD_RADIUS = 0.21f; +const float CONSTRAINT_RELAXATION = 10.0f; +const glm::vec3 HAIR_GRAVITY(0.0f, -0.007f, 0.0f); const float HAIR_ACCELERATION_COUPLING = 0.025f; const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f; -const float HAIR_MAX_LINEAR_ACCELERATION = 4.f; -const float HAIR_THICKNESS = 0.020f; -const float HAIR_STIFFNESS = 0.0006f; +const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f; +const float HAIR_THICKNESS = 0.015f; +const float HAIR_STIFFNESS = 0.0000f; const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f); const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f); -const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.f); -const float MAX_WIND_STRENGTH = 0.01f; -const float FINGER_LENGTH = 0.25; -const float FINGER_RADIUS = 0.10; +const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.0f); +const float MAX_WIND_STRENGTH = 0.02f; +const float FINGER_LENGTH = 0.25f; +const float FINGER_RADIUS = 0.10f; void Avatar::renderHair() { // @@ -403,26 +411,6 @@ void Avatar::renderHair() { // glm::vec3 headPosition = getHead()->getPosition(); - /* - glm::vec3 leftHandPosition, rightHandPosition; - getSkeletonModel().getLeftHandPosition(leftHandPosition); - getSkeletonModel().getRightHandPosition(rightHandPosition); - glm::quat leftRotation, rightRotation; - getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); - rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(rightRotation); - - glPushMatrix(); - glTranslatef(leftHandPosition.x, leftHandPosition.y, leftHandPosition.z); - glColor4f(1.0f, 0.0f, 0.0f, 0.5f); - glutSolidSphere(FINGER_RADIUS, 20, 20); - glPopMatrix(); - glPushMatrix(); - glTranslatef(rightHandPosition.x, rightHandPosition.y, rightHandPosition.z); - glColor4f(1.0f, 0.0f, 0.0f, 0.5f); - glutSolidSphere(FINGER_RADIUS, 20, 20); - glPopMatrix(); - */ - glPushMatrix(); glTranslatef(headPosition.x, headPosition.y, headPosition.z); const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame(); @@ -458,7 +446,7 @@ void Avatar::renderHair() { void Avatar::simulateHair(float deltaTime) { - deltaTime = glm::clamp(deltaTime, 0.f, 1.f / 30.f); + deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f); glm::vec3 acceleration = getAcceleration(); if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) { acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION; @@ -476,8 +464,8 @@ void Avatar::simulateHair(float deltaTime) { glm::quat leftRotation, rightRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); - leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(leftRotation); - rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.f) * glm::inverse(rightRotation); + leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(leftRotation); + rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(rightRotation); leftHandPosition = leftHandPosition * rotation; rightHandPosition = rightHandPosition * rotation; @@ -499,9 +487,9 @@ void Avatar::simulateHair(float deltaTime) { // Resolve collision with head sphere if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) { _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) * - (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex])); // * COLLISION_RELAXATION * deltaTime; + (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex])); } - // Collide with hands + // Resolve collision with hands if (glm::length(_hairPosition[vertexIndex] - leftHandPosition) < FINGER_RADIUS) { _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - leftHandPosition) * (FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - leftHandPosition)); @@ -518,7 +506,7 @@ void Avatar::simulateHair(float deltaTime) { // Add linear acceleration of the avatar body _hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime; - // Add stiffness (product) + // Add stiffness (like hair care products do) _hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex]) * powf(1.f - link / HAIR_LINKS, 2.f) * HAIR_STIFFNESS; @@ -580,13 +568,8 @@ void Avatar::initializeHair() { float strandAngle = randFloat() * PI; float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH)); float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI); - /*if ((azimuth > FACE_WIDTH) || (azimuth < -FACE_WIDTH)) { - elevation = randFloat() * PI_OVER_TWO; - } else { - elevation = (PI_OVER_TWO / 2.f) + randFloat() * (PI_OVER_TWO / 2.f); - }*/ glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation)); - thisStrand *= HEAD_RADIUS + 0.01f; + thisStrand *= HEAD_RADIUS; for (int link = 0; link < HAIR_LINKS; link++) { int vertexIndex = strand * HAIR_LINKS + link; @@ -610,6 +593,7 @@ void Avatar::initializeHair() { _hairOriginalPosition[vertexIndex] = _hairPosition[vertexIndex]; _hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * HAIR_THICKNESS, 0.f, sin(strandAngle) * HAIR_THICKNESS); + _hairQuadDelta[vertexIndex] *= 1.f - (link / HAIR_LINKS); _hairNormals[vertexIndex] = glm::normalize(randVector()); if (randFloat() < elevation / PI_OVER_TWO) { _hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)HAIR_LINKS); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 40f92b468c..f20db1019d 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -32,9 +32,9 @@ static const float RESCALING_TOLERANCE = .02f; extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; -const int HAIR_STRANDS = 200; // Number of strands of hair -const int HAIR_LINKS = 10; // Number of links in a hair strand -const int HAIR_MAX_CONSTRAINTS = 2; +const int HAIR_STRANDS = 150; // Number of strands of hair +const int HAIR_LINKS = 10; // Number of links in a hair strand +const int HAIR_MAX_CONSTRAINTS = 2; // Hair verlet is connected to at most how many others enum DriveKeys { FWD = 0, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5d6b381296..b853f3ff8f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -195,7 +195,12 @@ void MyAvatar::simulate(float deltaTime) { head->simulate(deltaTime, true); } - simulateHair(deltaTime); + { + PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate"); + if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + simulateHair(deltaTime); + } + } { PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll"); @@ -370,6 +375,7 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { if (!_shouldRender) { return; // exit early } + Avatar::render(cameraPosition, renderMode); // don't display IK constraints in shadow mode @@ -852,7 +858,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { // Render head so long as the camera isn't inside it if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) { getHead()->render(1.0f, modelRenderMode); - renderHair(); + if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { + renderHair(); + } } getHand()->render(true, modelRenderMode); } From 9af2b2b4dc331cf8fd3b9c91b7ebd68c9cda53b6 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 25 Jun 2014 15:24:21 -0700 Subject: [PATCH 31/37] removed commented code --- examples/sit.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/sit.js b/examples/sit.js index 87b4f232ca..32d2eebf4d 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -50,12 +50,6 @@ var pose = [ {joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}}, {joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}}, {joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}} - - //{joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}}, - - //{joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}}, - //{joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}} - ]; var startPoseAndTransition = []; From 93b131d520ee80fa6ed538eae500ed373fe25252 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 25 Jun 2014 15:41:47 -0700 Subject: [PATCH 32/37] Fix build warning from wrong init order --- interface/src/ui/ApplicationOverlay.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 9be556cf62..342a145953 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -39,9 +39,10 @@ inline float min(float a, float b) { ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL), _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE), - _crosshairTexture(0), _alpha(1.0f), - _active(true) { + _active(true), + _crosshairTexture(0) +{ memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); From 705445ce622e762481d3c9f71654d1cbd3f441c0 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 25 Jun 2014 15:48:46 -0700 Subject: [PATCH 33/37] Basic congestion control using TCP-esque strategy. --- .../metavoxels/src/DatagramSequencer.cpp | 33 ++++++++++++++++++- libraries/metavoxels/src/DatagramSequencer.h | 10 ++++++ tests/metavoxels/src/MetavoxelTests.cpp | 27 ++++++++++----- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index bb3e8723ac..0097c0fd1d 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -23,6 +23,9 @@ const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE; const int DEFAULT_MAX_PACKET_SIZE = 3000; +// the default slow-start threshold, which will be lowered quickly when we first encounter packet loss +const float DEFAULT_SLOW_START_THRESHOLD = 1000.0f; + DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* parent) : QObject(parent), _outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly), @@ -37,7 +40,12 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* _incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly), _inputStream(_incomingPacketStream), _receivedHighPriorityMessages(0), - _maxPacketSize(DEFAULT_MAX_PACKET_SIZE) { + _maxPacketSize(DEFAULT_MAX_PACKET_SIZE), + _packetsPerGroup(1.0f), + _packetsToWrite(0.0f), + _slowStartThreshold(DEFAULT_SLOW_START_THRESHOLD), + _packetRateIncreasePacketNumber(0), + _packetRateDecreasePacketNumber(0) { _outgoingPacketStream.setByteOrder(QDataStream::LittleEndian); _incomingDatagramStream.setByteOrder(QDataStream::LittleEndian); @@ -71,6 +79,14 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) { return channel; } +int DatagramSequencer::startPacketGroup() { + // increment our packet counter and subtract/return the integer portion + _packetsToWrite += _packetsPerGroup; + int wholePackets = (int)_packetsToWrite; + _packetsToWrite -= wholePackets; + return wholePackets; +} + Bitstream& DatagramSequencer::startPacket() { // start with the list of acknowledgements _outgoingPacketStream << (quint32)_receiveRecords.size(); @@ -256,6 +272,14 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { foreach (const ChannelSpan& span, record.spans) { getReliableOutputChannel(span.channel)->spanAcknowledged(span); } + + // increase the packet rate with every ack until we pass the slow start threshold; then, every round trip + if (record.packetNumber >= _packetRateIncreasePacketNumber) { + if (_packetsPerGroup >= _slowStartThreshold) { + _packetRateIncreasePacketNumber = _outgoingPacketNumber + 1; + } + _packetsPerGroup += 1.0f; + } } void DatagramSequencer::sendRecordLost(const SendRecord& record) { @@ -263,6 +287,13 @@ void DatagramSequencer::sendRecordLost(const SendRecord& record) { foreach (const ChannelSpan& span, record.spans) { getReliableOutputChannel(span.channel)->spanLost(record.packetNumber, _outgoingPacketNumber + 1); } + + // halve the rate and remember as threshold + if (record.packetNumber >= _packetRateDecreasePacketNumber) { + _packetsPerGroup = qMax(_packetsPerGroup * 0.5f, 1.0f); + _slowStartThreshold = _packetsPerGroup; + _packetRateDecreasePacketNumber = _outgoingPacketNumber + 1; + } } void DatagramSequencer::appendReliableData(int bytes, QVector& spans) { diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 4ab9f7667b..e2ea9d00af 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -99,6 +99,10 @@ public: /// Returns the intput channel at the specified index, creating it if necessary. ReliableChannel* getReliableInputChannel(int index = 0); + /// Starts a packet group. + /// \return the number of packets to write in the group + int startPacketGroup(); + /// Starts a new packet for transmission. /// \return a reference to the Bitstream to use for writing to the packet Bitstream& startPacket(); @@ -203,6 +207,12 @@ private: int _maxPacketSize; + float _packetsPerGroup; + float _packetsToWrite; + float _slowStartThreshold; + int _packetRateIncreasePacketNumber; + int _packetRateDecreasePacketNumber; + QHash _reliableOutputChannels; QHash _reliableInputChannels; }; diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 688749f39b..9cb21faf06 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -652,7 +652,7 @@ Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) : output->setMessagesEnabled(false); QByteArray bytes; if (mode == CONGESTION_MODE) { - const int HUGE_STREAM_BYTES = 50 * 1024 * 1024; + const int HUGE_STREAM_BYTES = 60 * 1024 * 1024; bytes = createRandomBytes(HUGE_STREAM_BYTES, HUGE_STREAM_BYTES); // initialize the pipeline @@ -820,14 +820,23 @@ bool Endpoint::simulate(int iterationNumber) { bytesReceived += datagram.size(); _remainingPipelineCapacity += datagram.size(); } - - Bitstream& out = _sequencer->startPacket(); - out << QVariant(); - _sequencer->endPacket(); - - // record the send - SendRecord record = { _sequencer->getOutgoingPacketNumber() }; - _sendRecords.append(record); + int packetCount = _sequencer->startPacketGroup(); + for (int i = 0; i < packetCount; i++) { + oldDatagramsSent = datagramsSent; + oldBytesSent = bytesSent; + + Bitstream& out = _sequencer->startPacket(); + out << QVariant(); + _sequencer->endPacket(); + + maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent); + maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent); + + // record the send + SendRecord record = { _sequencer->getOutgoingPacketNumber() }; + _sendRecords.append(record); + } + return false; } else if (_mode == METAVOXEL_CLIENT_MODE) { Bitstream& out = _sequencer->startPacket(); From 5effcd24ff5e65c7d6551537232d68a6fe2a7cfb Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 25 Jun 2014 16:56:02 -0700 Subject: [PATCH 34/37] Only increase/decrease rate when we want to send more/have sent more than the minimum, respectively. --- .../metavoxels/src/DatagramSequencer.cpp | 21 ++++++++++++++++++- libraries/metavoxels/src/DatagramSequencer.h | 3 ++- tests/metavoxels/src/MetavoxelTests.cpp | 12 ++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 0097c0fd1d..a75a4bf95b 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -79,11 +79,30 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) { return channel; } -int DatagramSequencer::startPacketGroup() { +int DatagramSequencer::startPacketGroup(int desiredPackets) { + // figure out how much data we have enqueued and increase the number of packets desired + int totalAvailable = 0; + foreach (ReliableChannel* channel, _reliableOutputChannels) { + totalAvailable += channel->getBytesAvailable(); + } + desiredPackets += (totalAvailable / _maxPacketSize); + // increment our packet counter and subtract/return the integer portion _packetsToWrite += _packetsPerGroup; int wholePackets = (int)_packetsToWrite; _packetsToWrite -= wholePackets; + wholePackets = qMin(wholePackets, desiredPackets); + + // if we don't want to send any more, push out the rate increase number past the group + if (desiredPackets <= _packetsPerGroup) { + _packetRateIncreasePacketNumber = _outgoingPacketNumber + wholePackets + 1; + } + + // likewise, if we're only sending one packet, don't let its loss cause rate decrease + if (wholePackets == 1) { + _packetRateDecreasePacketNumber = _outgoingPacketNumber + 2; + } + return wholePackets; } diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index e2ea9d00af..9a4cd1334b 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -100,8 +100,9 @@ public: ReliableChannel* getReliableInputChannel(int index = 0); /// Starts a packet group. + /// \param desiredPackets the number of packets we'd like to write in the group /// \return the number of packets to write in the group - int startPacketGroup(); + int startPacketGroup(int desiredPackets = 1); /// Starts a new packet for transmission. /// \return a reference to the Bitstream to use for writing to the packet diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 9cb21faf06..68aaf7ec70 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -147,6 +147,8 @@ static int bytesSent = 0; static int bytesReceived = 0; static int maxDatagramsPerPacket = 0; static int maxBytesPerPacket = 0; +static int groupsSent = 0; +static int maxPacketsPerGroup = 0; static int highPriorityMessagesSent = 0; static int highPriorityMessagesReceived = 0; static int unreliableMessagesSent = 0; @@ -508,10 +510,12 @@ bool MetavoxelTests::run() { } qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; - qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" << - datagramsReceived << "with" << bytesReceived << "bytes"; + qDebug() << "Sent" << datagramsSent << "datagrams in" << groupsSent << "groups with" << bytesSent << + "bytes, received" << datagramsReceived << "with" << bytesReceived << "bytes"; qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet"; - qDebug() << "Average" << (bytesReceived / datagramsReceived) << "bytes per datagram"; + qDebug() << "Max" << maxPacketsPerGroup << "packets per group"; + qDebug() << "Average" << (bytesReceived / datagramsReceived) << "bytes per datagram," << + (datagramsSent / groupsSent) << "datagrams per group"; qDebug() << "Speed:" << (bytesReceived / SIMULATION_ITERATIONS) << "bytes per iteration"; qDebug() << "Efficiency:" << ((float)streamedBytesReceived / bytesReceived); } @@ -821,6 +825,8 @@ bool Endpoint::simulate(int iterationNumber) { _remainingPipelineCapacity += datagram.size(); } int packetCount = _sequencer->startPacketGroup(); + groupsSent++; + maxPacketsPerGroup = qMax(maxPacketsPerGroup, packetCount); for (int i = 0; i < packetCount; i++) { oldDatagramsSent = datagramsSent; oldBytesSent = bytesSent; From c72e41dcd298c60026a6fb8b45b77df3eef9a819 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Wed, 25 Jun 2014 22:27:54 -0700 Subject: [PATCH 35/37] added Oculus angular velocity, moved sit icon up --- examples/sit.js | 2 +- interface/src/avatar/MyAvatar.cpp | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/examples/sit.js b/examples/sit.js index 32d2eebf4d..77a626c63f 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -19,7 +19,7 @@ var buttonHeight = 46; var buttonPadding = 10; var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth; -var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 ; +var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 - (buttonHeight + buttonPadding); var sitDownButton = Overlays.addOverlay("image", { x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b853f3ff8f..7367f64d73 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -911,11 +911,19 @@ void MyAvatar::updateOrientation(float deltaTime) { float yaw, pitch, roll; OculusManager::getEulerAngles(yaw, pitch, roll); // ... so they need to be converted to degrees before we do math... - + yaw *= DEGREES_PER_RADIAN; + pitch *= DEGREES_PER_RADIAN; + roll *= DEGREES_PER_RADIAN; + + // Record the angular velocity Head* head = getHead(); - head->setBaseYaw(yaw * DEGREES_PER_RADIAN); - head->setBasePitch(pitch * DEGREES_PER_RADIAN); - head->setBaseRoll(roll * DEGREES_PER_RADIAN); + glm::vec3 angularVelocity(yaw - head->getBaseYaw(), pitch - head->getBasePitch(), roll - head->getBaseRoll()); + head->setAngularVelocity(angularVelocity); + + head->setBaseYaw(yaw); + head->setBasePitch(pitch); + head->setBaseRoll(roll); + } // update the euler angles From 0d18f2fbaba2b759b0663235c243fe1608d32f9c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 26 Jun 2014 09:12:44 -0700 Subject: [PATCH 36/37] fix for avatar models with multiple roots --- interface/src/avatar/SkeletonModel.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index fdb3ce03d8..e9328d32ca 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -637,12 +637,15 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { // compute the default transforms and slam the ragdoll positions accordingly // (which puts the shapes where we want them) - transforms[0] = _jointStates[0].getTransform(); - _ragdollPoints[0]._position = extractTranslation(transforms[0]); - _ragdollPoints[0]._lastPosition = _ragdollPoints[0]._position; - for (int i = 1; i < numJoints; i++) { + for (int i = 0; i < numJoints; i++) { const FBXJoint& joint = geometry.joints.at(i); int parentIndex = joint.parentIndex; + if (parentIndex == -1) { + transforms[i] = _jointStates[i].getTransform(); + _ragdollPoints[i]._position = extractTranslation(transforms[i]); + _ragdollPoints[i]._lastPosition = _ragdollPoints[i]._position; + continue; + } assert(parentIndex != -1); glm::quat modifiedRotation = joint.preRotation * joint.rotation * joint.postRotation; From 64c89df5c1ce25aaeeaba7be6c669889b24b949e Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Thu, 26 Jun 2014 10:52:37 -0700 Subject: [PATCH 37/37] Added another concert view --- examples/concertCamera.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/concertCamera.js b/examples/concertCamera.js index 0d26fd8ae0..7280958bc6 100644 --- a/examples/concertCamera.js +++ b/examples/concertCamera.js @@ -16,8 +16,8 @@ var avatarPosition; var cameraNumber = 0; var freeCamera = false; -var cameraLocations = [ {x: 7972.0, y: 241.6, z: 7304.1}, {x: 7973.7, y: 241.6, z: 7304.1}, {x: 7975.5, y: 241.6, z: 7304.1}, {x: 7972.3, y: 241.6, z: 7303.3}, {x: 7971.4, y: 241.6, z: 7304.3} ]; -var cameraLookAts = [ {x: 7971.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1} ]; +var cameraLocations = [ {x: 7972.2, y: 241.6, z: 7304.1}, {x: 7973.0, y: 241.6, z: 7304.1}, {x: 7975.5, y: 241.6, z: 7304.1}, {x: 7972.3, y: 241.6, z: 7303.3}, {x: 7971.4, y: 241.6, z: 7304.3}, {x: 7973.5, y: 240.6, z: 7302.5} ]; +var cameraLookAts = [ {x: 7971.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241.6, z: 7304.1}, {x: 7972.1, y: 241., z: 7304.1} ]; function saveCameraState() { oldMode = Camera.getMode();