From 069199554ccee24f98fcbc8229dc6e67e124c069 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 15 Dec 2015 16:07:37 -0800 Subject: [PATCH 01/41] checkpoint --- interface/src/Application.cpp | 6 ++- interface/src/LODManager.cpp | 66 ++++++++++++++++++++++++-- interface/src/LODManager.h | 22 +++++++++ interface/src/ui/PreferencesDialog.cpp | 17 ++++++- interface/src/ui/PreferencesDialog.h | 1 + interface/src/ui/Stats.cpp | 4 +- interface/ui/preferencesDialog.ui | 45 ++++++++++++++++-- 7 files changed, 148 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4494203a49..97bc3f3689 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2801,7 +2801,11 @@ void Application::update(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); - updateLOD(); + if (DependencyManager::get()->getUseAcuity()) { + updateLOD(); + } else { + DependencyManager::get()->updatePIDRenderDistance(getTargetFrameRate(), getLastInstanteousFps(), deltaTime, isThrottleRendering()); + } { PerformanceTimer perfTimer("devices"); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 368143e36e..dfe03137d5 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -20,9 +20,25 @@ Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); +// There are two different systems in use, based on lodPreference: +// pid: renderDistance is automatically adjusted such that frame rate targets are met. +// acuity: a pseudo-acuity target is held, or adjusted to match minimum frame rates. +// If unspecified, acuity is used only if user has specified non-default minumum frame rates. +Setting::Handle lodPreference("lodPreference", (int)LODManager::LODPreference::unspecified); LODManager::LODManager() { calculateAvatarLODDistanceMultiplier(); + + _renderDistanceController.setControlledValueHighLimit(20.0f); + _renderDistanceController.setControlledValueLowLimit(1.0f / (float)TREE_SCALE); + // Advice for tuning parameters: + // See PIDController.h. There's a section on tuning in the reference. + // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) + //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); + // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. + _renderDistanceController.setKP(0.000012f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. + _renderDistanceController.setKI(0.00002f); // Big enough to bring us to target with the above KP. + _renderDistanceController.setHistorySize("FIXME", 240); } float LODManager::getLODDecreaseFPS() { @@ -39,7 +55,6 @@ float LODManager::getLODIncreaseFPS() { return getDesktopLODIncreaseFPS(); } - void LODManager::autoAdjustLOD(float currentFPS) { // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't @@ -217,15 +232,53 @@ QString LODManager::getLODFeedbackText() { return result; } +static float renderDistance = (float)TREE_SCALE; +static int renderedCount = 0; +static int lastRenderedCount = 0; +bool LODManager::getUseAcuity() { return lodPreference.get() == (int)LODManager::LODPreference::acuity; } +void LODManager::setUseAcuity(bool newValue) { lodPreference.set(newValue ? (int)LODManager::LODPreference::acuity : (int)LODManager::LODPreference::pid); } +float LODManager::getRenderDistance() { + return renderDistance; +} +int LODManager::getRenderedCount() { + return lastRenderedCount; +} +// compare audoAdjustLOD() +void LODManager::updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled) { + float distance; + if (!isThrottled) { + _renderDistanceController.setMeasuredValueSetpoint(targetFps); // No problem updating in flight. + // The PID controller raises the controlled value when the measured value goes up. + // The measured value is frame rate. When the controlled value (1 / render cutoff distance) + // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate + // goes up. + distance = 1.0f / _renderDistanceController.update(measuredFps, deltaTime); + } + else { + // Here we choose to just use the maximum render cutoff distance if throttled. + distance = 1.0f / _renderDistanceController.getControlledValueLowLimit(); + } + _renderDistanceAverage.updateAverage(distance); + renderDistance = _renderDistanceAverage.getAverage(); // average only once per cycle + lastRenderedCount = renderedCount; + renderedCount = 0; +} + bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { + float distanceToCamera = glm::length(bounds.calcCenter() - args->_viewFrustum->getPosition()); + float largestDimension = bounds.getLargestDimension(); + if (!getUseAcuity()) { + bool isRendered = fabsf(distanceToCamera - largestDimension) < renderDistance; + renderedCount += isRendered ? 1 : 0; + return isRendered; + } + const float maxScale = (float)TREE_SCALE; const float octreeToMeshRatio = 4.0f; // must be this many times closer to a mesh than a voxel to see it. float octreeSizeScale = args->_sizeScale; int boundaryLevelAdjust = args->_boundaryLevelAdjust; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / octreeToMeshRatio; - float distanceToCamera = glm::length(bounds.calcCenter() - args->_viewFrustum->getPosition()); - float largestDimension = bounds.getLargestDimension(); - + static bool shouldRenderTableNeedsBuilding = true; static QMap shouldRenderTable; if (shouldRenderTableNeedsBuilding) { @@ -315,6 +368,11 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { void LODManager::loadSettings() { setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); + + if (lodPreference.get() == (int)LODManager::LODPreference::unspecified) { + setUseAcuity((getDesktopLODDecreaseFPS() != DEFAULT_DESKTOP_LOD_DOWN_FPS) || (getHMDLODDecreaseFPS() != DEFAULT_HMD_LOD_DOWN_FPS)); + } + Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(getUseAcuity()); } void LODManager::saveSettings() { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index b0185b528f..21a30bceaf 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -15,6 +15,7 @@ #include #include #include +#include #include const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 15.0; @@ -81,6 +82,24 @@ public: Q_INVOKABLE float getLODDecreaseFPS(); Q_INVOKABLE float getLODIncreaseFPS(); + enum class LODPreference { + pid = 0, + acuity, + unspecified + }; + static bool getUseAcuity(); + static void setUseAcuity(bool newValue); + Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } + Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } + Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); } + Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } + Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } + Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } + Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); } + void updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled); + float getRenderDistance(); + int getRenderedCount(); + static bool shouldRender(const RenderArgs* args, const AABox& bounds); bool shouldRenderMesh(float largestDimension, float distanceToCamera); void autoAdjustLOD(float currentFPS); @@ -116,6 +135,9 @@ private: bool _shouldRenderTableNeedsRebuilding = true; QMap _shouldRenderTable; + + PIDController _renderDistanceController{}; + SimpleMovingAverage _renderDistanceAverage{ 10 }; }; #endif // hifi_LODManager_h diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 9a995263cd..bc08391bbe 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -48,6 +48,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); connect(ui.appearanceDescription, &QLineEdit::editingFinished, this, &PreferencesDialog::changeFullAvatarURL); + connect(ui.useAcuityCheckBox, &QCheckBox::clicked, this, &PreferencesDialog::changeUseAcuity); connect(qApp, &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); @@ -58,6 +59,15 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : UIUtil::scaleWidgetFontSizes(this); } +void PreferencesDialog::changeUseAcuity() { + bool useAcuity = ui.useAcuityCheckBox->isChecked(); + ui.label_desktopMinimumFPSSpin->setEnabled(useAcuity); + ui.desktopMinimumFPSSpin->setEnabled(useAcuity); + ui.label_hmdMinimumFPSSpin->setEnabled(useAcuity); + ui.hmdMinimumFPSSpin->setEnabled(useAcuity); + ui.label_smallestReasonableRenderHorizon->setText(useAcuity ? "Minimum Avatar Display Distance (@half speed)" : "Minimum Display Distance (@half speed)"); + Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(useAcuity); +} void PreferencesDialog::changeFullAvatarURL() { DependencyManager::get()->getMyAvatar()->useFullAvatarURL(ui.appearanceDescription->text(), ""); this->fullAvatarURLChanged(ui.appearanceDescription->text(), ""); @@ -212,9 +222,11 @@ void PreferencesDialog::loadPreferences() { // LOD items auto lodManager = DependencyManager::get(); + ui.useAcuityCheckBox->setChecked(lodManager->getUseAcuity()); ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS()); ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS()); - ui.avatarRenderSmallestReasonableHorizon->setValue(1.0f / DependencyManager::get()->getRenderDistanceInverseHighLimit()); + ui.smallestReasonableRenderHorizon->setValue(1.0f / DependencyManager::get()->getRenderDistanceInverseHighLimit()); + changeUseAcuity(); } void PreferencesDialog::savePreferences() { @@ -303,7 +315,8 @@ void PreferencesDialog::savePreferences() { // LOD items auto lodManager = DependencyManager::get(); + lodManager->setUseAcuity(ui.useAcuityCheckBox->isChecked()); lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value()); lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value()); - DependencyManager::get()->setRenderDistanceInverseHighLimit(1.0f / ui.avatarRenderSmallestReasonableHorizon->value()); + DependencyManager::get()->setRenderDistanceInverseHighLimit(1.0f / ui.smallestReasonableRenderHorizon->value()); } diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index 1536eca3ee..a6c27dee08 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -51,6 +51,7 @@ private slots: void openScriptsLocationBrowser(); void changeFullAvatarURL(); void fullAvatarURLChanged(const QString& newValue, const QString& modelName); + void changeUseAcuity(); }; #endif // hifi_PreferencesDialog_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 6acacee41d..e391d5c40a 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -116,8 +116,8 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); - STAT_UPDATE(avatarRenderableCount, avatarManager->getNumberInRenderRange()); - STAT_UPDATE(avatarRenderDistance, (int) round(avatarManager->getRenderDistance())); // deliberately truncating + STAT_UPDATE(avatarRenderableCount, DependencyManager::get()->getRenderedCount()); //FIXME avatarManager->getNumberInRenderRange()); + STAT_UPDATE(avatarRenderDistance, (int)round(DependencyManager::get()->getRenderDistance())); // FIXME avatarManager->getRenderDistance())); // deliberately truncating STAT_UPDATE(serverCount, (int)nodeList->size()); STAT_UPDATE(renderrate, (int)qApp->getFps()); if (qApp->getActiveDisplayPlugin()) { diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index e6a5e2228d..a66c4bf7ee 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -742,6 +742,43 @@ + + + + + 0 + 0 + + + + + 32 + 28 + + + + + 0 + 0 + + + + + Arial + + + + Render based on visual acuity + + + + 0 + 0 + + + + + @@ -757,7 +794,7 @@ 7 - + Arial @@ -842,7 +879,7 @@ 7 - + Arial @@ -927,7 +964,7 @@ 7 - + Arial @@ -963,7 +1000,7 @@ - + 100 From f47873bac3b581609a88e73b322f20051b1a8d52 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 15 Dec 2015 17:00:49 -0800 Subject: [PATCH 02/41] stats checkpoint --- interface/src/LODManager.cpp | 2 +- interface/src/ui/Stats.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index dfe03137d5..5ac2f687c7 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -247,7 +247,7 @@ int LODManager::getRenderedCount() { void LODManager::updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled) { float distance; if (!isThrottled) { - _renderDistanceController.setMeasuredValueSetpoint(targetFps); // No problem updating in flight. + _renderDistanceController.setMeasuredValueSetpoint(targetFps / 2.0f); // No problem updating in flight. // The PID controller raises the controlled value when the measured value goes up. // The measured value is frame rate. When the controlled value (1 / render cutoff distance) // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e391d5c40a..a86ed97f0c 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -116,8 +116,6 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); - STAT_UPDATE(avatarRenderableCount, DependencyManager::get()->getRenderedCount()); //FIXME avatarManager->getNumberInRenderRange()); - STAT_UPDATE(avatarRenderDistance, (int)round(DependencyManager::get()->getRenderDistance())); // FIXME avatarManager->getRenderDistance())); // deliberately truncating STAT_UPDATE(serverCount, (int)nodeList->size()); STAT_UPDATE(renderrate, (int)qApp->getFps()); if (qApp->getActiveDisplayPlugin()) { @@ -285,6 +283,8 @@ void Stats::updateStats(bool force) { STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount()); // LOD Details STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get()->getLODFeedbackText()); + STAT_UPDATE(avatarRenderableCount, DependencyManager::get()->getRenderedCount()); //FIXME avatarManager->getNumberInRenderRange()); + STAT_UPDATE(avatarRenderDistance, (int)round(DependencyManager::get()->getRenderDistance())); // FIXME avatarManager->getRenderDistance())); // deliberately truncating } bool performanceTimerIsActive = PerformanceTimer::isActive(); From 09d4a06ad4dbe8c1adbe093d66757efcb475fbe4 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 16 Dec 2015 15:30:48 -0800 Subject: [PATCH 03/41] LODManager-centric stats and preferences. --- interface/resources/qml/Stats.qml | 4 ++-- interface/src/LODManager.cpp | 30 ++++++++++++++++++++------ interface/src/LODManager.h | 9 +++++--- interface/src/avatar/AvatarManager.cpp | 12 +++-------- interface/src/avatar/AvatarManager.h | 2 +- interface/src/ui/PreferencesDialog.cpp | 4 ++-- interface/src/ui/Stats.cpp | 4 ++-- interface/src/ui/Stats.h | 11 ++++++---- 8 files changed, 46 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 56d4f9c14b..7048efe55c 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -258,14 +258,14 @@ Item { Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded + visible: root.showAcuity text: "LOD: " + root.lodStatus; } Text { color: root.fontColor; font.pixelSize: root.fontSize visible: root.expanded - text: "Renderable avatars: " + root.avatarRenderableCount + " w/in " + root.avatarRenderDistance + "m"; + text: root.lodStatsRenderText; } } } diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 5ac2f687c7..9d61a1c02c 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include @@ -21,24 +22,31 @@ Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); // There are two different systems in use, based on lodPreference: -// pid: renderDistance is automatically adjusted such that frame rate targets are met. -// acuity: a pseudo-acuity target is held, or adjusted to match minimum frame rates. +// pid: renderDistance is adjusted by a PID such that frame rate targets are met. +// acuity: a pseudo-acuity target is held, or adjusted to match minimum frame rates (and a PID controlls avatar rendering distance) // If unspecified, acuity is used only if user has specified non-default minumum frame rates. Setting::Handle lodPreference("lodPreference", (int)LODManager::LODPreference::unspecified); +const float SMALLEST_REASONABLE_HORIZON = 50.0f; // meters +Setting::Handle renderDistanceInverseHighLimit("renderDistanceInverseHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON); +void LODManager::setRenderDistanceInverseHighLimit(float newValue) { + renderDistanceInverseHighLimit.set(newValue); // persist it, and tell all the controllers that use it + _renderDistanceController.setControlledValueHighLimit(newValue); + DependencyManager::get()->setRenderDistanceInverseHighLimit(newValue); +} LODManager::LODManager() { calculateAvatarLODDistanceMultiplier(); - _renderDistanceController.setControlledValueHighLimit(20.0f); - _renderDistanceController.setControlledValueLowLimit(1.0f / (float)TREE_SCALE); + setRenderDistanceInverseHighLimit(renderDistanceInverseHighLimit.get()); + setRenderDistanceInverseLowLimit(1.0f / (float)TREE_SCALE); // Advice for tuning parameters: // See PIDController.h. There's a section on tuning in the reference. // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. - _renderDistanceController.setKP(0.000012f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. - _renderDistanceController.setKI(0.00002f); // Big enough to bring us to target with the above KP. - _renderDistanceController.setHistorySize("FIXME", 240); + setRenderDistanceKP(0.000012f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. + setRenderDistanceKI(0.00002f); // Big enough to bring us to target with the above KP. + //setRenderDistanceControllerHistory("FIXME", 240); } float LODManager::getLODDecreaseFPS() { @@ -243,6 +251,14 @@ float LODManager::getRenderDistance() { int LODManager::getRenderedCount() { return lastRenderedCount; } +QString LODManager::getLODStatsRenderText() { + if (getUseAcuity()) { + auto avatarManager = DependencyManager::get(); + return QString("Renderable avatars: ") + QString::number(avatarManager->getNumberInRenderRange()) + " w/in " + QString::number((int)avatarManager->getRenderDistance()) + "m"; + } else { + return QString("Rendered objects: ") + QString::number(getRenderedCount()) + " w/in " + QString::number((int)getRenderDistance()) + "m"; + } +} // compare audoAdjustLOD() void LODManager::updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled) { float distance; diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 21a30bceaf..6b141004e5 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -89,16 +89,19 @@ public: }; static bool getUseAcuity(); static void setUseAcuity(bool newValue); - Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } - Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); } Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } + Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } + Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } + Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); } Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } - Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); } + Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); } + Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue); void updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled); float getRenderDistance(); int getRenderedCount(); + QString getLODStatsRenderText(); static bool shouldRender(const RenderArgs* args, const AABox& bounds); bool shouldRenderMesh(float largestDimension, float distanceToCamera); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 7c1a52f1b3..4f59e21cda 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -34,6 +34,7 @@ #include "Application.h" #include "Avatar.h" #include "AvatarManager.h" +#include "LODManager.h" #include "Menu.h" #include "MyAvatar.h" #include "SceneScriptingInterface.h" @@ -76,13 +77,6 @@ AvatarManager::AvatarManager(QObject* parent) : packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket"); } -const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters -Setting::Handle avatarRenderDistanceInverseHighLimit("avatarRenderDistanceHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON); -void AvatarManager::setRenderDistanceInverseHighLimit(float newValue) { - avatarRenderDistanceInverseHighLimit.set(newValue); - _renderDistanceController.setControlledValueHighLimit(newValue); -} - void AvatarManager::init() { _myAvatar->init(); { @@ -100,8 +94,8 @@ void AvatarManager::init() { scene->enqueuePendingChanges(pendingChanges); const float target_fps = qApp->getTargetFrameRate(); - _renderDistanceController.setMeasuredValueSetpoint(target_fps); - _renderDistanceController.setControlledValueHighLimit(avatarRenderDistanceInverseHighLimit.get()); + _renderDistanceController.setMeasuredValueSetpoint(target_fps / 2.0f); + _renderDistanceController.setControlledValueHighLimit(DependencyManager::get()->getRenderDistanceInverseHighLimit()); _renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE); // Advice for tuning parameters: // See PIDController.h. There's a section on tuning in the reference. diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 84a4bc44b8..290dda3668 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -79,7 +79,7 @@ public: Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } - Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue); + Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); } public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index bc08391bbe..133fc409fb 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -225,7 +225,7 @@ void PreferencesDialog::loadPreferences() { ui.useAcuityCheckBox->setChecked(lodManager->getUseAcuity()); ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS()); ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS()); - ui.smallestReasonableRenderHorizon->setValue(1.0f / DependencyManager::get()->getRenderDistanceInverseHighLimit()); + ui.smallestReasonableRenderHorizon->setValue(1.0f / lodManager->getRenderDistanceInverseHighLimit()); changeUseAcuity(); } @@ -318,5 +318,5 @@ void PreferencesDialog::savePreferences() { lodManager->setUseAcuity(ui.useAcuityCheckBox->isChecked()); lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value()); lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value()); - DependencyManager::get()->setRenderDistanceInverseHighLimit(1.0f / ui.smallestReasonableRenderHorizon->value()); + lodManager->setRenderDistanceInverseHighLimit(1.0f / ui.smallestReasonableRenderHorizon->value()); } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index a86ed97f0c..b82ab93068 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -283,9 +283,9 @@ void Stats::updateStats(bool force) { STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount()); // LOD Details STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get()->getLODFeedbackText()); - STAT_UPDATE(avatarRenderableCount, DependencyManager::get()->getRenderedCount()); //FIXME avatarManager->getNumberInRenderRange()); - STAT_UPDATE(avatarRenderDistance, (int)round(DependencyManager::get()->getRenderDistance())); // FIXME avatarManager->getRenderDistance())); // deliberately truncating + STAT_UPDATE(lodStatsRenderText, DependencyManager::get()->getLODStatsRenderText()); } + STAT_UPDATE(showAcuity, (_expanded || force) && DependencyManager::get()->getUseAcuity()); bool performanceTimerIsActive = PerformanceTimer::isActive(); bool displayPerf = _expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index eb28883001..5e948ce0f0 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -30,6 +30,7 @@ class Stats : public QQuickItem { Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT) Q_PROPERTY(float audioPacketlossUpstream READ getAudioPacketLossUpstream) Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream) + Q_PROPERTY(bool showAcuity READ getShowAcuity WRITE setShowAcuity NOTIFY showAcuityChanged) STATS_PROPERTY(int, serverCount, 0) STATS_PROPERTY(int, renderrate, 0) @@ -37,8 +38,6 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarCount, 0) - STATS_PROPERTY(int, avatarRenderableCount, 0) - STATS_PROPERTY(int, avatarRenderDistance, 0) STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetOutCount, 0) STATS_PROPERTY(float, mbpsIn, 0) @@ -77,6 +76,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(QString, packetStats, QString()) STATS_PROPERTY(QString, lodStatus, QString()) STATS_PROPERTY(QString, timingStats, QString()) + STATS_PROPERTY(QString, lodStatsRenderText, QString()) STATS_PROPERTY(int, serverElements, 0) STATS_PROPERTY(int, serverInternal, 0) STATS_PROPERTY(int, serverLeaves, 0) @@ -108,12 +108,15 @@ public: emit expandedChanged(); } } + bool getShowAcuity() { return _showAcuity; } + void setShowAcuity(bool newValue) { _showAcuity = newValue; } public slots: void forceUpdateStats() { updateStats(true); } signals: void expandedChanged(); + void showAcuityChanged(); void timingExpandedChanged(); void serverCountChanged(); void renderrateChanged(); @@ -121,8 +124,7 @@ signals: void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); - void avatarRenderableCountChanged(); - void avatarRenderDistanceChanged(); + void lodStatsRenderTextChanged(); void packetInCountChanged(); void packetOutCountChanged(); void mbpsInChanged(); @@ -172,6 +174,7 @@ private: int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process bool _resetRecentMaxPacketsSoon{ true }; bool _expanded{ false }; + bool _showAcuity{ false }; bool _timingExpanded{ false }; QString _monospaceFont; const AudioIOStats* _audioStats; From e058057e3da120b2b183faa5605b8948f85136f6 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 16 Dec 2015 15:41:23 -0800 Subject: [PATCH 04/41] fix logging --- libraries/shared/src/PIDController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index 0b2411530a..0a376872cc 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -31,7 +31,7 @@ public: float update(float measuredValue, float dt, bool resetAccumulator = false); // returns the new computedValue void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging - bool getIsLogging() { return _history.capacity(); } + bool getIsLogging() { return !_label.isEmpty(); } float getMeasuredValueSetpoint() const { return _measuredValueSetpoint; } // In normal operation (where we can easily reach setpoint), controlledValue is typcially pinned at max. // Defaults to [0, max float], but for 1/LODdistance, it might be, say, [0, 0.2 or 0.1] From 23ffcdca3e354259e3afbd8dc9c5cd829b315745 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 16 Dec 2015 17:01:14 -0800 Subject: [PATCH 05/41] big sweep --- interface/src/LODManager.cpp | 9 ++----- interface/src/Menu.h | 2 +- interface/src/avatar/Avatar.cpp | 20 --------------- interface/src/avatar/AvatarManager.cpp | 34 -------------------------- interface/src/avatar/AvatarManager.h | 20 +-------------- interface/src/ui/PreferencesDialog.cpp | 1 + 6 files changed, 5 insertions(+), 81 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 9d61a1c02c..eafc3b29f2 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -31,7 +31,6 @@ Setting::Handle renderDistanceInverseHighLimit("renderDistanceInverseHigh void LODManager::setRenderDistanceInverseHighLimit(float newValue) { renderDistanceInverseHighLimit.set(newValue); // persist it, and tell all the controllers that use it _renderDistanceController.setControlledValueHighLimit(newValue); - DependencyManager::get()->setRenderDistanceInverseHighLimit(newValue); } LODManager::LODManager() { @@ -252,12 +251,8 @@ int LODManager::getRenderedCount() { return lastRenderedCount; } QString LODManager::getLODStatsRenderText() { - if (getUseAcuity()) { - auto avatarManager = DependencyManager::get(); - return QString("Renderable avatars: ") + QString::number(avatarManager->getNumberInRenderRange()) + " w/in " + QString::number((int)avatarManager->getRenderDistance()) + "m"; - } else { - return QString("Rendered objects: ") + QString::number(getRenderedCount()) + " w/in " + QString::number((int)getRenderDistance()) + "m"; - } + QString label = getUseAcuity() ? "Renderable avatars: " : "Rendered objects: "; + return label + QString::number(getRenderedCount()) + " w/in " + QString::number((int)getRenderDistance()) + "m"; } // compare audoAdjustLOD() void LODManager::updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled) { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 55266cf062..22da7d9127 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -64,6 +64,7 @@ public: void saveSettings(); MenuWrapper* getMenu(const QString& menuName); + MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); @@ -128,7 +129,6 @@ private: const QString& grouping = QString()); QAction* getActionFromName(const QString& menuName, MenuWrapper* menu); - MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart); QAction* getMenuAction(const QString& menuName); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 7a234f2b47..9cede63ab0 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -164,26 +164,6 @@ void Avatar::simulate(float deltaTime) { _shouldRenderBillboard = true; qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); } - - const bool isControllerLogging = DependencyManager::get()->getRenderDistanceControllerIsLogging(); - float renderDistance = DependencyManager::get()->getRenderDistance(); - const float SKIP_HYSTERESIS_PROPORTION = isControllerLogging ? 0.0f : BILLBOARD_HYSTERESIS_PROPORTION; - float distance = glm::distance(qApp->getCamera()->getPosition(), getPosition()); - if (_shouldSkipRender) { - if (distance < renderDistance * (1.0f - SKIP_HYSTERESIS_PROPORTION)) { - _shouldSkipRender = false; - _skeletonModel.setVisibleInScene(true, qApp->getMain3DScene()); - if (!isControllerLogging) { // Test for isMyAvatar is prophylactic. Never occurs in current code. - qCDebug(interfaceapp) << "Rerendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance; - } - } - } else if (distance > renderDistance * (1.0f + SKIP_HYSTERESIS_PROPORTION)) { - _shouldSkipRender = true; - _skeletonModel.setVisibleInScene(false, qApp->getMain3DScene()); - if (!isControllerLogging) { - qCDebug(interfaceapp) << "Unrendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance; - } - } // simple frustum check float boundingRadius = getBillboardSize(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4f59e21cda..a6fa611914 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -92,19 +92,6 @@ void AvatarManager::init() { _myAvatar->addToScene(_myAvatar, scene, pendingChanges); } scene->enqueuePendingChanges(pendingChanges); - - const float target_fps = qApp->getTargetFrameRate(); - _renderDistanceController.setMeasuredValueSetpoint(target_fps / 2.0f); - _renderDistanceController.setControlledValueHighLimit(DependencyManager::get()->getRenderDistanceInverseHighLimit()); - _renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE); - // Advice for tuning parameters: - // See PIDController.h. There's a section on tuning in the reference. - // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) - //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); - // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. - _renderDistanceController.setKP(0.0008f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. - _renderDistanceController.setKI(0.0006f); // Big enough to bring us to target with the above KP. - _renderDistanceController.setKD(0.000001f); // A touch of kd increases the speed by which we get there. } void AvatarManager::updateMyAvatar(float deltaTime) { @@ -139,23 +126,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); - float distance; - if (!qApp->isThrottleRendering()) { - _renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight. - // The PID controller raises the controlled value when the measured value goes up. - // The measured value is frame rate. When the controlled value (1 / render cutoff distance) - // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate - // goes up. - const float deduced = qApp->getLastUnsynchronizedFps(); - distance = 1.0f / _renderDistanceController.update(deduced, deltaTime); - } else { - // Here we choose to just use the maximum render cutoff distance if throttled. - distance = 1.0f / _renderDistanceController.getControlledValueLowLimit(); - } - _renderDistanceAverage.updateAverage(distance); - _renderDistance = _renderDistanceAverage.getAverage(); - int renderableCount = 0; - // simulate avatars auto hashCopy = getHashCopy(); @@ -173,14 +143,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } else { avatar->startUpdate(); avatar->simulate(deltaTime); - if (avatar->getShouldRender()) { - renderableCount++; - } avatar->endUpdate(); ++avatarIterator; } } - _renderedAvatarCount = renderableCount; // simulate avatar fades simulateAvatarFades(deltaTime); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 290dda3668..0e3dab109d 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -45,7 +45,6 @@ public: void clearOtherAvatars(); bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } - PIDController& getRenderDistanceController() { return _renderDistanceController; } class LocalLight { public: @@ -67,20 +66,7 @@ public: void handleCollisionEvents(const CollisionEvents& collisionEvents); void updateAvatarPhysicsShape(Avatar* avatar); - - // Expose results and parameter-tuning operations to other systems, such as stats and javascript. - Q_INVOKABLE float getRenderDistance() { return _renderDistance; } - Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); } - Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); } - Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; } - Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } - Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } - Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); } - Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } - Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } - Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } - Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); } - + public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); @@ -106,10 +92,6 @@ private: QVector _localLights; bool _shouldShowReceiveStats = false; - float _renderDistance { (float) TREE_SCALE }; - int _renderedAvatarCount { 0 }; - PIDController _renderDistanceController { }; - SimpleMovingAverage _renderDistanceAverage { 10 }; SetOfAvatarMotionStates _avatarMotionStates; SetOfMotionStates _motionStatesToAdd; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 133fc409fb..c561aa1b36 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -67,6 +67,7 @@ void PreferencesDialog::changeUseAcuity() { ui.hmdMinimumFPSSpin->setEnabled(useAcuity); ui.label_smallestReasonableRenderHorizon->setText(useAcuity ? "Minimum Avatar Display Distance (@half speed)" : "Minimum Display Distance (@half speed)"); Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(useAcuity); + Menu::getInstance()->getSubMenuFromName(MenuOption::RenderResolution, Menu::getInstance()->getSubMenuFromName("Render", Menu::getInstance()->getMenu("Developer")))->setEnabled(useAcuity); } void PreferencesDialog::changeFullAvatarURL() { DependencyManager::get()->getMyAvatar()->useFullAvatarURL(ui.appearanceDescription->text(), ""); From 7e54e1f7536694f3a7f3107e8dd7592815740ee7 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 16 Dec 2015 20:25:37 -0800 Subject: [PATCH 06/41] missed menu --- interface/src/LODManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index eafc3b29f2..5f55e0fed0 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -384,6 +384,7 @@ void LODManager::loadSettings() { setUseAcuity((getDesktopLODDecreaseFPS() != DEFAULT_DESKTOP_LOD_DOWN_FPS) || (getHMDLODDecreaseFPS() != DEFAULT_HMD_LOD_DOWN_FPS)); } Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(getUseAcuity()); + Menu::getInstance()->getSubMenuFromName(MenuOption::RenderResolution, Menu::getInstance()->getSubMenuFromName("Render", Menu::getInstance()->getMenu("Developer")))->setEnabled(getUseAcuity()); } void LODManager::saveSettings() { From 5ac5ecfc224b7695bfa1bc11e54d37ead8bf45a4 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 16 Dec 2015 20:35:56 -0800 Subject: [PATCH 07/41] drop obsolete include --- interface/src/avatar/AvatarManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index a6fa611914..b550ce131f 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -34,7 +34,6 @@ #include "Application.h" #include "Avatar.h" #include "AvatarManager.h" -#include "LODManager.h" #include "Menu.h" #include "MyAvatar.h" #include "SceneScriptingInterface.h" From 75814e712c9ed4804b36d4dcc7919dc371b8307b Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 16 Dec 2015 20:56:54 -0800 Subject: [PATCH 08/41] compiler warning --- interface/src/ui/ApplicationCompositor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index e5ecdfe217..abc3520c83 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -34,7 +34,6 @@ static const quint64 MSECS_TO_USECS = 1000ULL; static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; -static const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; static const float reticleSize = TWO_PI / 100.0f; static const float CURSOR_PIXEL_SIZE = 32.0f; From 6b5b272cd755a0f79561780433b44d1584702747 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Dec 2015 09:52:11 -0800 Subject: [PATCH 09/41] Added SoftAttachmentModel class. Is a subclass of Model, it overrides the updateClusterMatrices so it will pull the actual joint matrices from a different rig override. For the avatar soft attachment system, this override will be the Avatar::_skeletonModel rig. This will give us the ability for an avatar to "wear" non-rigid attachments, such as clothing. --- interface/src/avatar/SoftAttachmentModel.cpp | 83 ++++++++++++++++++++ interface/src/avatar/SoftAttachmentModel.h | 34 ++++++++ libraries/animation/src/Rig.cpp | 8 ++ libraries/animation/src/Rig.h | 1 + libraries/render-utils/src/Model.cpp | 3 + libraries/render-utils/src/Model.h | 5 +- 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 interface/src/avatar/SoftAttachmentModel.cpp create mode 100644 interface/src/avatar/SoftAttachmentModel.h diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp new file mode 100644 index 0000000000..a9f6b300ac --- /dev/null +++ b/interface/src/avatar/SoftAttachmentModel.cpp @@ -0,0 +1,83 @@ +// +// SoftAttachmentModel.cpp +// interface/src/avatar +// +// Created by Anthony J. Thibault on 12/17/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SoftAttachmentModel.h" + +SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) : + Model(rig, parent), + _rigOverride(rigOverride) { + assert(_rig); + assert(_rigOverride); +} + +SoftAttachmentModel::~SoftAttachmentModel() { +} + +// virtual +void SoftAttachmentModel::updateRig(float deltaTime, glm::mat4 parentTransform) { + _needsUpdateClusterMatrices = true; +} + +int SoftAttachmentModel::getJointIndexOverride(int i) const { + QString name = _rig->nameOfJoint(i); + if (name.isEmpty()) { + return -1; + } + return _rigOverride->indexOfJoint(name); +} + +// virtual +// use the _rigOverride matrices instead of the Model::_rig +void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { + if (!_needsUpdateClusterMatrices) { + return; + } + _needsUpdateClusterMatrices = false; + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + + glm::mat4 modelToWorld = glm::mat4_cast(modelOrientation); + for (int i = 0; i < _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + const FBXMesh& mesh = geometry.meshes.at(i); + + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + + // TODO: cache these look ups as an optimization + int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); + glm::mat4 jointMatrix(glm::mat4::_null); + if (jointIndexOverride >= 0 && jointIndexOverride < getJointStateCount()) { + jointMatrix = _rigOverride->getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig->getJointTransform(cluster.jointIndex); + } + state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix; + } + + // Once computed the cluster matrices, update the buffer(s) + if (mesh.clusters.size() > 1) { + if (!state.clusterBuffer) { + state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); + } else { + state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); + } + } + } + + // post the blender if we're not currently waiting for one to finish + if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + _blendedBlendshapeCoefficients = _blendshapeCoefficients; + DependencyManager::get()->noteRequiresBlend(this); + } +} diff --git a/interface/src/avatar/SoftAttachmentModel.h b/interface/src/avatar/SoftAttachmentModel.h new file mode 100644 index 0000000000..eda587af68 --- /dev/null +++ b/interface/src/avatar/SoftAttachmentModel.h @@ -0,0 +1,34 @@ +// +// SoftAttachmentModel.h +// interface/src/avatar +// +// Created by Anthony J. Thibault on 12/17/15. +// Copyright 2015 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_SoftAttachmentModel_h +#define hifi_SoftAttachmentModel_h + +#include + +class SoftAttachmentModel : public Model { + Q_OBJECT + +public: + + SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride); + ~SoftAttachmentModel(); + + virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override; + virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) override; + +protected: + int getJointIndexOverride(int i) const; + + RigPointer _rigOverride; +}; + +#endif // hifi_SoftAttachmentModel_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 68f382d2d9..bfd4888961 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -245,6 +245,14 @@ int Rig::indexOfJoint(const QString& jointName) const { } } +QString Rig::nameOfJoint(int jointIndex) const { + if (_animSkeleton) { + return _animSkeleton->getJointName(jointIndex); + } else { + return ""; + } +} + void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { AnimPose newModelOffset = AnimPose(modelOffsetMat); if (!isEqual(_modelOffset.trans, newModelOffset.trans) || diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 9faf93e40b..669af2ea64 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -91,6 +91,7 @@ public: bool jointStatesEmpty(); int getJointStateCount() const; int indexOfJoint(const QString& jointName) const; + QString nameOfJoint(int jointIndex) const; void setModelOffset(const glm::mat4& modelOffsetMat); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index dbd3a6289f..efaa79ddbb 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -971,11 +971,14 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { _needsUpdateClusterMatrices = true; _rig->updateAnimations(deltaTime, parentTransform); } + void Model::simulateInternal(float deltaTime) { // update the world space transforms for all joints glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); updateRig(deltaTime, parentTransform); } + +// virtual void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { PerformanceTimer perfTimer("Model::updateClusterMatrices"); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index a93338e41a..3416a9b71e 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -112,7 +112,7 @@ public: bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); - void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); + virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -312,7 +312,7 @@ protected: // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; -private: +protected: void deleteGeometry(); void initJointTransforms(); @@ -370,7 +370,6 @@ private: bool _showCollisionHull = false; friend class ModelMeshPartPayload; -protected: RigPointer _rig; }; From 1618e0a92f1e72a937ab59c24472037dde6fe64d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Dec 2015 13:32:09 -0800 Subject: [PATCH 10/41] Network, Preferences and UI support for soft attachments Added an isSoft field to the AttachmentData which is edited by the Attachment Dialog Menu, sent over the network via AvatarData identity packets and saved in the Interface.ini preferences. AvatarData and AvatarBulkData version number has been bumped. Changed Avatar attachment collections to use smart pointers to models instead of raw ones. Removed _unusedAttachmentModels. I don't think the caching was worth the added code complexity. --- interface/src/Application.cpp | 3 +- interface/src/avatar/Avatar.cpp | 84 ++++++++++++++----- interface/src/avatar/Avatar.h | 8 +- interface/src/avatar/MyAvatar.cpp | 14 +++- interface/src/avatar/MyAvatar.h | 4 +- interface/src/avatar/SoftAttachmentModel.cpp | 3 +- interface/src/ui/AttachmentsDialog.cpp | 37 ++++---- interface/src/ui/AttachmentsDialog.h | 11 +-- libraries/avatars/src/AvatarData.cpp | 10 ++- libraries/avatars/src/AvatarData.h | 5 +- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 5 ++ 12 files changed, 132 insertions(+), 54 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fa5565417d..960e5adda1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3017,8 +3017,7 @@ void Application::update(float deltaTime) { getEntities()->update(); // update the models... } - myAvatar->harvestResultsFromPhysicsSimulation(); - myAvatar->simulateAttachments(deltaTime); + myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f8040754d7..0ab2425131 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -42,6 +42,7 @@ #include "Util.h" #include "world.h" #include "InterfaceLogging.h" +#include "SoftAttachmentModel.h" #include using namespace std; @@ -108,9 +109,6 @@ Avatar::Avatar(RigPointer rig) : Avatar::~Avatar() { assert(_motionState == nullptr); - for(auto attachment : _unusedAttachments) { - delete attachment; - } } const float BILLBOARD_LOD_DISTANCE = 40.0f; @@ -257,6 +255,8 @@ void Avatar::simulate(float deltaTime) { // until velocity is included in AvatarData update message. //_position += _velocity * deltaTime; measureMotionDerivatives(deltaTime); + + simulateAttachments(deltaTime); } bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { @@ -324,7 +324,7 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr _skeletonModel.addToScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges); - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, pendingChanges); } @@ -335,7 +335,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrgetFaceModel().removeFromScene(scene, pendingChanges); - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { attachmentModel->removeFromScene(scene, pendingChanges); } } @@ -565,15 +565,14 @@ void Avatar::fixupModelsInScene() { faceModel.removeFromScene(scene, pendingChanges); faceModel.addToScene(scene, pendingChanges); } - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges); } } - for (auto attachmentModelToRemove : _attachmentsToRemove) { + for (auto& attachmentModelToRemove : _attachmentsToRemove) { attachmentModelToRemove->removeFromScene(scene, pendingChanges); - _unusedAttachments << attachmentModelToRemove; } _attachmentsToRemove.clear(); scene->enqueuePendingChanges(pendingChanges); @@ -603,21 +602,29 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { return true; } +// virtual void Avatar::simulateAttachments(float deltaTime) { for (int i = 0; i < _attachmentModels.size(); i++) { const AttachmentData& attachment = _attachmentData.at(i); - Model* model = _attachmentModels.at(i); + auto& model = _attachmentModels.at(i); int jointIndex = getJointIndex(attachment.jointName); glm::vec3 jointPosition; glm::quat jointRotation; - if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && - _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { - model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); - model->setRotation(jointRotation * attachment.rotation); - model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale - model->setSnapModelToCenter(false); // hack to force resnap - model->setSnapModelToCenter(true); + if (attachment.isSoft) { + // soft attachments do not have transform offsets + model->setTranslation(getPosition()); + model->setRotation(getOrientation() * Quaternions::Y_180); model->simulate(deltaTime); + } else { + if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && + _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { + model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); + model->setRotation(jointRotation * attachment.rotation); + model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale + model->setSnapModelToCenter(false); // hack to force resnap + model->setSnapModelToCenter(true); + model->simulate(deltaTime); + } } } } @@ -940,13 +947,48 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModel.setURL(_skeletonModelURL); } +// create new model, can return an instance of a SoftAttachmentModel rather then Model +static std::shared_ptr allocateAttachmentModel(bool isSoft, RigPointer rigOverride) { + if (isSoft) { + // cast to std::shared_ptr + return std::dynamic_pointer_cast(std::make_shared(std::make_shared(), nullptr, rigOverride)); + } else { + return std::make_shared(std::make_shared()); + } +} + void Avatar::setAttachmentData(const QVector& attachmentData) { - AvatarData::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, Q_ARG(const QVector, attachmentData)); return; } + + auto oldAttachmentData = _attachmentData; + AvatarData::setAttachmentData(attachmentData); + + // if number of attachments has been reduced, remove excess models. + while (_attachmentModels.size() > attachmentData.size()) { + auto attachmentModel = _attachmentModels.back(); + _attachmentModels.pop_back(); + _attachmentsToRemove.push_back(attachmentModel); + } + + for (int i = 0; i < attachmentData.size(); i++) { + if (i == _attachmentModels.size()) { + // if number of attachments has been increased, we need to allocate a new model + _attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig())); + } + else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) { + // if the attachment has changed type, we need to re-allocate a new one. + _attachmentsToRemove.push_back(_attachmentModels[i]); + _attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig()); + } + _attachmentModels[i]->setURL(attachmentData[i].modelURL); + } + + // AJT: TODO REMOVE + /* // make sure we have as many models as attachments while (_attachmentModels.size() < attachmentData.size()) { Model* model = nullptr; @@ -959,16 +1001,20 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { _attachmentModels.append(model); } while (_attachmentModels.size() > attachmentData.size()) { - auto attachmentModel = _attachmentModels.takeLast(); - _attachmentsToRemove << attachmentModel; + auto attachmentModel = _attachmentModels.back(); + _attachmentModels.pop_back(); + _attachmentsToRemove.push_back(attachmentModel); } + */ + /* // update the urls for (int i = 0; i < attachmentData.size(); i++) { _attachmentModels[i]->setURL(attachmentData.at(i).modelURL); _attachmentModels[i]->setSnapModelToCenter(true); _attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale); } + */ } void Avatar::setBillboard(const QByteArray& billboard) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 4926212b4d..6d4477e93f 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -68,7 +68,7 @@ public: void init(); void simulate(float deltaTime); - void simulateAttachments(float deltaTime); + virtual void simulateAttachments(float deltaTime); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition); @@ -177,9 +177,9 @@ protected: SkeletonModel _skeletonModel; glm::vec3 _skeletonOffset; - QVector _attachmentModels; - QVector _attachmentsToRemove; - QVector _unusedAttachments; + std::vector> _attachmentModels; + std::vector> _attachmentsToRemove; + float _bodyYawDelta; // degrees/sec // These position histories and derivatives are in the world-frame. diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5c8230bd88..1028f9aa64 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -201,6 +201,11 @@ MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); } +// virtual +void MyAvatar::simulateAttachments(float deltaTime) { + // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() +} + QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { CameraMode mode = qApp->getCamera()->getMode(); _globalPosition = getPosition(); @@ -621,6 +626,7 @@ void MyAvatar::saveData() { settings.setValue("rotation_y", eulers.y); settings.setValue("rotation_z", eulers.z); settings.setValue("scale", attachment.scale); + settings.setValue("isSoft", attachment.isSoft); } settings.endArray(); @@ -702,6 +708,7 @@ void MyAvatar::loadData() { eulers.z = loadSetting(settings, "rotation_z", 0.0f); attachment.rotation = glm::quat(eulers); attachment.scale = loadSetting(settings, "scale", 1.0f); + attachment.isSoft = settings.value("isSoft").toBool(); attachmentData.append(attachment); } settings.endArray(); @@ -1057,7 +1064,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setFollowVelocity(_followVelocity); } -void MyAvatar::harvestResultsFromPhysicsSimulation() { +void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaType) { glm::vec3 position = getPosition(); glm::quat orientation = getOrientation(); _characterController.getPositionAndOrientation(position, orientation); @@ -1068,6 +1075,9 @@ void MyAvatar::harvestResultsFromPhysicsSimulation() { } else { setVelocity(_characterController.getLinearVelocity()); } + + // now that physics has adjusted our position, we can update attachements. + Avatar::simulateAttachments(deltaType); } void MyAvatar::adjustSensorTransform() { @@ -1599,7 +1609,7 @@ void MyAvatar::maybeUpdateBillboard() { if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { return; } - foreach (Model* model, _attachmentModels) { + for (auto& model : _attachmentModels) { if (!model->isLoadedWithTextures()) { return; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 019ba0f992..1a5753d0a5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -83,6 +83,8 @@ public: MyAvatar(RigPointer rig); ~MyAvatar(); + virtual void simulateAttachments(float deltaTime) override; + AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; } AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } @@ -204,7 +206,7 @@ public: MyCharacterController* getCharacterController() { return &_characterController; } void prepareForPhysicsSimulation(); - void harvestResultsFromPhysicsSimulation(); + void harvestResultsFromPhysicsSimulation(float deltaTime); void adjustSensorTransform(); const QString& getCollisionSoundURL() { return _collisionSoundURL; } diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp index a9f6b300ac..92534f01ea 100644 --- a/interface/src/avatar/SoftAttachmentModel.cpp +++ b/interface/src/avatar/SoftAttachmentModel.cpp @@ -10,6 +10,7 @@ // #include "SoftAttachmentModel.h" +#include "InterfaceLogging.h" SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) : Model(rig, parent), @@ -55,7 +56,7 @@ void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::qu // TODO: cache these look ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); glm::mat4 jointMatrix(glm::mat4::_null); - if (jointIndexOverride >= 0 && jointIndexOverride < getJointStateCount()) { + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride->getJointStateCount()) { jointMatrix = _rigOverride->getJointTransform(jointIndexOverride); } else { jointMatrix = _rig->getJointTransform(cluster.jointIndex); diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 1d53eb3871..d718b52d6d 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -27,13 +28,13 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) : QDialog(parent) { - + setWindowTitle("Edit Attachments"); setAttribute(Qt::WA_DeleteOnClose); - + QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); - + QScrollArea* area = new QScrollArea(); layout->addWidget(area); area->setWidgetResizable(true); @@ -42,26 +43,26 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) : container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); area->setWidget(container); _attachments->addStretch(1); - + foreach (const AttachmentData& data, DependencyManager::get()->getMyAvatar()->getAttachmentData()) { addAttachment(data); } - + QPushButton* newAttachment = new QPushButton("New Attachment"); connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment())); layout->addWidget(newAttachment); - + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(buttons); connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); _ok = buttons->button(QDialogButtonBox::Ok); - + setMinimumSize(600, 600); } void AttachmentsDialog::setVisible(bool visible) { QDialog::setVisible(visible); - + // un-default the OK button if (visible) { _ok->setDefault(false); @@ -104,11 +105,11 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData _dialog(dialog), _applying(false) { setFrameStyle(QFrame::StyledPanel); - + QFormLayout* layout = new QFormLayout(); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); setLayout(layout); - + QHBoxLayout* urlBox = new QHBoxLayout(); layout->addRow("Model URL:", urlBox); urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); @@ -117,7 +118,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData QPushButton* chooseURL = new QPushButton("Choose"); urlBox->addWidget(chooseURL); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); - + layout->addRow("Joint:", _jointName = new QComboBox()); QSharedPointer geometry = DependencyManager::get()->getMyAvatar()->getSkeletonModel().getGeometry(); if (geometry && geometry->isLoaded()) { @@ -127,26 +128,30 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData } _jointName->setCurrentText(data.jointName); connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged())); - + QHBoxLayout* translationBox = new QHBoxLayout(); translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x)); translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y)); translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z)); layout->addRow("Translation:", translationBox); - + QHBoxLayout* rotationBox = new QHBoxLayout(); glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation)); rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x)); rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y)); rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z)); layout->addRow("Rotation:", rotationBox); - + layout->addRow("Scale:", _scale = new QDoubleSpinBox()); _scale->setSingleStep(0.01); _scale->setMaximum(FLT_MAX); _scale->setValue(data.scale); connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); - + + layout->addRow("Is Soft:", _isSoft = new QCheckBox()); + _isSoft->setChecked(data.isSoft); + connect(_isSoft, SIGNAL(stateChanged(int)), SLOT(updateAttachmentData())); + QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); @@ -160,6 +165,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const { data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value()); data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value()))); data.scale = _scale->value(); + data.isSoft = _isSoft->isChecked(); return data; } @@ -227,6 +233,7 @@ void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) { _rotationY->setValue(eulers.y); _rotationZ->setValue(eulers.z); _scale->setValue(attachment.scale); + _isSoft->setChecked(attachment.isSoft); _applying = false; _dialog->updateAttachmentData(); } diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index d3b9219a16..43ba5f8f3e 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -36,11 +36,11 @@ public slots: void updateAttachmentData(); private slots: - + void addAttachment(const AttachmentData& data = AttachmentData()); private: - + QVBoxLayout* _attachments; QPushButton* _ok; }; @@ -50,7 +50,7 @@ class AttachmentPanel : public QFrame { Q_OBJECT public: - + AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData()); AttachmentData getAttachmentData() const; @@ -64,9 +64,9 @@ private slots: void updateAttachmentData(); private: - + void applyAttachmentData(const AttachmentData& attachment); - + AttachmentsDialog* _dialog; QLineEdit* _modelURL; QComboBox* _jointName; @@ -77,6 +77,7 @@ private: QDoubleSpinBox* _rotationY; QDoubleSpinBox* _rotationZ; QDoubleSpinBox* _scale; + QCheckBox* _isSoft; bool _applying; }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2ed9a47e02..789f32d4c6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1260,6 +1260,7 @@ void AvatarData::updateJointMappings() { static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); +static const QString JSON_ATTACHMENT_IS_SOFT = QStringLiteral("isSoft"); QJsonObject AttachmentData::toJson() const { QJsonObject result; @@ -1278,6 +1279,7 @@ QJsonObject AttachmentData::toJson() const { if (!transform.isIdentity()) { result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform); } + result[JSON_ATTACHMENT_IS_SOFT] = isSoft; return result; } @@ -1302,6 +1304,10 @@ void AttachmentData::fromJson(const QJsonObject& json) { rotation = transform.getRotation(); scale = transform.getScale().x; } + + if (json.contains(JSON_ATTACHMENT_IS_SOFT)) { + isSoft = json[JSON_ATTACHMENT_IS_SOFT].toBool(); + } } bool AttachmentData::operator==(const AttachmentData& other) const { @@ -1311,12 +1317,12 @@ bool AttachmentData::operator==(const AttachmentData& other) const { QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) { return out << attachment.modelURL << attachment.jointName << - attachment.translation << attachment.rotation << attachment.scale; + attachment.translation << attachment.rotation << attachment.scale << attachment.isSoft; } QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { return in >> attachment.modelURL >> attachment.jointName >> - attachment.translation >> attachment.rotation >> attachment.scale; + attachment.translation >> attachment.rotation >> attachment.scale >> attachment.isSoft; } void AttachmentDataObject::setModelURL(const QString& modelURL) const { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 64c215cee7..bb35a6ea29 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -437,9 +437,10 @@ public: glm::vec3 translation; glm::quat rotation; float scale { 1.0f }; - + bool isSoft { false }; + bool isValid() const { return modelURL.isValid(); } - + bool operator==(const AttachmentData& other) const; QJsonObject toJson() const; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 4daf7f83b6..ca869ec29f 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -44,7 +44,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP; case PacketType::AvatarData: case PacketType::BulkAvatarData: - return 17; + return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); default: return 17; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5daa185af4..3b4b358daf 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -163,4 +163,9 @@ const PacketVersion VERSION_ENTITIES_POLYLINE_TEXTURE = 50; const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51; const PacketVersion VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52; +enum class AvatarMixerPacketVersion : PacketVersion { + TranslationSupport = 17, + SoftAttachmentSupport +}; + #endif // hifi_PacketHeaders_h From 098a455b67999b8082559cfa65219466f4a95bf2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Dec 2015 14:49:33 -0800 Subject: [PATCH 11/41] AttachmentData operator== bug fix This was causing some changes to not be reflected across the network. --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 789f32d4c6..d5e756d6db 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1312,7 +1312,7 @@ void AttachmentData::fromJson(const QJsonObject& json) { bool AttachmentData::operator==(const AttachmentData& other) const { return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation && - rotation == other.rotation && scale == other.scale; + rotation == other.rotation && scale == other.scale && isSoft == other.isSoft; } QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) { From 41052083c3fd141747c6ef4f7bd86c9582c0cb4e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 18 Dec 2015 17:05:58 -0800 Subject: [PATCH 12/41] Render scenery even if it is far away. --- interface/src/LODManager.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 5f55e0fed0..1dff1da0a7 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -40,12 +40,11 @@ LODManager::LODManager() { setRenderDistanceInverseLowLimit(1.0f / (float)TREE_SCALE); // Advice for tuning parameters: // See PIDController.h. There's a section on tuning in the reference. - // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) - //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); + // Turn on logging with the following (or from js with LODManager.setRenderDistanceControllerHistory("render pid", 240)) + //setRenderDistanceControllerHistory("render pid", 60 * 4); // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. setRenderDistanceKP(0.000012f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. setRenderDistanceKI(0.00002f); // Big enough to bring us to target with the above KP. - //setRenderDistanceControllerHistory("FIXME", 240); } float LODManager::getLODDecreaseFPS() { @@ -279,7 +278,9 @@ bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { float distanceToCamera = glm::length(bounds.calcCenter() - args->_viewFrustum->getPosition()); float largestDimension = bounds.getLargestDimension(); if (!getUseAcuity()) { - bool isRendered = fabsf(distanceToCamera - largestDimension) < renderDistance; + const float scenerySize = 300; // meters + bool isRendered = (largestDimension > scenerySize) || // render scenery regardless of distance + (fabsf(distanceToCamera - largestDimension) < renderDistance); renderedCount += isRendered ? 1 : 0; return isRendered; } From cc35eaf4be5cf306fb7a1627c7c269e18d78caaf Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Mon, 28 Dec 2015 17:32:10 -0800 Subject: [PATCH 13/41] Added glow effect to ravestick --- examples/flowArts/raveStick/raveStick.js | 91 ++++++++++++++++++++---- unpublishedScripts/hiddenEntityReset.js | 64 ++++++++++++++++- unpublishedScripts/masterReset.js | 66 ++++++++++++++++- 3 files changed, 206 insertions(+), 15 deletions(-) diff --git a/examples/flowArts/raveStick/raveStick.js b/examples/flowArts/raveStick/raveStick.js index 5fb019bf97..5021559824 100644 --- a/examples/flowArts/raveStick/raveStick.js +++ b/examples/flowArts/raveStick/raveStick.js @@ -24,6 +24,9 @@ RaveStick = function(spawnPosition) { green: 10, blue: 40 }]; + + + var stick = Entities.addEntity({ type: "Model", name: "raveStick", @@ -40,23 +43,25 @@ RaveStick = function(spawnPosition) { userData: JSON.stringify({ grabbableKey: { spatialKey: { - rightRelativePosition: { - x: 0.02, - y: 0, - z: 0 - }, - leftRelativePosition: { - x: -0.02, - y: 0, - z: 0 - }, - relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0) + rightRelativePosition: { + x: 0.02, + y: 0, + z: 0 }, + leftRelativePosition: { + x: -0.02, + y: 0, + z: 0 + }, + relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0) + }, invertSolidWhileHeld: true } }) }); + var glowEmitter = createGlowEmitter(); + var light = Entities.addEntity({ type: 'Light', name: "raveLight", @@ -82,14 +87,76 @@ RaveStick = function(spawnPosition) { green: 200, blue: 40 }; - function cleanup() { Entities.deleteEntity(stick); Entities.deleteEntity(light); + Entities.deleteEntity(glowEmitter); } this.cleanup = cleanup; + + function createGlowEmitter() { + var props = Entities.getEntityProperties(stick, ["position", "rotation"]); + var forwardVec = Quat.getFront(props.rotation); + var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec); + var position = props.position; + var color = { + red: 150, + green: 20, + blue: 100 + } + var props = { + type: "ParticleEffect", + name: "Rave Stick Glow Emitter", + position: position, + parentID: stick, + isEmitting: true, + colorStart: color, + color: { + red: 200, + green: 200, + blue: 255 + }, + colorFinish: color, + maxParticles: 100000, + lifespan: 0.8, + emitRate: 1000, + emitOrientation: forwardQuat, + emitSpeed: 0.2, + speedSpread: 0.0, + emitDimensions: { + x: 0, + y: 0, + z: 0 + }, + polarStart: 0, + polarFinish: 0, + azimuthStart: 0.1, + azimuthFinish: 0.01, + emitAcceleration: { + x: 0, + y: 0, + z: 0 + }, + accelerationSpread: { + x: .00, + y: .00, + z: .00 + }, + radiusStart: 0.01, + radiusFinish: 0.005, + alpha: 0.7, + alphaSpread: 0.1, + alphaStart: 0.1, + alphaFinish: 0.1, + textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png", + emitterShouldTrail: false + } + var glowEmitter = Entities.addEntity(props); + return glowEmitter; + + } } \ No newline at end of file diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index a53d6e721f..6d29858fb9 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -170,10 +170,12 @@ function createRaveStick(position) { var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx"; + var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); var stick = Entities.addEntity({ type: "Model", name: "raveStick", modelURL: modelURL, + rotation: rotation, position: position, shapeType: 'box', collisionsWillMove: true, @@ -206,6 +208,66 @@ }) }); + var forwardVec = Quat.getFront(rotation); + var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec); + var color = { + red: 150, + green: 20, + blue: 100 + } + var raveGlowEmitter = Entities.addEntity({ + type: "ParticleEffect", + name: "Rave Stick Glow Emitter", + position: position, + parentID: stick, + isEmitting: true, + colorStart: color, + color: { + red: 200, + green: 200, + blue: 255 + }, + colorFinish: color, + maxParticles: 100000, + lifespan: 0.8, + emitRate: 1000, + emitOrientation: forwardQuat, + emitSpeed: 0.2, + speedSpread: 0.0, + emitDimensions: { + x: 0, + y: 0, + z: 0 + }, + polarStart: 0, + polarFinish: 0, + azimuthStart: 0.1, + azimuthFinish: 0.01, + emitAcceleration: { + x: 0, + y: 0, + z: 0 + }, + accelerationSpread: { + x: .00, + y: .00, + z: .00 + }, + radiusStart: 0.01, + radiusFinish: 0.005, + alpha: 0.7, + alphaSpread: 0.1, + alphaStart: 0.1, + alphaFinish: 0.1, + textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png", + emitterShouldTrail: false, + userData: JSON.stringify({ + resetMe: { + resetMe: true + } + }) + }); + } function createGun(position) { @@ -1447,4 +1509,4 @@ }; // entity scripts always need to return a newly constructed object of our type return new ResetSwitch(); -}); +}); \ No newline at end of file diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 2d6d9a0d01..3f1025eb5e 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -149,10 +149,12 @@ MasterReset = function() { function createRaveStick(position) { var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx"; + var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); var stick = Entities.addEntity({ type: "Model", name: "raveStick", modelURL: modelURL, + rotation: rotation, position: position, shapeType: 'box', collisionsWillMove: true, @@ -189,6 +191,66 @@ MasterReset = function() { } }) }); + + var forwardVec = Quat.getFront(rotation); + var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec); + var color = { + red: 150, + green: 20, + blue: 100 + } + var raveGlowEmitter = Entities.addEntity({ + type: "ParticleEffect", + name: "Rave Stick Glow Emitter", + position: position, + parentID: stick, + isEmitting: true, + colorStart: color, + color: { + red: 200, + green: 200, + blue: 255 + }, + colorFinish: color, + maxParticles: 100000, + lifespan: 0.8, + emitRate: 1000, + emitOrientation: forwardQuat, + emitSpeed: 0.2, + speedSpread: 0.0, + emitDimensions: { + x: 0, + y: 0, + z: 0 + }, + polarStart: 0, + polarFinish: 0, + azimuthStart: 0.1, + azimuthFinish: 0.01, + emitAcceleration: { + x: 0, + y: 0, + z: 0 + }, + accelerationSpread: { + x: .00, + y: .00, + z: .00 + }, + radiusStart: 0.01, + radiusFinish: 0.005, + alpha: 0.7, + alphaSpread: 0.1, + alphaStart: 0.1, + alphaFinish: 0.1, + textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png", + emitterShouldTrail: false, + userData: JSON.stringify({ + resetMe: { + resetMe: true + } + }) + }); } function createGun(position) { @@ -1084,7 +1146,7 @@ MasterReset = function() { y: 0, z: 0.06 }, - relativeRotation: Quat.fromPitchYawRollDegrees(0,-90, -90) + relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, -90) }, invertSolidWhileHeld: true } @@ -1432,4 +1494,4 @@ MasterReset = function() { Script.scriptEnding.connect(cleanup); } -}; +}; \ No newline at end of file From 3a6478aac1e7548e44d24f0d690c95ac56c1bdb1 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 28 Dec 2015 21:15:52 -0800 Subject: [PATCH 14/41] Update raveStick.js leading zeroes --- examples/flowArts/raveStick/raveStick.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/flowArts/raveStick/raveStick.js b/examples/flowArts/raveStick/raveStick.js index 5021559824..d1180e2b34 100644 --- a/examples/flowArts/raveStick/raveStick.js +++ b/examples/flowArts/raveStick/raveStick.js @@ -142,9 +142,9 @@ RaveStick = function(spawnPosition) { z: 0 }, accelerationSpread: { - x: .00, - y: .00, - z: .00 + x: 0.00, + y: 0.00, + z: 0.00 }, radiusStart: 0.01, radiusFinish: 0.005, @@ -159,4 +159,4 @@ RaveStick = function(spawnPosition) { return glowEmitter; } -} \ No newline at end of file +} From 79a60c06484a090ad3e58b8d049dbb7d8a06925e Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 28 Dec 2015 21:16:15 -0800 Subject: [PATCH 15/41] Update hiddenEntityReset.js leading zeroes --- unpublishedScripts/hiddenEntityReset.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index 6d29858fb9..4352a5d562 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -249,9 +249,9 @@ z: 0 }, accelerationSpread: { - x: .00, - y: .00, - z: .00 + x: 0.00, + y: 0.00, + z: 0.00 }, radiusStart: 0.01, radiusFinish: 0.005, @@ -1509,4 +1509,4 @@ }; // entity scripts always need to return a newly constructed object of our type return new ResetSwitch(); -}); \ No newline at end of file +}); From 74badfb667813560db45c0ef406167c70ef3485f Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Mon, 28 Dec 2015 21:16:38 -0800 Subject: [PATCH 16/41] Update masterReset.js leading zeroes --- unpublishedScripts/masterReset.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 3f1025eb5e..8aedd1c650 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -233,9 +233,9 @@ MasterReset = function() { z: 0 }, accelerationSpread: { - x: .00, - y: .00, - z: .00 + x: 0.00, + y: 0.00, + z: 0.00 }, radiusStart: 0.01, radiusFinish: 0.005, @@ -1494,4 +1494,4 @@ MasterReset = function() { Script.scriptEnding.connect(cleanup); } -}; \ No newline at end of file +}; From f4f4bd44cb3f8cc3be377042b3ebdaac8f7640a2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 29 Dec 2015 13:28:04 -0800 Subject: [PATCH 17/41] SoftAttachmentModel: added a descriptive comment. --- interface/src/avatar/SoftAttachmentModel.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/interface/src/avatar/SoftAttachmentModel.h b/interface/src/avatar/SoftAttachmentModel.h index eda587af68..84a8b4cc66 100644 --- a/interface/src/avatar/SoftAttachmentModel.h +++ b/interface/src/avatar/SoftAttachmentModel.h @@ -14,6 +14,14 @@ #include +// A model that allows the creator to specify a secondary rig instance. +// When the cluster matrices are created for rendering, the +// cluster matrices will use the secondary rig for the joint poses +// instead of the primary rig. +// +// This is used by Avatar instances to wear clothing that follows the same +// animated pose as the SkeletonModel. + class SoftAttachmentModel : public Model { Q_OBJECT From 07531353e4514c21d170ed618c32758a3c1ee099 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 00:30:41 -0500 Subject: [PATCH 18/41] Allow a script to set a resource override. --- interface/src/Application.cpp | 3 ++ libraries/networking/src/ResourceManager.cpp | 17 ++++++++-- .../src/ResourceScriptingInterface.cpp | 15 +++++++++ .../src/ResourceScriptingInterface.h | 31 +++++++++++++++++++ libraries/script-engine/src/ScriptEngine.cpp | 5 +-- 5 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 libraries/networking/src/ResourceScriptingInterface.cpp create mode 100644 libraries/networking/src/ResourceScriptingInterface.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e81aa7ec52..0c32b5de6f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -47,6 +47,7 @@ #include #include +#include #include #include #include @@ -337,6 +338,8 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + #if defined(Q_OS_MAC) || defined(Q_OS_WIN) DependencyManager::set(); diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index fd465a0aed..f195011290 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -23,16 +23,27 @@ QMutex ResourceManager::_prefixMapLock; void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { QMutexLocker locker(&_prefixMapLock); - _prefixMap[prefix] = replacement; + if (replacement.isEmpty()) { + _prefixMap.erase(prefix); + } else { + _prefixMap[prefix] = replacement; + } } QString ResourceManager::normalizeURL(const QString& urlString) { QString result = urlString; - QMutexLocker locker(&_prefixMapLock); - foreach(const auto& entry, _prefixMap) { + PrefixMap copy; + + { + QMutexLocker locker(&_prefixMapLock); + copy = _prefixMap; + } + + foreach(const auto& entry, copy) { const auto& prefix = entry.first; const auto& replacement = entry.second; if (result.startsWith(prefix)) { + qDebug() << "Replacing " << prefix << " with " << replacement; result.replace(0, prefix.size(), replacement); } } diff --git a/libraries/networking/src/ResourceScriptingInterface.cpp b/libraries/networking/src/ResourceScriptingInterface.cpp new file mode 100644 index 0000000000..38be49049c --- /dev/null +++ b/libraries/networking/src/ResourceScriptingInterface.cpp @@ -0,0 +1,15 @@ +// +// Created by Bradley Austin Davis on 2015/12/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ResourceScriptingInterface.h" + +#include "ResourceManager.h" + +void ResourceScriptingInterface::overrideUrlPrefix(const QString& prefix, const QString& replacement) { + ResourceManager::setUrlPrefixOverride(prefix, replacement); +} diff --git a/libraries/networking/src/ResourceScriptingInterface.h b/libraries/networking/src/ResourceScriptingInterface.h new file mode 100644 index 0000000000..d9777e7514 --- /dev/null +++ b/libraries/networking/src/ResourceScriptingInterface.h @@ -0,0 +1,31 @@ +// +// AssetClient.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/21 +// Copyright 2015 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_networking_ResourceScriptingInterface_h +#define hifi_networking_ResourceScriptingInterface_h + +#include + +#include + +class ResourceScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE void overrideUrlPrefix(const QString& prefix, const QString& replacement); + + Q_INVOKABLE void restoreUrlPrefix(const QString& prefix) { + overrideUrlPrefix(prefix, ""); + } +}; + + +#endif diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index ef448e93f0..6aa48a532d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -391,7 +392,7 @@ void ScriptEngine::init() { registerGlobalObject("Recording", recordingInterface.data()); registerGlobalObject("Assets", &_assetScriptingInterface); - + registerGlobalObject("Resources", DependencyManager::get().data()); } void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { @@ -1296,4 +1297,4 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS entityScript.property(methodName).call(entityScript, args); } } -} \ No newline at end of file +} From 0901e3aae1abb139418dfcb9969c2b2f439e90df Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 22 Dec 2015 20:39:33 -0800 Subject: [PATCH 19/41] Support QML and Web content in overlay windows --- CMakeGraphvizOptions.cmake | 1 + examples/edit.js | 9 +- examples/tests/qmlTest.js | 40 ++++ examples/tests/qmlWebTest.js | 3 +- interface/resources/qml/QmlWebWindow.qml | 14 +- interface/resources/qml/QmlWindow.qml | 44 ++++ interface/src/Application.cpp | 1 + libraries/ui/src/QmlWebWindowClass.cpp | 201 +--------------- libraries/ui/src/QmlWebWindowClass.h | 75 +----- libraries/ui/src/QmlWindowClass.cpp | 280 +++++++++++++++++++++++ libraries/ui/src/QmlWindowClass.h | 103 +++++++++ 11 files changed, 498 insertions(+), 273 deletions(-) create mode 100644 CMakeGraphvizOptions.cmake create mode 100644 examples/tests/qmlTest.js create mode 100644 interface/resources/qml/QmlWindow.qml create mode 100644 libraries/ui/src/QmlWindowClass.cpp create mode 100644 libraries/ui/src/QmlWindowClass.h diff --git a/CMakeGraphvizOptions.cmake b/CMakeGraphvizOptions.cmake new file mode 100644 index 0000000000..f1b73f1cae --- /dev/null +++ b/CMakeGraphvizOptions.cmake @@ -0,0 +1 @@ +set(GRAPHVIZ_EXTERNAL_LIBS FALSE) \ No newline at end of file diff --git a/examples/edit.js b/examples/edit.js index 074b43c8c1..99219fcaa2 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -140,8 +140,13 @@ var importingSVOTextOverlay = Overlays.addOverlay("text", { }); var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; -var marketplaceWindow = new OverlayWebWindow('Marketplace', "about:blank", 900, 700, false); -marketplaceWindow.setVisible(false); +var marketplaceWindow = new OverlayWebWindow({ + title: 'Marketplace', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); function showMarketplace(marketplaceID) { var url = MARKETPLACE_URL; diff --git a/examples/tests/qmlTest.js b/examples/tests/qmlTest.js new file mode 100644 index 0000000000..f1aa59cc09 --- /dev/null +++ b/examples/tests/qmlTest.js @@ -0,0 +1,40 @@ +print("Launching web window"); +qmlWindow = new OverlayWindow({ + title: 'Test Qml', + source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml", + height: 240, + width: 320, + toolWindow: false, + visible: true +}); + +//qmlWindow.eventBridge.webEventReceived.connect(function(data) { +// print("JS Side event received: " + data); +//}); +// +//var titles = ["A", "B", "C"]; +//var titleIndex = 0; +// +//Script.setInterval(function() { +// qmlWindow.eventBridge.emitScriptEvent("JS Event sent"); +// var size = qmlWindow.size; +// var position = qmlWindow.position; +// print("Window visible: " + qmlWindow.visible) +// if (qmlWindow.visible) { +// print("Window size: " + size.x + "x" + size.y) +// print("Window pos: " + position.x + "x" + position.y) +// qmlWindow.setVisible(false); +// } else { +// qmlWindow.setVisible(true); +// qmlWindow.setTitle(titles[titleIndex]); +// qmlWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100); +// titleIndex += 1; +// titleIndex %= titles.length; +// } +//}, 2 * 1000); +// +//Script.setTimeout(function() { +// print("Closing script"); +// qmlWindow.close(); +// Script.stop(); +//}, 15 * 1000) diff --git a/examples/tests/qmlWebTest.js b/examples/tests/qmlWebTest.js index f905e494dc..5faa68668d 100644 --- a/examples/tests/qmlWebTest.js +++ b/examples/tests/qmlWebTest.js @@ -1,6 +1,7 @@ print("Launching web window"); -webWindow = new OverlayWebWindow('Test Event Bridge', "file:///C:/Users/bdavis/Git/hifi/examples/html/qmlWebTest.html", 320, 240, false); +var htmlUrl = Script.resolvePath("..//html/qmlWebTest.html") +webWindow = new OverlayWebWindow('Test Event Bridge', htmlUrl, 320, 240, false); print("JS Side window: " + webWindow); print("JS Side bridge: " + webWindow.eventBridge); webWindow.eventBridge.webEventReceived.connect(function(data) { diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 6e9502edb2..d9345e8539 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -1,9 +1,6 @@ - import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 -import QtWebChannel 1.0 -import QtWebSockets 1.0 import "controls" import "styles" @@ -13,6 +10,7 @@ VrDialog { HifiConstants { id: hifi } title: "WebWindow" resizable: true + enabled: false // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false contentImplicitWidth: clientArea.implicitWidth @@ -24,18 +22,18 @@ VrDialog { function stop() { webview.stop(); } - Component.onCompleted: { - enabled = true - console.log("Web Window Created " + root); + // Ensure the JS from the web-engine makes it to our logging webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); }); + + // Required to support clicking on "hifi://" links webview.loadingChanged.connect(handleWebviewLoading) } - + // Required to support clicking on "hifi://" links function handleWebviewLoading(loadRequest) { if (WebEngineView.LoadStartedStatus == loadRequest.status) { var newUrl = loadRequest.url.toString(); @@ -56,6 +54,7 @@ VrDialog { id: webview url: root.source anchors.fill: parent + onUrlChanged: { var currentUrl = url.toString(); var newUrl = urlFixer.fixupUrl(currentUrl); @@ -63,6 +62,7 @@ VrDialog { url = newUrl; } } + profile: WebEngineProfile { id: webviewProfile httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)" diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml new file mode 100644 index 0000000000..71d66fb99f --- /dev/null +++ b/interface/resources/qml/QmlWindow.qml @@ -0,0 +1,44 @@ + +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtWebChannel 1.0 +import QtWebSockets 1.0 + +import "controls" +import "styles" + +VrDialog { + id: root + HifiConstants { id: hifi } + title: "QmlWindow" + resizable: true + enabled: false + // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer + destroyOnCloseButton: false + contentImplicitWidth: clientArea.implicitWidth + contentImplicitHeight: clientArea.implicitHeight + property url source: "" + + onEnabledChanged: { + if (enabled) { + clientArea.forceActiveFocus() + } + } + + Item { + id: clientArea + implicitHeight: 600 + implicitWidth: 800 + x: root.clientX + y: root.clientY + width: root.clientWidth + height: root.clientHeight + + Loader { + id: pageLoader + objectName: "Loader" + source: root.source + anchors.fill: parent + } + } // item +} // dialog diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e81aa7ec52..d10679cc3a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4111,6 +4111,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); + scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 781b8c1b76..dd85016ae8 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -8,83 +8,27 @@ #include "QmlWebWindowClass.h" -#include - -#include -#include -#include #include #include #include + #include + #include #include -#include -#include -#include + +#include #include +#include #include #include #include "OffscreenUi.h" -QWebSocketServer* QmlWebWindowClass::_webChannelServer { nullptr }; -static QWebChannel webChannel; -static const uint16_t WEB_CHANNEL_PORT = 51016; -static std::atomic nextWindowId; static const char* const URL_PROPERTY = "source"; -static const char* const TITLE_PROPERTY = "title"; static const QRegExp HIFI_URL_PATTERN { "^hifi://" }; -void QmlScriptEventBridge::emitWebEvent(const QString& data) { - QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); -} - -void QmlScriptEventBridge::emitScriptEvent(const QString& data) { - QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, - Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data)); -} - -class QmlWebTransport : public QWebChannelAbstractTransport { - Q_OBJECT -public: - QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) { - // Translate from the websocket layer to the webchannel layer - connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) { - QJsonParseError error; - QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error); - if (error.error || !document.isObject()) { - qWarning() << "Unable to parse incoming JSON message" << message; - return; - } - emit messageReceived(document.object(), this); - }); - } - - virtual void sendMessage(const QJsonObject &message) override { - // Translate from the webchannel layer to the websocket layer - _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact)); - } - -private: - QWebSocket* const _webSocket; -}; - - -void QmlWebWindowClass::setupServer() { - if (!_webChannelServer) { - _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode); - if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) { - qFatal("Failed to open web socket server."); - } - - QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] { - webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection())); - }); - } -} - class UrlFixer : public QObject { Q_OBJECT public: @@ -110,38 +54,14 @@ static UrlFixer URL_FIXER; // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { - QmlWebWindowClass* retVal { nullptr }; - const QString title = context->argument(0).toString(); - QString url = context->argument(1).toString(); - if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) { - url = QUrl::fromLocalFile(url).toString(); - } - const int width = std::max(100, std::min(1280, context->argument(2).toInt32()));; - const int height = std::max(100, std::min(720, context->argument(3).toInt32()));; - - - // Build the event bridge and wrapper on the main thread - QMetaObject::invokeMethod(DependencyManager::get().data(), "load", Qt::BlockingQueuedConnection, - Q_ARG(const QString&, "QmlWebWindow.qml"), - Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { - setupServer(); - retVal = new QmlWebWindowClass(object); - webChannel.registerObject(url.toLower(), retVal); + return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine, + [&](QQmlContext* context, QObject* object) { context->setContextProperty("urlFixer", &URL_FIXER); - retVal->setTitle(title); - retVal->setURL(url); - retVal->setSize(width, height); - })); - connect(engine, &QScriptEngine::destroyed, retVal, &QmlWebWindowClass::deleteLater); - return engine->newQObject(retVal); + return new QmlWebWindowClass(object); + }); } -QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) - : _windowId(++nextWindowId), _qmlWindow(qmlWindow) -{ - qDebug() << "Created window with ID " << _windowId; - Q_ASSERT(_qmlWindow); - Q_ASSERT(dynamic_cast(_qmlWindow)); +QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString))); } @@ -165,80 +85,6 @@ void QmlWebWindowClass::handleNavigation(const QString& url) { } } -void QmlWebWindowClass::setVisible(bool visible) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible)); - return; - } - - auto qmlWindow = asQuickItem(); - if (qmlWindow->isEnabled() != visible) { - qmlWindow->setEnabled(visible); - emit visibilityChanged(visible); - } -} - -QQuickItem* QmlWebWindowClass::asQuickItem() const { - return dynamic_cast(_qmlWindow); -} - -bool QmlWebWindowClass::isVisible() const { - if (QThread::currentThread() != thread()) { - bool result; - QMetaObject::invokeMethod(const_cast(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result)); - return result; - } - - return asQuickItem()->isEnabled(); -} - - -glm::vec2 QmlWebWindowClass::getPosition() const { - if (QThread::currentThread() != thread()) { - glm::vec2 result; - QMetaObject::invokeMethod(const_cast(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); - return result; - } - - return glm::vec2(asQuickItem()->x(), asQuickItem()->y()); -} - - -void QmlWebWindowClass::setPosition(const glm::vec2& position) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position)); - return; - } - - asQuickItem()->setPosition(QPointF(position.x, position.y)); -} - -void QmlWebWindowClass::setPosition(int x, int y) { - setPosition(glm::vec2(x, y)); -} - -glm::vec2 QmlWebWindowClass::getSize() const { - if (QThread::currentThread() != thread()) { - glm::vec2 result; - QMetaObject::invokeMethod(const_cast(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); - return result; - } - - return glm::vec2(asQuickItem()->width(), asQuickItem()->height()); -} - -void QmlWebWindowClass::setSize(const glm::vec2& size) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size)); - } - - asQuickItem()->setSize(QSizeF(size.x, size.y)); -} - -void QmlWebWindowClass::setSize(int width, int height) { - setSize(glm::vec2(width, height)); -} - QString QmlWebWindowClass::getURL() const { if (QThread::currentThread() != thread()) { QString result; @@ -256,29 +102,4 @@ void QmlWebWindowClass::setURL(const QString& urlString) { _qmlWindow->setProperty(URL_PROPERTY, urlString); } - -void QmlWebWindowClass::setTitle(const QString& title) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); - } - - _qmlWindow->setProperty(TITLE_PROPERTY, title); -} - -void QmlWebWindowClass::close() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); - } - _qmlWindow->setProperty("destroyOnInvisible", true); - _qmlWindow->setProperty("visible", false); - _qmlWindow->deleteLater(); -} - -void QmlWebWindowClass::hasClosed() { -} - -void QmlWebWindowClass::raise() { - // FIXME -} - -#include "QmlWebWindowClass.moc" +#include "QmlWebWindowClass.moc" \ No newline at end of file diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 0f2531deb8..14e533c7b4 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -9,97 +9,26 @@ #ifndef hifi_ui_QmlWebWindowClass_h #define hifi_ui_QmlWebWindowClass_h -#include -#include -#include -#include - -#include - -class QScriptEngine; -class QScriptContext; -class QmlWebWindowClass; -class QWebSocketServer; -class QWebSocket; - -class QmlScriptEventBridge : public QObject { - Q_OBJECT -public: - QmlScriptEventBridge(const QmlWebWindowClass* webWindow) : _webWindow(webWindow) {} - -public slots : - void emitWebEvent(const QString& data); - void emitScriptEvent(const QString& data); - -signals: - void webEventReceived(const QString& data); - void scriptEventReceived(int windowId, const QString& data); - -private: - const QmlWebWindowClass* _webWindow { nullptr }; - QWebSocket *_socket { nullptr }; -}; +#include "QmlWindowClass.h" // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping -class QmlWebWindowClass : public QObject { +class QmlWebWindowClass : public QmlWindowClass { Q_OBJECT - Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) - Q_PROPERTY(int windowId READ getWindowId CONSTANT) Q_PROPERTY(QString url READ getURL CONSTANT) - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) - Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) - Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); QmlWebWindowClass(QObject* qmlWindow); public slots: - bool isVisible() const; - void setVisible(bool visible); - - glm::vec2 getPosition() const; - void setPosition(const glm::vec2& position); - void setPosition(int x, int y); - - glm::vec2 getSize() const; - void setSize(const glm::vec2& size); - void setSize(int width, int height); - QString getURL() const; void setURL(const QString& url); - void setTitle(const QString& title); - - // Ugh.... do not want to do - Q_INVOKABLE void raise(); - Q_INVOKABLE void close(); - Q_INVOKABLE int getWindowId() const { return _windowId; }; - Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; }; - signals: - void visibilityChanged(bool visible); // Tool window void urlChanged(); - void moved(glm::vec2 position); - void resized(QSizeF size); - void closed(); private slots: - void hasClosed(); void handleNavigation(const QString& url); - -private: - static void setupServer(); - static QWebSocketServer* _webChannelServer; - - QQuickItem* asQuickItem() const; - QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) }; - - // FIXME needs to be initialized in the ctor once we have support - // for tool window panes in QML - const bool _isToolWindow { false }; - const int _windowId; - QObject* const _qmlWindow; }; #endif diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp new file mode 100644 index 0000000000..29eca50724 --- /dev/null +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -0,0 +1,280 @@ +// +// Created by Bradley Austin Davis on 2015-12-15 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QmlWindowClass.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "OffscreenUi.h" + +QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr }; +static QWebChannel webChannel; +static const uint16_t WEB_CHANNEL_PORT = 51016; +static std::atomic nextWindowId; +static const char* const SOURCE_PROPERTY = "source"; +static const char* const TITLE_PROPERTY = "title"; +static const char* const WIDTH_PROPERTY = "width"; +static const char* const HEIGHT_PROPERTY = "height"; +static const char* const VISIBILE_PROPERTY = "visible"; + +void QmlScriptEventBridge::emitWebEvent(const QString& data) { + QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); +} + +void QmlScriptEventBridge::emitScriptEvent(const QString& data) { + QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, + Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data)); +} + +class QmlWebTransport : public QWebChannelAbstractTransport { + Q_OBJECT +public: + QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) { + // Translate from the websocket layer to the webchannel layer + connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error); + if (error.error || !document.isObject()) { + qWarning() << "Unable to parse incoming JSON message" << message; + return; + } + emit messageReceived(document.object(), this); + }); + } + + virtual void sendMessage(const QJsonObject &message) override { + // Translate from the webchannel layer to the websocket layer + _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact)); + } + +private: + QWebSocket* const _webSocket; +}; + + +void QmlWindowClass::setupServer() { + if (!_webChannelServer) { + _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode); + if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) { + qFatal("Failed to open web socket server."); + } + + QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] { + webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection())); + }); + } +} + +QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, + QScriptContext* context, QScriptEngine* engine, + std::function function) +{ + const auto argumentCount = context->argumentCount(); + QString url; + QString title; + int width = 100, height = 100; + bool isToolWindow = false; + bool visible = true; + if (argumentCount > 1) { + + if (!context->argument(0).isUndefined()) { + title = context->argument(0).toString(); + } + if (!context->argument(1).isUndefined()) { + url = context->argument(1).toString(); + } + if (context->argument(2).isNumber()) { + width = context->argument(2).toInt32(); + } + if (context->argument(3).isNumber()) { + height = context->argument(3).toInt32(); + } + } else { + auto argumentObject = context->argument(0); + qDebug() << argumentObject.toString(); + if (!argumentObject.property(TITLE_PROPERTY).isUndefined()) { + title = argumentObject.property(TITLE_PROPERTY).toString(); + } + if (!argumentObject.property(SOURCE_PROPERTY).isUndefined()) { + url = argumentObject.property(SOURCE_PROPERTY).toString(); + } + if (argumentObject.property(WIDTH_PROPERTY).isNumber()) { + width = argumentObject.property(WIDTH_PROPERTY).toInt32(); + } + if (argumentObject.property(HEIGHT_PROPERTY).isNumber()) { + height = argumentObject.property(HEIGHT_PROPERTY).toInt32(); + } + if (argumentObject.property(VISIBILE_PROPERTY).isBool()) { + visible = argumentObject.property(VISIBILE_PROPERTY).isBool(); + } + } + + if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) { + url = QUrl::fromLocalFile(url).toString(); + } + + width = std::max(100, std::min(1280, width)); + height = std::max(100, std::min(720, height)); + + QmlWindowClass* retVal{ nullptr }; + + // Build the event bridge and wrapper on the main thread + QMetaObject::invokeMethod(DependencyManager::get().data(), "load", Qt::BlockingQueuedConnection, + Q_ARG(const QString&, qmlSource), + Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { + setupServer(); + retVal = function(context, object); + registerObject(url.toLower(), retVal); + if (!title.isEmpty()) { + retVal->setTitle(title); + } + retVal->setSize(width, height); + object->setProperty(SOURCE_PROPERTY, url); + if (visible) { + object->setProperty("enabled", true); + } + })); + connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); + return engine->newQObject(retVal); +} + + +// Method called by Qt scripts to create a new web window in the overlay +QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { + return internalConstructor("QmlWindow.qml", context, engine, [&](QQmlContext* context, QObject* object){ + return new QmlWindowClass(object); + }); +} + +QmlWindowClass::QmlWindowClass(QObject* qmlWindow) + : _windowId(++nextWindowId), _qmlWindow(qmlWindow) +{ + qDebug() << "Created window with ID " << _windowId; + Q_ASSERT(_qmlWindow); + Q_ASSERT(dynamic_cast(_qmlWindow)); +} + +void QmlWindowClass::registerObject(const QString& name, QObject* object) { + webChannel.registerObject(name, object); +} + +void QmlWindowClass::deregisterObject(QObject* object) { + webChannel.deregisterObject(object); +} + +void QmlWindowClass::setVisible(bool visible) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible)); + return; + } + + auto qmlWindow = asQuickItem(); + if (qmlWindow->isEnabled() != visible) { + qmlWindow->setEnabled(visible); + emit visibilityChanged(visible); + } +} + +QQuickItem* QmlWindowClass::asQuickItem() const { + return dynamic_cast(_qmlWindow); +} + +bool QmlWindowClass::isVisible() const { + if (QThread::currentThread() != thread()) { + bool result; + QMetaObject::invokeMethod(const_cast(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result)); + return result; + } + + return asQuickItem()->isEnabled(); +} + + +glm::vec2 QmlWindowClass::getPosition() const { + if (QThread::currentThread() != thread()) { + glm::vec2 result; + QMetaObject::invokeMethod(const_cast(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); + return result; + } + + return glm::vec2(asQuickItem()->x(), asQuickItem()->y()); +} + + +void QmlWindowClass::setPosition(const glm::vec2& position) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position)); + return; + } + + asQuickItem()->setPosition(QPointF(position.x, position.y)); +} + +void QmlWindowClass::setPosition(int x, int y) { + setPosition(glm::vec2(x, y)); +} + +glm::vec2 QmlWindowClass::getSize() const { + if (QThread::currentThread() != thread()) { + glm::vec2 result; + QMetaObject::invokeMethod(const_cast(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); + return result; + } + + return glm::vec2(asQuickItem()->width(), asQuickItem()->height()); +} + +void QmlWindowClass::setSize(const glm::vec2& size) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size)); + } + + asQuickItem()->setSize(QSizeF(size.x, size.y)); +} + +void QmlWindowClass::setSize(int width, int height) { + setSize(glm::vec2(width, height)); +} + +void QmlWindowClass::setTitle(const QString& title) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); + } + + _qmlWindow->setProperty(TITLE_PROPERTY, title); +} + +void QmlWindowClass::close() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); + } + _qmlWindow->setProperty("destroyOnInvisible", true); + _qmlWindow->setProperty("visible", false); + _qmlWindow->deleteLater(); +} + +void QmlWindowClass::hasClosed() { +} + +void QmlWindowClass::raise() { + // FIXME +} + +#include "QmlWindowClass.moc" diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h new file mode 100644 index 0000000000..41572b448d --- /dev/null +++ b/libraries/ui/src/QmlWindowClass.h @@ -0,0 +1,103 @@ +// +// Created by Bradley Austin Davis on 2015-12-15 +// Copyright 2015 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_ui_QmlWindowClass_h +#define hifi_ui_QmlWindowClass_h + +#include +#include +#include +#include +#include + +class QScriptEngine; +class QScriptContext; +class QmlWindowClass; +class QWebSocketServer; +class QWebSocket; + +class QmlScriptEventBridge : public QObject { + Q_OBJECT +public: + QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {} + +public slots : + void emitWebEvent(const QString& data); + void emitScriptEvent(const QString& data); + +signals: + void webEventReceived(const QString& data); + void scriptEventReceived(int windowId, const QString& data); + +private: + const QmlWindowClass* _webWindow { nullptr }; + QWebSocket *_socket { nullptr }; +}; + +// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping +class QmlWindowClass : public QObject { + Q_OBJECT + Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) + Q_PROPERTY(int windowId READ getWindowId CONSTANT) + Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) + +public: + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + QmlWindowClass(QObject* qmlWindow); + +public slots: + bool isVisible() const; + void setVisible(bool visible); + + glm::vec2 getPosition() const; + void setPosition(const glm::vec2& position); + void setPosition(int x, int y); + + glm::vec2 getSize() const; + void setSize(const glm::vec2& size); + void setSize(int width, int height); + + void setTitle(const QString& title); + + // Ugh.... do not want to do + Q_INVOKABLE void raise(); + Q_INVOKABLE void close(); + Q_INVOKABLE int getWindowId() const { return _windowId; }; + Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; }; + +signals: + void visibilityChanged(bool visible); // Tool window + void moved(glm::vec2 position); + void resized(QSizeF size); + void closed(); + +protected slots: + void hasClosed(); + +protected: + static QScriptValue internalConstructor(const QString& qmlSource, + QScriptContext* context, QScriptEngine* engine, + std::function function); + static void setupServer(); + static void registerObject(const QString& name, QObject* object); + static void deregisterObject(QObject* object); + static QWebSocketServer* _webChannelServer; + + QQuickItem* asQuickItem() const; + QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) }; + + // FIXME needs to be initialized in the ctor once we have support + // for tool window panes in QML + const bool _isToolWindow { false }; + const int _windowId; + QObject* const _qmlWindow; +}; + +#endif From d6f5296c3b68d265e801128ccdb918401cccb763 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 29 Dec 2015 12:22:12 -0500 Subject: [PATCH 20/41] Working on event bridge and scripting interfaces --- interface/resources/qml/QmlWindow.qml | 64 ++++++++++++++++++++++++--- interface/src/Application.cpp | 49 ++++++++++++++++++-- 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 71d66fb99f..c5d942518f 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -3,6 +3,7 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebChannel 1.0 import QtWebSockets 1.0 +import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel import "controls" import "styles" @@ -12,19 +13,57 @@ VrDialog { HifiConstants { id: hifi } title: "QmlWindow" resizable: true - enabled: false + enabled: false + focus: true + property var channel; + + // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false contentImplicitWidth: clientArea.implicitWidth contentImplicitHeight: clientArea.implicitHeight - property url source: "" + property alias source: pageLoader.source - onEnabledChanged: { - if (enabled) { - clientArea.forceActiveFocus() + /* + WebSocket { + id: socket + url: "ws://localhost:51016"; + active: false + + // the following three properties/functions are required to align the QML WebSocket API with the HTML5 WebSocket API. + property var send: function (arg) { + sendTextMessage(arg); } - } + onTextMessageReceived: { + onmessage({data: message}); + } + + property var onmessage; + + onStatusChanged: { + if (socket.status == WebSocket.Error) { + console.error("Error: " + socket.errorString) + } else if (socket.status == WebSocket.Closed) { + console.log("Socket closed"); + } else if (socket.status == WebSocket.Open) { + console.log("Connected") + //open the webchannel with the socket as transport + new WebChannel.QWebChannel(socket, function(ch) { + root.channel = ch; + var myUrl = root.source.toString().toLowerCase(); + console.log(myUrl); + var bridge = root.channel.objects[myUrl]; + console.log(bridge); + }); + } + } + } + */ + Keys.onPressed: { + console.log("QmlWindow keypress") + } + Item { id: clientArea implicitHeight: 600 @@ -33,12 +72,23 @@ VrDialog { y: root.clientY width: root.clientWidth height: root.clientHeight + focus: true Loader { id: pageLoader objectName: "Loader" - source: root.source anchors.fill: parent + focus: true + + onLoaded: { + console.log("Loaded content") + //socket.active = true; //connect + forceActiveFocus() + } + + Keys.onPressed: { + console.log("QmlWindow pageLoader keypress") + } } } // item } // dialog diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d10679cc3a..5ad12102f7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1094,9 +1094,52 @@ void Application::initializeUi() { offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->load("Root.qml"); offscreenUi->load("RootMenu.qml"); - auto scriptingInterface = DependencyManager::get(); - offscreenUi->getRootContext()->setContextProperty("Controller", scriptingInterface.data()); - offscreenUi->getRootContext()->setContextProperty("MyAvatar", getMyAvatar()); + + auto rootContext = offscreenUi->getRootContext(); + rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance()); + rootContext->setContextProperty("AnimationCache", DependencyManager::get().data()); + rootContext->setContextProperty("Controller", DependencyManager::get().data()); + rootContext->setContextProperty("Entities", DependencyManager::get().data()); + rootContext->setContextProperty("MyAvatar", getMyAvatar()); + rootContext->setContextProperty("Messages", DependencyManager::get().data()); + rootContext->setContextProperty("Recording", DependencyManager::get().data()); + + rootContext->setContextProperty("TREE_SCALE", TREE_SCALE); + rootContext->setContextProperty("Quat", new Quat()); + rootContext->setContextProperty("Vec3", new Vec3()); + rootContext->setContextProperty("Uuid", new ScriptUUID()); + + rootContext->setContextProperty("AvatarList", DependencyManager::get().data()); + + rootContext->setContextProperty("Camera", &_myCamera); + +#if defined(Q_OS_MAC) || defined(Q_OS_WIN) + rootContext->setContextProperty("SpeechRecognizer", DependencyManager::get().data()); +#endif + + rootContext->setContextProperty("Overlays", &_overlays); + rootContext->setContextProperty("Desktop", DependencyManager::get().data()); + + rootContext->setContextProperty("Window", DependencyManager::get().data()); + rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance()); + rootContext->setContextProperty("Stats", Stats::getInstance()); + rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); + rootContext->setContextProperty("AudioDevice", AudioDeviceScriptingInterface::getInstance()); + rootContext->setContextProperty("AnimationCache", DependencyManager::get().data()); + rootContext->setContextProperty("SoundCache", DependencyManager::get().data()); + rootContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); + rootContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); + rootContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); + rootContext->setContextProperty("FaceTracker", DependencyManager::get().data()); + rootContext->setContextProperty("AvatarManager", DependencyManager::get().data()); + rootContext->setContextProperty("UndoStack", &_undoStackScriptingInterface); + rootContext->setContextProperty("LODManager", DependencyManager::get().data()); + rootContext->setContextProperty("Paths", DependencyManager::get().data()); + rootContext->setContextProperty("HMD", DependencyManager::get().data()); + rootContext->setContextProperty("Scene", DependencyManager::get().data()); + rootContext->setContextProperty("Render", DependencyManager::get().data()); + rootContext->setContextProperty("ScriptDiscoveryService", this->getRunningScriptsWidget()); + _glWidget->installEventFilter(offscreenUi.data()); VrMenu::load(); VrMenu::executeQueuedLambdas(); From f4bd2afc8e4dd5417821babfdc310d9ec5ac7dd1 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 11:38:34 -0800 Subject: [PATCH 21/41] Make script launched windows invisible at creation time --- examples/directory.js | 9 ++++- interface/resources/qml/QmlWebWindow.qml | 6 ++- interface/resources/qml/QmlWindow.qml | 40 +------------------ interface/resources/qml/controls/VrDialog.qml | 5 +++ libraries/ui/src/QmlWindowClass.cpp | 2 +- 5 files changed, 19 insertions(+), 43 deletions(-) diff --git a/examples/directory.js b/examples/directory.js index d2a3768051..8d9993ffda 100644 --- a/examples/directory.js +++ b/examples/directory.js @@ -62,8 +62,13 @@ var directory = (function () { function setUp() { viewport = Controller.getViewportDimensions(); - directoryWindow = new OverlayWebWindow('Directory', DIRECTORY_URL, 900, 700, false); - directoryWindow.setVisible(false); + directoryWindow = new OverlayWebWindow({ + title: 'Directory', + source: DIRECTORY_URL, + width: 900, + height: 700, + visible: false + }); directoryButton = Overlays.addOverlay("image", { imageURL: DIRECTORY_BUTTON_URL, diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index d9345e8539..22ff5708dc 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -10,7 +10,8 @@ VrDialog { HifiConstants { id: hifi } title: "WebWindow" resizable: true - enabled: false + enabled: false + visible: false // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false contentImplicitWidth: clientArea.implicitWidth @@ -29,7 +30,7 @@ VrDialog { console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); }); - // Required to support clicking on "hifi://" links + // Required to support clicking on "hifi://" links webview.loadingChanged.connect(handleWebviewLoading) } @@ -54,6 +55,7 @@ VrDialog { id: webview url: root.source anchors.fill: parent + focus: true onUrlChanged: { var currentUrl = url.toString(); diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index c5d942518f..7b79b04343 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -14,52 +14,16 @@ VrDialog { title: "QmlWindow" resizable: true enabled: false + visible: false focus: true property var channel; - // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false contentImplicitWidth: clientArea.implicitWidth contentImplicitHeight: clientArea.implicitHeight property alias source: pageLoader.source - /* - WebSocket { - id: socket - url: "ws://localhost:51016"; - active: false - - // the following three properties/functions are required to align the QML WebSocket API with the HTML5 WebSocket API. - property var send: function (arg) { - sendTextMessage(arg); - } - - onTextMessageReceived: { - onmessage({data: message}); - } - - property var onmessage; - - onStatusChanged: { - if (socket.status == WebSocket.Error) { - console.error("Error: " + socket.errorString) - } else if (socket.status == WebSocket.Closed) { - console.log("Socket closed"); - } else if (socket.status == WebSocket.Open) { - console.log("Connected") - //open the webchannel with the socket as transport - new WebChannel.QWebChannel(socket, function(ch) { - root.channel = ch; - var myUrl = root.source.toString().toLowerCase(); - console.log(myUrl); - var bridge = root.channel.objects[myUrl]; - console.log(bridge); - }); - } - } - } - */ Keys.onPressed: { console.log("QmlWindow keypress") } @@ -73,6 +37,7 @@ VrDialog { width: root.clientWidth height: root.clientHeight focus: true + clip: true Loader { id: pageLoader @@ -82,7 +47,6 @@ VrDialog { onLoaded: { console.log("Loaded content") - //socket.active = true; //connect forceActiveFocus() } diff --git a/interface/resources/qml/controls/VrDialog.qml b/interface/resources/qml/controls/VrDialog.qml index aa14e2fcba..411fdbbb0b 100644 --- a/interface/resources/qml/controls/VrDialog.qml +++ b/interface/resources/qml/controls/VrDialog.qml @@ -41,6 +41,11 @@ DialogBase { // modify the visibility onEnabledChanged: { opacity = enabled ? 1.0 : 0.0 + // If the dialog is initially invisible, setting opacity doesn't + // trigger making it visible. + if (enabled) { + visible = true; + } } // The actual animator diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 29eca50724..956a5a42c7 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -122,7 +122,7 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, height = argumentObject.property(HEIGHT_PROPERTY).toInt32(); } if (argumentObject.property(VISIBILE_PROPERTY).isBool()) { - visible = argumentObject.property(VISIBILE_PROPERTY).isBool(); + visible = argumentObject.property(VISIBILE_PROPERTY).toBool(); } } From bfe7ab4d9431c53d67a62fa3963d7c209a593cb8 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 11:39:34 -0800 Subject: [PATCH 22/41] Allow QML windows to quit the application --- interface/src/Application.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5ad12102f7..7c789992dd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -28,11 +28,13 @@ #include #include #include -#include #include #include #include +#include +#include + #include #include #include @@ -1094,8 +1096,13 @@ void Application::initializeUi() { offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->load("Root.qml"); offscreenUi->load("RootMenu.qml"); + auto rootContext = offscreenUi->getRootContext(); + auto engine = rootContext->engine(); + connect(engine, &QQmlEngine::quit, [] { + qApp->quit(); + }); rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance()); rootContext->setContextProperty("AnimationCache", DependencyManager::get().data()); rootContext->setContextProperty("Controller", DependencyManager::get().data()); From e863d4edeee0eda335e1eeddff437038118a3b25 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 16:48:19 -0800 Subject: [PATCH 23/41] Fix SDL breakage due to missing init --- libraries/plugins/src/plugins/PluginManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 27e326fcba..4429f49346 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -79,6 +79,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { auto& container = PluginContainer::getInstance(); for (auto plugin : displayPlugins) { plugin->setContainer(&container); + plugin->init(); } }); @@ -104,6 +105,7 @@ const InputPluginList& PluginManager::getInputPlugins() { auto& container = PluginContainer::getInstance(); for (auto plugin : inputPlugins) { plugin->setContainer(&container); + plugin->init(); } }); return inputPlugins; From af132e267f86f73ec0051be2380dbd796cba6818 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 17:13:20 -0800 Subject: [PATCH 24/41] Support a 'nav focus' state to allow joystick / hydra navigation of UI --- libraries/ui/src/OffscreenUi.cpp | 40 ++++++++++++++++++++++++++++++++ libraries/ui/src/OffscreenUi.h | 2 ++ 2 files changed, 42 insertions(+) diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index a1f00ab5ad..dec5757d4b 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -142,4 +142,44 @@ void OffscreenUi::error(const QString& text) { OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {}; +static const char * const NAVIGATION_FOCUSED_PROPERTY = "NavigationFocused"; +class OffscreenFlags : public QObject{ + Q_OBJECT + Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) +public: + OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {} + bool isNavigationFocused() const { return _navigationFocused; } + void setNavigationFocused(bool focused) { + if (_navigationFocused != focused) { + _navigationFocused = focused; + emit navigationFocusedChanged(); + } + } + +signals: + void navigationFocusedChanged(); + +private: + bool _navigationFocused { false }; + +}; + + +OffscreenFlags* getFlags(QQmlContext* context) { + static OffscreenFlags* offscreenFlags { nullptr }; + if (!offscreenFlags) { + offscreenFlags = new OffscreenFlags(context); + context->setContextProperty("OffscreenFlags", offscreenFlags); + } + return offscreenFlags; +} + +bool OffscreenUi::navigationFocused() { + return getFlags(getRootContext())->isNavigationFocused(); +} + +void OffscreenUi::setNavigationFocused(bool focused) { + getFlags(getRootContext())->setNavigationFocused(focused); +} + #include "OffscreenUi.moc" diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index be7f6b5e2e..5927acd0ae 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -29,6 +29,8 @@ public: void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); bool shouldSwallowShortcut(QEvent* event); + bool navigationFocused(); + void setNavigationFocused(bool focused); // Messagebox replacement functions using ButtonCallback = std::function; From a834f28eca57210657c74528712624073f4b7ef6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 17:14:03 -0800 Subject: [PATCH 25/41] Add a tag to the top level QML dialog so loaded content can manipulate it easily --- interface/resources/qml/QmlWindow.qml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 7b79b04343..951aa24471 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -10,6 +10,7 @@ import "styles" VrDialog { id: root + objectName: "topLevelWindow" HifiConstants { id: hifi } title: "QmlWindow" resizable: true @@ -24,10 +25,6 @@ VrDialog { contentImplicitHeight: clientArea.implicitHeight property alias source: pageLoader.source - Keys.onPressed: { - console.log("QmlWindow keypress") - } - Item { id: clientArea implicitHeight: 600 @@ -44,9 +41,9 @@ VrDialog { objectName: "Loader" anchors.fill: parent focus: true + property var dialog: root onLoaded: { - console.log("Loaded content") forceActiveFocus() } From 4c26627622afdea02276556ee7fc61c505f60e6b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 17:14:40 -0800 Subject: [PATCH 26/41] Add navigation actions and wire them up in the standard controller --- interface/resources/controllers/standard.json | 9 +++ interface/src/Application.cpp | 55 ++++++++++++++++++- .../controllers/src/controllers/Actions.cpp | 9 +++ .../controllers/src/controllers/Actions.h | 10 ++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 6a8fc0d803..47c3b3ef17 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -1,6 +1,15 @@ { "name": "Standard to Action", "channels": [ + { "from": "Standard.DU", "when": "Application.NavigationFocused", "to": "Actions.UiNavUp" }, + { "from": "Standard.DD", "when": "Application.NavigationFocused", "to": "Actions.UiNavDown" }, + { "from": "Standard.DL", "when": "Application.NavigationFocused", "to": "Actions.UiNavLeft" }, + { "from": "Standard.DR", "when": "Application.NavigationFocused", "to": "Actions.UiNavRight" }, + { "from": "Standard.A", "when": "Application.NavigationFocused", "to": "Actions.UiNavSelect" }, + { "from": "Standard.B", "when": "Application.NavigationFocused", "to": "Actions.UiNavBack" }, + { "from": "Standard.LB", "when": "Application.NavigationFocused", "to": "Actions.UiNavPreviousGroup" }, + { "from": "Standard.RB", "when": "Application.NavigationFocused", "to": "Actions.UiNavNextGroup" }, + { "from": "Standard.LY", "to": "Actions.TranslateZ" }, { "from": "Standard.LX", "to": "Actions.TranslateX" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7c789992dd..abd08d6d07 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -11,6 +11,8 @@ #include "Application.h" +#include + #include #include #include @@ -34,6 +36,7 @@ #include #include +#include #include #include @@ -684,6 +687,50 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Setup the userInputMapper with the actions auto userInputMapper = DependencyManager::get(); connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { + using namespace controller; + static auto offscreenUi = DependencyManager::get(); + if (offscreenUi->navigationFocused()) { + auto actionEnum = static_cast(action); + int key = Qt::Key_unknown; + switch (actionEnum) { + case Action::UI_NAV_UP: + key = Qt::Key_Up; + break; + case Action::UI_NAV_DOWN: + key = Qt::Key_Down; + break; + case Action::UI_NAV_LEFT: + key = Qt::Key_Left; + break; + case Action::UI_NAV_RIGHT: + key = Qt::Key_Right; + break; + case Action::UI_NAV_BACK: + key = Qt::Key_Escape; + break; + case Action::UI_NAV_SELECT: + key = Qt::Key_Return; + break; + case Action::UI_NAV_NEXT_GROUP: + key = Qt::Key_Tab; + break; + case Action::UI_NAV_PREVIOUS_GROUP: + key = Qt::Key_Backtab; + break; + } + + if (key != Qt::Key_unknown) { + if (state) { + QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + } else { + QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + } + return; + } + } + if (action == controller::toInt(controller::Action::RETICLE_CLICK)) { auto globalPos = QCursor::pos(); auto localPos = _glWidget->mapFromGlobal(globalPos); @@ -754,6 +801,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float { return (float)qApp->getMyAvatar()->getCharacterController()->onGround(); })); + _applicationStateDevice->addInputVariant(QString("NavigationFocused"), controller::StateController::ReadLambda([]() -> float { + static auto offscreenUi = DependencyManager::get(); + return offscreenUi->navigationFocused() ? 1.0 : 0.0; + })); userInputMapper->registerDevice(_applicationStateDevice); @@ -1096,7 +1147,9 @@ void Application::initializeUi() { offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->load("Root.qml"); offscreenUi->load("RootMenu.qml"); - + // FIXME either expose so that dialogs can set this themselves or + // do better detection in the offscreen UI of what has focus + offscreenUi->setNavigationFocused(false); auto rootContext = offscreenUi->getRootContext(); auto engine = rootContext->engine(); diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 0bda52b237..2ff1b8ce0f 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -62,6 +62,15 @@ namespace controller { makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), + makeButtonPair(Action::UI_NAV_UP, "UiNavUp"), + makeButtonPair(Action::UI_NAV_DOWN, "UiNavDown"), + makeButtonPair(Action::UI_NAV_LEFT, "UiNavLeft"), + makeButtonPair(Action::UI_NAV_RIGHT, "UiNavRight"), + makeButtonPair(Action::UI_NAV_SELECT, "UiNavSelect"), + makeButtonPair(Action::UI_NAV_BACK, "UiNavBack"), + makeButtonPair(Action::UI_NAV_NEXT_GROUP, "UiNavNextGroup"), + makeButtonPair(Action::UI_NAV_PREVIOUS_GROUP, "UiNavPreviousGroup"), + makeAxisPair(Action::RETICLE_CLICK, "ReticleClick"), makeAxisPair(Action::RETICLE_X, "ReticleX"), makeAxisPair(Action::RETICLE_Y, "ReticleY"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 56dd9660d9..d358a7a277 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -55,6 +55,15 @@ enum class Action { SHIFT, + UI_NAV_UP, + UI_NAV_DOWN, + UI_NAV_LEFT, + UI_NAV_RIGHT, + UI_NAV_SELECT, + UI_NAV_BACK, + UI_NAV_NEXT_GROUP, + UI_NAV_PREVIOUS_GROUP, + // Pointer/Reticle control RETICLE_CLICK, RETICLE_X, @@ -90,6 +99,7 @@ enum class Action { BOOM_IN, BOOM_OUT, + NUM_ACTIONS, }; From 3eddf8d4a43f8db4bcb2876f8139015660d5ffc0 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 22:24:34 -0800 Subject: [PATCH 27/41] Allow and conditionals to be initialized from simple pairs --- .../src/controllers/impl/conditionals/AndConditional.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h index c60e4b15df..2299843a24 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h +++ b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h @@ -18,7 +18,11 @@ class AndConditional : public Conditional { public: using Pointer = std::shared_ptr; - AndConditional(Conditional::List children) : _children(children) { } + AndConditional(Conditional::List children) + : _children(children) {} + + AndConditional(Conditional::Pointer& first, Conditional::Pointer& second) + : _children({ first, second }) {} virtual bool satisfied() override; From c77b66f88c2a88c8f9aa75d1de618f3beb12702c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 22:27:33 -0800 Subject: [PATCH 28/41] Make navigation directions into axes --- interface/src/Application.cpp | 59 ++++++++++++++----- .../controllers/src/controllers/Actions.cpp | 15 ++--- .../controllers/src/controllers/Actions.h | 9 +-- 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index abd08d6d07..a92f019e34 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -692,34 +692,61 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : if (offscreenUi->navigationFocused()) { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; + bool navAxis = false; + static int lastKey = Qt::Key_unknown; switch (actionEnum) { - case Action::UI_NAV_UP: - key = Qt::Key_Up; + case Action::UI_NAV_VERTICAL: + navAxis = true; + if (state > 0.0f) { + key = Qt::Key_Up; + } else if (state < 0.0f) { + key = Qt::Key_Down; + } break; - case Action::UI_NAV_DOWN: - key = Qt::Key_Down; + + case Action::UI_NAV_LATERAL: + navAxis = true; + if (state > 0.0f) { + key = Qt::Key_Right; + } else if (state < 0.0f) { + key = Qt::Key_Left; + } break; - case Action::UI_NAV_LEFT: - key = Qt::Key_Left; - break; - case Action::UI_NAV_RIGHT: - key = Qt::Key_Right; + + case Action::UI_NAV_GROUP: + navAxis = true; + if (state > 0.0f) { + key = Qt::Key_Tab; + } else if (state < 0.0f) { + key = Qt::Key_Backtab; + } break; + case Action::UI_NAV_BACK: key = Qt::Key_Escape; break; + case Action::UI_NAV_SELECT: key = Qt::Key_Return; break; - case Action::UI_NAV_NEXT_GROUP: - key = Qt::Key_Tab; - break; - case Action::UI_NAV_PREVIOUS_GROUP: - key = Qt::Key_Backtab; - break; } - if (key != Qt::Key_unknown) { + if (navAxis) { + qDebug() << "Axis " << action << " value " << state; + if (lastKey != Qt::Key_unknown) { + qDebug() << "Releasing key " << lastKey; + QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + lastKey = Qt::Key_unknown; + } + + if (key != Qt::Key_unknown) { + qDebug() << "Pressing key " << key; + QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + lastKey = key; + } + } else if (key != Qt::Key_unknown) { if (state) { QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 2ff1b8ce0f..7bee5101b1 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -62,15 +62,6 @@ namespace controller { makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), - makeButtonPair(Action::UI_NAV_UP, "UiNavUp"), - makeButtonPair(Action::UI_NAV_DOWN, "UiNavDown"), - makeButtonPair(Action::UI_NAV_LEFT, "UiNavLeft"), - makeButtonPair(Action::UI_NAV_RIGHT, "UiNavRight"), - makeButtonPair(Action::UI_NAV_SELECT, "UiNavSelect"), - makeButtonPair(Action::UI_NAV_BACK, "UiNavBack"), - makeButtonPair(Action::UI_NAV_NEXT_GROUP, "UiNavNextGroup"), - makeButtonPair(Action::UI_NAV_PREVIOUS_GROUP, "UiNavPreviousGroup"), - makeAxisPair(Action::RETICLE_CLICK, "ReticleClick"), makeAxisPair(Action::RETICLE_X, "ReticleX"), makeAxisPair(Action::RETICLE_Y, "ReticleY"), @@ -79,6 +70,12 @@ namespace controller { makeAxisPair(Action::RETICLE_UP, "ReticleUp"), makeAxisPair(Action::RETICLE_DOWN, "ReticleDown"), + makeAxisPair(Action::UI_NAV_LATERAL, "UiNavLateral"), + makeAxisPair(Action::UI_NAV_VERTICAL, "UiNavVertical"), + makeAxisPair(Action::UI_NAV_GROUP, "UiNavGroup"), + makeAxisPair(Action::UI_NAV_SELECT, "UiNavSelect"), + makeAxisPair(Action::UI_NAV_BACK, "UiNavBack"), + // Aliases and bisected versions makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"), makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index d358a7a277..812d3b8df3 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -55,14 +55,11 @@ enum class Action { SHIFT, - UI_NAV_UP, - UI_NAV_DOWN, - UI_NAV_LEFT, - UI_NAV_RIGHT, + UI_NAV_LATERAL, + UI_NAV_VERTICAL, + UI_NAV_GROUP, UI_NAV_SELECT, UI_NAV_BACK, - UI_NAV_NEXT_GROUP, - UI_NAV_PREVIOUS_GROUP, // Pointer/Reticle control RETICLE_CLICK, From 000130617eff64bb71ad9d07040b1349f3c910e7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 22:25:16 -0800 Subject: [PATCH 29/41] Allow input devices to break up their mappings into multiple files --- interface/resources/controllers/standard.json | 9 --- .../controllers/standard_navigation.json | 61 +++++++++++++++++++ .../controllers/src/controllers/InputDevice.h | 1 + .../src/controllers/StandardController.cpp | 6 +- .../src/controllers/StandardController.h | 2 +- .../src/controllers/UserInputMapper.cpp | 47 +++++++++++++- .../src/controllers/UserInputMapper.h | 1 + 7 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 interface/resources/controllers/standard_navigation.json diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 47c3b3ef17..6a8fc0d803 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -1,15 +1,6 @@ { "name": "Standard to Action", "channels": [ - { "from": "Standard.DU", "when": "Application.NavigationFocused", "to": "Actions.UiNavUp" }, - { "from": "Standard.DD", "when": "Application.NavigationFocused", "to": "Actions.UiNavDown" }, - { "from": "Standard.DL", "when": "Application.NavigationFocused", "to": "Actions.UiNavLeft" }, - { "from": "Standard.DR", "when": "Application.NavigationFocused", "to": "Actions.UiNavRight" }, - { "from": "Standard.A", "when": "Application.NavigationFocused", "to": "Actions.UiNavSelect" }, - { "from": "Standard.B", "when": "Application.NavigationFocused", "to": "Actions.UiNavBack" }, - { "from": "Standard.LB", "when": "Application.NavigationFocused", "to": "Actions.UiNavPreviousGroup" }, - { "from": "Standard.RB", "when": "Application.NavigationFocused", "to": "Actions.UiNavNextGroup" }, - { "from": "Standard.LY", "to": "Actions.TranslateZ" }, { "from": "Standard.LX", "to": "Actions.TranslateX" }, diff --git a/interface/resources/controllers/standard_navigation.json b/interface/resources/controllers/standard_navigation.json new file mode 100644 index 0000000000..c3b30e8607 --- /dev/null +++ b/interface/resources/controllers/standard_navigation.json @@ -0,0 +1,61 @@ +{ + "name": "Standard to Action", + "when": "Application.NavigationFocused", + "channels": [ + { "disabled_from": { "makeAxis" : [ "Standard.DD", "Standard.DU" ] }, "to": "Actions.UiNavVertical" }, + { "disabled_from": { "makeAxis" : [ "Standard.DL", "Standard.DR" ] }, "to": "Actions.UiNavLateral" }, + { "disabled_from": { "makeAxis" : [ "Standard.LB", "Standard.RB" ] }, "to": "Actions.UiNavGroup" }, + { "from": "Standard.DU", "to": "Actions.UiNavVertical" }, + { "from": "Standard.DD", "to": "Actions.UiNavVertical", "filters": "invert" }, + { "from": "Standard.DL", "to": "Actions.UiNavLateral", "filters": "invert" }, + { "from": "Standard.DR", "to": "Actions.UiNavLateral" }, + { "from": "Standard.LB", "to": "Actions.UiNavGroup","filters": "invert" }, + { "from": "Standard.RB", "to": "Actions.UiNavGroup" }, + { "from": [ "Standard.A", "Standard.X", "Standard.RT", "Standard.LT" ], "to": "Actions.UiNavSelect" }, + { "from": [ "Standard.B", "Standard.Y", "Standard.RightPrimaryThumb", "Standard.LeftPrimaryThumb" ], "to": "Actions.UiNavBack" }, + { + "from": [ "Standard.RT", "Standard.LT" ], + "to": "Actions.UiNavSelect", + "filters": [ + { "type": "deadZone", "min": 0.5 }, + "constrainToInteger" + ] + }, + { + "from": "Standard.LX", "to": "Actions.UiNavLateral", + "filters": [ + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + }, + { + "from": "Standard.LY", "to": "Actions.UiNavVertical", + "filters": [ + "invert", + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + }, + { + "from": "Standard.RX", "to": "Actions.UiNavLateral", + "filters": [ + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + }, + { + "from": "Standard.RY", "to": "Actions.UiNavVertical", + "filters": [ + "invert", + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + } + ] +} + + diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index fc3477b41a..3add7d236f 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -83,6 +83,7 @@ protected: friend class UserInputMapper; virtual Input::NamedVector getAvailableInputs() const = 0; + virtual QStringList getDefaultMappingConfigs() const { return QStringList() << getDefaultMappingConfig(); } virtual QString getDefaultMappingConfig() const { return QString(); } virtual EndpointPointer createEndpoint(const Input& input) const; diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index fadbeee326..e101c5f4ff 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -131,10 +131,10 @@ EndpointPointer StandardController::createEndpoint(const Input& input) const { return std::make_shared(input); } -QString StandardController::getDefaultMappingConfig() const { +QStringList StandardController::getDefaultMappingConfigs() const { static const QString DEFAULT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard.json"; - return DEFAULT_MAPPING_JSON; + static const QString DEFAULT_NAV_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_navigation.json"; + return QStringList() << DEFAULT_NAV_MAPPING_JSON << DEFAULT_MAPPING_JSON; } - } diff --git a/libraries/controllers/src/controllers/StandardController.h b/libraries/controllers/src/controllers/StandardController.h index 6c18c76371..57bd0faba5 100644 --- a/libraries/controllers/src/controllers/StandardController.h +++ b/libraries/controllers/src/controllers/StandardController.h @@ -27,7 +27,7 @@ class StandardController : public QObject, public InputDevice { public: virtual EndpointPointer createEndpoint(const Input& input) const override; virtual Input::NamedVector getAvailableInputs() const override; - virtual QString getDefaultMappingConfig() const override; + virtual QStringList getDefaultMappingConfigs() const override; virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 9251a663ba..fe64566b29 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -100,7 +100,7 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { } _registeredDevices[deviceID] = device; - auto mapping = loadMapping(device->getDefaultMappingConfig()); + auto mapping = loadMappings(device->getDefaultMappingConfigs()); if (mapping) { _mappingsByDevice[deviceID] = mapping; enableMapping(mapping); @@ -139,7 +139,7 @@ void UserInputMapper::loadDefaultMapping(uint16 deviceID) { } - auto mapping = loadMapping(proxyEntry->second->getDefaultMappingConfig()); + auto mapping = loadMappings(proxyEntry->second->getDefaultMappingConfigs()); if (mapping) { auto prevMapping = _mappingsByDevice[deviceID]; disableMapping(prevMapping); @@ -710,6 +710,21 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) { return parseMapping(json); } +MappingPointer UserInputMapper::loadMappings(const QStringList& jsonFiles) { + Mapping::Pointer result; + for (const QString& jsonFile : jsonFiles) { + auto subMapping = loadMapping(jsonFile); + if (subMapping) { + if (!result) { + result = subMapping; + } else { + auto& routes = result->routes; + routes.insert(routes.end(), subMapping->routes.begin(), subMapping->routes.end()); + } + } + } + return result; +} static const QString JSON_NAME = QStringLiteral("name"); @@ -888,7 +903,7 @@ Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) { if (value.isObject()) { - auto object = value.toObject(); + auto object = value.toObject(); if (object.contains("makeAxis")) { auto axisValue = object.value("makeAxis"); if (axisValue.isArray()) { @@ -985,6 +1000,20 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { return result; } +void injectConditional(Route::Pointer& route, Conditional::Pointer& conditional) { + if (!conditional) { + return; + } + + if (!route->conditional) { + route->conditional = conditional; + return; + } + + route->conditional = std::make_shared(conditional, route->conditional); +} + + Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { if (!json.isObject()) { return Mapping::Pointer(); @@ -994,12 +1023,24 @@ Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { auto mapping = std::make_shared("default"); mapping->name = obj[JSON_NAME].toString(); const auto& jsonChannels = obj[JSON_CHANNELS].toArray(); + Conditional::Pointer globalConditional; + if (obj.contains(JSON_CHANNEL_WHEN)) { + auto conditionalsValue = obj[JSON_CHANNEL_WHEN]; + globalConditional = parseConditional(conditionalsValue); + } + for (const auto& channelIt : jsonChannels) { Route::Pointer route = parseRoute(channelIt); + if (!route) { qWarning() << "Couldn't parse route"; continue; } + + if (globalConditional) { + injectConditional(route, globalConditional); + } + mapping->routes.push_back(route); } _mappingsByName[mapping->name] = mapping; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index d93a93016c..98a85a2a44 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -107,6 +107,7 @@ namespace controller { MappingPointer newMapping(const QString& mappingName); MappingPointer parseMapping(const QString& json); MappingPointer loadMapping(const QString& jsonFile); + MappingPointer loadMappings(const QStringList& jsonFiles); void loadDefaultMapping(uint16 deviceID); void enableMapping(const QString& mappingName, bool enable = true); From a883891197c95092cbefc48bb145c89b24e79f23 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 22:26:22 -0800 Subject: [PATCH 30/41] Expose the username to scripts (and thus QML) --- interface/src/scripting/AccountScriptingInterface.cpp | 10 +++++++++- interface/src/scripting/AccountScriptingInterface.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp index 87ea3220a4..126cd53003 100644 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ b/interface/src/scripting/AccountScriptingInterface.cpp @@ -17,7 +17,6 @@ AccountScriptingInterface::AccountScriptingInterface() { AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, &AccountManager::balanceChanged, this, &AccountScriptingInterface::updateBalance); - } AccountScriptingInterface* AccountScriptingInterface::getInstance() { @@ -39,3 +38,12 @@ void AccountScriptingInterface::updateBalance() { AccountManager& accountManager = AccountManager::getInstance(); emit balanceChanged(accountManager.getAccountInfo().getBalanceInSatoshis()); } + +QString AccountScriptingInterface::getUsername() { + AccountManager& accountManager = AccountManager::getInstance(); + if (accountManager.isLoggedIn()) { + return accountManager.getAccountInfo().getUsername(); + } else { + return "Unknown user"; + } +} diff --git a/interface/src/scripting/AccountScriptingInterface.h b/interface/src/scripting/AccountScriptingInterface.h index e9cf0ede5f..578a9d6728 100644 --- a/interface/src/scripting/AccountScriptingInterface.h +++ b/interface/src/scripting/AccountScriptingInterface.h @@ -24,6 +24,7 @@ signals: public slots: static AccountScriptingInterface* getInstance(); float getBalance(); + QString getUsername(); bool isLoggedIn(); void updateBalance(); }; From 7d71a187d841158cbb432049cc6e5309cba3a7d2 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 30 Dec 2015 16:48:19 -0800 Subject: [PATCH 31/41] Fix SDL breakage due to missing init --- libraries/plugins/src/plugins/PluginManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 27e326fcba..4429f49346 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -79,6 +79,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { auto& container = PluginContainer::getInstance(); for (auto plugin : displayPlugins) { plugin->setContainer(&container); + plugin->init(); } }); @@ -104,6 +105,7 @@ const InputPluginList& PluginManager::getInputPlugins() { auto& container = PluginContainer::getInstance(); for (auto plugin : inputPlugins) { plugin->setContainer(&container); + plugin->init(); } }); return inputPlugins; From c6e6aceb8527fb35753c0d603db069916dfb71df Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 30 Dec 2015 15:37:28 -0800 Subject: [PATCH 32/41] Optimize getVertexStream() by using a const& --- libraries/model/src/model/Geometry.h | 2 +- libraries/render-utils/src/DeferredLightingEffect.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index fa29faff5f..f9d9b0eeb4 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -58,7 +58,7 @@ public: const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; } // BufferStream on the mesh vertices and attributes matching the vertex format - const gpu::BufferStream getVertexStream() const { return _vertexStream; } + const gpu::BufferStream& getVertexStream() const { return _vertexStream; } // Index Buffer void setIndexBuffer(const BufferView& buffer); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index db0e47de5e..e6892b95f4 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -819,8 +819,6 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { //DEBUG: model::Mesh::Part part(0, indices, 0, model::Mesh::LINE_STRIP); _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(sizeof(part), (gpu::Byte*) &part), gpu::Element::PART_DRAWCALL)); - - _spotLightMesh->getVertexStream(); } return _spotLightMesh; } From 462cc325e5b03c46b53360c821e0bef3418e8c87 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 31 Dec 2015 12:39:56 -0800 Subject: [PATCH 33/41] Cleanup, moving some QML objects to OffscreenUI --- interface/src/Application.cpp | 5 +- libraries/gl/src/gl/OffscreenQmlSurface.h | 2 +- libraries/ui/src/OffscreenUi.cpp | 101 +++++++++++++--------- libraries/ui/src/OffscreenUi.h | 2 +- libraries/ui/src/QmlWebWindowClass.cpp | 32 +------ 5 files changed, 65 insertions(+), 77 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a92f019e34..9fb812a0fa 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -692,8 +692,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : if (offscreenUi->navigationFocused()) { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; - bool navAxis = false; static int lastKey = Qt::Key_unknown; + bool navAxis = false; switch (actionEnum) { case Action::UI_NAV_VERTICAL: navAxis = true; @@ -732,16 +732,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } if (navAxis) { - qDebug() << "Axis " << action << " value " << state; if (lastKey != Qt::Key_unknown) { - qDebug() << "Releasing key " << lastKey; QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); lastKey = Qt::Key_unknown; } if (key != Qt::Key_unknown) { - qDebug() << "Pressing key " << key; QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); lastKey = key; diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index d66cbeb285..608e811b4b 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -37,7 +37,7 @@ public: using MouseTranslator = std::function; - void create(QOpenGLContext* context); + virtual void create(QOpenGLContext* context); void resize(const QSize& size); QSize size() const; Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index dec5757d4b..572b3c018e 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -9,10 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OffscreenUi.h" -#include -#include -#include -#include + +#include +#include + +#include #include "ErrorDialog.h" #include "MessageDialog.h" @@ -27,7 +28,52 @@ public: } }; +class OffscreenFlags : public QObject { + Q_OBJECT + Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) +public: + + OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {} + bool isNavigationFocused() const { return _navigationFocused; } + void setNavigationFocused(bool focused) { + if (_navigationFocused != focused) { + _navigationFocused = focused; + emit navigationFocusedChanged(); + } + } + +signals: + void navigationFocusedChanged(); + +private: + bool _navigationFocused { false }; +}; + + +class UrlFixer : public QObject { + Q_OBJECT +public: + Q_INVOKABLE QString fixupUrl(const QString& originalUrl) { + static const QString ACCESS_TOKEN_PARAMETER = "access_token"; + static const QString ALLOWED_HOST = "metaverse.highfidelity.com"; + QString result = originalUrl; + QUrl url(originalUrl); + QUrlQuery query(url); + if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) { + qDebug() << "Updating URL with auth token"; + AccountManager& accountManager = AccountManager::getInstance(); + query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token); + url.setQuery(query.query()); + result = url.toString(); + } + + return result; + } +}; + +static UrlFixer * urlFixer { nullptr }; +static OffscreenFlags* offscreenFlags { nullptr }; // This hack allows the QML UI to work with keys that are also bound as // shortcuts at the application level. However, it seems as though the @@ -58,9 +104,15 @@ OffscreenUi::OffscreenUi() { ::qmlRegisterType("Hifi", 1, 0, "Root"); } -OffscreenUi::~OffscreenUi() { -} +void OffscreenUi::create(QOpenGLContext* context) { + OffscreenQmlSurface::create(context); + auto rootContext = getRootContext(); + offscreenFlags = new OffscreenFlags(); + rootContext->setContextProperty("offscreenFlags", offscreenFlags); + urlFixer = new UrlFixer(); + rootContext->setContextProperty("urlFixer", urlFixer); +} void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = getRootItem()->findChild(name); @@ -139,47 +191,14 @@ void OffscreenUi::error(const QString& text) { pDialog->setEnabled(true); } - OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {}; -static const char * const NAVIGATION_FOCUSED_PROPERTY = "NavigationFocused"; -class OffscreenFlags : public QObject{ - Q_OBJECT - Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) -public: - OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {} - bool isNavigationFocused() const { return _navigationFocused; } - void setNavigationFocused(bool focused) { - if (_navigationFocused != focused) { - _navigationFocused = focused; - emit navigationFocusedChanged(); - } - } - -signals: - void navigationFocusedChanged(); - -private: - bool _navigationFocused { false }; - -}; - - -OffscreenFlags* getFlags(QQmlContext* context) { - static OffscreenFlags* offscreenFlags { nullptr }; - if (!offscreenFlags) { - offscreenFlags = new OffscreenFlags(context); - context->setContextProperty("OffscreenFlags", offscreenFlags); - } - return offscreenFlags; -} - bool OffscreenUi::navigationFocused() { - return getFlags(getRootContext())->isNavigationFocused(); + return offscreenFlags->isNavigationFocused(); } void OffscreenUi::setNavigationFocused(bool focused) { - getFlags(getRootContext())->setNavigationFocused(focused); + offscreenFlags->setNavigationFocused(focused); } #include "OffscreenUi.moc" diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 5927acd0ae..d6845c1d37 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -25,7 +25,7 @@ class OffscreenUi : public OffscreenQmlSurface, public Dependency { public: OffscreenUi(); - virtual ~OffscreenUi(); + virtual void create(QOpenGLContext* context) override; void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); bool shouldSwallowShortcut(QEvent* event); diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index dd85016ae8..7e78a43879 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -29,36 +29,10 @@ static const char* const URL_PROPERTY = "source"; static const QRegExp HIFI_URL_PATTERN { "^hifi://" }; -class UrlFixer : public QObject { - Q_OBJECT -public: - Q_INVOKABLE QString fixupUrl(const QString& originalUrl) { - static const QString ACCESS_TOKEN_PARAMETER = "access_token"; - static const QString ALLOWED_HOST = "metaverse.highfidelity.com"; - QString result = originalUrl; - QUrl url(originalUrl); - QUrlQuery query(url); - if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) { - qDebug() << "Updating URL with auth token"; - AccountManager& accountManager = AccountManager::getInstance(); - query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token); - url.setQuery(query.query()); - result = url.toString(); - } - - return result; - } -}; - -static UrlFixer URL_FIXER; - // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine, - [&](QQmlContext* context, QObject* object) { - context->setContextProperty("urlFixer", &URL_FIXER); - return new QmlWebWindowClass(object); - }); + [&](QQmlContext* context, QObject* object) { return new QmlWebWindowClass(object); }); } QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { @@ -100,6 +74,4 @@ void QmlWebWindowClass::setURL(const QString& urlString) { QMetaObject::invokeMethod(this, "setURL", Qt::QueuedConnection, Q_ARG(QString, urlString)); } _qmlWindow->setProperty(URL_PROPERTY, urlString); -} - -#include "QmlWebWindowClass.moc" \ No newline at end of file +} \ No newline at end of file From 67e32cc1f0e9dd6afd949510185714fe724691d3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 31 Dec 2015 14:14:48 -0800 Subject: [PATCH 34/41] Consolidating URL handling, exposing to all QML objects --- interface/resources/qml/QmlWebWindow.qml | 24 +++++++++++++----------- libraries/ui/src/OffscreenUi.cpp | 22 +++++++++++++++++----- libraries/ui/src/QmlWebWindowClass.cpp | 15 ++++----------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 22ff5708dc..c2076c93e3 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -30,16 +30,6 @@ VrDialog { console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); }); - // Required to support clicking on "hifi://" links - webview.loadingChanged.connect(handleWebviewLoading) - } - - // Required to support clicking on "hifi://" links - function handleWebviewLoading(loadRequest) { - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - var newUrl = loadRequest.url.toString(); - root.navigating(newUrl) - } } Item { @@ -59,11 +49,23 @@ VrDialog { onUrlChanged: { var currentUrl = url.toString(); - var newUrl = urlFixer.fixupUrl(currentUrl); + var newUrl = urlHandler.fixupUrl(currentUrl); if (newUrl != currentUrl) { url = newUrl; } } + + onLoadingChanged: { + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + webview.stop(); + } + } + } + } profile: WebEngineProfile { id: webviewProfile diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 572b3c018e..db5a9a6009 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -13,7 +13,9 @@ #include #include +#include #include + #include "ErrorDialog.h" #include "MessageDialog.h" @@ -50,10 +52,20 @@ private: bool _navigationFocused { false }; }; - -class UrlFixer : public QObject { +class UrlHandler : public QObject { Q_OBJECT public: + Q_INVOKABLE bool canHandleUrl(const QString& url) { + static auto handler = dynamic_cast(qApp); + return handler->canAcceptURL(url); + } + + Q_INVOKABLE bool handleUrl(const QString& url) { + static auto handler = dynamic_cast(qApp); + return handler->acceptURL(url); + } + + // FIXME hack for authentication, remove when we migrate to Qt 5.6 Q_INVOKABLE QString fixupUrl(const QString& originalUrl) { static const QString ACCESS_TOKEN_PARAMETER = "access_token"; static const QString ALLOWED_HOST = "metaverse.highfidelity.com"; @@ -72,7 +84,7 @@ public: } }; -static UrlFixer * urlFixer { nullptr }; +static UrlHandler * urlHandler { nullptr }; static OffscreenFlags* offscreenFlags { nullptr }; // This hack allows the QML UI to work with keys that are also bound as @@ -110,8 +122,8 @@ void OffscreenUi::create(QOpenGLContext* context) { offscreenFlags = new OffscreenFlags(); rootContext->setContextProperty("offscreenFlags", offscreenFlags); - urlFixer = new UrlFixer(); - rootContext->setContextProperty("urlFixer", urlFixer); + urlHandler = new UrlHandler(); + rootContext->setContextProperty("urlHandler", urlHandler); } void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 7e78a43879..940ba121f3 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -27,7 +27,6 @@ #include "OffscreenUi.h" static const char* const URL_PROPERTY = "source"; -static const QRegExp HIFI_URL_PATTERN { "^hifi://" }; // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { @@ -41,16 +40,10 @@ QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWin void QmlWebWindowClass::handleNavigation(const QString& url) { bool handled = false; - - if (url.contains(HIFI_URL_PATTERN)) { - DependencyManager::get()->handleLookupString(url); - handled = true; - } else { - static auto handler = dynamic_cast(qApp); - if (handler) { - if (handler->canAcceptURL(url)) { - handled = handler->acceptURL(url); - } + static auto handler = dynamic_cast(qApp); + if (handler) { + if (handler->canAcceptURL(url)) { + handled = handler->acceptURL(url); } } From a0196f0cde408f20cfd1e6c4e58dc5ac2165d485 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 31 Dec 2015 15:23:39 -0800 Subject: [PATCH 35/41] Ensure we don't hang if the QML thread doesn't shutdown --- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 29 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 26a564e20b..f89f1f5b72 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -254,10 +254,33 @@ private: _quit = true; } + static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 5; void stop() { - QMutexLocker lock(&_mutex); - post(STOP); - _cond.wait(&_mutex); + if (_thread.isRunning()) { + qDebug() << "Stopping QML render thread " << _thread.currentThreadId(); + { + QMutexLocker lock(&_mutex); + post(STOP); + } + auto start = usecTimestampNow(); + auto now = usecTimestampNow(); + bool shutdownClean = false; + while (now - start < (MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) { + QMutexLocker lock(&_mutex); + if (_cond.wait(&_mutex, MSECS_PER_SECOND)) { + shutdownClean = true; + break; + } + now = usecTimestampNow(); + } + + if (!shutdownClean) { + qWarning() << "Failed to shut down the QML render thread"; + } + + } else { + qDebug() << "QML render thread already completed"; + } } bool allowNewFrame(uint8_t fps) { From 3b4f5d36a5cd96ab8d2544e9c15e3f1d23ea34b2 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 3 Jan 2016 12:29:32 -0800 Subject: [PATCH 36/41] PR feedback --- assignment-client/src/AssignmentClient.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index bbee597797..0d62b8dcc7 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "AssignmentFactory.h" #include "AssignmentActionFactory.h" @@ -61,6 +62,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::registerInheritance(); auto actionFactory = DependencyManager::set(); + DependencyManager::set(); // setup a thread for the NodeList and its PacketReceiver QThread* nodeThread = new QThread(this); From bfa9213a10faaec206b064979993f701aeb24f18 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 3 Jan 2016 20:22:39 -0800 Subject: [PATCH 37/41] Allow keyboard navigation of 'vr menu' --- interface/resources/qml/VrMenu.qml | 143 ++++++++++++++----------- interface/resources/qml/VrMenuItem.qml | 84 ++------------- interface/resources/qml/VrMenuView.qml | 78 ++++++++++++++ 3 files changed, 167 insertions(+), 138 deletions(-) create mode 100644 interface/resources/qml/VrMenuView.qml diff --git a/interface/resources/qml/VrMenu.qml b/interface/resources/qml/VrMenu.qml index 14a4a449fd..eef87ed943 100644 --- a/interface/resources/qml/VrMenu.qml +++ b/interface/resources/qml/VrMenu.qml @@ -1,7 +1,9 @@ import Hifi 1.0 as Hifi + import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 + import "controls" import "styles" @@ -21,15 +23,18 @@ Hifi.VrMenu { property var models: [] property var columns: [] - onEnabledChanged: { + console.log("Activating menu " + enabled); if (enabled && columns.length == 0) { + console.log(rootMenu) + console.log(rootMenu.items) pushColumn(rootMenu.items); } opacity = enabled ? 1.0 : 0.0 - if (enabled) { - forceActiveFocus() - } + offscreenFlags.navigationFocused = enabled; +// if (enabled) { + //forceActiveFocus() +// } } // The actual animator @@ -49,13 +54,12 @@ Hifi.VrMenu { } property var menuBuilder: Component { - Border { - HifiConstants { id: hifi } - property int menuDepth + VrMenuView { + property int menuDepth: root.models.length - 1 + model: root.models[menuDepth] Component.onCompleted: { - menuDepth = root.models.length - 1 - if (menuDepth == 0) { + if (menuDepth === 0) { x = lastMousePosition.x - 20 y = lastMousePosition.y - 20 } else { @@ -63,50 +67,11 @@ Hifi.VrMenu { x = lastColumn.x + 64; y = lastMousePosition.y - height / 2; } + //recalcSize(); } - border.color: hifi.colors.hifiBlue - color: hifi.colors.window - implicitHeight: listView.implicitHeight + 16 - implicitWidth: listView.implicitWidth + 16 - - Column { - id: listView - property real minWidth: 0 - anchors { - top: parent.top - topMargin: 8 - left: parent.left - leftMargin: 8 - right: parent.right - rightMargin: 8 - } - - Repeater { - model: root.models[menuDepth] - delegate: Loader { - id: loader - source: "VrMenuItem.qml" - Binding { - target: loader.item - property: "menuContainer" - value: root - when: loader.status == Loader.Ready - } - Binding { - target: loader.item - property: "source" - value: modelData - when: loader.status == Loader.Ready - } - Binding { - target: loader.item - property: "listView" - value: listView - when: loader.status == Loader.Ready - } - } - } + onSelected: { + root.selectItem(menuDepth, item) } } } @@ -116,14 +81,14 @@ Hifi.VrMenu { } function pushColumn(items) { - models.push(items) + models.push(itemsToModel(items)) if (columns.length) { var oldColumn = lastColumn(); //oldColumn.enabled = false } var newColumn = menuBuilder.createObject(root); columns.push(newColumn); - newColumn.forceActiveFocus(); + forceActiveFocus(); } function popColumn() { @@ -145,13 +110,41 @@ Hifi.VrMenu { curColumn.forceActiveFocus(); } - function selectItem(source) { + function itemsToModel(items) { + var newListModel = Qt.createQmlObject('import QtQuick 2.2; ListModel {}', root); + console.log(items) + console.log(items.length) + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + switch (item.type) { + case 2: + newListModel.append({"type":item.type, "name": item.title, "item": item}) + break; + case 1: + newListModel.append({"type":item.type, "name": item.text, "item": item}) + break; + case 0: + newListModel.append({"type":item.type, "name": "-----", "item": item}) + break; + } + } + return newListModel; + } + + function selectItem(depth, source) { + var popped = false; + while (depth + 1 < columns.length) { + popColumn() + popped = true + } + switch (source.type) { case 2: + lastColumn().enabled = false pushColumn(source.items) break; case 1: - source.trigger() + if (!popped) source.trigger() enabled = false break; case 0: @@ -165,14 +158,6 @@ Hifi.VrMenu { } } - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Escape: - root.popColumn() - event.accepted = true; - } - } - MouseArea { anchors.fill: parent id: mouseArea @@ -206,4 +191,36 @@ Hifi.VrMenu { function removeItem(menu, menuItem) { menu.removeItem(menuItem); } + + function previousItem() { + if (columns.length) { + lastColumn().incrementCurrentIndex() + } + } + + function nextItem() { + if (columns.length) { + lastColumn().decrementCurrentIndex() + } + } + + function selectCurrentItem() { + if (columns.length) { + var depth = columns.length - 1; + var index = lastColumn().currentIndex; + if (index >= 0) { + var model = models[depth]; + var item = model.get(index).item; + selectItem(depth, item); + } + } + } + + Keys.onDownPressed: previousItem(); + Keys.onUpPressed: nextItem(); + Keys.onSpacePressed: selectCurrentItem(); + Keys.onReturnPressed: selectCurrentItem(); + Keys.onRightPressed: selectCurrentItem(); + Keys.onLeftPressed: popColumn(); + Keys.onEscapePressed: popColumn(); } diff --git a/interface/resources/qml/VrMenuItem.qml b/interface/resources/qml/VrMenuItem.qml index fbde35059d..4dcde0e2ae 100644 --- a/interface/resources/qml/VrMenuItem.qml +++ b/interface/resources/qml/VrMenuItem.qml @@ -1,62 +1,23 @@ import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 -import "controls" + import "styles" +import "controls" Item { id: root - HifiConstants { - id: hifi - } + HifiConstants { id: hifi } + // The model object + property alias text: label.text property var source - property var menuContainer - property var listView - MouseArea { - anchors.left: parent.left - anchors.right: tag.right - anchors.rightMargin: -4 - anchors.top: parent.top - anchors.bottom: parent.bottom - acceptedButtons: Qt.LeftButton - hoverEnabled: true - - Rectangle { - id: highlight - visible: false - anchors.fill: parent - color: "#7f0e7077" - } - - onEntered: { - //if (source.type == 2 && enabled) { - // timer.start() - //} - highlight.visible = source.enabled - } - - onExited: { - timer.stop() - highlight.visible = false - } - - onClicked: { - select() - } - } implicitHeight: source.visible ? label.implicitHeight * 1.5 : 0 - implicitWidth: label.implicitWidth + label.height * 2.5 + implicitWidth: label.width + label.height * 2.5 visible: source.visible - - Timer { - id: timer - interval: 1000 - onTriggered: parent.select() - } - + width: parent.width FontAwesome { clip: true @@ -84,32 +45,18 @@ Item { Text { id: label - text: typedText() anchors.left: check.right anchors.leftMargin: 4 anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter color: source.enabled ? hifi.colors.text : hifi.colors.disabledText - enabled: source.enabled && source.visible + enabled: source.visible && (source.type !== 0 ? source.enabled : false) visible: source.visible - function typedText() { - if (source) { - switch (source.type) { - case 2: - return source.title - case 1: - return source.text - case 0: - return "-----" - } - } - return "" - } } FontAwesome { id: tag - x: listView.width - width - 4 + x: root.parent.width - width size: label.height width: implicitWidth visible: source.visible && (source.type == 2) @@ -117,17 +64,4 @@ Item { anchors.verticalCenter: parent.verticalCenter color: label.color } - - function select() { - //timer.stop(); - var popped = false - while (columns.length - 1 > listView.parent.menuDepth) { - popColumn() - popped = true - } - - if (!popped || source.type != 1) { - root.menuContainer.selectItem(source) - } - } } diff --git a/interface/resources/qml/VrMenuView.qml b/interface/resources/qml/VrMenuView.qml new file mode 100644 index 0000000000..6758031491 --- /dev/null +++ b/interface/resources/qml/VrMenuView.qml @@ -0,0 +1,78 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 + +import "styles" + +ListView { + id: root + HifiConstants { id: hifi } + width: 128 + height: count * 32 + onEnabledChanged: root.recalcSize(); + onVisibleChanged: root.recalcSize(); + signal selected(var item) + + highlight: Rectangle { + width: root.currentItem ? root.currentItem.width : 0 + height: root.currentItem ? root.currentItem.height : 0 + color: "lightsteelblue"; radius: 3 +// y: root.currentItem ? root.currentItem.y : 0 + } + + delegate: VrMenuItem { + text: name + source: item + onImplicitHeightChanged: root.recalcSize() + onImplicitWidthChanged: root.recalcSize() + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: root.currentIndex = index + onClicked: root.selected(item) + } + } + + onCountChanged: recalcSize(); + + function recalcSize() { + if (model.count !== count || !visible) { + return; + } + + var originalIndex = currentIndex; + var maxWidth = width; + var newHeight = 0; + for (var i = 0; i < count; ++i) { + currentIndex = i; + if (!currentItem) { + continue; + } + if (currentItem && currentItem.implicitWidth > maxWidth) { + maxWidth = currentItem.implicitWidth + } + if (currentItem.visible) { + newHeight += currentItem.implicitHeight + } + } + if (maxWidth > width) { + width = maxWidth; + } + if (newHeight > height) { + height = newHeight + } + currentIndex = originalIndex; + } + + Border { + id: border + anchors.fill: parent + anchors.margins: -8 + z: parent.z - 1 + border.color: hifi.colors.hifiBlue + color: hifi.colors.window + } +} + + From 435ab8c4e8dd7f60f28df34d0da83dffa530e228 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 3 Jan 2016 20:22:55 -0800 Subject: [PATCH 38/41] Fix possible log spam issue --- interface/resources/qml/QmlWebWindow.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index c2076c93e3..188351c113 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -49,7 +49,7 @@ VrDialog { onUrlChanged: { var currentUrl = url.toString(); - var newUrl = urlHandler.fixupUrl(currentUrl); + var newUrl = urlHandler.fixupUrl(currentUrl).toString(); if (newUrl != currentUrl) { url = newUrl; } From 297e58fbc6db05b2d43f6a7bffd2e10b329c7ac4 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 3 Jan 2016 20:29:31 -0800 Subject: [PATCH 39/41] Cleanup --- interface/resources/qml/VrMenu.qml | 9 --------- interface/resources/qml/VrMenuItem.qml | 8 ++++---- interface/resources/qml/VrMenuView.qml | 9 ++++----- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/interface/resources/qml/VrMenu.qml b/interface/resources/qml/VrMenu.qml index eef87ed943..738ec34a02 100644 --- a/interface/resources/qml/VrMenu.qml +++ b/interface/resources/qml/VrMenu.qml @@ -24,17 +24,11 @@ Hifi.VrMenu { property var columns: [] onEnabledChanged: { - console.log("Activating menu " + enabled); if (enabled && columns.length == 0) { - console.log(rootMenu) - console.log(rootMenu.items) pushColumn(rootMenu.items); } opacity = enabled ? 1.0 : 0.0 offscreenFlags.navigationFocused = enabled; -// if (enabled) { - //forceActiveFocus() -// } } // The actual animator @@ -67,7 +61,6 @@ Hifi.VrMenu { x = lastColumn.x + 64; y = lastMousePosition.y - height / 2; } - //recalcSize(); } onSelected: { @@ -112,8 +105,6 @@ Hifi.VrMenu { function itemsToModel(items) { var newListModel = Qt.createQmlObject('import QtQuick 2.2; ListModel {}', root); - console.log(items) - console.log(items.length) for (var i = 0; i < items.length; ++i) { var item = items[i]; switch (item.type) { diff --git a/interface/resources/qml/VrMenuItem.qml b/interface/resources/qml/VrMenuItem.qml index 4dcde0e2ae..2b1a4a3b5a 100644 --- a/interface/resources/qml/VrMenuItem.qml +++ b/interface/resources/qml/VrMenuItem.qml @@ -1,19 +1,19 @@ import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 - -import "styles" import "controls" +import "styles" Item { id: root - HifiConstants { id: hifi } + HifiConstants { + id: hifi + } // The model object property alias text: label.text property var source - implicitHeight: source.visible ? label.implicitHeight * 1.5 : 0 implicitWidth: label.width + label.height * 2.5 visible: source.visible diff --git a/interface/resources/qml/VrMenuView.qml b/interface/resources/qml/VrMenuView.qml index 6758031491..b00e21ba93 100644 --- a/interface/resources/qml/VrMenuView.qml +++ b/interface/resources/qml/VrMenuView.qml @@ -9,15 +9,16 @@ ListView { HifiConstants { id: hifi } width: 128 height: count * 32 - onEnabledChanged: root.recalcSize(); - onVisibleChanged: root.recalcSize(); + onEnabledChanged: recalcSize(); + onVisibleChanged: recalcSize(); + onCountChanged: recalcSize(); + signal selected(var item) highlight: Rectangle { width: root.currentItem ? root.currentItem.width : 0 height: root.currentItem ? root.currentItem.height : 0 color: "lightsteelblue"; radius: 3 -// y: root.currentItem ? root.currentItem.y : 0 } delegate: VrMenuItem { @@ -34,8 +35,6 @@ ListView { } } - onCountChanged: recalcSize(); - function recalcSize() { if (model.count !== count || !visible) { return; From 342d3e80bc87be9d00eb22b4582d4c6df132d9f1 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 4 Jan 2016 09:49:25 -0800 Subject: [PATCH 40/41] fix MSVC formatting --- interface/src/LODManager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 1dff1da0a7..10a288a44e 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -263,8 +263,7 @@ void LODManager::updatePIDRenderDistance(float targetFps, float measuredFps, flo // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate // goes up. distance = 1.0f / _renderDistanceController.update(measuredFps, deltaTime); - } - else { + } else { // Here we choose to just use the maximum render cutoff distance if throttled. distance = 1.0f / _renderDistanceController.getControlledValueLowLimit(); } From 4360595ed673a268c06fc0fb2f2e9e9cc8306d49 Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Mon, 4 Jan 2016 11:29:57 -0800 Subject: [PATCH 41/41] Fix to pistol.js so muzzle flash particle system doesn't stick around forever --- examples/toybox/pistol/pistol.js | 68 +++++++++++++------------------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/examples/toybox/pistol/pistol.js b/examples/toybox/pistol/pistol.js index 7fb05d992f..e0f063d463 100644 --- a/examples/toybox/pistol/pistol.js +++ b/examples/toybox/pistol/pistol.js @@ -37,6 +37,13 @@ this.bulletForce = 10; this.showLaser = false; + this.laserOffsets = { + y: 0.095 + }; + this.firingOffsets = { + z: 0.16 + } + }; Pistol.prototype = { @@ -272,46 +279,12 @@ }); }, 100); - Entities.editEntity(this.flash, { - isEmitting: true - }); - Script.setTimeout(function() { - Entities.editEntity(_this.flash, { - isEmitting: false - }); - }, 100) - - }, - - preload: function(entityID) { - this.entityID = entityID; - this.laser = Overlays.addOverlay("line3d", { - start: ZERO_VECTOR, - end: ZERO_VECTOR, - color: COLORS.RED, - alpha: 1, - visible: true, - lineWidth: 2 - }); - this.laserOffsets = { - y: 0.095 - }; - this.firingOffsets = { - z: 0.16 - } - var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); - var position = gunProps.position; - var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); - this.firingDirection = Quat.getFront(rotation); - var upVec = Quat.getUp(rotation); - this.barrelPoint = Vec3.sum(position, Vec3.multiply(upVec, this.laserOffsets.y)); - this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z)) - - this.flash = Entities.addEntity({ + var flash = Entities.addEntity({ type: "ParticleEffect", position: this.barrelPoint, "name": "Muzzle Flash", - isEmitting: false, + lifetime: 4, + parentID: this.entityID, "color": { red: 228, green: 128, @@ -363,14 +336,27 @@ "additiveBlending": true, "textures": "http://ericrius1.github.io/PartiArt/assets/star.png" }); + Script.setTimeout(function() { + Entities.editEntity(flash, { + isEmitting: false + }); + }, 100) - Script.setTimeout(function() { - Entities.editEntity(_this.flash, {parentID: _this.entityID}); - }, 500) + }, + preload: function(entityID) { + this.entityID = entityID; + this.laser = Overlays.addOverlay("line3d", { + start: ZERO_VECTOR, + end: ZERO_VECTOR, + color: COLORS.RED, + alpha: 1, + visible: true, + lineWidth: 2 + }); }, }; // entity scripts always need to return a newly constructed object of our type return new Pistol(); -}); +}); \ No newline at end of file