diff --git a/examples/particles.js b/examples/particles.js index deb6228fff..fc1a936a72 100644 --- a/examples/particles.js +++ b/examples/particles.js @@ -39,10 +39,11 @@ this.entity = Entities.addEntity({ type: "ParticleEffect", animationSettings: animationSettings, position: spawnPoint, - textures: "http://www.hyperlogic.org/images/particle.png", - emitRate: emitRate, - emitStrength: emitStrength, - emitDirection: emitDirection, + dimensions: {x: 2, y: 2, z: 2}, + emitVelocity: {x: 0, y: 5, z: 0}, + velocitySpread: {x: 2, y: 0, z: 2}, + emitAcceleration: {x: 0, y: -9.8, z: 0}, + textures: "https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png", color: color, lifespan: 1.0, visible: true, diff --git a/interface/interface_da.ts b/interface/i18n/interface_da.ts similarity index 100% rename from interface/interface_da.ts rename to interface/i18n/interface_da.ts diff --git a/interface/interface_en.ts b/interface/i18n/interface_en.ts similarity index 100% rename from interface/interface_en.ts rename to interface/i18n/interface_en.ts diff --git a/interface/resources/fonts/fontawesome-webfont.ttf b/interface/resources/fonts/fontawesome-webfont.ttf index ed9372f8ea..d7994e1308 100644 Binary files a/interface/resources/fonts/fontawesome-webfont.ttf and b/interface/resources/fonts/fontawesome-webfont.ttf differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a8023761ec..3eb73b89c5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -104,6 +104,7 @@ #include #include "AudioClient.h" +#include "CrashHandler.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" #include "LODManager.h" @@ -189,6 +190,11 @@ static QTimer* billboardPacketTimer = NULL; static QTimer* checkFPStimer = NULL; static QTimer* idleTimer = NULL; +static const unsigned int TARGET_SIM_FRAMERATE = 60; +static const unsigned int THROTTLED_SIM_FRAMERATE = 15; +static const int TARGET_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / TARGET_SIM_FRAMERATE; +static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE; + const QString CHECK_VERSION_URL = "https://highfidelity.com/latestVersion.xml"; const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; @@ -257,6 +263,12 @@ bool setupEssentials(int& argc, char** argv) { // Set build version QCoreApplication::setApplicationVersion(BUILD_VERSION); + Setting::preInit(); + + CrashHandler::checkForAndHandleCrash(); + CrashHandler::writeRunningMarkerFiler(); + qAddPostRoutine(CrashHandler::deleteRunningMarkerFile); + DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); @@ -347,7 +359,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _trayIcon(new QSystemTrayIcon(_window)), _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()), - _isVSyncOn(true), _isThrottleFPSEnabled(true), _aboutToQuit(false), _notifiedPacketVersionMismatchThisDomain(false), @@ -745,8 +756,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : }); connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); - - setVSyncEnabled(); // make sure VSync is set properly at startup } void Application::aboutToQuit() { @@ -921,7 +930,7 @@ void Application::initializeGL() { // call our idle function whenever we can idleTimer = new QTimer(this); connect(idleTimer, SIGNAL(timeout()), SLOT(idle())); - idleTimer->start(0); + idleTimer->start(TARGET_SIM_FRAME_PERIOD_MS); _idleLoopStdev.reset(); if (_justStarted) { @@ -2034,38 +2043,38 @@ void Application::checkFPS() { } void Application::idle() { - PROFILE_RANGE(__FUNCTION__); - static SimpleAverage interIdleDurations; - - static uint64_t lastIdleStart{ 0 }; - static uint64_t lastIdleEnd{ 0 }; - uint64_t now = usecTimestampNow(); - uint64_t idleStartToStartDuration = now - lastIdleStart; - - if (lastIdleStart > 0 && idleStartToStartDuration > 0) { - _simsPerSecond.updateAverage((float)USECS_PER_SECOND / (float)idleStartToStartDuration); - } - - lastIdleStart = now; - - if (lastIdleEnd != 0) { - interIdleDurations.update(now - lastIdleEnd); - static uint64_t lastReportTime = now; - if ((now - lastReportTime) >= (USECS_PER_SECOND)) { - static QString LOGLINE("Average inter-idle time: %1 us for %2 samples"); - if (Menu::getInstance()->isOptionChecked(MenuOption::LogExtraTimings)) { - qCDebug(interfaceapp_timing) << LOGLINE.arg((int)interIdleDurations.getAverage()).arg(interIdleDurations.getCount()); - } - interIdleDurations.reset(); - lastReportTime = now; - } - } - - PerformanceTimer perfTimer("idle"); if (_aboutToQuit) { return; // bail early, nothing to do here. } + // depending on whether we're throttling or not. + // Once rendering is off on another thread we should be able to have Application::idle run at start(0) in + // perpetuity and not expect events to get backed up. + bool isThrottled = getActiveDisplayPlugin()->isThrottled(); + // Only run simulation code if more than the targetFramePeriod have passed since last time we ran + // This attempts to lock the simulation at 60 updates per second, regardless of framerate + float timeSinceLastUpdateUs = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC; + float secondsSinceLastUpdate = timeSinceLastUpdateUs / USECS_PER_SECOND; + + if (isThrottled && (timeSinceLastUpdateUs / USECS_PER_MSEC) < THROTTLED_SIM_FRAME_PERIOD_MS) { + return; // bail early, we're throttled and not enough time has elapsed + } + + _lastTimeUpdated.start(); + + + { + PROFILE_RANGE(__FUNCTION__); + uint64_t now = usecTimestampNow(); + static uint64_t lastIdleStart{ now }; + uint64_t idleStartToStartDuration = now - lastIdleStart; + if (idleStartToStartDuration != 0) { + _simsPerSecond.updateAverage((float)USECS_PER_SECOND / (float)idleStartToStartDuration); + } + lastIdleStart = now; + } + + PerformanceTimer perfTimer("idle"); // Drop focus from _keyboardFocusedItem if no keyboard messages for 30 seconds if (!_keyboardFocusedItem.isInvalidID()) { const quint64 LOSE_FOCUS_AFTER_ELAPSED_TIME = 30 * USECS_PER_SECOND; // if idle for 30 seconds, drop focus @@ -2081,69 +2090,42 @@ void Application::idle() { bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "idle()"); - // Only run simulation code if more than the targetFramePeriod have passed since last time we ran - double targetFramePeriod = 0.0; - unsigned int targetFramerate = getRenderTargetFramerate(); - if (targetFramerate > 0) { - targetFramePeriod = 1000.0 / targetFramerate; + { + PerformanceTimer perfTimer("update"); + PerformanceWarning warn(showWarnings, "Application::idle()... update()"); + static const float BIGGEST_DELTA_TIME_SECS = 0.25f; + update(glm::clamp(secondsSinceLastUpdate, 0.0f, BIGGEST_DELTA_TIME_SECS)); } - double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0; - if (timeSinceLastUpdate > targetFramePeriod) { - _lastTimeUpdated.start(); - { - PerformanceTimer perfTimer("update"); - PerformanceWarning warn(showWarnings, "Application::idle()... update()"); - const float BIGGEST_DELTA_TIME_SECS = 0.25f; - PROFILE_RANGE(__FUNCTION__ "/idleUpdate"); - update(glm::clamp((float)timeSinceLastUpdate / 1000.0f, 0.0f, BIGGEST_DELTA_TIME_SECS)); - } - { - PerformanceTimer perfTimer("updateGL"); - PerformanceWarning warn(showWarnings, "Application::idle()... updateGL()"); - getActiveDisplayPlugin()->idle(); - auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); - foreach(auto inputPlugin, inputPlugins) { - QString name = inputPlugin->getName(); - QAction* action = Menu::getInstance()->getActionForOption(name); - if (action && action->isChecked()) { - inputPlugin->idle(); - } + { + PerformanceTimer perfTimer("pluginIdle"); + PerformanceWarning warn(showWarnings, "Application::idle()... pluginIdle()"); + getActiveDisplayPlugin()->idle(); + auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); + foreach(auto inputPlugin, inputPlugins) { + QString name = inputPlugin->getName(); + QAction* action = Menu::getInstance()->getActionForOption(name); + if (action && action->isChecked()) { + inputPlugin->idle(); } } - { - PerformanceTimer perfTimer("rest"); - PerformanceWarning warn(showWarnings, "Application::idle()... rest of it"); - _idleLoopStdev.addValue(timeSinceLastUpdate); + } + { + PerformanceTimer perfTimer("rest"); + PerformanceWarning warn(showWarnings, "Application::idle()... rest of it"); + _idleLoopStdev.addValue(secondsSinceLastUpdate); - // Record standard deviation and reset counter if needed - const int STDEV_SAMPLES = 500; - if (_idleLoopStdev.getSamples() > STDEV_SAMPLES) { - _idleLoopMeasuredJitter = _idleLoopStdev.getStDev(); - _idleLoopStdev.reset(); - } - } - - float secondsSinceLastUpdate = (float)timeSinceLastUpdate / 1000.0f; - _overlayConductor.update(secondsSinceLastUpdate); - - // depending on whether we're throttling or not. - // Once rendering is off on another thread we should be able to have Application::idle run at start(0) in - // perpetuity and not expect events to get backed up. - - bool isThrottled = getActiveDisplayPlugin()->isThrottled(); - static const int THROTTLED_IDLE_TIMER_DELAY = MSECS_PER_SECOND / 15; - static const int IDLE_TIMER_DELAY_MS = 2; - int desiredInterval = isThrottled ? THROTTLED_IDLE_TIMER_DELAY : IDLE_TIMER_DELAY_MS; - //qDebug() << "isThrottled:" << isThrottled << "desiredInterval:" << desiredInterval; - - if (idleTimer->interval() != desiredInterval) { - idleTimer->start(desiredInterval); + // Record standard deviation and reset counter if needed + const int STDEV_SAMPLES = 500; + if (_idleLoopStdev.getSamples() > STDEV_SAMPLES) { + _idleLoopMeasuredJitter = _idleLoopStdev.getStDev(); + _idleLoopStdev.reset(); } } + + _overlayConductor.update(secondsSinceLastUpdate); // check for any requested background downloads. emit checkBackgroundDownloads(); - lastIdleEnd = usecTimestampNow(); } float Application::getAverageSimsPerSecond() { @@ -4485,90 +4467,10 @@ void Application::takeSnapshot() { } -void Application::setVSyncEnabled() { - _glWidget->makeCurrent(); -#if defined(Q_OS_WIN) - bool vsyncOn = Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn); - if (wglewGetExtension("WGL_EXT_swap_control")) { - wglSwapIntervalEXT(vsyncOn); - int swapInterval = wglGetSwapIntervalEXT(); - qCDebug(interfaceapp, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } else { - qCDebug(interfaceapp, "V-Sync is FORCED ON on this system\n"); - } -#elif defined(Q_OS_LINUX) - // TODO: write the poper code for linux - /* - if (glQueryExtension.... ("GLX_EXT_swap_control")) { - glxSwapIntervalEXT(vsyncOn); - int swapInterval = xglGetSwapIntervalEXT(); - _isVSyncOn = swapInterval; - qCDebug(interfaceapp, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } else { - qCDebug(interfaceapp, "V-Sync is FORCED ON on this system\n"); - } - */ -#else - qCDebug(interfaceapp, "V-Sync is FORCED ON on this system\n"); -#endif - _offscreenContext->makeCurrent(); -} - void Application::setThrottleFPSEnabled() { _isThrottleFPSEnabled = Menu::getInstance()->isOptionChecked(MenuOption::ThrottleFPSIfNotFocus); } -bool Application::isVSyncOn() const { -#if defined(Q_OS_WIN) - if (wglewGetExtension("WGL_EXT_swap_control")) { - int swapInterval = wglGetSwapIntervalEXT(); - return (swapInterval > 0); - } -#elif defined(Q_OS_LINUX) - // TODO: write the poper code for linux - /* - if (glQueryExtension.... ("GLX_EXT_swap_control")) { - int swapInterval = xglGetSwapIntervalEXT(); - return (swapInterval > 0); - } else { - return true; - } - */ -#endif - return true; -} - -bool Application::isVSyncEditable() const { -#if defined(Q_OS_WIN) - if (wglewGetExtension("WGL_EXT_swap_control")) { - return true; - } -#elif defined(Q_OS_LINUX) - // TODO: write the poper code for linux - /* - if (glQueryExtension.... ("GLX_EXT_swap_control")) { - return true; - } - */ -#endif - return false; -} - -unsigned int Application::getRenderTargetFramerate() const { - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateUnlimited)) { - return 0; - } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) { - return 60; - } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) { - return 50; - } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) { - return 40; - } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) { - return 30; - } - return 0; -} - float Application::getRenderResolutionScale() const { if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) { return 1.0f; @@ -5150,3 +5052,9 @@ void Application::emulateMouse(Hand* hand, float click, float shift, int index) _oldHandLeftClick[index] = false; } } + +void Application::crashApplication() { + QObject* object = nullptr; + bool value = object->isWindowType(); + qCDebug(interfaceapp) << "Intentionally crashed Interface"; +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 335f474253..336f9b4cbe 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -301,9 +301,6 @@ public: float getRenderResolutionScale() const; int getRenderAmbientLight() const; - unsigned int getRenderTargetFramerate() const; - bool isVSyncOn() const; - bool isVSyncEditable() const; bool isAboutToQuit() const { return _aboutToQuit; } // the isHMDmode is true whenever we use the interface from an HMD and not a standard flat display @@ -418,8 +415,6 @@ public slots: void domainSettingsReceived(const QJsonObject& domainSettingsObject); - void setVSyncEnabled(); - void setThrottleFPSEnabled(); bool isThrottleFPSEnabled() { return _isThrottleFPSEnabled; } @@ -445,6 +440,8 @@ public slots: void reloadResourceCaches(); + void crashApplication(); + private slots: void clearDomainOctreeDetails(); void checkFPS(); @@ -636,7 +633,6 @@ private: quint64 _lastNackTime; quint64 _lastSendDownstreamAudioStats; - bool _isVSyncOn; bool _isThrottleFPSEnabled; bool _aboutToQuit; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp new file mode 100644 index 0000000000..ce5facb580 --- /dev/null +++ b/interface/src/CrashHandler.cpp @@ -0,0 +1,183 @@ +// +// CrashHandler.cpp +// interface/src +// +// Created by David Rowe on 24 Aug 2015. +// 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 "CrashHandler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DataServerAccountInfo.h" +#include "Menu.h" + +Q_DECLARE_METATYPE(DataServerAccountInfo) + +static const QString RUNNING_MARKER_FILENAME = "Interface.running"; + +void CrashHandler::checkForAndHandleCrash() { + QFile runningMarkerFile(runningMarkerFilePath()); + if (runningMarkerFile.exists()) { + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings settings; + settings.beginGroup("Developer"); + QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions); + settings.endGroup(); + if (!displayCrashOptions.isValid() // Option does not exist in Interface.ini so assume default behavior. + || displayCrashOptions.toBool()) { + Action action = promptUserForAction(); + if (action != DO_NOTHING) { + handleCrash(action); + } + } + } +} + +CrashHandler::Action CrashHandler::promptUserForAction() { + QDialog crashDialog; + crashDialog.setWindowTitle("Interface Crashed Last Run"); + + QVBoxLayout* layout = new QVBoxLayout; + + QLabel* label = new QLabel("If you are having trouble starting would you like to reset your settings?"); + layout->addWidget(label); + + QRadioButton* option1 = new QRadioButton("Reset all my settings"); + QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info."); + QRadioButton* option3 = new QRadioButton("Continue with my current settings"); + option3->setChecked(true); + layout->addWidget(option1); + layout->addWidget(option2); + layout->addWidget(option3); + layout->addSpacing(12); + layout->addStretch(); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); + layout->addWidget(buttons); + crashDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + + crashDialog.setLayout(layout); + + int result = crashDialog.exec(); + + if (result == QDialog::Accepted) { + if (option1->isChecked()) { + return CrashHandler::DELETE_INTERFACE_INI; + } + if (option2->isChecked()) { + return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO; + } + } + + // Dialog cancelled or "do nothing" option chosen + return CrashHandler::DO_NOTHING; +} + +void CrashHandler::handleCrash(CrashHandler::Action action) { + if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + // CrashHandler::DO_NOTHING or unexpected value + return; + } + + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings settings; + const QString ADDRESS_MANAGER_GROUP = "AddressManager"; + const QString ADDRESS_KEY = "address"; + const QString AVATAR_GROUP = "Avatar"; + const QString DISPLAY_NAME_KEY = "displayName"; + const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; + const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; + const QString ACCOUNTS_GROUP = "accounts"; + QString displayName; + QUrl fullAvatarURL; + QString fullAvatarModelName; + QUrl address; + QMap accounts; + + if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + // Read login and avatar info + + qRegisterMetaType("DataServerAccountInfo"); + qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); + + // Location and orientation + settings.beginGroup(ADDRESS_MANAGER_GROUP); + address = settings.value(ADDRESS_KEY).toUrl(); + settings.endGroup(); + + // Display name and avatar + settings.beginGroup(AVATAR_GROUP); + displayName = settings.value(DISPLAY_NAME_KEY).toString(); + fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); + fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); + settings.endGroup(); + + // Accounts + settings.beginGroup(ACCOUNTS_GROUP); + foreach(const QString& key, settings.allKeys()) { + accounts.insert(key, settings.value(key).value()); + } + settings.endGroup(); + } + + // Delete Interface.ini + QFile settingsFile(settings.fileName()); + if (settingsFile.exists()) { + settingsFile.remove(); + } + + if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) { + // Write login and avatar info + + // Location and orientation + settings.beginGroup(ADDRESS_MANAGER_GROUP); + settings.setValue(ADDRESS_KEY, address); + settings.endGroup(); + + // Display name and avatar + settings.beginGroup(AVATAR_GROUP); + settings.setValue(DISPLAY_NAME_KEY, displayName); + settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); + settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); + settings.endGroup(); + + // Accounts + settings.beginGroup(ACCOUNTS_GROUP); + foreach(const QString& key, accounts.keys()) { + settings.setValue(key, QVariant::fromValue(accounts.value(key))); + } + settings.endGroup(); + } +} + +void CrashHandler::writeRunningMarkerFiler() { + QFile runningMarkerFile(runningMarkerFilePath()); + if (!runningMarkerFile.exists()) { + runningMarkerFile.open(QIODevice::WriteOnly); + runningMarkerFile.close(); + } +} +void CrashHandler::deleteRunningMarkerFile() { + QFile runningMarkerFile(runningMarkerFilePath()); + if (runningMarkerFile.exists()) { + runningMarkerFile.remove(); + } +} + +const QString CrashHandler::runningMarkerFilePath() { + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + RUNNING_MARKER_FILENAME; +} diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h new file mode 100644 index 0000000000..fc754cf1ac --- /dev/null +++ b/interface/src/CrashHandler.h @@ -0,0 +1,38 @@ +// +// CrashHandler.h +// interface/src +// +// Created by David Rowe on 24 Aug 2015. +// 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_CrashHandler_h +#define hifi_CrashHandler_h + +#include + +class CrashHandler { + +public: + static void checkForAndHandleCrash(); + + static void writeRunningMarkerFiler(); + static void deleteRunningMarkerFile(); + +private: + enum Action { + DELETE_INTERFACE_INI, + RETAIN_LOGIN_AND_AVATAR_INFO, + DO_NOTHING + }; + + static Action promptUserForAction(); + static void handleCrash(Action action); + + static const QString runningMarkerFilePath(); +}; + +#endif // hifi_CrashHandler_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9d47df1c73..17e64b5765 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -222,7 +222,7 @@ Menu::Menu() { addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); - MenuWrapper* displayMenu = addMenu(DisplayPlugin::MENU_PATH); + MenuWrapper* displayMenu = addMenu(DisplayPlugin::MENU_PATH()); { MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); @@ -333,25 +333,8 @@ Menu::Menu() { ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight8, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight9, 0, false)); - { - MenuWrapper* framerateMenu = renderOptionsMenu->addMenu(MenuOption::RenderTargetFramerate); - QActionGroup* framerateGroup = new QActionGroup(framerateMenu); - framerateGroup->setExclusive(true); - framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerateUnlimited, 0, true)); - framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate60, 0, false)); - framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate50, 0, false)); - framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate40, 0, false)); - framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate30, 0, false)); - -#if defined(Q_OS_MAC) -#else - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderTargetFramerateVSyncOn, 0, true, - qApp, SLOT(setVSyncEnabled())); -#endif - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true, + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true, qApp, SLOT(setThrottleFPSEnabled())); - } - MenuWrapper* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu); @@ -577,6 +560,9 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls); + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); + addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication())); + MenuWrapper* helpMenu = addMenu("Help"); addActionToQMenuAndActionHash(helpMenu, MenuOption::EditEntitiesHelp, 0, qApp, SLOT(showEditEntitiesHelp())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4bd1e7f664..9bcfd1f7aa 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -164,6 +164,7 @@ namespace MenuOption { const QString CopyAddress = "Copy Address to Clipboard"; const QString CopyPath = "Copy Path to Clipboard"; const QString CoupleEyelids = "Couple Eyelids"; + const QString CrashInterface = "Crash Interface"; const QString DebugAmbientOcclusion = "Debug Ambient Occlusion"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; @@ -172,6 +173,7 @@ namespace MenuOption { const QString DisableLightEntities = "Disable Light Entities"; const QString DisableNackPackets = "Disable Entity NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; + const QString DisplayCrashOptions = "Display Crash Options"; const QString DisplayHands = "Show Hand Info"; const QString DisplayHandTargets = "Show Hand Targets"; const QString DisplayModelBounds = "Display Model Bounds"; @@ -238,13 +240,6 @@ namespace MenuOption { const QString RenderLookAtTargets = "Show Look-at Targets"; const QString RenderLookAtVectors = "Show Look-at Vectors"; const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes"; - const QString RenderTargetFramerate = "Framerate"; - const QString RenderTargetFramerateUnlimited = "Unlimited"; - const QString RenderTargetFramerate60 = "60"; - const QString RenderTargetFramerate50 = "50"; - const QString RenderTargetFramerate40 = "40"; - const QString RenderTargetFramerate30 = "30"; - const QString RenderTargetFramerateVSyncOn = "V-Sync On"; const QString RenderResolution = "Scale Resolution"; const QString RenderResolutionOne = "1"; const QString RenderResolutionTwoThird = "2/3"; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index e591034fb5..1de8f9224c 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -103,7 +103,7 @@ int main(int argc, const char* argv[]) { Application app(argc, const_cast(argv), startupTime); QTranslator translator; - translator.load("interface_en"); + translator.load("i18n/interface_en"); app.installTranslator(&translator); qCDebug(interfaceapp, "Created QT Application."); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index da8deefa74..914d30d58a 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -7,21 +7,35 @@ // #include "Basic2DWindowOpenGLDisplayPlugin.h" +#include + +#include +#include +#include + #include -#include -#include const QString Basic2DWindowOpenGLDisplayPlugin::NAME("2D Display"); static const QString FULLSCREEN = "Fullscreen"; +static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; +static const QString FRAMERATE_UNLIMITED = "Unlimited"; +static const QString FRAMERATE_60 = "60"; +static const QString FRAMERATE_50 = "50"; +static const QString FRAMERATE_40 = "40"; +static const QString FRAMERATE_30 = "30"; +static const QString VSYNC_ON = "V-Sync On"; const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { return NAME; } +std::vector _framerateActions; +QAction* _vsyncAction{ nullptr }; + void Basic2DWindowOpenGLDisplayPlugin::activate() { - CONTAINER->addMenu(MENU_PATH); - CONTAINER->addMenuItem(MENU_PATH, FULLSCREEN, + _framerateActions.clear(); + CONTAINER->addMenuItem(MENU_PATH(), FULLSCREEN, [this](bool clicked) { if (clicked) { CONTAINER->setFullscreen(getFullscreenTarget()); @@ -29,18 +43,65 @@ void Basic2DWindowOpenGLDisplayPlugin::activate() { CONTAINER->unsetFullscreen(); } }, true, false); + CONTAINER->addMenu(FRAMERATE); + _framerateActions.push_back( + CONTAINER->addMenuItem(FRAMERATE, FRAMERATE_UNLIMITED, + [this](bool) { updateFramerate(); }, true, true, FRAMERATE)); + _framerateActions.push_back( + CONTAINER->addMenuItem(FRAMERATE, FRAMERATE_60, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + _framerateActions.push_back( + CONTAINER->addMenuItem(FRAMERATE, FRAMERATE_50, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + _framerateActions.push_back( + CONTAINER->addMenuItem(FRAMERATE, FRAMERATE_40, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + _framerateActions.push_back( + CONTAINER->addMenuItem(FRAMERATE, FRAMERATE_30, + [this](bool) { updateFramerate(); }, true, false, FRAMERATE)); + WindowOpenGLDisplayPlugin::activate(); + + // Vsync detection happens in the parent class activate, so we need to check after that + if (_vsyncSupported) { + _vsyncAction = CONTAINER->addMenuItem(MENU_PATH(), VSYNC_ON, [this](bool) {}, true, true); + } else { + _vsyncAction = nullptr; + } + + updateFramerate(); } void Basic2DWindowOpenGLDisplayPlugin::deactivate() { WindowOpenGLDisplayPlugin::deactivate(); } -int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval(bool isThrottled) const { - static const int THROTTLED_PAINT_TIMER_DELAY = MSECS_PER_SECOND / 15; - static const int PAINT_TIMER_DELAY_MS = 1; +void Basic2DWindowOpenGLDisplayPlugin::display(GLuint sceneTexture, const glm::uvec2& sceneSize) { + if (_vsyncAction) { + bool wantVsync = _vsyncAction->isChecked(); + bool vsyncEnabed = isVsyncEnabled(); + if (vsyncEnabed ^ wantVsync) { + enableVsync(wantVsync); + } + } - return isThrottled ? THROTTLED_PAINT_TIMER_DELAY : PAINT_TIMER_DELAY_MS; + WindowOpenGLDisplayPlugin::display(sceneTexture, sceneSize); +} + + +int Basic2DWindowOpenGLDisplayPlugin::getDesiredInterval() const { + static const int THROTTLED_PAINT_TIMER_DELAY_MS = MSECS_PER_SECOND / 15; + static const int ULIMIITED_PAINT_TIMER_DELAY_MS = 1; + int result = ULIMIITED_PAINT_TIMER_DELAY_MS; + if (_isThrottled) { + result = THROTTLED_PAINT_TIMER_DELAY_MS; + } + if (0 != _framerateTarget) { + result = MSECS_PER_SECOND / _framerateTarget; + } + + qDebug() << "New interval " << result; + return result; } bool Basic2DWindowOpenGLDisplayPlugin::isThrottled() const { @@ -49,14 +110,42 @@ bool Basic2DWindowOpenGLDisplayPlugin::isThrottled() const { bool shouldThrottle = (!CONTAINER->isForeground() && CONTAINER->isOptionChecked(ThrottleFPSIfNotFocus)); if (_isThrottled != shouldThrottle) { - int desiredInterval = getDesiredInterval(shouldThrottle); - _timer.start(desiredInterval); _isThrottled = shouldThrottle; + _timer.start(getDesiredInterval()); } return shouldThrottle; } + +void Basic2DWindowOpenGLDisplayPlugin::updateFramerate() { + QAction* checkedFramerate{ nullptr }; + foreach(auto action, _framerateActions) { + if (action->isChecked()) { + checkedFramerate = action; + break; + } + } + + _framerateTarget = 0; + if (checkedFramerate) { + QString actionText = checkedFramerate->text(); + if (FRAMERATE_60 == actionText) { + _framerateTarget = 60; + } else if (FRAMERATE_50 == actionText) { + _framerateTarget = 50; + } else if (FRAMERATE_40 == actionText) { + _framerateTarget = 40; + } else if (FRAMERATE_30 == actionText) { + _framerateTarget = 30; + } + } + + int newInterval = getDesiredInterval(); + qDebug() << newInterval; + _timer.start(getDesiredInterval()); +} + // FIXME target the screen the window is currently on QScreen* Basic2DWindowOpenGLDisplayPlugin::getFullscreenTarget() { return qApp->primaryScreen(); diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 64edfe3600..f4655ab79f 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -14,19 +14,23 @@ class Basic2DWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { Q_OBJECT public: + virtual const QString & getName() const override; + virtual void activate() override; virtual void deactivate() override; - virtual const QString & getName() const override; + virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; virtual bool isThrottled() const override; protected: - int getDesiredInterval(bool isThrottled) const; + int getDesiredInterval() const; mutable bool _isThrottled = false; private: + void updateFramerate(); static const QString NAME; QScreen* getFullscreenTarget(); + uint32_t _framerateTarget{ 0 }; int _fullscreenTarget{ -1 }; }; diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp index 5840b3cbba..598e78e500 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -18,7 +18,10 @@ #include "oculus/OculusDisplayPlugin.h" #include "oculus/OculusLegacyDisplayPlugin.h" -const QString DisplayPlugin::MENU_PATH{ "Display" }; +const QString& DisplayPlugin::MENU_PATH() { + static const QString value = "Display"; + return value; +} // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class DisplayPluginList getDisplayPlugins() { diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index 0696cc6229..a9220d68f6 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -120,7 +120,7 @@ public: virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } - static const QString MENU_PATH; + static const QString& MENU_PATH(); signals: void recommendedFramebufferSizeChanged(const QSize & size); void requestRender(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 0409899739..eb38e1bf4f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -38,6 +38,11 @@ void OpenGLDisplayPlugin::finishFrame() { void OpenGLDisplayPlugin::customizeContext() { using namespace oglplus; + // TODO: write the poper code for linux +#if defined(Q_OS_WIN) + _vsyncSupported = wglewGetExtension("WGL_EXT_swap_control"); +#endif + Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); Context::Disable(Capability::Blend); Context::Disable(Capability::DepthTest); @@ -46,6 +51,8 @@ void OpenGLDisplayPlugin::customizeContext() { _program = loadDefaultShader(); _plane = loadPlane(_program); + + enableVsync(); } void OpenGLDisplayPlugin::activate() { @@ -114,4 +121,24 @@ void OpenGLDisplayPlugin::display( void OpenGLDisplayPlugin::drawUnitQuad() { _program->Bind(); _plane->Draw(); +} + +void OpenGLDisplayPlugin::enableVsync(bool enable) { + if (!_vsyncSupported) { + return; + } +#ifdef Q_OS_WIN + wglSwapIntervalEXT(enable ? 1 : 0); +#endif +} + +bool OpenGLDisplayPlugin::isVsyncEnabled() { + if (!_vsyncSupported) { + return true; + } +#ifdef Q_OS_WIN + return wglGetSwapIntervalEXT() != 0; +#else + return true; +#endif } \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 3152500232..0dc94b72f5 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -38,9 +38,13 @@ protected: virtual void doneCurrent() = 0; virtual void swapBuffers() = 0; + virtual bool isVsyncEnabled(); + virtual void enableVsync(bool enable = true); + mutable QTimer _timer; ProgramPtr _program; ShapeWrapperPtr _plane; + bool _vsyncSupported{ false }; }; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index df691f06f3..017977bf69 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -69,7 +69,7 @@ void StereoDisplayPlugin::activate() { if (screen == qApp->primaryScreen()) { checked = true; } - auto action = CONTAINER->addMenuItem(MENU_PATH, name, + auto action = CONTAINER->addMenuItem(MENU_PATH(), name, [this](bool clicked) { updateScreen(); }, true, checked, "Screens"); _screenActions[i] = action; } diff --git a/libraries/embedded-webserver/src/HTTPSManager.cpp b/libraries/embedded-webserver/src/HTTPSManager.cpp index 94e1a35e20..a745d7605e 100644 --- a/libraries/embedded-webserver/src/HTTPSManager.cpp +++ b/libraries/embedded-webserver/src/HTTPSManager.cpp @@ -30,6 +30,7 @@ void HTTPSManager::incomingConnection(qintptr socketDescriptor) { sslSocket->setLocalCertificate(_certificate); sslSocket->setPrivateKey(_privateKey); + sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone); if (sslSocket->setSocketDescriptor(socketDescriptor)) { new HTTPSConnection(sslSocket, this); @@ -48,4 +49,4 @@ bool HTTPSManager::handleHTTPSRequest(HTTPSConnection* connection, const QUrl& u bool HTTPSManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) { return _sslRequestHandler && _sslRequestHandler->handleHTTPSRequest(reinterpret_cast(connection), url); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index d9d4ec4a5b..f683083ed1 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -247,7 +247,6 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { glm::vec3 pos = _transform.getTranslation(); Transform t; t.setRotation(rot); - t.setTranslation(pos); payload.setModelTransform(t); // transform _particleMinBound and _particleMaxBound corners into world coords @@ -285,7 +284,7 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { payload.setPipeline(_untexturedPipeline); } }); - + _scene->enqueuePendingChanges(pendingChanges); } @@ -295,7 +294,7 @@ void RenderableParticleEffectEntityItem::createPipelines() { state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, - gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, + gpu::State::ONE, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); auto vertShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(untextured_particle_vert))); auto fragShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(untextured_particle_frag))); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 0ffcc00ead..9fa6ccac65 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -610,6 +610,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE) { // pack SimulationOwner and terse update properties near each other diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index d8b555cb59..c6c02f248c 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -84,9 +84,10 @@ CONSTRUCT_PROPERTY(shapeType, SHAPE_TYPE_NONE), CONSTRUCT_PROPERTY(maxParticles, ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES), CONSTRUCT_PROPERTY(lifespan, ParticleEffectEntityItem::DEFAULT_LIFESPAN), CONSTRUCT_PROPERTY(emitRate, ParticleEffectEntityItem::DEFAULT_EMIT_RATE), -CONSTRUCT_PROPERTY(emitDirection, ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION), -CONSTRUCT_PROPERTY(emitStrength, ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH), -CONSTRUCT_PROPERTY(localGravity, ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY), +CONSTRUCT_PROPERTY(emitVelocity, ParticleEffectEntityItem::DEFAULT_EMIT_VELOCITY), +CONSTRUCT_PROPERTY(velocitySpread, ParticleEffectEntityItem::DEFAULT_VELOCITY_SPREAD), +CONSTRUCT_PROPERTY(emitAcceleration, ParticleEffectEntityItem::DEFAULT_EMIT_ACCELERATION), +CONSTRUCT_PROPERTY(accelerationSpread, ParticleEffectEntityItem::DEFAULT_ACCELERATION_SPREAD), CONSTRUCT_PROPERTY(particleRadius, ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS), CONSTRUCT_PROPERTY(marketplaceID, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID), CONSTRUCT_PROPERTY(keyLightColor, ZoneEntityItem::DEFAULT_KEYLIGHT_COLOR), @@ -349,9 +350,10 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles); CHECK_PROPERTY_CHANGE(PROP_LIFESPAN, lifespan); CHECK_PROPERTY_CHANGE(PROP_EMIT_RATE, emitRate); - CHECK_PROPERTY_CHANGE(PROP_EMIT_DIRECTION, emitDirection); - CHECK_PROPERTY_CHANGE(PROP_EMIT_STRENGTH, emitStrength); - CHECK_PROPERTY_CHANGE(PROP_LOCAL_GRAVITY, localGravity); + CHECK_PROPERTY_CHANGE(PROP_EMIT_VELOCITY, emitVelocity); + CHECK_PROPERTY_CHANGE(PROP_VELOCITY_SPREAD, velocitySpread); + CHECK_PROPERTY_CHANGE(PROP_EMIT_ACCELERATION, emitAcceleration); + CHECK_PROPERTY_CHANGE(PROP_ACCELERATION_SPREAD, accelerationSpread); CHECK_PROPERTY_CHANGE(PROP_PARTICLE_RADIUS, particleRadius); CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID); CHECK_PROPERTY_CHANGE(PROP_NAME, name); @@ -451,9 +453,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(maxParticles); COPY_PROPERTY_TO_QSCRIPTVALUE(lifespan); COPY_PROPERTY_TO_QSCRIPTVALUE(emitRate); - COPY_PROPERTY_TO_QSCRIPTVALUE(emitDirection); - COPY_PROPERTY_TO_QSCRIPTVALUE(emitStrength); - COPY_PROPERTY_TO_QSCRIPTVALUE(localGravity); + COPY_PROPERTY_TO_QSCRIPTVALUE(emitVelocity); + COPY_PROPERTY_TO_QSCRIPTVALUE(velocitySpread); + COPY_PROPERTY_TO_QSCRIPTVALUE(emitAcceleration); + COPY_PROPERTY_TO_QSCRIPTVALUE(accelerationSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(particleRadius); COPY_PROPERTY_TO_QSCRIPTVALUE(marketplaceID); COPY_PROPERTY_TO_QSCRIPTVALUE(name); @@ -571,9 +574,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(maxParticles, float, setMaxParticles); COPY_PROPERTY_FROM_QSCRIPTVALUE(lifespan, float, setLifespan); COPY_PROPERTY_FROM_QSCRIPTVALUE(emitRate, float, setEmitRate); - COPY_PROPERTY_FROM_QSCRIPTVALUE(emitDirection, glmVec3, setEmitDirection); - COPY_PROPERTY_FROM_QSCRIPTVALUE(emitStrength, float, setEmitStrength); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localGravity, float, setLocalGravity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emitVelocity, glmVec3, setEmitVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(velocitySpread, glmVec3, setVelocitySpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emitAcceleration, glmVec3, setEmitAcceleration); + COPY_PROPERTY_FROM_QSCRIPTVALUE(accelerationSpread, glmVec3, setAccelerationSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(particleRadius, float, setParticleRadius); COPY_PROPERTY_FROM_QSCRIPTVALUE(marketplaceID, QString, setMarketplaceID); COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); @@ -812,10 +816,12 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType::Value command, Ent APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, properties.getMaxParticles()); APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, properties.getLifespan()); APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, properties.getEmitRate()); - APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, properties.getEmitDirection()); - APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, properties.getEmitStrength()); - APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, properties.getLocalGravity()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_VELOCITY, properties.getEmitVelocity()); + APPEND_ENTITY_PROPERTY(PROP_VELOCITY_SPREAD, properties.getVelocitySpread()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, properties.getEmitAcceleration()); + APPEND_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, properties.getAccelerationSpread()); APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, properties.getParticleRadius()); + } if (properties.getType() == EntityTypes::Zone) { @@ -1080,9 +1086,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, float, setMaxParticles); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFESPAN, float, setLifespan); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RATE, float, setEmitRate); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_STRENGTH, float, setEmitStrength); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCAL_GRAVITY, float, setLocalGravity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_VELOCITY, glm::vec3, setEmitVelocity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY_SPREAD, glm::vec3, setVelocitySpread); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_ACCELERATION, glm::vec3, setEmitAcceleration); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACCELERATION_SPREAD, glm::vec3, setAccelerationSpread); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius); } @@ -1211,9 +1218,10 @@ void EntityItemProperties::markAllChanged() { _maxParticlesChanged = true; _lifespanChanged = true; _emitRateChanged = true; - _emitDirectionChanged = true; - _emitStrengthChanged = true; - _localGravityChanged = true; + _emitVelocityChanged = true; + _velocitySpreadChanged = true; + _emitAccelerationChanged = true; + _accelerationSpreadChanged = true; _particleRadiusChanged = true; _marketplaceIDChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 31c2ffad1e..6d95faa9b1 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -131,9 +131,10 @@ public: DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32); DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float); DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float); - DEFINE_PROPERTY_REF(PROP_EMIT_DIRECTION, EmitDirection, emitDirection, glm::vec3); - DEFINE_PROPERTY(PROP_EMIT_STRENGTH, EmitStrength, emitStrength, float); - DEFINE_PROPERTY(PROP_LOCAL_GRAVITY, LocalGravity, localGravity, float); + DEFINE_PROPERTY_REF(PROP_EMIT_VELOCITY, EmitVelocity, emitVelocity, glm::vec3); + DEFINE_PROPERTY_REF(PROP_VELOCITY_SPREAD, VelocitySpread, velocitySpread, glm::vec3); + DEFINE_PROPERTY(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, glm::vec3); + DEFINE_PROPERTY(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, glm::vec3); DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float); DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString); DEFINE_PROPERTY_REF(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); @@ -311,9 +312,9 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, MaxParticles, maxParticles, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifespan, lifespan, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitRate, emitRate, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitDirection, emitDirection, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitStrength, emitStrength, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, LocalGravity, localGravity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitVelocity, emitVelocity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EmitAcceleration, emitAcceleration, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, AccelerationSpread, accelerationSpread, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, MarketplaceID, marketplaceID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index e439710695..abb8241d8f 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -96,9 +96,9 @@ enum EntityPropertyList { PROP_MAX_PARTICLES, PROP_LIFESPAN, PROP_EMIT_RATE, - PROP_EMIT_DIRECTION, + PROP_EMIT_VELOCITY, PROP_EMIT_STRENGTH, - PROP_LOCAL_GRAVITY, + PROP_EMIT_ACCELERATION, PROP_PARTICLE_RADIUS, PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities @@ -134,7 +134,10 @@ enum EntityPropertyList { // Used by PolyLine entity PROP_NORMALS, PROP_STROKE_WIDTHS, - + + // used by particles + PROP_VELOCITY_SPREAD, + PROP_ACCELERATION_SPREAD, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line @@ -168,9 +171,9 @@ enum EntityPropertyList { PROP_ATMOSPHERE_CENTER = PROP_MAX_PARTICLES, PROP_ATMOSPHERE_INNER_RADIUS = PROP_LIFESPAN, PROP_ATMOSPHERE_OUTER_RADIUS = PROP_EMIT_RATE, - PROP_ATMOSPHERE_MIE_SCATTERING = PROP_EMIT_DIRECTION, + PROP_ATMOSPHERE_MIE_SCATTERING = PROP_EMIT_VELOCITY, PROP_ATMOSPHERE_RAYLEIGH_SCATTERING = PROP_EMIT_STRENGTH, - PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS = PROP_LOCAL_GRAVITY, + PROP_ATMOSPHERE_SCATTERING_WAVELENGTHS = PROP_EMIT_ACCELERATION, PROP_ATMOSPHERE_HAS_STARS = PROP_PARTICLE_RADIUS, PROP_BACKGROUND_MODE = PROP_MODEL_URL, PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 349e0c4d46..9e45efe88d 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -16,7 +16,6 @@ // - Just to get this out the door, I just did forward Euler integration. There are better ways. // - Gravity always points along the Y axis. Support an actual gravity vector. // - Add the ability to add arbitrary forces to the simulation. -// - Add controls for spread (which is currently hard-coded) and varying emission strength (not currently implemented). // - Add drag. // - Add some kind of support for collisions. // - There's no synchronization of the simulation across clients at all. In fact, it's using rand() under the hood, so @@ -50,9 +49,10 @@ const float ParticleEffectEntityItem::DEFAULT_ANIMATION_FPS = 30.0f; const quint32 ParticleEffectEntityItem::DEFAULT_MAX_PARTICLES = 1000; const float ParticleEffectEntityItem::DEFAULT_LIFESPAN = 3.0f; const float ParticleEffectEntityItem::DEFAULT_EMIT_RATE = 15.0f; -const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_DIRECTION(0.0f, 1.0f, 0.0f); -const float ParticleEffectEntityItem::DEFAULT_EMIT_STRENGTH = 25.0f; -const float ParticleEffectEntityItem::DEFAULT_LOCAL_GRAVITY = -9.8f; +const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_VELOCITY(0.0f, 5.0f, 0.0f); +const glm::vec3 ParticleEffectEntityItem::DEFAULT_VELOCITY_SPREAD(3.0f, 0.0f, 3.0f); +const glm::vec3 ParticleEffectEntityItem::DEFAULT_EMIT_ACCELERATION(0.0f, -9.8f, 0.0f); +const glm::vec3 ParticleEffectEntityItem::DEFAULT_ACCELERATION_SPREAD(0.0f, 0.0f, 0.0f); const float ParticleEffectEntityItem::DEFAULT_PARTICLE_RADIUS = 0.025f; const QString ParticleEffectEntityItem::DEFAULT_TEXTURES = ""; @@ -67,9 +67,10 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte _maxParticles(DEFAULT_MAX_PARTICLES), _lifespan(DEFAULT_LIFESPAN), _emitRate(DEFAULT_EMIT_RATE), - _emitDirection(DEFAULT_EMIT_DIRECTION), - _emitStrength(DEFAULT_EMIT_STRENGTH), - _localGravity(DEFAULT_LOCAL_GRAVITY), + _emitVelocity(DEFAULT_EMIT_VELOCITY), + _velocitySpread(DEFAULT_VELOCITY_SPREAD), + _emitAcceleration(DEFAULT_EMIT_ACCELERATION), + _accelerationSpread(DEFAULT_ACCELERATION_SPREAD), _particleRadius(DEFAULT_PARTICLE_RADIUS), _lastAnimated(usecTimestampNow()), _animationLoop(), @@ -80,6 +81,7 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte _particleLifetimes(DEFAULT_MAX_PARTICLES, 0.0f), _particlePositions(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)), _particleVelocities(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)), + _particleAccelerations(DEFAULT_MAX_PARTICLES, glm::vec3(0.0f, 0.0f, 0.0f)), _timeUntilNextEmit(0.0f), _particleHeadIndex(0), _particleTailIndex(0), @@ -94,75 +96,59 @@ ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityIte ParticleEffectEntityItem::~ParticleEffectEntityItem() { } -void ParticleEffectEntityItem::setDimensions(const glm::vec3& value) { - computeAndUpdateDimensions(); -} void ParticleEffectEntityItem::setLifespan(float lifespan) { _lifespan = lifespan; +} + +void ParticleEffectEntityItem::setEmitVelocity(const glm::vec3& emitVelocity) { + _emitVelocity = emitVelocity; computeAndUpdateDimensions(); } -void ParticleEffectEntityItem::setEmitDirection(glm::vec3 emitDirection) { - _emitDirection = glm::normalize(emitDirection); +void ParticleEffectEntityItem::setVelocitySpread(const glm::vec3& velocitySpread) { + _velocitySpread = velocitySpread; computeAndUpdateDimensions(); } -void ParticleEffectEntityItem::setEmitStrength(float emitStrength) { - _emitStrength = emitStrength; + +void ParticleEffectEntityItem::setEmitAcceleration(const glm::vec3& emitAcceleration) { + _emitAcceleration = emitAcceleration; computeAndUpdateDimensions(); } -void ParticleEffectEntityItem::setLocalGravity(float localGravity) { - _localGravity = localGravity; +void ParticleEffectEntityItem::setAccelerationSpread(const glm::vec3& accelerationSpread){ + _accelerationSpread = accelerationSpread; computeAndUpdateDimensions(); } void ParticleEffectEntityItem::setParticleRadius(float particleRadius) { _particleRadius = particleRadius; - computeAndUpdateDimensions(); } void ParticleEffectEntityItem::computeAndUpdateDimensions() { - - const float t = _lifespan * 1.1f; // add 10% extra time, to account for incremental timer accumulation error. - const float MAX_RANDOM_FACTOR = (0.5f * 0.25f); - const float maxOffset = (MAX_RANDOM_FACTOR * _emitStrength) + _particleRadius; - - // bounds for x and z is easy to compute because there is no at^2 term. - float xMax = (_emitDirection.x * _emitStrength + maxOffset) * t; - float xMin = (_emitDirection.x * _emitStrength - maxOffset) * t; - - float zMax = (_emitDirection.z * _emitStrength + maxOffset) * t; - float zMin = (_emitDirection.z * _emitStrength - maxOffset) * t; - - // yEnd is where the particle will end. - float a = _localGravity; - float atSquared = a * t * t; - float v = _emitDirection.y * _emitStrength + maxOffset; - float vt = v * t; - float yEnd = 0.5f * atSquared + vt; - - // yApex is where the particle is at it's apex. - float yApexT = (-v / a); - float yApex = 0.0f; - - // only set apex if it's within the lifespan of the particle. - if (yApexT >= 0.0f && yApexT <= t) { - yApex = -(v * v) / (2.0f * a); - } - - float yMax = std::max(yApex, yEnd); - float yMin = std::min(yApex, yEnd); - - // times 2 because dimensions are diameters not radii. - glm::vec3 dims(2.0f * std::max(fabsf(xMin), fabsf(xMax)), - 2.0f * std::max(fabsf(yMin), fabsf(yMax)), - 2.0f * std::max(fabsf(zMin), fabsf(zMax))); - + const float time = _lifespan * 1.1f; // add 10% extra time to account for incremental timer accumulation error + + float maxVelocityX = fabsf(_velocity.x) + _velocitySpread.x; + float maxAccelerationX = fabsf(_acceleration.x) + _accelerationSpread.x; + float maxXDistance = (maxVelocityX * time) + (0.5 * maxAccelerationX * time * time); + + float maxVelocityY = fabs(_velocity.y) + _velocitySpread.y; + float maxAccelerationY = fabsf(_acceleration.y) + _accelerationSpread.y; + float maxYDistance = (maxVelocityY * time) + (0.5 * maxAccelerationY * time * time); + + float maxVelocityZ = fabsf(_velocity.z) + _velocitySpread.z; + float maxAccelerationZ = fabsf(_acceleration.z) + _accelerationSpread.z; + float maxZDistance = (maxVelocityZ * time) + (0.5 * maxAccelerationZ * time * time); + + float maxDistance = std::max(maxXDistance, std::max(maxYDistance, maxZDistance)); + + //times 2 because dimensions are diameters not radii + glm::vec3 dims(2.0 * maxDistance); EntityItem::setDimensions(dims); } + EntityItemProperties ParticleEffectEntityItem::getProperties() const { EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class @@ -176,9 +162,9 @@ EntityItemProperties ParticleEffectEntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRate, getEmitRate); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitDirection, getEmitDirection); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitStrength, getEmitStrength); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(localGravity, getLocalGravity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitVelocity, getEmitVelocity); + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitAcceleration, getEmitAcceleration); COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); @@ -198,11 +184,12 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRate, setEmitRate); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitDirection, setEmitDirection); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitStrength, setEmitStrength); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(localGravity, setLocalGravity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitVelocity, setEmitVelocity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitAcceleration, setEmitAcceleration); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(accelerationSpread, setAccelerationSpread); SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocitySpread, setVelocitySpread); if (somethingChanged) { bool wantDebug = false; @@ -247,17 +234,28 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) { setAnimationFrameIndex(animationFrameIndex); } - READ_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, QString, setAnimationSettings); READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType); READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles); READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan); READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate); - READ_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection); - READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, setEmitStrength); - READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, setLocalGravity); - READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); - READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + READ_ENTITY_PROPERTY(PROP_EMIT_VELOCITY, glm::vec3, setEmitVelocity); + + if (args.bitstreamVersion >= VERSION_ENTITIES_PARTICLE_MODIFICATIONS) { + READ_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, glm::vec3, setEmitAcceleration); + READ_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, glm::vec3, setAccelerationSpread); + READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); + READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + READ_ENTITY_PROPERTY(PROP_VELOCITY_SPREAD, glm::vec3, setVelocitySpread); + } else { + // EMIT_STRENGTH FAKEOUT + READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); + // LOCAL_GRAVITY FAKEOUT + READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); + // ACTUALLY PARTICLE RADIUS + READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); + READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + } return bytesRead; } @@ -276,11 +274,12 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_MAX_PARTICLES; requestedProperties += PROP_LIFESPAN; requestedProperties += PROP_EMIT_RATE; - requestedProperties += PROP_EMIT_DIRECTION; - requestedProperties += PROP_EMIT_STRENGTH; - requestedProperties += PROP_LOCAL_GRAVITY; + requestedProperties += PROP_EMIT_VELOCITY; + requestedProperties += PROP_EMIT_ACCELERATION; + requestedProperties += PROP_ACCELERATION_SPREAD; requestedProperties += PROP_PARTICLE_RADIUS; requestedProperties += PROP_TEXTURES; + requestedProperties += PROP_VELOCITY_SPREAD; return requestedProperties; } @@ -303,11 +302,12 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, getMaxParticles()); APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, getLifespan()); APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, getEmitRate()); - APPEND_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, getEmitDirection()); - APPEND_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, getEmitStrength()); - APPEND_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, getLocalGravity()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_VELOCITY, getEmitVelocity()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, getEmitAcceleration()); + APPEND_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, getAccelerationSpread()); APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, getParticleRadius()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); + APPEND_ENTITY_PROPERTY(PROP_VELOCITY_SPREAD, getVelocitySpread()); } bool ParticleEffectEntityItem::isAnimatingSomething() const { @@ -487,8 +487,9 @@ void ParticleEffectEntityItem::extendBounds(const glm::vec3& point) { } void ParticleEffectEntityItem::integrateParticle(quint32 index, float deltaTime) { - glm::vec3 atSquared(0.0f, 0.5f * _localGravity * deltaTime * deltaTime, 0.0f); - glm::vec3 at(0.0f, _localGravity * deltaTime, 0.0f); + glm::vec3 accel = _particleAccelerations[index]; + glm::vec3 atSquared = (0.5f * deltaTime * deltaTime) * accel; + glm::vec3 at = accel * deltaTime; _particlePositions[index] += _particleVelocities[index] * deltaTime + atSquared; _particleVelocities[index] += at; } @@ -526,15 +527,22 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) { quint32 i = _particleTailIndex; _particleLifetimes[i] = _lifespan; - // jitter the _emitDirection by a random offset - glm::vec3 randOffset; - randOffset.x = (randFloat() - 0.5f) * 0.25f * _emitStrength; - randOffset.y = (randFloat() - 0.5f) * 0.25f * _emitStrength; - randOffset.z = (randFloat() - 0.5f) * 0.25f * _emitStrength; + + glm::vec3 spreadOffset; + spreadOffset.x = -_velocitySpread.x + randFloat() * (_velocitySpread.x * 2.0f); + spreadOffset.y = -_velocitySpread.y + randFloat() * (_velocitySpread.y * 2.0f); + spreadOffset.z = -_velocitySpread.z + randFloat() * (_velocitySpread.z * 2.0f); + // set initial conditions - _particlePositions[i] = glm::vec3(0.0f, 0.0f, 0.0f); - _particleVelocities[i] = _emitDirection * _emitStrength + randOffset; + _particlePositions[i] = getPosition(); + _particleVelocities[i] = _emitVelocity + spreadOffset; + + spreadOffset.x = -_accelerationSpread.x + randFloat() * (_accelerationSpread.x * 2.0f); + spreadOffset.y = -_accelerationSpread.y + randFloat() * (_accelerationSpread.y * 2.0f); + spreadOffset.z = -_accelerationSpread.z + randFloat() * (_accelerationSpread.z * 2.0f); + + _particleAccelerations[i] = _emitAcceleration + spreadOffset; integrateParticle(i, timeLeftInFrame); extendBounds(_particlePositions[i]); diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 994c609f0f..4ed9216e85 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -86,8 +86,6 @@ public: void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } - virtual void setDimensions(const glm::vec3& value) override; - static const quint32 DEFAULT_MAX_PARTICLES; void setMaxParticles(quint32 maxParticles); quint32 getMaxParticles() const { return _maxParticles; } @@ -100,24 +98,31 @@ public: void setEmitRate(float emitRate) { _emitRate = emitRate; } float getEmitRate() const { return _emitRate; } - static const glm::vec3 DEFAULT_EMIT_DIRECTION; - void setEmitDirection(glm::vec3 emitDirection); - const glm::vec3& getEmitDirection() const { return _emitDirection; } + static const glm::vec3 DEFAULT_EMIT_VELOCITY; + void setEmitVelocity(const glm::vec3& emitVelocity); + const glm::vec3& getEmitVelocity() const { return _emitVelocity; } + + + static const glm::vec3 DEFAULT_VELOCITY_SPREAD; + void setVelocitySpread(const glm::vec3& velocitySpread); + const glm::vec3& getVelocitySpread() const { return _velocitySpread; } - static const float DEFAULT_EMIT_STRENGTH; - void setEmitStrength(float emitStrength); - float getEmitStrength() const { return _emitStrength; } - static const float DEFAULT_LOCAL_GRAVITY; - void setLocalGravity(float localGravity); - float getLocalGravity() const { return _localGravity; } + static const glm::vec3 DEFAULT_EMIT_ACCELERATION; + void setEmitAcceleration(const glm::vec3& emitAcceleration); + const glm::vec3& getEmitAcceleration() const { return _emitAcceleration; } + + static const glm::vec3 DEFAULT_ACCELERATION_SPREAD; + void setAccelerationSpread(const glm::vec3& accelerationSpread); + const glm::vec3& getAccelerationSpread() const { return _accelerationSpread; } static const float DEFAULT_PARTICLE_RADIUS; void setParticleRadius(float particleRadius); float getParticleRadius() const { return _particleRadius; } - + void computeAndUpdateDimensions(); + bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); } float getAnimationFrameIndex() const { return _animationLoop.getFrameIndex(); } float getAnimationFPS() const { return _animationLoop.getFPS(); } @@ -145,9 +150,10 @@ protected: quint32 _maxParticles; float _lifespan; float _emitRate; - glm::vec3 _emitDirection; - float _emitStrength; - float _localGravity; + glm::vec3 _emitVelocity; + glm::vec3 _velocitySpread; + glm::vec3 _emitAcceleration; + glm::vec3 _accelerationSpread; float _particleRadius; quint64 _lastAnimated; AnimationLoop _animationLoop; @@ -160,6 +166,7 @@ protected: QVector _particleLifetimes; QVector _particlePositions; QVector _particleVelocities; + QVector _particleAccelerations; float _timeUntilNextEmit; // particle arrays are a ring buffer, use these indicies diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index db2a426696..7e73b4c660 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -67,7 +67,7 @@ PacketVersion versionForPacketType(PacketType::Value packetType) { case EntityAdd: case EntityEdit: case EntityData: - return VERSION_OCTREE_CENTERED_ORIGIN; + return VERSION_ENTITIES_PARTICLE_MODIFICATIONS; case AvatarData: return 12; default: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 75d8105dca..fa6178b627 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -143,5 +143,6 @@ const PacketVersion VERSION_ENTITIES_NEW_PROTOCOL_LAYER = 35; const PacketVersion VERSION_POLYVOX_TEXTURES = 36; const PacketVersion VERSION_ENTITIES_POLYLINE = 37; const PacketVersion VERSION_OCTREE_CENTERED_ORIGIN = 38; +const PacketVersion VERSION_ENTITIES_PARTICLE_MODIFICATIONS = 39; #endif // hifi_PacketHeaders_h \ No newline at end of file diff --git a/libraries/shared/src/NumericalConstants.h b/libraries/shared/src/NumericalConstants.h index b632147641..9a946e35f7 100644 --- a/libraries/shared/src/NumericalConstants.h +++ b/libraries/shared/src/NumericalConstants.h @@ -31,6 +31,7 @@ const float METERS_PER_DECIMETER = 0.1f; const float METERS_PER_CENTIMETER = 0.01f; const float METERS_PER_MILLIMETER = 0.001f; const float MILLIMETERS_PER_METER = 1000.0f; +const quint64 NSECS_PER_USEC = 1000; const quint64 USECS_PER_MSEC = 1000; const quint64 MSECS_PER_SECOND = 1000; const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index b60ffc0891..11ed64cac4 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -35,9 +35,9 @@ namespace Setting { settingsManagerThread->quit(); settingsManagerThread->wait(); } - - // Sets up the settings private instance. Should only be run once at startup - void init() { + + // Set up application settings. Should only be run once at startup. + void preInit() { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings::setDefaultFormat(QSettings::IniFormat); QSettings applicationInfo(PathUtils::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -46,7 +46,19 @@ namespace Setting { QCoreApplication::setApplicationName(applicationInfo.value("name").toString()); QCoreApplication::setOrganizationName(applicationInfo.value("organizationName").toString()); QCoreApplication::setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); - + + // Delete Interface.ini.lock file if it exists, otherwise Interface freezes. + QSettings settings; + QString settingsLockFilename = settings.fileName() + ".lock"; + QFile settingsLockFile(settingsLockFilename); + if (settingsLockFile.exists()) { + bool deleted = settingsLockFile.remove(); + qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; + } + } + + // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, + void init() { // Let's set up the settings Private instance on its own thread QThread* thread = new QThread(); Q_CHECK_PTR(thread); @@ -55,14 +67,6 @@ namespace Setting { privateInstance = new Manager(); Q_CHECK_PTR(privateInstance); - // Delete Interface.ini.lock file if it exists, otherwise Interface freezes. - QString settingsLockFilename = privateInstance->fileName() + ".lock"; - QFile settingsLockFile(settingsLockFilename); - if (settingsLockFile.exists()) { - bool deleted = settingsLockFile.remove(); - qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; - } - QObject::connect(privateInstance, SIGNAL(destroyed()), thread, SLOT(quit())); QObject::connect(thread, SIGNAL(started()), privateInstance, SLOT(startTimer())); QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); diff --git a/libraries/shared/src/SettingInterface.h b/libraries/shared/src/SettingInterface.h index 5092fd09c8..c8b1595a75 100644 --- a/libraries/shared/src/SettingInterface.h +++ b/libraries/shared/src/SettingInterface.h @@ -16,6 +16,7 @@ #include namespace Setting { + void preInit(); void init(); void cleanupSettings();