diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 47836727fe..f517716b72 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -410,6 +410,7 @@ void Agent::executeScript() { bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened bool closedInLastBlock = _audioGateOpen && !audioGateOpen; // the gate just closed _audioGateOpen = audioGateOpen; + Q_UNUSED(openedInLastBlock); // the codec must be flushed to silence before sending silent packets, // so delay the transition to silent packets by one packet after becoming silent. diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 591b6f515e..a0e9bd30d4 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -35,35 +35,35 @@ { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] }, - { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }, + { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], - "when": [ "Application.InHMD"] + "when": [ "Application.InHMD" ] }, { "from": "Vive.RightFoot", "to" : "Standard.RightFoot", "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], - "when": [ "Application.InHMD"] + "when": [ "Application.InHMD" ] }, { "from": "Vive.Hips", "to" : "Standard.Hips", "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], - "when": [ "Application.InHMD"] + "when": [ "Application.InHMD" ] }, { "from": "Vive.Spine2", "to" : "Standard.Spine2", "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], - "when": [ "Application.InHMD"] + "when": [ "Application.InHMD" ] }, - { "from": "Vive.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }, + { "from": "Vive.Head", "to" : "Standard.Head", "when": [ "Application.InHMD" ] }, - { "from": "Vive.RightArm", "to" : "Standard.RightArm", "when" : [ "Application.InHMD"] }, - { "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when" : [ "Application.InHMD"] } + { "from": "Vive.RightArm", "to" : "Standard.RightArm", "when": [ "Application.InHMD" ] }, + { "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when": [ "Application.InHMD" ] } ] } diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 93f6fe6d13..548f61e1de 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 8f6b00f459..2d6b21b219 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -388,8 +388,13 @@ Rectangle { sortIndicatorColumn: settings.nearbySortIndicatorColumn; sortIndicatorOrder: settings.nearbySortIndicatorOrder; onSortIndicatorColumnChanged: { - settings.nearbySortIndicatorColumn = sortIndicatorColumn; - sortModel(); + if (sortIndicatorColumn > 2) { + // these are not sortable, switch back to last column + sortIndicatorColumn = settings.nearbySortIndicatorColumn; + } else { + settings.nearbySortIndicatorColumn = sortIndicatorColumn; + sortModel(); + } } onSortIndicatorOrderChanged: { settings.nearbySortIndicatorOrder = sortIndicatorOrder; diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 33af7da1ae..97c8854c86 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -170,20 +170,24 @@ Item { objectName: "loader" asynchronous: false - width: parent.width height: parent.height + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: null + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); + } + } + } + onLoaded: { if (loader.item.hasOwnProperty("eventBridge")) { loader.item.eventBridge = eventBridge; - - // Hook up callback for clara.io download from the marketplace. - eventBridge.webEventReceived.connect(function (event) { - if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); - } - }); + eventBridgeConnection.target = eventBridge } if (loader.item.hasOwnProperty("sendToScript")) { loader.item.sendToScript.connect(tabletRoot.sendToScript); diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index 470fd4a830..ee8dbbff59 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -90,16 +90,21 @@ Windows.ScrollingWindow { anchors.left: parent.left anchors.top: parent.top + // Hook up callback for clara.io download from the marketplace. + Connections { + id: eventBridgeConnection + target: null + onWebEventReceived: { + if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { + ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); + } + } + } + onLoaded: { if (loader.item.hasOwnProperty("eventBridge")) { loader.item.eventBridge = eventBridge; - - // Hook up callback for clara.io download from the marketplace. - eventBridge.webEventReceived.connect(function (event) { - if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); - } - }); + eventBridgeConnection.target = eventBridge } if (loader.item.hasOwnProperty("sendToScript")) { loader.item.sendToScript.connect(tabletRoot.sendToScript); diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 3e497b053e..fcb3e9ff92 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -136,7 +136,7 @@ Item { for (var i = 0; i < sections.length; i++) { totalHeight += sections[i].height + sections[i].getPreferencesHeight(); } - var bottomPadding = 100; + var bottomPadding = 170; return (totalHeight + bottomPadding); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46c4c0bd4e..a726785746 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -11,6 +11,9 @@ #include "Application.h" +#include +#include + #include #include #include @@ -144,6 +147,7 @@ #include "InterfaceLogging.h" #include "LODManager.h" #include "ModelPackager.h" +#include "networking/CloseEventSender.h" #include "networking/HFWebEngineProfile.h" #include "networking/HFTabletWebEngineProfile.h" #include "networking/FileTypeProfile.h" @@ -534,6 +538,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -1459,6 +1464,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateSystemTabletMode(); connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged); + + qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); } void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { @@ -1568,6 +1575,14 @@ void Application::aboutToQuit() { getActiveDisplayPlugin()->deactivate(); + // use the CloseEventSender via a QThread to send an event that says the user asked for the app to close + auto closeEventSender = DependencyManager::get(); + QThread* closureEventThread = new QThread(this); + closeEventSender->moveToThread(closureEventThread); + // sendQuitEventAsync will bail immediately if the UserActivityLogger is not enabled + connect(closureEventThread, &QThread::started, closeEventSender.data(), &CloseEventSender::sendQuitEventAsync); + closureEventThread->start(); + // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. DependencyManager::get()->hide("RunningScripts"); @@ -1681,6 +1696,10 @@ Application::~Application() { _physicsEngine->setCharacterController(nullptr); + // the _shapeManager should have zero references + _shapeManager.collectGarbage(); + assert(_shapeManager.getNumShapes() == 0); + // shutdown render engine _main3DScene = nullptr; _renderEngine = nullptr; @@ -1732,6 +1751,15 @@ Application::~Application() { _window->deleteLater(); + // make sure that the quit event has finished sending before we take the application down + auto closeEventSender = DependencyManager::get(); + while (!closeEventSender->hasFinishedQuitEvent() && !closeEventSender->hasTimedOutQuitEvent()) { + // sleep a little so we're not spinning at 100% + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + // quit the thread used by the closure event sender + closeEventSender->thread()->quit(); + // Can't log to file passed this point, FileLogger about to be deleted qInstallMessageHandler(LogHandler::verboseMessageHandler); } @@ -2183,6 +2211,9 @@ void Application::paintGL() { }); renderArgs._context->setStereoProjections(eyeProjections); renderArgs._context->setStereoViews(eyeOffsets); + + // Configure the type of display / stereo + renderArgs._displayMode = (isHMDMode() ? RenderArgs::STEREO_HMD : RenderArgs::STEREO_MONITOR); } renderArgs._blitFramebuffer = finalFramebuffer; displaySide(&renderArgs, _myCamera); @@ -2382,15 +2413,16 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { // Check HMD use (may be technically available without being in use) bool hasHMD = PluginUtils::isHMDAvailable(); - bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd(); + bool isUsingHMD = _displayPlugin->isHmd(); + bool isUsingHMDAndHandControllers = hasHMD && hasHandControllers && isUsingHMD; Setting::Handle tutorialComplete{ "tutorialComplete", false }; Setting::Handle firstRun{ Settings::firstRun, true }; bool isTutorialComplete = tutorialComplete.get(); - bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete; + bool shouldGoToTutorial = isUsingHMDAndHandControllers && hasTutorialContent && !isTutorialComplete; - qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD; + qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMDAndHandControllers; qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent << ", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial; @@ -2404,10 +2436,18 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { const QString TUTORIAL_PATH = "/tutorial_begin"; + static const QString SENT_TO_TUTORIAL = "tutorial"; + static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location"; + static const QString SENT_TO_ENTRY = "entry"; + static const QString SENT_TO_SANDBOX = "sandbox"; + + QString sentTo; + if (shouldGoToTutorial) { if (sandboxIsRunning) { qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; DependencyManager::get()->goToLocalSandbox(TUTORIAL_PATH); + sentTo = SENT_TO_TUTORIAL; } else { qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; if (firstRun.get()) { @@ -2415,8 +2455,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { } if (addressLookupString.isEmpty()) { DependencyManager::get()->goToEntry(); + sentTo = SENT_TO_ENTRY; } else { DependencyManager::get()->loadSettings(addressLookupString); + sentTo = SENT_TO_PREVIOUS_LOCATION; } } } else { @@ -2429,23 +2471,40 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { // If this is a first run we short-circuit the address passed in if (isFirstRun) { - if (isUsingHMD) { + if (isUsingHMDAndHandControllers) { if (sandboxIsRunning) { qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; DependencyManager::get()->goToLocalSandbox(); + sentTo = SENT_TO_SANDBOX; } else { qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; DependencyManager::get()->goToEntry(); + sentTo = SENT_TO_ENTRY; } } else { DependencyManager::get()->goToEntry(); + sentTo = SENT_TO_ENTRY; } } else { qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); DependencyManager::get()->loadSettings(addressLookupString); + sentTo = SENT_TO_PREVIOUS_LOCATION; } } + UserActivityLogger::getInstance().logAction("startup_sent_to", { + { "sent_to", sentTo }, + { "sandbox_is_running", sandboxIsRunning }, + { "has_hmd", hasHMD }, + { "has_hand_controllers", hasHandControllers }, + { "is_using_hmd", isUsingHMD }, + { "is_using_hmd_and_hand_controllers", isUsingHMDAndHandControllers }, + { "content_version", contentVersion }, + { "is_tutorial_complete", isTutorialComplete }, + { "has_tutorial_content", hasTutorialContent }, + { "should_go_to_tutorial", shouldGoToTutorial } + }); + _connectionMonitor.init(); // After all of the constructor is completed, then set firstRun to false. @@ -2774,6 +2833,17 @@ void Application::keyPressEvent(QKeyEvent* event) { if (isShifted && isMeta && !isOption) { Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); } else if (!isOption && !isShifted && isMeta) { + AudioInjectorOptions options; + options.localOnly = true; + options.stereo = true; + + if (_snapshotSoundInjector) { + _snapshotSoundInjector->setOptions(options); + _snapshotSoundInjector->restart(); + } else { + QByteArray samples = _snapshotSound->getByteArray(); + _snapshotSoundInjector = AudioInjector::playSound(samples, options); + } takeSnapshot(true); } break; @@ -4488,12 +4558,13 @@ void Application::update(float deltaTime) { getEntities()->getTree()->withWriteLock([&] { PerformanceTimer perfTimer("handleOutgoingChanges"); - const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates(); - _entitySimulation->handleDeactivatedMotionStates(deactivations); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates(); _entitySimulation->handleChangedMotionStates(outgoingChanges); avatarManager->handleChangedMotionStates(outgoingChanges); + + const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates(); + _entitySimulation->handleDeactivatedMotionStates(deactivations); }); if (!_aboutToQuit) { @@ -6305,21 +6376,6 @@ void Application::loadAddAvatarBookmarkDialog() const { } void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { - - //keep sound thread out of event loop scope - - AudioInjectorOptions options; - options.localOnly = true; - options.stereo = true; - - if (_snapshotSoundInjector) { - _snapshotSoundInjector->setOptions(options); - _snapshotSoundInjector->restart(); - } else { - QByteArray samples = _snapshotSound->getByteArray(); - _snapshotSoundInjector = AudioInjector::playSound(samples, options); - } - postLambdaEvent([notify, includeAnimated, aspectRatio, this] { // Get a screenshot and save it QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio)); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4138798484..463069430d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -407,6 +407,12 @@ Menu::Menu() { #endif + { + auto action = addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::RenderClearKtxCache); + connect(action, &QAction::triggered, []{ + Setting::Handle(KTXCache::SETTING_VERSION_NAME, KTXCache::INVALID_VERSION).set(KTXCache::INVALID_VERSION); + }); + } // Developer > Render > LOD Tools addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b6d72f5446..41bf89a20a 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -145,6 +145,7 @@ namespace MenuOption { const QString Quit = "Quit"; const QString ReloadAllScripts = "Reload All Scripts"; const QString ReloadContent = "Reload Content (Clears all caches)"; + const QString RenderClearKtxCache = "Clear KTX Cache (requires restart)"; const QString RenderMaxTextureMemory = "Maximum Texture Memory"; const QString RenderMaxTextureAutomatic = "Automatic Texture Memory"; const QString RenderMaxTexture4MB = "4 MB"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 72a9281564..966bca252e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2770,7 +2770,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const { auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex); return createMatFromQuatAndPos(leftFootRot, leftFootPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_POS, DEFAULT_AVATAR_LEFTFOOT_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTFOOT_ROT, DEFAULT_AVATAR_LEFTFOOT_POS); } } @@ -2782,7 +2782,7 @@ glm::mat4 MyAvatar::getRightFootCalibrationMat() const { auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex); return createMatFromQuatAndPos(rightFootRot, rightFootPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_POS, DEFAULT_AVATAR_RIGHTFOOT_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_RIGHTFOOT_ROT, DEFAULT_AVATAR_RIGHTFOOT_POS); } } @@ -2805,7 +2805,7 @@ glm::mat4 MyAvatar::getLeftArmCalibrationMat() const { auto leftArmRot = getAbsoluteDefaultJointRotationInObjectFrame(leftArmIndex); return createMatFromQuatAndPos(leftArmRot, leftArmPos); } else { - return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_RIGHTARM_POS); + return createMatFromQuatAndPos(DEFAULT_AVATAR_LEFTARM_ROT, DEFAULT_AVATAR_LEFTARM_POS); } } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 49517eb38e..83cac6d9bb 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -24,7 +24,6 @@ #include #include - #include "AddressManager.h" #include "Application.h" #include "InterfaceLogging.h" @@ -191,7 +190,7 @@ int main(int argc, const char* argv[]) { int exitCode; { - RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME); + RunningMarker runningMarker(RUNNING_MARKER_FILENAME); bool runningMarkerExisted = runningMarker.fileExists(); runningMarker.writeRunningMarkerFile(); @@ -200,14 +199,11 @@ int main(int argc, const char* argv[]) { bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); if (runServer) { - SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater); + SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater); } Application app(argc, const_cast(argv), startupTime, runningMarkerExisted); - // Now that the main event loop is setup, launch running marker thread - runningMarker.startRunningMarker(); - // If we failed the OpenGLVersion check, log it. if (override) { auto accountManager = DependencyManager::get(); diff --git a/interface/src/networking/CloseEventSender.cpp b/interface/src/networking/CloseEventSender.cpp new file mode 100644 index 0000000000..8c3d6ae888 --- /dev/null +++ b/interface/src/networking/CloseEventSender.cpp @@ -0,0 +1,90 @@ +// +// CloseEventSender.cpp +// interface/src/networking +// +// Created by Stephen Birarda on 5/31/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "CloseEventSender.h" + +QNetworkRequest createNetworkRequest() { + + QNetworkRequest request; + + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + requestURL.setPath(USER_ACTIVITY_URL); + + request.setUrl(requestURL); + + auto accountManager = DependencyManager::get(); + + if (accountManager->hasValidAccessToken()) { + request.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, + accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue()); + } + + request.setRawHeader(METAVERSE_SESSION_ID_HEADER, + uuidStringWithoutCurlyBraces(accountManager->getSessionID()).toLocal8Bit()); + + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + request.setPriority(QNetworkRequest::HighPriority); + + return request; +} + +QByteArray postDataForAction(QString action) { + return QString("{\"action_name\": \"" + action + "\"}").toUtf8(); +} + +QNetworkReply* replyForAction(QString action) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + return networkAccessManager.post(createNetworkRequest(), postDataForAction(action)); +} + +void CloseEventSender::sendQuitEventAsync() { + if (UserActivityLogger::getInstance().isEnabled()) { + QNetworkReply* reply = replyForAction("quit"); + connect(reply, &QNetworkReply::finished, this, &CloseEventSender::handleQuitEventFinished); + _quitEventStartTimestamp = QDateTime::currentMSecsSinceEpoch(); + } else { + _hasFinishedQuitEvent = true; + } +} + +void CloseEventSender::handleQuitEventFinished() { + _hasFinishedQuitEvent = true; + + auto reply = qobject_cast(sender()); + if (reply->error() == QNetworkReply::NoError) { + qCDebug(networking) << "Quit event sent successfully"; + } else { + qCDebug(networking) << "Failed to send quit event -" << reply->errorString(); + } + + reply->deleteLater(); +} + +bool CloseEventSender::hasTimedOutQuitEvent() { + const int CLOSURE_EVENT_TIMEOUT_MS = 5000; + return _quitEventStartTimestamp != 0 + && QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS; +} + + diff --git a/interface/src/networking/CloseEventSender.h b/interface/src/networking/CloseEventSender.h new file mode 100644 index 0000000000..05e6f81ad4 --- /dev/null +++ b/interface/src/networking/CloseEventSender.h @@ -0,0 +1,41 @@ +// +// CloseEventSender.h +// interface/src/networking +// +// Created by Stephen Birarda on 5/31/17. +// Copyright 2017 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_CloseEventSender_h +#define hifi_CloseEventSender_h + +#include + +#include +#include + +#include + +class CloseEventSender : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + bool hasTimedOutQuitEvent(); + bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; } + +public slots: + void sendQuitEventAsync(); + +private slots: + void handleQuitEventFinished(); + +private: + std::atomic _hasFinishedQuitEvent { false }; + std::atomic _quitEventStartTimestamp; +}; + +#endif // hifi_CloseEventSender_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 6b478da5fc..0e48cd8bd1 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1135,9 +1135,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm } glm::vec3 headUp = headQuat * Vectors::UNIT_Y; - glm::vec3 z, y, x; - generateBasisVectors(lookAtVector, headUp, z, y, x); - glm::mat3 m(glm::cross(y, z), y, z); + glm::vec3 z, y, zCrossY; + generateBasisVectors(lookAtVector, headUp, z, y, zCrossY); + glm::mat3 m(-zCrossY, y, z); glm::quat desiredQuat = glm::normalize(glm::quat_cast(m)); glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d82068b8ac..4407e12295 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -850,7 +850,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { glm::quat sensorToWorldQuat; unpackOrientationQuatFromSixBytes(data->sensorToWorldQuat, sensorToWorldQuat); float sensorToWorldScale; - unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&data->sensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX); + // Grab a local copy of sensorToWorldScale to be able to use the unpack function with a pointer on it, + // a direct pointer on the struct attribute triggers warnings because of potential misalignement. + auto srcSensorToWorldScale = data->sensorToWorldScale; + unpackFloatScalarFromSignedTwoByteFixed((int16_t*)&srcSensorToWorldScale, &sensorToWorldScale, SENSOR_TO_WORLD_SCALE_RADIX); glm::vec3 sensorToWorldTrans(data->sensorToWorldTrans[0], data->sensorToWorldTrans[1], data->sensorToWorldTrans[2]); glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans); if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) { diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 5727d4906e..2cb500c42a 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -42,8 +42,6 @@ enum class Action { LEFT_HAND = NUM_COMBINED_AXES, RIGHT_HAND, - LEFT_ARM, - RIGHT_ARM, LEFT_FOOT, RIGHT_FOOT, HIPS, @@ -103,6 +101,8 @@ enum class Action { // Bisected aliases for TRANSLATE_CAMERA_Z BOOM_IN, BOOM_OUT, + LEFT_ARM, + RIGHT_ARM, NUM_ACTIONS, diff --git a/libraries/controllers/src/controllers/InputRecorder.cpp b/libraries/controllers/src/controllers/InputRecorder.cpp index 60ff592144..54d1aaf131 100644 --- a/libraries/controllers/src/controllers/InputRecorder.cpp +++ b/libraries/controllers/src/controllers/InputRecorder.cpp @@ -9,7 +9,6 @@ #include "InputRecorder.h" #include -#include #include #include #include @@ -19,13 +18,17 @@ #include #include #include +#include +#include #include #include +#include +#include "UserInputMapper.h" QString SAVE_DIRECTORY = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + BuildInfo::MODIFIED_ORGANIZATION + "/" + BuildInfo::INTERFACE_NAME + "/hifi-input-recordings/"; QString FILE_PREFIX_NAME = "input-recording-"; -QString COMPRESS_EXTENSION = "json.gz"; +QString COMPRESS_EXTENSION = ".json.gz"; namespace controller { QJsonObject poseToJsonObject(const Pose pose) { @@ -91,23 +94,27 @@ namespace controller { } - void exportToFile(QJsonObject& object) { + void exportToFile(const QJsonObject& object, const QString& fileName) { if (!QDir(SAVE_DIRECTORY).exists()) { QDir().mkdir(SAVE_DIRECTORY); } - - QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate); - timeStamp.replace(":", "-"); - QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION; - qDebug() << fileName; + QFile saveFile (fileName); if (!saveFile.open(QIODevice::WriteOnly)) { qWarning() << "could not open file: " << fileName; return; } QJsonDocument saveData(object); - QByteArray compressedData = qCompress(saveData.toJson(QJsonDocument::Compact)); - saveFile.write(compressedData); + QByteArray jsonData = saveData.toJson(QJsonDocument::Indented); + QByteArray jsonDataForFile; + + if (!gzip(jsonData, jsonDataForFile, -1)) { + qCritical("unable to gzip while saving to json."); + return; + } + + saveFile.write(jsonDataForFile); + saveFile.close(); } QJsonObject openFile(const QString& file, bool& status) { @@ -118,10 +125,19 @@ namespace controller { status = false; return object; } - QByteArray compressedData = qUncompress(openFile.readAll()); - QJsonDocument jsonDoc = QJsonDocument::fromJson(compressedData); + QByteArray compressedData = openFile.readAll(); + QByteArray jsonData; + + if (!gunzip(compressedData, jsonData)) { + qCritical() << "json file not in gzip format: " << file; + status = false; + return object; + } + + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData); object = jsonDoc.object(); status = true; + openFile.close(); return object; } @@ -146,31 +162,47 @@ namespace controller { _actionStateList.clear(); } - void InputRecorder::saveRecording() { + QJsonObject InputRecorder::recordDataToJson() { QJsonObject data; data["frameCount"] = _framesRecorded; - + data["version"] = "0.0"; + QJsonArray actionArrayList; QJsonArray poseArrayList; for(const ActionStates actionState: _actionStateList) { QJsonArray actionArray; - for (const float value: actionState) { - actionArray.append(value); + for (const auto action: actionState) { + QJsonObject actionJson; + actionJson["name"] = action.first; + actionJson["value"] = action.second; + actionArray.append(actionJson); } actionArrayList.append(actionArray); } for (const PoseStates poseState: _poseStateList) { QJsonArray poseArray; - for (const Pose pose: poseState) { - poseArray.append(poseToJsonObject(pose)); + for (const auto pose: poseState) { + QJsonObject poseJson; + poseJson["name"] = pose.first; + poseJson["pose"] = poseToJsonObject(pose.second); + poseArray.append(poseJson); } poseArrayList.append(poseArray); } data["actionList"] = actionArrayList; data["poseList"] = poseArrayList; - exportToFile(data); + + return data; + } + + void InputRecorder::saveRecording() { + QJsonObject jsonData = recordDataToJson(); + QString timeStamp = QDateTime::currentDateTime().toString(Qt::ISODate); + timeStamp.replace(":", "-"); + QString fileName = SAVE_DIRECTORY + FILE_PREFIX_NAME + timeStamp + COMPRESS_EXTENSION; + exportToFile(jsonData, fileName); } void InputRecorder::loadRecording(const QString& path) { @@ -181,17 +213,20 @@ namespace controller { resetFrame(); _poseStateList.clear(); _actionStateList.clear(); - QString filePath = path; - filePath.remove(0,8); + QUrl urlPath(path); + QString filePath = urlPath.toLocalFile(); QFileInfo info(filePath); QString extension = info.suffix(); + if (extension != "gz") { qWarning() << "can not load file with exentsion of " << extension; return; } + bool success = false; - QJsonObject data = openFile(info.absoluteFilePath(), success); - if (success) { + QJsonObject data = openFile(filePath, success); + auto keyValue = data.find("version"); + if (success && keyValue != data.end()) { _framesRecorded = data["frameCount"].toInt(); QJsonArray actionArrayList = data["actionList"].toArray(); QJsonArray poseArrayList = data["poseList"].toArray(); @@ -199,30 +234,31 @@ namespace controller { for (int actionIndex = 0; actionIndex < actionArrayList.size(); actionIndex++) { QJsonArray actionState = actionArrayList[actionIndex].toArray(); for (int index = 0; index < actionState.size(); index++) { - _currentFrameActions[index] = actionState[index].toDouble(); + QJsonObject actionObject = actionState[index].toObject(); + _currentFrameActions[actionObject["name"].toString()] = actionObject["value"].toDouble(); } _actionStateList.push_back(_currentFrameActions); - _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS)); + _currentFrameActions.clear(); } for (int poseIndex = 0; poseIndex < poseArrayList.size(); poseIndex++) { QJsonArray poseState = poseArrayList[poseIndex].toArray(); for (int index = 0; index < poseState.size(); index++) { - _currentFramePoses[index] = jsonObjectToPose(poseState[index].toObject()); + QJsonObject poseObject = poseState[index].toObject(); + _currentFramePoses[poseObject["name"].toString()] = jsonObjectToPose(poseObject["pose"].toObject()); } _poseStateList.push_back(_currentFramePoses); - _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS)); + _currentFramePoses.clear(); } - } - + } _loading = false; } - + void InputRecorder::stopRecording() { _recording = false; _framesRecorded = (int)_actionStateList.size(); } - + void InputRecorder::startPlayback() { _playback = true; _recording = false; @@ -234,41 +270,36 @@ namespace controller { _playCount = 0; } - void InputRecorder::setActionState(controller::Action action, float value) { + void InputRecorder::setActionState(const QString& action, float value) { if (_recording) { - _currentFrameActions[toInt(action)] += value; + _currentFrameActions[action] += value; } } - void InputRecorder::setActionState(controller::Action action, const controller::Pose pose) { + void InputRecorder::setActionState(const QString& action, const controller::Pose& pose) { if (_recording) { - _currentFramePoses[toInt(action)] = pose; + _currentFramePoses[action] = pose; } } void InputRecorder::resetFrame() { if (_recording) { - for(auto& channel : _currentFramePoses) { - channel = Pose(); - } - - for(auto& channel : _currentFrameActions) { - channel = 0.0f; - } + _currentFramePoses.clear(); + _currentFrameActions.clear(); } } - float InputRecorder::getActionState(controller::Action action) { + float InputRecorder::getActionState(const QString& action) { if (_actionStateList.size() > 0 ) { - return _actionStateList[_playCount][toInt(action)]; + return _actionStateList[_playCount][action]; } return 0.0f; } - controller::Pose InputRecorder::getPoseState(controller::Action action) { + controller::Pose InputRecorder::getPoseState(const QString& action) { if (_poseStateList.size() > 0) { - return _poseStateList[_playCount][toInt(action)]; + return _poseStateList[_playCount][action]; } return Pose(); diff --git a/libraries/controllers/src/controllers/InputRecorder.h b/libraries/controllers/src/controllers/InputRecorder.h index d1cc9a32eb..9adb8e386f 100644 --- a/libraries/controllers/src/controllers/InputRecorder.h +++ b/libraries/controllers/src/controllers/InputRecorder.h @@ -12,8 +12,10 @@ #include #include #include +#include #include +#include #include "Pose.h" #include "Actions.h" @@ -21,8 +23,8 @@ namespace controller { class InputRecorder { public: - using PoseStates = std::vector; - using ActionStates = std::vector; + using PoseStates = std::map; + using ActionStates = std::map; InputRecorder(); ~InputRecorder(); @@ -40,20 +42,21 @@ namespace controller { void resetFrame(); bool isRecording() { return _recording; } bool isPlayingback() { return (_playback && !_loading); } - void setActionState(controller::Action action, float value); - void setActionState(controller::Action action, const controller::Pose pose); - float getActionState(controller::Action action); - controller::Pose getPoseState(controller::Action action); + void setActionState(const QString& action, float value); + void setActionState(const QString& action, const controller::Pose& pose); + float getActionState(const QString& action); + controller::Pose getPoseState(const QString& action); QString getSaveDirectory(); void frameTick(); private: + QJsonObject recordDataToJson(); bool _recording { false }; bool _playback { false }; bool _loading { false }; std::vector _poseStateList = std::vector(); std::vector _actionStateList = std::vector(); - PoseStates _currentFramePoses = PoseStates(toInt(Action::NUM_ACTIONS)); - ActionStates _currentFrameActions = ActionStates(toInt(Action::NUM_ACTIONS)); + PoseStates _currentFramePoses; + ActionStates _currentFrameActions; int _framesRecorded { 0 }; int _playCount { 0 }; diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 570081d1f1..79f4325ae6 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -329,6 +329,16 @@ QString UserInputMapper::getActionName(Action action) const { return QString(); } +QString UserInputMapper::getStandardPoseName(uint16_t pose) { + Locker locker(_lock); + for (auto posePair : getStandardInputs()) { + if (posePair.first.channel == pose && posePair.first.getType() == ChannelType::POSE) { + return posePair.second; + } + } + return QString(); +} + QVector UserInputMapper::getActionNames() const { Locker locker(_lock); QVector result; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 05a286cc10..0c8bb51008 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -80,6 +80,7 @@ namespace controller { QVector getAllActions() const; QString getActionName(Action action) const; + QString getStandardPoseName(uint16_t pose); float getActionState(Action action) const { return _actionStates[toInt(action)]; } Pose getPoseState(Action action) const; int findAction(const QString& actionName) const; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp index 6c14533f02..ef9f04402b 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp @@ -17,31 +17,30 @@ using namespace controller; void ActionEndpoint::apply(float newValue, const Pointer& source) { InputRecorder* inputRecorder = InputRecorder::getInstance(); + auto userInputMapper = DependencyManager::get(); + QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); if(inputRecorder->isPlayingback()) { - newValue = inputRecorder->getActionState(Action(_input.getChannel())); + newValue = inputRecorder->getActionState(actionName); } _currentValue += newValue; if (_input != Input::INVALID_INPUT) { - auto userInputMapper = DependencyManager::get(); userInputMapper->deltaActionState(Action(_input.getChannel()), newValue); } - inputRecorder->setActionState(Action(_input.getChannel()), newValue); + inputRecorder->setActionState(actionName, newValue); } void ActionEndpoint::apply(const Pose& value, const Pointer& source) { _currentPose = value; InputRecorder* inputRecorder = InputRecorder::getInstance(); - inputRecorder->setActionState(Action(_input.getChannel()), _currentPose); - if (inputRecorder->isPlayingback()) { - _currentPose = inputRecorder->getPoseState(Action(_input.getChannel())); - } + auto userInputMapper = DependencyManager::get(); + QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); + inputRecorder->setActionState(actionName, _currentPose); if (!_currentPose.isValid()) { return; } if (_input != Input::INVALID_INPUT) { - auto userInputMapper = DependencyManager::get(); userInputMapper->setActionState(Action(_input.getChannel()), _currentPose); } } diff --git a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h index dfa728d2b6..2006809fed 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h @@ -12,6 +12,11 @@ #include "../Endpoint.h" +#include + +#include "../../InputRecorder.h" +#include "../../UserInputMapper.h" + namespace controller { class StandardEndpoint : public VirtualEndpoint { @@ -40,6 +45,12 @@ public: virtual Pose pose() override { _read = true; + InputRecorder* inputRecorder = InputRecorder::getInstance(); + if (inputRecorder->isPlayingback()) { + auto userInputMapper = DependencyManager::get(); + QString actionName = userInputMapper->getStandardPoseName(_input.getChannel()); + return inputRecorder->getPoseState(actionName); + } return VirtualEndpoint::pose(); } diff --git a/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h b/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h index d870a5c551..b1c6be1f58 100644 --- a/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h @@ -21,9 +21,9 @@ namespace controller { LowVelocityFilter(float rotationConstant, float translationConstant) : _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} - virtual float apply(float value) const override { return value; } - virtual Pose apply(Pose newPose) const; - virtual bool parseParameters(const QJsonValue& parameters) override; + float apply(float value) const override { return value; } + Pose apply(Pose newPose) const override; + bool parseParameters(const QJsonValue& parameters) override; private: float _translationConstant { 0.1f }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 06227cdcfc..1b92adbc37 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -192,7 +192,7 @@ void EntityTreeRenderer::update() { tree->update(); // Handle enter/leave entity logic - bool updated = checkEnterLeaveEntities(); + checkEnterLeaveEntities(); // Even if we're not moving the mouse, if we started clicking on an entity and we have // not yet released the hold then this is still considered a holdingClickOnEntity event diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 34dc86d92a..d813a73773 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "EntitiesRendererLogging.h" @@ -292,6 +293,7 @@ void RenderableParticleEffectEntityItem::createPipelines() { state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMask(*state); auto vertShader = gpu::Shader::createVertex(std::string(untextured_particle_vert)); auto fragShader = gpu::Shader::createPixel(std::string(untextured_particle_frag)); @@ -305,6 +307,7 @@ void RenderableParticleEffectEntityItem::createPipelines() { state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMask(*state); auto vertShader = gpu::Shader::createVertex(std::string(textured_particle_vert)); auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag)); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index a34a1814b4..1e20956301 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -69,6 +70,7 @@ void RenderablePolyLineEntityItem::createPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMask(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index fd5346093b..7567566919 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -46,6 +46,9 @@ #endif #include "model/Geometry.h" + +#include "StencilMaskPass.h" + #include "EntityTreeRenderer.h" #include "polyvox_vert.h" #include "polyvox_frag.h" @@ -743,6 +746,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { auto state = std::make_shared(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMaskDrawShape(*state); _pipeline = gpu::Pipeline::create(program, state); @@ -750,6 +754,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) { wireframeState->setCullMode(gpu::State::CULL_BACK); wireframeState->setDepthTest(true, true, gpu::LESS_EQUAL); wireframeState->setFillMode(gpu::State::FILL_LINE); + PrepareStencil::testMaskDrawShape(*wireframeState); _wireframePipeline = gpu::Pipeline::create(program, wireframeState); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 1ad60bf7c6..27dd678d91 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -93,6 +94,7 @@ void RenderableShapeEntityItem::render(RenderArgs* args) { _procedural->_fragmentSource = simple_frag; _procedural->_opaqueState->setCullMode(gpu::State::CULL_NONE); _procedural->_opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMaskDrawShape(*_procedural->_opaqueState); _procedural->_opaqueState->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 3f7c0937e2..d3fd9a0980 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -367,7 +367,6 @@ void RenderableZoneEntityItem::sceneUpdateRenderItemFromEntity(render::Transacti bool sunChanged = _keyLightPropertiesChanged; bool backgroundChanged = _backgroundPropertiesChanged; - bool stageChanged = _stagePropertiesChanged; bool skyboxChanged = _skyboxPropertiesChanged; transaction.updateItem(_myMetaItem, [=](RenderableZoneEntityItemMeta& data) { diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 431b87cc61..530e356137 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -681,7 +681,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // and pretend that we own it (we assume we'll recover it soon) // However, for now, when the server uses a newer time than what we sent, listen to what we're told. - if (overwriteLocalData) weOwnSimulation = false; + if (overwriteLocalData) { + weOwnSimulation = false; + } } else if (_simulationOwner.set(newSimOwner)) { markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); somethingChanged = true; @@ -1293,27 +1295,15 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c properties._accelerationChanged = true; } -void EntityItem::pokeSimulationOwnership() { - markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE); +void EntityItem::flagForOwnershipBid(uint8_t priority) { + markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); auto nodeList = DependencyManager::get(); if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { // we already own it - _simulationOwner.promotePriority(SCRIPT_POKE_SIMULATION_PRIORITY); + _simulationOwner.promotePriority(priority); } else { // we don't own it yet - _simulationOwner.setPendingPriority(SCRIPT_POKE_SIMULATION_PRIORITY, usecTimestampNow()); - } -} - -void EntityItem::grabSimulationOwnership() { - markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB); - auto nodeList = DependencyManager::get(); - if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { - // we already own it - _simulationOwner.promotePriority(SCRIPT_GRAB_SIMULATION_PRIORITY); - } else { - // we don't own it yet - _simulationOwner.setPendingPriority(SCRIPT_GRAB_SIMULATION_PRIORITY, usecTimestampNow()); + _simulationOwner.setPendingPriority(priority, usecTimestampNow()); } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 3243f50556..7c08137a1c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -321,6 +321,7 @@ public: void updateSimulationOwner(const SimulationOwner& owner); void clearSimulationOwnership(); void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); + uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); } void rememberHasSimulationOwnershipBid() const; QString getMarketplaceID() const; @@ -394,8 +395,7 @@ public: void getAllTerseUpdateProperties(EntityItemProperties& properties) const; - void pokeSimulationOwnership(); - void grabSimulationOwnership(); + void flagForOwnershipBid(uint8_t priority); void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; } QString actionsToDebugString(); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index b184d648da..90dc6893b4 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -230,6 +230,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties } entity->setLastBroadcast(usecTimestampNow()); + // since we're creating this object we will immediately volunteer to own its simulation + entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); propertiesWithSimID.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; @@ -440,7 +442,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); - entity->pokeSimulationOwnership(); + entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); entity->rememberHasSimulationOwnershipBid(); } } @@ -1194,7 +1196,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, } action->setIsMine(true); success = entity->addAction(simulation, action); - entity->grabSimulationOwnership(); + entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); return false; // Physics will cause a packet to be sent, so don't send from here. }); if (success) { @@ -1210,7 +1212,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { bool success = entity->updateAction(simulation, actionID, arguments); if (success) { - entity->grabSimulationOwnership(); + entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); } return success; }); @@ -1224,7 +1226,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& success = entity->removeAction(simulation, actionID); if (success) { // reduce from grab to poke - entity->pokeSimulationOwnership(); + entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); } return false; // Physics will cause a packet to be sent, so don't send from here. }); diff --git a/libraries/entities/src/SimulationFlags.h b/libraries/entities/src/SimulationFlags.h index dbcf51afc0..e2b2224b4a 100644 --- a/libraries/entities/src/SimulationFlags.h +++ b/libraries/entities/src/SimulationFlags.h @@ -26,12 +26,10 @@ namespace Simulation { const uint32_t DIRTY_MATERIAL = 0x00400; const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed - const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_POKE = 0x2000; // bid for simulation ownership at "poke" - const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB = 0x4000; // bid for simulation ownership at "grab" + const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; - const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = DIRTY_SIMULATION_OWNERSHIP_FOR_POKE | DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB; }; #endif // hifi_SimulationFlags_h diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index 77645a9e62..4398582673 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -26,9 +26,9 @@ const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; SimulationOwner::SimulationOwner() : _id(), _expiry(0), - _pendingTimestamp(0), + _pendingBidTimestamp(0), _priority(0), - _pendingPriority(0), + _pendingBidPriority(0), _pendingState(PENDING_STATE_NOTHING) { } @@ -36,9 +36,9 @@ SimulationOwner::SimulationOwner() : SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) : _id(id), _expiry(0), - _pendingTimestamp(0), + _pendingBidTimestamp(0), _priority(priority), - _pendingPriority(0) + _pendingBidPriority(0) { } @@ -61,9 +61,9 @@ bool SimulationOwner::fromByteArray(const QByteArray& data) { void SimulationOwner::clear() { _id = QUuid(); _expiry = 0; - _pendingTimestamp = 0; + _pendingBidTimestamp = 0; _priority = 0; - _pendingPriority = 0; + _pendingBidPriority = 0; _pendingState = PENDING_STATE_NOTHING; } @@ -102,9 +102,9 @@ bool SimulationOwner::set(const SimulationOwner& owner) { } void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) { - _pendingPriority = priority; - _pendingTimestamp = timestamp; - _pendingState = (_pendingPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; + _pendingBidPriority = priority; + _pendingBidTimestamp = timestamp; + _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; } void SimulationOwner::updateExpiry() { @@ -113,11 +113,11 @@ void SimulationOwner::updateExpiry() { } bool SimulationOwner::pendingRelease(const quint64& timestamp) { - return _pendingPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingTimestamp >= timestamp; + return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp; } bool SimulationOwner::pendingTake(const quint64& timestamp) { - return _pendingPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingTimestamp >= timestamp; + return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp; } void SimulationOwner::clearCurrentOwner() { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 5f940bbe25..94ed1d9a08 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -66,6 +66,7 @@ public: bool hasExpired() const { return usecTimestampNow() > _expiry; } + uint8_t getPendingPriority() const { return _pendingBidPriority; } bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE void clearCurrentOwner(); @@ -84,9 +85,9 @@ public: private: QUuid _id; // owner quint64 _expiry; // time when ownership can transition at equal priority - quint64 _pendingTimestamp; // time when pending update was set + quint64 _pendingBidTimestamp; // time when pending bid was set quint8 _priority; // priority of current owner - quint8 _pendingPriority; // priority of pendingTake + quint8 _pendingBidPriority; // priority at which we'd like to own it quint8 _pendingState; // NOTHING, TAKE, or RELEASE }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 26ce56b387..ef9b6c4297 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -211,6 +211,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { break; case gpu::NUINT32: case gpu::NINT32: + case gpu::COMPRESSED: case gpu::NUM_TYPES: // quiet compiler Q_UNREACHABLE(); } @@ -484,6 +485,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_R8_SNORM; break; } + case gpu::COMPRESSED: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -527,6 +529,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH_COMPONENT24; break; } + case gpu::COMPRESSED: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -641,6 +644,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::NUINT32: case gpu::NINT32: + case gpu::COMPRESSED: case gpu::NUM_TYPES: // quiet compiler Q_UNREACHABLE(); } diff --git a/libraries/gpu/src/gpu/DrawTransformVertexPosition.slv b/libraries/gpu/src/gpu/DrawTransformVertexPosition.slv new file mode 100644 index 0000000000..cf66a615f5 --- /dev/null +++ b/libraries/gpu/src/gpu/DrawTransformVertexPosition.slv @@ -0,0 +1,28 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Draw and transform the fed vertex position with the standard MVP stack +// Output the clip position +// +// Created by Sam Gateau on 5/30/2017 +// Copyright 2017 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 gpu/Transform.slh@> + +<$declareStandardTransform()$> + +layout(location = 0) in vec4 inPosition; + +out vec3 varWorldPos; + +void main(void) { + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> +} diff --git a/libraries/render-utils/src/hit_effect.slv b/libraries/gpu/src/gpu/DrawVertexPosition.slv similarity index 51% rename from libraries/render-utils/src/hit_effect.slv rename to libraries/gpu/src/gpu/DrawVertexPosition.slv index b9b0638f9e..b12280d577 100644 --- a/libraries/render-utils/src/hit_effect.slv +++ b/libraries/gpu/src/gpu/DrawVertexPosition.slv @@ -2,25 +2,18 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// hit_effect.vert -// vertex shader +// Draw the fed vertex position, pass straight as clip pos +// Output the clip position // -// Created by Eric Levin on 7/20/15. -// Copyright 2015 High Fidelity, Inc. +// Created by Sam Gateau on 5/30/2017 +// Copyright 2017 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 gpu/Inputs.slh@> - -<@include gpu/Transform.slh@> - -<$declareStandardTransform()$> - -out vec2 varQuadPosition; +layout(location = 0) in vec4 inPosition; void main(void) { - varQuadPosition = inPosition.xy; gl_Position = inPosition; -} \ No newline at end of file +} diff --git a/libraries/render-utils/src/drawOpaqueStencil.slf b/libraries/gpu/src/gpu/DrawWhite.slf similarity index 63% rename from libraries/render-utils/src/drawOpaqueStencil.slf rename to libraries/gpu/src/gpu/DrawWhite.slf index 43e9c5c27a..bdecc0c5c5 100644 --- a/libraries/render-utils/src/drawOpaqueStencil.slf +++ b/libraries/gpu/src/gpu/DrawWhite.slf @@ -2,15 +2,17 @@ <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // -// drawOpaqueStencil.frag -// fragment shader +// Draw white // -// Created by Sam Gateau on 9/29/15. -// Copyright 2015 High Fidelity, Inc. +// Created by Sam Gateau on 5/30/2017 +// Copyright 2017 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 // +out vec4 outFragColor; + void main(void) { + outFragColor = vec4(1.0); } diff --git a/libraries/gpu/src/gpu/StandardShaderLib.cpp b/libraries/gpu/src/gpu/StandardShaderLib.cpp index 81500347fd..756070ff68 100755 --- a/libraries/gpu/src/gpu/StandardShaderLib.cpp +++ b/libraries/gpu/src/gpu/StandardShaderLib.cpp @@ -16,6 +16,12 @@ #include "DrawTransformUnitQuad_vert.h" #include "DrawTexcoordRectTransformUnitQuad_vert.h" #include "DrawViewportQuadTransformTexcoord_vert.h" +#include "DrawVertexPosition_vert.h" +#include "DrawTransformVertexPosition_vert.h" + +const char DrawNada_frag[] = "void main(void) {}"; // DrawNada is really simple... + +#include "DrawWhite_frag.h" #include "DrawTexture_frag.h" #include "DrawTextureOpaque_frag.h" #include "DrawColoredTexture_frag.h" @@ -26,6 +32,10 @@ ShaderPointer StandardShaderLib::_drawUnitQuadTexcoordVS; ShaderPointer StandardShaderLib::_drawTransformUnitQuadVS; ShaderPointer StandardShaderLib::_drawTexcoordRectTransformUnitQuadVS; ShaderPointer StandardShaderLib::_drawViewportQuadTransformTexcoordVS; +ShaderPointer StandardShaderLib::_drawVertexPositionVS; +ShaderPointer StandardShaderLib::_drawTransformVertexPositionVS; +ShaderPointer StandardShaderLib::_drawNadaPS; +ShaderPointer StandardShaderLib::_drawWhitePS; ShaderPointer StandardShaderLib::_drawTexturePS; ShaderPointer StandardShaderLib::_drawTextureOpaquePS; ShaderPointer StandardShaderLib::_drawColoredTexturePS; @@ -85,6 +95,34 @@ ShaderPointer StandardShaderLib::getDrawViewportQuadTransformTexcoordVS() { return _drawViewportQuadTransformTexcoordVS; } +ShaderPointer StandardShaderLib::getDrawVertexPositionVS() { + if (!_drawVertexPositionVS) { + _drawVertexPositionVS = gpu::Shader::createVertex(std::string(DrawVertexPosition_vert)); + } + return _drawVertexPositionVS; +} + +ShaderPointer StandardShaderLib::getDrawTransformVertexPositionVS() { + if (!_drawTransformVertexPositionVS) { + _drawTransformVertexPositionVS = gpu::Shader::createVertex(std::string(DrawTransformVertexPosition_vert)); + } + return _drawTransformVertexPositionVS; +} + +ShaderPointer StandardShaderLib::getDrawNadaPS() { + if (!_drawNadaPS) { + _drawNadaPS = gpu::Shader::createPixel(std::string(DrawNada_frag)); + } + return _drawNadaPS; +} + +ShaderPointer StandardShaderLib::getDrawWhitePS() { + if (!_drawWhitePS) { + _drawWhitePS = gpu::Shader::createPixel(std::string(DrawWhite_frag)); + } + return _drawWhitePS; +} + ShaderPointer StandardShaderLib::getDrawTexturePS() { if (!_drawTexturePS) { _drawTexturePS = gpu::Shader::createPixel(std::string(DrawTexture_frag)); @@ -99,8 +137,6 @@ ShaderPointer StandardShaderLib::getDrawTextureOpaquePS() { return _drawTextureOpaquePS; } - - ShaderPointer StandardShaderLib::getDrawColoredTexturePS() { if (!_drawColoredTexturePS) { _drawColoredTexturePS = gpu::Shader::createPixel(std::string(DrawColoredTexture_frag)); diff --git a/libraries/gpu/src/gpu/StandardShaderLib.h b/libraries/gpu/src/gpu/StandardShaderLib.h index 12ea9045c2..a21d4dea9a 100755 --- a/libraries/gpu/src/gpu/StandardShaderLib.h +++ b/libraries/gpu/src/gpu/StandardShaderLib.h @@ -37,6 +37,15 @@ public: // Shader draws the unit quad in the full viewport clipPos = ([(-1,-1),(1,1)]) and transform the texcoord = [(0,0),(1,1)] by the model transform. static ShaderPointer getDrawViewportQuadTransformTexcoordVS(); + // Shader draw the fed vertex position and transform it by the full model transform stack (Model, View, Proj). + // simply output the world pos and the clip pos to the next stage + static ShaderPointer getDrawVertexPositionVS(); + static ShaderPointer getDrawTransformVertexPositionVS(); + + // PShader does nothing, no really nothing, but still needed for defining a program triggering rasterization + static ShaderPointer getDrawNadaPS(); + + static ShaderPointer getDrawWhitePS(); static ShaderPointer getDrawTexturePS(); static ShaderPointer getDrawTextureOpaquePS(); static ShaderPointer getDrawColoredTexturePS(); @@ -51,6 +60,12 @@ protected: static ShaderPointer _drawTransformUnitQuadVS; static ShaderPointer _drawTexcoordRectTransformUnitQuadVS; static ShaderPointer _drawViewportQuadTransformTexcoordVS; + + static ShaderPointer _drawVertexPositionVS; + static ShaderPointer _drawTransformVertexPositionVS; + + static ShaderPointer _drawNadaPS; + static ShaderPointer _drawWhitePS; static ShaderPointer _drawTexturePS; static ShaderPointer _drawTextureOpaquePS; static ShaderPointer _drawColoredTexturePS; diff --git a/libraries/ktx/src/khronos/KHR.h b/libraries/ktx/src/khronos/KHR.h index a98f2cc0d4..98cc1a4736 100644 --- a/libraries/ktx/src/khronos/KHR.h +++ b/libraries/ktx/src/khronos/KHR.h @@ -211,6 +211,8 @@ namespace khronos { template inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) { + enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) }; + static_assert(val, "template parameter ALIGNMENT must be a power of 2"); // FIXME add static assert that ALIGNMENT is a power of 2 static uint32_t ALIGNMENT_REMAINDER = ALIGNMENT - 1; return (value + ALIGNMENT_REMAINDER) / ALIGNMENT; diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp index c94856e598..6d6cfa81a2 100644 --- a/libraries/ktx/src/ktx/Writer.cpp +++ b/libraries/ktx/src/ktx/Writer.cpp @@ -229,7 +229,7 @@ namespace ktx { } else { Image::FaceBytes faceBytes(NUM_CUBEMAPFACES); auto faceSize = srcImages[l]._faceSize; - for (int face = 0; face < NUM_CUBEMAPFACES; face++) { + for (uint32_t face = 0; face < NUM_CUBEMAPFACES; face++) { memcpy(currentPtr, srcImages[l]._faceBytes[face], faceSize); faceBytes[face] = currentPtr; currentPtr += faceSize; diff --git a/libraries/model-networking/src/model-networking/KTXCache.cpp b/libraries/model-networking/src/model-networking/KTXCache.cpp index e0447af8e6..6443748920 100644 --- a/libraries/model-networking/src/model-networking/KTXCache.cpp +++ b/libraries/model-networking/src/model-networking/KTXCache.cpp @@ -11,14 +11,28 @@ #include "KTXCache.h" +#include #include using File = cache::File; using FilePointer = cache::FilePointer; +// Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible, +// this value should be incremented. This will force the KTX cache to be wiped +const int KTXCache::CURRENT_VERSION = 0x01; +const int KTXCache::INVALID_VERSION = 0x00; +const char* KTXCache::SETTING_VERSION_NAME = "hifi.ktx.cache_version"; + KTXCache::KTXCache(const std::string& dir, const std::string& ext) : FileCache(dir, ext) { initialize(); + + Setting::Handle cacheVersionHandle(SETTING_VERSION_NAME, INVALID_VERSION); + auto cacheVersion = cacheVersionHandle.get(); + if (cacheVersion != CURRENT_VERSION) { + wipe(); + cacheVersionHandle.set(CURRENT_VERSION); + } } KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) { diff --git a/libraries/model-networking/src/model-networking/KTXCache.h b/libraries/model-networking/src/model-networking/KTXCache.h index bbf7ceadea..5617019c52 100644 --- a/libraries/model-networking/src/model-networking/KTXCache.h +++ b/libraries/model-networking/src/model-networking/KTXCache.h @@ -27,6 +27,12 @@ class KTXCache : public cache::FileCache { Q_OBJECT public: + // Whenever a change is made to the serialized format for the KTX cache that isn't backward compatible, + // this value should be incremented. This will force the KTX cache to be wiped + static const int CURRENT_VERSION; + static const int INVALID_VERSION; + static const char* SETTING_VERSION_NAME; + KTXCache(const std::string& dir, const std::string& ext); KTXFilePointer writeFile(const char* data, Metadata&& metadata); diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index 16608ab63e..f88c8233ea 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -241,6 +241,42 @@ void Mesh::forEach(std::function vertexFunc, } } +MeshPointer Mesh::createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numIndices, const glm::vec3* vertices, const uint32_t* indices) { + MeshPointer mesh; + if (numVertices == 0) { return mesh; } + if (numIndices < 3) { return mesh; } + + mesh = std::make_shared(); + + // Vertex buffer + mesh->setVertexBuffer(gpu::BufferView(new gpu::Buffer(numVertices * sizeof(glm::vec3), (gpu::Byte*) vertices), gpu::Element::VEC3F_XYZ)); + + // trim down the indices to shorts if possible + if (numIndices < std::numeric_limits::max()) { + Indices16 shortIndicesVector; + int16_t* shortIndices = nullptr; + if (indices) { + shortIndicesVector.resize(numIndices); + for (uint32_t i = 0; i < numIndices; i++) { + shortIndicesVector[i] = indices[i]; + } + shortIndices = shortIndicesVector.data(); + } + + mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint16_t), (gpu::Byte*) shortIndices), gpu::Element::INDEX_UINT16)); + } else { + + mesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(numIndices * sizeof(uint32_t), (gpu::Byte*) indices), gpu::Element::INDEX_INT32)); + } + + + std::vector parts; + parts.push_back(model::Mesh::Part(0, numIndices, 0, model::Mesh::TRIANGLES)); + mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); + + return mesh; +} + Geometry::Geometry() { } @@ -256,3 +292,5 @@ Geometry::~Geometry() { void Geometry::setMesh(const MeshPointer& mesh) { _mesh = mesh; } + + diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index 7ba3e83407..2375944f04 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -65,6 +65,9 @@ public: const gpu::BufferStream& getVertexStream() const { return _vertexStream; } // Index Buffer + using Indices16 = std::vector; + using Indices32 = std::vector; + void setIndexBuffer(const BufferView& buffer); const BufferView& getIndexBuffer() const { return _indexBuffer; } size_t getNumIndices() const { return _indexBuffer.getNumElements(); } @@ -127,6 +130,9 @@ public: std::function normalFunc, std::function indexFunc); + + static MeshPointer createIndexedTriangles_P3F(uint32_t numVertices, uint32_t numTriangles, const glm::vec3* vertices = nullptr, const uint32_t* indices = nullptr); + protected: gpu::Stream::FormatPointer _vertexFormat; diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 4901a3c61b..d327593573 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -97,7 +97,7 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky } auto skyState = std::make_shared(); - skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + skyState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); thePipeline = gpu::Pipeline::create(skyShader, skyState); } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 6266ad0f89..fa6b49597d 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -45,7 +45,6 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) Q_DECLARE_METATYPE(JSONCallbackParameters) const QString ACCOUNTS_GROUP = "accounts"; -static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit(); JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, QObject* errorCallbackReceiver, const QString& errorCallbackMethod, @@ -201,6 +200,13 @@ void AccountManager::setAuthURL(const QUrl& authURL) { } } +void AccountManager::setSessionID(const QUuid& sessionID) { + if (_sessionID != sessionID) { + qCDebug(networking) << "Metaverse session ID changed to" << uuidStringWithoutCurlyBraces(sessionID); + _sessionID = sessionID; + } +} + void AccountManager::sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation, diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index dd2216957f..b37846ec1b 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -52,6 +52,7 @@ namespace AccountManagerAuth { Q_DECLARE_METATYPE(AccountManagerAuth::Type); const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; +const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit(); using UserAgentGetter = std::function; @@ -90,7 +91,7 @@ public: static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); QUuid getSessionID() const { return _sessionID; } - void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } + void setSessionID(const QUuid& sessionID); void setTemporaryDomain(const QUuid& domainID, const QString& key); const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); } diff --git a/libraries/networking/src/FileCache.cpp b/libraries/networking/src/FileCache.cpp index 8f3509d8f3..95304e3866 100644 --- a/libraries/networking/src/FileCache.cpp +++ b/libraries/networking/src/FileCache.cpp @@ -11,52 +11,81 @@ #include "FileCache.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include + +#include +#include +#include +#include #include +#include +#ifdef Q_OS_WIN +#include +#else +#include +#endif + +#ifdef NDEBUG Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache", QtWarningMsg) - +#else +Q_LOGGING_CATEGORY(file_cache, "hifi.file_cache") +#endif using namespace cache; -static const std::string MANIFEST_NAME = "manifest"; +static const char DIR_SEP = '/'; +static const char EXT_SEP = '.'; -static const size_t BYTES_PER_MEGABYTES = 1024 * 1024; -static const size_t BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES; -const size_t FileCache::DEFAULT_UNUSED_MAX_SIZE = 5 * BYTES_PER_GIGABYTES; // 5GB -const size_t FileCache::MAX_UNUSED_MAX_SIZE = 100 * BYTES_PER_GIGABYTES; // 100GB -const size_t FileCache::DEFAULT_OFFLINE_MAX_SIZE = 2 * BYTES_PER_GIGABYTES; // 2GB +const size_t FileCache::DEFAULT_MAX_SIZE { GB_TO_BYTES(5) }; +const size_t FileCache::MAX_MAX_SIZE { GB_TO_BYTES(100) }; +const size_t FileCache::DEFAULT_MIN_FREE_STORAGE_SPACE { GB_TO_BYTES(1) }; -void FileCache::setUnusedFileCacheSize(size_t unusedFilesMaxSize) { - _unusedFilesMaxSize = std::min(unusedFilesMaxSize, MAX_UNUSED_MAX_SIZE); - reserve(0); + +std::string getCacheName(const std::string& dirname_str) { + QString dirname { dirname_str.c_str() }; + QDir dir(dirname); + if (!dir.isAbsolute()) { + return dirname_str; + } + return dir.dirName().toStdString(); +} + +std::string getCachePath(const std::string& dirname_str) { + QString dirname { dirname_str.c_str() }; + QDir dir(dirname); + if (dir.isAbsolute()) { + return dirname_str; + } + return PathUtils::getAppLocalDataFilePath(dirname).toStdString(); +} + +void FileCache::setMinFreeSize(size_t size) { + _minFreeSpaceSize = size; + clean(); emit dirty(); } -void FileCache::setOfflineFileCacheSize(size_t offlineFilesMaxSize) { - _offlineFilesMaxSize = std::min(offlineFilesMaxSize, MAX_UNUSED_MAX_SIZE); +void FileCache::setMaxSize(size_t maxSize) { + _maxSize = std::min(maxSize, MAX_MAX_SIZE); + clean(); + emit dirty(); } FileCache::FileCache(const std::string& dirname, const std::string& ext, QObject* parent) : QObject(parent), _ext(ext), - _dirname(dirname), - _dirpath(PathUtils::getAppLocalDataFilePath(dirname.c_str()).toStdString()) {} + _dirname(getCacheName(dirname)), + _dirpath(getCachePath(dirname)) { +} FileCache::~FileCache() { clear(); } -void fileDeleter(File* file) { - file->deleter(); -} - void FileCache::initialize() { QDir dir(_dirpath.c_str()); @@ -84,7 +113,7 @@ void FileCache::initialize() { } FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath) { - FilePointer file(createFile(std::move(metadata), filepath).release(), &fileDeleter); + FilePointer file(createFile(std::move(metadata), filepath).release(), std::mem_fn(&File::deleter)); if (file) { _numTotalFiles += 1; _totalFilesSize += file->getLength(); @@ -141,6 +170,7 @@ FilePointer FileCache::getFile(const Key& key) { if (it != _files.cend()) { file = it->second.lock(); if (file) { + file->touch(); // if it exists, it is active - remove it from the cache removeUnusedFile(file); qCDebug(file_cache, "[%s] Found %s", _dirname.c_str(), key.c_str()); @@ -155,82 +185,127 @@ FilePointer FileCache::getFile(const Key& key) { } std::string FileCache::getFilepath(const Key& key) { - return _dirpath + '/' + key + '.' + _ext; + return _dirpath + DIR_SEP + key + EXT_SEP + _ext; } -void FileCache::addUnusedFile(const FilePointer file) { +void FileCache::addUnusedFile(const FilePointer& file) { { Lock lock(_filesMutex); _files[file->getKey()] = file; } - reserve(file->getLength()); - file->_LRUKey = ++_lastLRUKey; - { Lock lock(_unusedFilesMutex); - _unusedFiles.insert({ file->_LRUKey, file }); + _unusedFiles.insert(file); _numUnusedFiles += 1; _unusedFilesSize += file->getLength(); } + clean(); emit dirty(); } -void FileCache::removeUnusedFile(const FilePointer file) { +void FileCache::removeUnusedFile(const FilePointer& file) { Lock lock(_unusedFilesMutex); - const auto it = _unusedFiles.find(file->_LRUKey); - if (it != _unusedFiles.cend()) { - _unusedFiles.erase(it); + if (_unusedFiles.erase(file)) { _numUnusedFiles -= 1; _unusedFilesSize -= file->getLength(); } } -void FileCache::reserve(size_t length) { - Lock unusedLock(_unusedFilesMutex); - while (!_unusedFiles.empty() && - _unusedFilesSize + length > _unusedFilesMaxSize) { - auto it = _unusedFiles.begin(); - auto file = it->second; - auto length = file->getLength(); +size_t FileCache::getOverbudgetAmount() const { + size_t result = 0; - unusedLock.unlock(); - { - file->_cache = nullptr; - Lock lock(_filesMutex); - _files.erase(file->getKey()); + size_t currentFreeSpace = QStorageInfo(_dirpath.c_str()).bytesFree(); + if (_minFreeSpaceSize > currentFreeSpace) { + result = _minFreeSpaceSize - currentFreeSpace; + } + + if (_totalFilesSize > _maxSize) { + result = std::max(_totalFilesSize - _maxSize, result); + } + + return result; +} + +namespace cache { + struct FilePointerComparator { + bool operator()(const FilePointer& a, const FilePointer& b) { + return a->_modified > b->_modified; } - unusedLock.lock(); + }; +} - _unusedFiles.erase(it); - _numTotalFiles -= 1; - _numUnusedFiles -= 1; - _totalFilesSize -= length; - _unusedFilesSize -= length; +void FileCache::eject(const FilePointer& file) { + file->_cache = nullptr; + const auto& length = file->getLength(); + const auto& key = file->getKey(); + + { + Lock lock(_filesMutex); + if (0 != _files.erase(key)) { + _numTotalFiles -= 1; + _totalFilesSize -= length; + } + } + + { + Lock unusedLock(_unusedFilesMutex); + if (0 != _unusedFiles.erase(file)) { + _numUnusedFiles -= 1; + _unusedFilesSize -= length; + } + } +} + +void FileCache::clean() { + size_t overbudgetAmount = getOverbudgetAmount(); + + // Avoid sorting the unused files by LRU if we're not over budget / under free space + if (0 == overbudgetAmount) { + return; + } + + Lock unusedLock(_unusedFilesMutex); + using Queue = std::priority_queue, FilePointerComparator>; + Queue queue; + for (const auto& file : _unusedFiles) { + queue.push(file); + } + + while (!queue.empty() && overbudgetAmount > 0) { + auto file = queue.top(); + queue.pop(); + eject(file); + auto length = file->getLength(); + overbudgetAmount -= std::min(length, overbudgetAmount); + } +} + +void FileCache::wipe() { + Lock unusedFilesLock(_unusedFilesMutex); + while (!_unusedFiles.empty()) { + eject(*_unusedFiles.begin()); } } void FileCache::clear() { - Lock unusedFilesLock(_unusedFilesMutex); - for (const auto& pair : _unusedFiles) { - auto& file = pair.second; - file->_cache = nullptr; + // Eliminate any overbudget files + clean(); - if (_totalFilesSize > _offlineFilesMaxSize) { - _totalFilesSize -= file->getLength(); - } else { - file->_shouldPersist = true; - qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str()); - } + // Mark everything remaining as persisted + Lock unusedFilesLock(_unusedFilesMutex); + for (auto& file : _unusedFiles) { + file->_shouldPersist = true; + file->_cache = nullptr; + qCDebug(file_cache, "[%s] Persisting %s", _dirname.c_str(), file->getKey().c_str()); } _unusedFiles.clear(); } void File::deleter() { if (_cache) { - FilePointer self(this, &fileDeleter); - _cache->addUnusedFile(self); + _cache->addUnusedFile(FilePointer(this, std::mem_fn(&File::deleter))); } else { deleteLater(); } @@ -239,7 +314,9 @@ void File::deleter() { File::File(Metadata&& metadata, const std::string& filepath) : _key(std::move(metadata.key)), _length(metadata.length), - _filepath(filepath) {} + _filepath(filepath), + _modified(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch()) { +} File::~File() { QFile file(getFilepath().c_str()); @@ -248,3 +325,8 @@ File::~File() { file.remove(); } } + +void File::touch() { + utime(_filepath.c_str(), nullptr); + _modified = std::max(QFileInfo(_filepath.c_str()).lastRead().toMSecsSinceEpoch(), _modified); +} \ No newline at end of file diff --git a/libraries/networking/src/FileCache.h b/libraries/networking/src/FileCache.h index 908ddcd285..f29d75f779 100644 --- a/libraries/networking/src/FileCache.h +++ b/libraries/networking/src/FileCache.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -35,24 +36,31 @@ class FileCache : public QObject { Q_PROPERTY(size_t sizeTotal READ getSizeTotalFiles NOTIFY dirty) Q_PROPERTY(size_t sizeCached READ getSizeCachedFiles NOTIFY dirty) - static const size_t DEFAULT_UNUSED_MAX_SIZE; - static const size_t MAX_UNUSED_MAX_SIZE; - static const size_t DEFAULT_OFFLINE_MAX_SIZE; + static const size_t DEFAULT_MAX_SIZE; + static const size_t MAX_MAX_SIZE; + static const size_t DEFAULT_MIN_FREE_STORAGE_SPACE; public: + // You can initialize the FileCache with a directory name (ex.: "temp_jpgs") that will be relative to the application local data, OR with a full path + // The file cache will ignore any file that doesn't match the extension provided + FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr); + virtual ~FileCache(); + + // Remove all unlocked items from the cache + void wipe(); + size_t getNumTotalFiles() const { return _numTotalFiles; } size_t getNumCachedFiles() const { return _numUnusedFiles; } size_t getSizeTotalFiles() const { return _totalFilesSize; } size_t getSizeCachedFiles() const { return _unusedFilesSize; } - void setUnusedFileCacheSize(size_t unusedFilesMaxSize); - size_t getUnusedFileCacheSize() const { return _unusedFilesSize; } + // Set the maximum amount of disk space to use on disk + void setMaxSize(size_t maxCacheSize); - void setOfflineFileCacheSize(size_t offlineFilesMaxSize); - - // initialize FileCache with a directory name (not a path, ex.: "temp_jpgs") and an ext (ex.: "jpg") - FileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr); - virtual ~FileCache(); + // Set the minumum amount of free disk space to retain. This supercedes the max size, + // so if the cache is consuming all but 500 MB of the drive, unused entries will be ejected + // to free up more space, regardless of the cache max size + void setMinFreeSize(size_t size); using Key = std::string; struct Metadata { @@ -76,10 +84,11 @@ public: signals: void dirty(); -protected: +public: /// must be called after construction to create the cache on the fs and restore persisted files void initialize(); + // Add file to the cache and return the cache entry. FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false); FilePointer getFile(const Key& key); @@ -89,17 +98,28 @@ protected: private: using Mutex = std::recursive_mutex; using Lock = std::unique_lock; + using Map = std::unordered_map>; + using Set = std::unordered_set; + using KeySet = std::unordered_set; friend class File; std::string getFilepath(const Key& key); FilePointer addFile(Metadata&& metadata, const std::string& filepath); - void addUnusedFile(const FilePointer file); - void removeUnusedFile(const FilePointer file); - void reserve(size_t length); + void addUnusedFile(const FilePointer& file); + void removeUnusedFile(const FilePointer& file); + void clean(); void clear(); + // Remove a file from the cache + void eject(const FilePointer& file); + size_t getOverbudgetAmount() const; + + // FIXME it might be desirable to have the min free space variable be static so it can be + // shared among multiple instances of FileCache + std::atomic _minFreeSpaceSize { DEFAULT_MIN_FREE_STORAGE_SPACE }; + std::atomic _maxSize { DEFAULT_MAX_SIZE }; std::atomic _numTotalFiles { 0 }; std::atomic _numUnusedFiles { 0 }; std::atomic _totalFilesSize { 0 }; @@ -110,15 +130,11 @@ private: std::string _dirpath; bool _initialized { false }; - std::unordered_map> _files; + Map _files; Mutex _filesMutex; - std::map _unusedFiles; + Set _unusedFiles; Mutex _unusedFilesMutex; - size_t _unusedFilesMaxSize { DEFAULT_UNUSED_MAX_SIZE }; - int _lastLRUKey { 0 }; - - size_t _offlineFilesMaxSize { DEFAULT_OFFLINE_MAX_SIZE }; }; class File : public QObject { @@ -128,8 +144,8 @@ public: using Key = FileCache::Key; using Metadata = FileCache::Metadata; - Key getKey() const { return _key; } - size_t getLength() const { return _length; } + const Key& getKey() const { return _key; } + const size_t& getLength() const { return _length; } std::string getFilepath() const { return _filepath; } virtual ~File(); @@ -142,13 +158,15 @@ protected: private: friend class FileCache; + friend struct FilePointerComparator; const Key _key; const size_t _length; const std::string _filepath; - FileCache* _cache; - int _LRUKey { 0 }; + void touch(); + FileCache* _cache { nullptr }; + int64_t _modified { 0 }; bool _shouldPersist { false }; }; diff --git a/libraries/networking/src/SandboxUtils.cpp b/libraries/networking/src/SandboxUtils.cpp index d816f7ebee..4a348b0662 100644 --- a/libraries/networking/src/SandboxUtils.cpp +++ b/libraries/networking/src/SandboxUtils.cpp @@ -52,9 +52,8 @@ bool readStatus(QByteArray statusData) { return false; } -void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) { +void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater) { QString serverPath = "./server-console/server-console.exe"; - qCDebug(networking) << "Running marker path is: " << runningMarkerName; qCDebug(networking) << "Server path is: " << serverPath; qCDebug(networking) << "autoShutdown: " << autoShutdown; qCDebug(networking) << "noUpdater: " << noUpdater; @@ -74,8 +73,8 @@ void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMark } if (autoShutdown) { - QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName); - args << "--shutdownWatcher" << interfaceRunningStateFile; + auto pid = QCoreApplication::applicationPid(); + args << "--shutdownWith" << QString::number(pid); } if (noUpdater) { diff --git a/libraries/networking/src/SandboxUtils.h b/libraries/networking/src/SandboxUtils.h index 42484b8edf..370b28e1b0 100644 --- a/libraries/networking/src/SandboxUtils.h +++ b/libraries/networking/src/SandboxUtils.h @@ -21,7 +21,7 @@ namespace SandboxUtils { QNetworkReply* getStatus(); bool readStatus(QByteArray statusData); - void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater); + void runLocalSandbox(QString contentPath, bool autoShutdown, bool noUpdater); }; #endif // hifi_SandboxUtils_h diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index e2dd110cfd..28117c0933 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -20,8 +20,6 @@ #include #include "AddressManager.h" -static const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; - UserActivityLogger& UserActivityLogger::getInstance() { static UserActivityLogger sharedInstance; return sharedInstance; diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index b41960a8ad..9fad498b86 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -22,6 +22,8 @@ #include #include "AddressManager.h" +const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; + class UserActivityLogger : public QObject { Q_OBJECT diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index ff69363570..61f2071c5f 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -56,7 +56,7 @@ void UserActivityLoggerScriptingInterface::palAction(QString action, QString tar } void UserActivityLoggerScriptingInterface::palOpened(float secondsOpened) { - doLogAction("pal_opened", { + doLogAction("pal_opened", { { "seconds_opened", secondsOpened } }); } @@ -71,6 +71,14 @@ void UserActivityLoggerScriptingInterface::makeUserConnection(QString otherID, b doLogAction("makeUserConnection", payload); } +void UserActivityLoggerScriptingInterface::bubbleToggled(bool newValue) { + doLogAction(newValue ? "bubbleOn" : "bubbleOff"); +} + +void UserActivityLoggerScriptingInterface::bubbleActivated() { + doLogAction("bubbleActivated"); +} + void UserActivityLoggerScriptingInterface::logAction(QString action, QVariantMap details) { doLogAction(action, QJsonObject::fromVariantMap(details)); } diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index b141e930f2..885f637a62 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -30,6 +30,8 @@ public: Q_INVOKABLE void palAction(QString action, QString target); Q_INVOKABLE void palOpened(float secondsOpen); Q_INVOKABLE void makeUserConnection(QString otherUser, bool success, QString details = ""); + Q_INVOKABLE void bubbleToggled(bool newValue); + Q_INVOKABLE void bubbleActivated(); Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); private: void doLogAction(QString action, QJsonObject details = {}); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index fdd290bfca..ecfb2b55e4 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -65,8 +65,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _lastStep(0), _loopsWithoutOwner(0), _accelerationNearlyGravityCount(0), - _numInactiveUpdates(1), - _outgoingPriority(0) + _numInactiveUpdates(1) { _type = MOTIONSTATE_TYPE_ENTITY; assert(_entity); @@ -75,6 +74,8 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // we need the side-effects of EntityMotionState::setShape() so we call it explicitly here // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); + + _outgoingPriority = _entity->getPendingOwnershipPriority(); } EntityMotionState::~EntityMotionState() { @@ -84,7 +85,7 @@ EntityMotionState::~EntityMotionState() { void EntityMotionState::updateServerPhysicsVariables() { assert(entityTreeIsLocked()); - if (_entity->getSimulatorID() == Physics::getSessionUUID()) { + if (isLocallyOwned()) { // don't slam these values if we are the simulation owner return; } @@ -114,6 +115,7 @@ void EntityMotionState::handleDeactivation() { // virtual void EntityMotionState::handleEasyChanges(uint32_t& flags) { + assert(_entity); assert(entityTreeIsLocked()); updateServerPhysicsVariables(); ObjectMotionState::handleEasyChanges(flags); @@ -135,23 +137,23 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } _loopsWithoutOwner = 0; - } else if (_entity->getSimulatorID() == Physics::getSessionUUID()) { + _numInactiveUpdates = 0; + } else if (isLocallyOwned()) { // we just inherited ownership, make sure our desired priority matches what we have upgradeOutgoingPriority(_entity->getSimulationPriority()); } else { _outgoingPriority = 0; _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + _numInactiveUpdates = 0; } } if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) { - // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bits really mean "we should bid for ownership because - // a local script has been changing physics properties, or we should adjust our own ownership priority". - // The desired priority is determined by which bits were set. - if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB) { - _outgoingPriority = SCRIPT_GRAB_SIMULATION_PRIORITY; - } else { - _outgoingPriority = SCRIPT_POKE_SIMULATION_PRIORITY; - } + // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bit means one of the following: + // (1) we own it but may need to change the priority OR... + // (2) we don't own it but should bid (because a local script has been changing physics properties) + uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); + _outgoingPriority = glm::max(_outgoingPriority, newPriority); + // reset bid expiry so that we bid ASAP _nextOwnershipBid = 0; } @@ -170,6 +172,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // virtual bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { + assert(_entity); updateServerPhysicsVariables(); return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } @@ -315,7 +318,7 @@ bool EntityMotionState::isCandidateForOwnership() const { assert(_entity); assert(entityTreeIsLocked()); return _outgoingPriority != 0 - || Physics::getSessionUUID() == _entity->getSimulatorID() + || isLocallyOwned() || _entity->dynamicDataNeedsTransmit(); } @@ -489,7 +492,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { return true; } - if (_entity->getSimulatorID() != Physics::getSessionUUID()) { + if (!isLocallyOwned()) { // we don't own the simulation // NOTE: we do not volunteer to own kinematic or static objects @@ -597,7 +600,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.clearSimulationOwner(); _outgoingPriority = 0; _entity->setPendingOwnershipPriority(_outgoingPriority, now); - } else if (Physics::getSessionUUID() != _entity->getSimulatorID()) { + } else if (!isLocallyOwned()) { // we don't own the simulation for this entity yet, but we're sending a bid for it quint8 bidPriority = glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); @@ -786,6 +789,10 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma _entity->computeCollisionGroupAndFinalMask(group, mask); } +bool EntityMotionState::isLocallyOwned() const { + return _entity->getSimulatorID() == Physics::getSessionUUID(); +} + bool EntityMotionState::shouldBeLocallyOwned() const { return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || _entity->getSimulatorID() == Physics::getSessionUUID(); diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 380edf3927..541ad7c93c 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -79,6 +79,7 @@ public: virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; + bool isLocallyOwned() const override; bool shouldBeLocallyOwned() const override; friend class PhysicalEntitySimulation; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index a877887840..b11e21366e 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -202,6 +202,7 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) { } void ObjectMotionState::handleEasyChanges(uint32_t& flags) { + assert(_body && _shape); if (flags & Simulation::DIRTY_POSITION) { btTransform worldTrans = _body->getWorldTransform(); btVector3 newPosition = glmToBullet(getObjectPosition()); @@ -282,6 +283,7 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) { } bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { + assert(_body && _shape); if (flags & Simulation::DIRTY_SHAPE) { // make sure the new shape is valid if (!isReadyToComputeShape()) { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 1e582ea854..81bfbc72b4 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -79,7 +79,7 @@ public: static ShapeManager* getShapeManager(); ObjectMotionState(const btCollisionShape* shape); - ~ObjectMotionState(); + virtual ~ObjectMotionState(); virtual void handleEasyChanges(uint32_t& flags); virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine); @@ -146,6 +146,7 @@ public: void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; } void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } + virtual bool isLocallyOwned() const { return false; } virtual bool shouldBeLocallyOwned() const { return false; } friend class PhysicsEngine; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 2e69ff987c..fe507ed1ee 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -130,7 +130,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { } // then remove the objects (aka MotionStates) from physics - _physicsEngine->removeObjects(_physicalObjects); + _physicsEngine->removeSetOfObjects(_physicalObjects); // delete the MotionStates // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 3a02e95e7c..a64796308e 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -129,6 +129,9 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { } body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); body->updateInertiaTensor(); + if (motionState->isLocallyOwned()) { + _activeStaticBodies.insert(body); + } break; } } @@ -174,19 +177,9 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { // frame (because the framerate is faster than our physics simulation rate). When this happens we must scan // _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer. for (auto object : objects) { - btRigidBody* body = object->getRigidBody(); - - std::vector::reverse_iterator itr = _activeStaticBodies.rbegin(); - while (itr != _activeStaticBodies.rend()) { - if (body == *itr) { - if (*itr != *(_activeStaticBodies.rbegin())) { - // swap with rbegin - *itr = *(_activeStaticBodies.rbegin()); - } - _activeStaticBodies.pop_back(); - break; - } - ++itr; + std::set::iterator itr = _activeStaticBodies.find(object->getRigidBody()); + if (itr != _activeStaticBodies.end()) { + _activeStaticBodies.erase(itr); } } } @@ -207,7 +200,7 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { } // Same as above, but takes a Set instead of a Vector. Should only be called during teardown. -void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) { +void PhysicsEngine::removeSetOfObjects(const SetOfMotionStates& objects) { _contactMap.clear(); for (auto object : objects) { btRigidBody* body = object->getRigidBody(); @@ -245,14 +238,16 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob object->clearIncomingDirtyFlags(); } if (object->getMotionType() == MOTION_TYPE_STATIC && object->isActive()) { - _activeStaticBodies.push_back(object->getRigidBody()); + _activeStaticBodies.insert(object->getRigidBody()); } } // active static bodies have changed (in an Easy way) and need their Aabbs updated // but we've configured Bullet to NOT update them automatically (for improved performance) // so we must do it ourselves - for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { - _dynamicsWorld->updateSingleAabb(_activeStaticBodies[i]); + std::set::const_iterator itr = _activeStaticBodies.begin(); + while (itr != _activeStaticBodies.end()) { + _dynamicsWorld->updateSingleAabb(*itr); + ++itr; } return stillNeedChange; } @@ -496,13 +491,23 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() { BT_PROFILE("copyOutgoingChanges"); + + _dynamicsWorld->synchronizeMotionStates(); + // Bullet will not deactivate static objects (it doesn't expect them to be active) // so we must deactivate them ourselves - for (size_t i = 0; i < _activeStaticBodies.size(); ++i) { - _activeStaticBodies[i]->forceActivationState(ISLAND_SLEEPING); + std::set::const_iterator itr = _activeStaticBodies.begin(); + while (itr != _activeStaticBodies.end()) { + btRigidBody* body = *itr; + body->forceActivationState(ISLAND_SLEEPING); + ObjectMotionState* motionState = static_cast(body->getUserPointer()); + if (motionState) { + _dynamicsWorld->addChangedMotionState(motionState); + } + ++itr; } _activeStaticBodies.clear(); - _dynamicsWorld->synchronizeMotionStates(); + _hasOutgoingChanges = false; return _dynamicsWorld->getChangedMotionStates(); } diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index e9b29a43a4..3063a4a89a 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -13,6 +13,7 @@ #define hifi_PhysicsEngine_h #include +#include #include #include @@ -53,7 +54,7 @@ public: uint32_t getNumSubsteps(); void removeObjects(const VectorOfMotionStates& objects); - void removeObjects(const SetOfMotionStates& objects); // only called during teardown + void removeSetOfObjects(const SetOfMotionStates& objects); // only called during teardown void addObjects(const VectorOfMotionStates& objects); VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects); @@ -114,7 +115,7 @@ private: CollisionEvents _collisionEvents; QHash _objectDynamics; QHash> _objectDynamicsByBody; - std::vector _activeStaticBodies; + std::set _activeStaticBodies; glm::vec3 _originOffset; diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.h b/libraries/physics/src/ThreadSafeDynamicsWorld.h index b4fcca8cdb..54c3ddb756 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.h +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.h @@ -51,6 +51,8 @@ public: const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; } const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; } + void addChangedMotionState(ObjectMotionState* motionState) { _changedMotionStates.push_back(motionState); } + private: // call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState() void synchronizeMotionState(btRigidBody* body); diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 0275a875ec..f5feb434fa 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -23,7 +23,7 @@ ProceduralSkybox::ProceduralSkybox() : model::Skybox() { _procedural._fragmentSource = skybox_frag; // Adjust the pipeline state for background using the stencil test _procedural.setDoesFade(false); - _procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + _procedural._opaqueState->setStencilTest(true, 0xFF, gpu::State::StencilTest(1, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); } bool ProceduralSkybox::empty() { diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 162745e76f..4f7f9ef5c4 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -260,7 +260,7 @@ static void addLink(const AnimPose& rootPose, const AnimPose& pose, const AnimPo // there is room, so lets draw a nice bone glm::vec3 uAxis, vAxis, wAxis; - generateBasisVectors(boneAxis0, glm::vec3(1, 0, 0), uAxis, vAxis, wAxis); + generateBasisVectors(boneAxis0, glm::vec3(1.0f, 0.0f, 0.0f), uAxis, vAxis, wAxis); glm::vec3 boneBaseCorners[NUM_BASE_CORNERS]; boneBaseCorners[0] = pose0 * ((uAxis * radius) + (vAxis * radius) + (wAxis * radius)); diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index cd378d4e5b..139f1ae091 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -17,6 +17,7 @@ #include #include "AntialiasingEffect.h" +#include "StencilMaskPass.h" #include "TextureCache.h" #include "FramebufferCache.h" #include "DependencyManager.h" @@ -70,6 +71,8 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testMask(*state); + state->setDepthTest(false, false, gpu::LESS_EQUAL); // Good to go add the brand new pipeline @@ -93,6 +96,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); + PrepareStencil::testMask(*state); // Good to go add the brand new pipeline _blendPipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 1a85a70863..5c2f55954a 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -112,7 +112,6 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, skybox->render(batch, args->getViewFrustum()); }); args->_batch = nullptr; - gpu::Batch& batch = *args->_batch; // break; } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index e1042912aa..36a9401d00 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -18,6 +18,7 @@ #include #include +#include "StencilMaskPass.h" #include "AbstractViewStateInterface.h" #include "GeometryCache.h" #include "TextureCache.h" @@ -27,18 +28,15 @@ #include "deferred_light_point_vert.h" #include "deferred_light_spot_vert.h" -#include "directional_light_frag.h" #include "directional_ambient_light_frag.h" #include "directional_skybox_light_frag.h" -#include "directional_light_shadow_frag.h" #include "directional_ambient_light_shadow_frag.h" #include "directional_skybox_light_shadow_frag.h" #include "local_lights_shading_frag.h" #include "local_lights_drawOutline_frag.h" -#include "point_light_frag.h" -#include "spot_light_frag.h" + using namespace render; @@ -82,48 +80,26 @@ enum DeferredShader_BufferSlot { }; static void loadLightProgram(const char* vertSource, const char* fragSource, bool lightVolume, gpu::PipelinePointer& program, LightLocationsPtr& locations); -static void loadLightVolumeProgram(const char* vertSource, const char* fragSource, bool front, gpu::PipelinePointer& program, LightLocationsPtr& locations); - -const char no_light_frag[] = -R"SCRIBE( -out vec4 _fragColor; - -void main(void) { - _fragColor = vec4(1.0, 1.0, 1.0, 1.0); -} -)SCRIBE" -; void DeferredLightingEffect::init() { - _directionalLightLocations = std::make_shared(); _directionalAmbientSphereLightLocations = std::make_shared(); _directionalSkyboxLightLocations = std::make_shared(); - _directionalLightShadowLocations = std::make_shared(); _directionalAmbientSphereLightShadowLocations = std::make_shared(); _directionalSkyboxLightShadowLocations = std::make_shared(); _localLightLocations = std::make_shared(); _localLightOutlineLocations = std::make_shared(); - _pointLightLocations = std::make_shared(); - _spotLightLocations = std::make_shared(); - loadLightProgram(deferred_light_vert, directional_light_frag, false, _directionalLight, _directionalLightLocations); loadLightProgram(deferred_light_vert, directional_ambient_light_frag, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); loadLightProgram(deferred_light_vert, directional_skybox_light_frag, false, _directionalSkyboxLight, _directionalSkyboxLightLocations); - loadLightProgram(deferred_light_vert, directional_light_shadow_frag, false, _directionalLightShadow, _directionalLightShadowLocations); loadLightProgram(deferred_light_vert, directional_ambient_light_shadow_frag, false, _directionalAmbientSphereLightShadow, _directionalAmbientSphereLightShadowLocations); loadLightProgram(deferred_light_vert, directional_skybox_light_shadow_frag, false, _directionalSkyboxLightShadow, _directionalSkyboxLightShadowLocations); loadLightProgram(deferred_light_vert, local_lights_shading_frag, true, _localLight, _localLightLocations); loadLightProgram(deferred_light_vert, local_lights_drawOutline_frag, true, _localLightOutline, _localLightOutlineLocations); - loadLightVolumeProgram(deferred_light_point_vert, no_light_frag, false, _pointLightBack, _pointLightLocations); - loadLightVolumeProgram(deferred_light_point_vert, no_light_frag, true, _pointLightFront, _pointLightLocations); - loadLightVolumeProgram(deferred_light_spot_vert, no_light_frag, false, _spotLightBack, _spotLightLocations); - loadLightVolumeProgram(deferred_light_spot_vert, no_light_frag, true, _spotLightFront, _spotLightLocations); - // Light Stage and clusters _lightStage = std::make_shared(); @@ -160,11 +136,11 @@ void DeferredLightingEffect::init() { lp->setAmbientIntensity(0.5f); - lp->setAmbientMap(_defaultSkyboxAmbientTexture); - auto irradianceSH = _defaultSkyboxAmbientTexture->getIrradiance(); - if (irradianceSH) { - lp->setAmbientSphere((*irradianceSH)); - } + lp->setAmbientMap(_defaultSkyboxAmbientTexture); + auto irradianceSH = _defaultSkyboxAmbientTexture->getIrradiance(); + if (irradianceSH) { + lp->setAmbientSphere((*irradianceSH)); + } } void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) { @@ -267,7 +243,7 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo state->setColorWriteMask(true, true, true, false); if (lightVolume) { - state->setStencilTest(true, 0x00, gpu::State::StencilTest(1, 0xFF, gpu::LESS_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + PrepareStencil::testShape(*state); state->setCullMode(gpu::State::CULL_BACK); // state->setCullMode(gpu::State::CULL_FRONT); @@ -280,7 +256,7 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo } else { // Stencil test all the light passes for objects pixels only, not the background - state->setStencilTest(true, 0x00, gpu::State::StencilTest(0, 0x01, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + PrepareStencil::testShape(*state); state->setCullMode(gpu::State::CULL_BACK); // additive blending @@ -290,39 +266,6 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo } - -static void loadLightVolumeProgram(const char* vertSource, const char* fragSource, bool front, gpu::PipelinePointer& pipeline, LightLocationsPtr& locations) { - gpu::ShaderPointer program = makeLightProgram(vertSource, fragSource, locations); - - auto state = std::make_shared(); - - // Stencil test all the light passes for objects pixels only, not the background - - if (front) { - state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_DECR, gpu::State::STENCIL_OP_KEEP)); - - // state->setDepthClampEnable(true); - // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases - // additive blending - // state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - - //state->setColorWriteMask(true, true, true, false); - state->setColorWriteMask(false, false, false, false); - } else { - state->setCullMode(gpu::State::CULL_FRONT); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_INCR, gpu::State::STENCIL_OP_KEEP)); - // additive blending - // state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - // state->setColorWriteMask(true, true, true, false); - state->setColorWriteMask(false, false, false, false); - } - pipeline = gpu::Pipeline::create(program, state); - -} - void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light) { /* auto globalLight = _allocatedLights.front(); globalLight->setDirection(light->getDirection()); @@ -535,7 +478,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 | gpu::Framebuffer::BUFFER_DEPTH | gpu::Framebuffer::BUFFER_STENCIL, - vec4(vec3(0), 0), 1.0, 0.0, true); + vec4(vec3(0), 0), 1.0, 1, true); // For the rest of the rendering, bind the lighting model batch.setUniformBuffer(LIGHTING_MODEL_BUFFER_SLOT, lightingModel->getParametersBuffer()); @@ -619,8 +562,8 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map); } - auto& program = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadow : deferredLightingEffect->_directionalLight; - LightLocationsPtr locations = deferredLightingEffect->_shadowMapEnabled ? deferredLightingEffect->_directionalLightShadowLocations : deferredLightingEffect->_directionalLightLocations; + auto& program = deferredLightingEffect->_directionalSkyboxLight; + LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; const auto& keyLight = deferredLightingEffect->_allocatedLights[deferredLightingEffect->_globalLights.front()]; // Setup the global directional pass pipeline diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 2f4f9901c3..c171973216 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -82,32 +82,21 @@ private: gpu::PipelinePointer _directionalSkyboxLight; gpu::PipelinePointer _directionalAmbientSphereLight; - gpu::PipelinePointer _directionalLight; gpu::PipelinePointer _directionalSkyboxLightShadow; gpu::PipelinePointer _directionalAmbientSphereLightShadow; - gpu::PipelinePointer _directionalLightShadow; gpu::PipelinePointer _localLight; gpu::PipelinePointer _localLightOutline; - gpu::PipelinePointer _pointLightBack; - gpu::PipelinePointer _pointLightFront; - gpu::PipelinePointer _spotLightBack; - gpu::PipelinePointer _spotLightFront; - LightLocationsPtr _directionalSkyboxLightLocations; LightLocationsPtr _directionalAmbientSphereLightLocations; - LightLocationsPtr _directionalLightLocations; LightLocationsPtr _directionalSkyboxLightShadowLocations; LightLocationsPtr _directionalAmbientSphereLightShadowLocations; - LightLocationsPtr _directionalLightShadowLocations; LightLocationsPtr _localLightLocations; LightLocationsPtr _localLightOutlineLocations; - LightLocationsPtr _pointLightLocations; - LightLocationsPtr _spotLightLocations; using Lights = std::vector; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f1c995b943..cd87cfdb3d 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -24,6 +24,7 @@ #include "TextureCache.h" #include "RenderUtilsLogging.h" +#include "StencilMaskPass.h" #include "gpu/StandardShaderLib.h" @@ -1610,6 +1611,9 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + + PrepareStencil::testMask(*state); + gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), LINE_DATA_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); @@ -1663,11 +1667,14 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { // enable decal blend state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + PrepareStencil::testMask(*state); _standardDrawPipeline = gpu::Pipeline::create(program, state); auto stateNoBlend = std::make_shared(); + PrepareStencil::testMaskDrawShape(*state); + auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); gpu::Shader::makeProgram((*programNoBlend)); @@ -1690,12 +1697,14 @@ void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bo auto stateLayered = std::make_shared(); stateLayered->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + PrepareStencil::testMask(*stateLayered); _gridPipelineLayered = gpu::Pipeline::create(program, stateLayered); auto state = std::make_shared(stateLayered->getValues()); const float DEPTH_BIAS = 0.001f; state->setDepthBias(DEPTH_BIAS); state->setDepthTest(true, false, gpu::LESS_EQUAL); + PrepareStencil::testMaskDrawShape(*state); _gridPipeline = gpu::Pipeline::create(program, state); } @@ -1773,6 +1782,11 @@ static void buildWebShader(const std::string& vertShaderText, const std::string& state->setBlendFunction(blendEnable, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + if (blendEnable) { + PrepareStencil::testMask(*state); + } else { + PrepareStencil::testMaskDrawShape(*state); + } pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state); } @@ -1858,6 +1872,12 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + if (config.isTransparent()) { + PrepareStencil::testMask(*state); + } else { + PrepareStencil::testMaskDrawShape(*state); + } + gpu::ShaderPointer program = (config.isUnlit()) ? _unlitShader : _simpleShader; gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state); _simplePrograms.insert(config, pipeline); diff --git a/libraries/render-utils/src/HitEffect.cpp b/libraries/render-utils/src/HitEffect.cpp deleted file mode 100644 index 319e94384f..0000000000 --- a/libraries/render-utils/src/HitEffect.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// -// HitEffect.cpp -// interface/src/renderer -// -// Created by Andrzej Kapolka on 7/14/13. -// 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 this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL - - -#include - -#include -#include -#include - -#include "AbstractViewStateInterface.h" -#include "HitEffect.h" - -#include "TextureCache.h" -#include "DependencyManager.h" -#include "ViewFrustum.h" -#include "GeometryCache.h" - -#include - -#include "hit_effect_vert.h" -#include "hit_effect_frag.h" - - -HitEffect::HitEffect() { - _geometryId = DependencyManager::get()->allocateID(); -} - -HitEffect::~HitEffect() { - auto geometryCache = DependencyManager::get(); - if (_geometryId && geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -const gpu::PipelinePointer& HitEffect::getHitEffectPipeline() { - if (!_hitEffectPipeline) { - auto vs = gpu::Shader::createVertex(std::string(hit_effect_vert)); - auto ps = gpu::Shader::createPixel(std::string(hit_effect_frag)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - - state->setDepthTest(false, false, gpu::LESS_EQUAL); - - // Blend on transparent - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - - // Good to go add the brand new pipeline - _hitEffectPipeline = gpu::Pipeline::create(program, state); - } - return _hitEffectPipeline; -} - -void HitEffect::run(const render::RenderContextPointer& renderContext) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - RenderArgs* args = renderContext->args; - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - batch.setModelTransform(Transform()); - - batch.setPipeline(getHitEffectPipeline()); - - static const glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); - static const glm::vec2 bottomLeft(-1.0f, -1.0f); - static const glm::vec2 topRight(1.0f, 1.0f); - DependencyManager::get()->renderQuad(batch, bottomLeft, topRight, color, _geometryId); - }); -} - diff --git a/libraries/render-utils/src/HitEffect.h b/libraries/render-utils/src/HitEffect.h deleted file mode 100644 index d025d2d980..0000000000 --- a/libraries/render-utils/src/HitEffect.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// hitEffect.h -// hifi -// -// Created by eric levin on 7/17/15. -// -// - -#ifndef hifi_hitEffect_h -#define hifi_hitEffect_h - -#include - -class HitEffectConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(bool enabled MEMBER enabled) -public: - HitEffectConfig() : render::Job::Config(false) {} -}; - -class HitEffect { -public: - using Config = HitEffectConfig; - using JobModel = render::Job::Model; - - HitEffect(); - ~HitEffect(); - void configure(const Config& config) {} - void run(const render::RenderContextPointer& renderContext); - - const gpu::PipelinePointer& getHitEffectPipeline(); - -private: - int _geometryId { 0 }; - gpu::PipelinePointer _hitEffectPipeline; -}; - -#endif diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index 4145264b2d..7e04b1c2a4 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -16,6 +16,8 @@ #include +#include "StencilMaskPass.h" + #include "lightClusters_drawGrid_vert.h" #include "lightClusters_drawGrid_frag.h" diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index b844da8bbe..1b99fe92ee 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -27,12 +27,12 @@ #include #include "LightingModel.h" +#include "StencilMaskPass.h" #include "DebugDeferredBuffer.h" #include "DeferredFramebuffer.h" #include "DeferredLightingEffect.h" #include "SurfaceGeometryPass.h" #include "FramebufferCache.h" -#include "HitEffect.h" #include "TextureCache.h" #include "ZoneRenderer.h" @@ -43,8 +43,6 @@ #include -#include "drawOpaqueStencil_frag.h" - using namespace render; extern void initOverlay3DPipelines(render::ShapePlumber& plumber); @@ -85,13 +83,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto deferredFramebuffer = prepareDeferredOutputs.getN(0); const auto lightingFramebuffer = prepareDeferredOutputs.getN(1); + // draw a stencil mask in hidden regions of the framebuffer. + task.addJob("PrepareStencil", primaryFramebuffer); + // Render opaque objects in DeferredBuffer const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).hasVarying(); task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); - // Once opaque is all rendered create stencil background - task.addJob("DrawOpaqueStencil", deferredFramebuffer); - task.addJob("OpaqueRangeTimer", opaqueRangeTimer); @@ -387,88 +385,6 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& } } - -gpu::PipelinePointer DrawStencilDeferred::getOpaquePipeline() { - if (!_opaquePipeline) { - const gpu::int8 STENCIL_OPAQUE = 1; - auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(drawOpaqueStencil_frag)); - auto program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram((*program)); - - auto state = std::make_shared(); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(STENCIL_OPAQUE, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_KEEP)); - state->setColorWriteMask(0); - - _opaquePipeline = gpu::Pipeline::create(program, state); - } - return _opaquePipeline; -} - -void DrawStencilDeferred::run(const RenderContextPointer& renderContext, const DeferredFramebufferPointer& deferredFramebuffer) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - - // from the touched pixel generate the stencil buffer - RenderArgs* args = renderContext->args; - doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - auto deferredFboColorDepthStencil = deferredFramebuffer->getDeferredFramebufferDepthColor(); - - - batch.enableStereo(false); - - batch.setFramebuffer(deferredFboColorDepthStencil); - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - batch.setPipeline(getOpaquePipeline()); - - batch.draw(gpu::TRIANGLE_STRIP, 4); - batch.setResourceTexture(0, nullptr); - - }); - args->_batch = nullptr; -} - -void DrawBackgroundDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - - const auto& inItems = inputs.get0(); - const auto& lightingModel = inputs.get1(); - if (!lightingModel->isBackgroundEnabled()) { - return; - } - - RenderArgs* args = renderContext->args; - doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - // _gpuTimer.begin(batch); - - batch.enableSkybox(true); - - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - - renderItems(renderContext, inItems); - // _gpuTimer.end(batch); - }); - args->_batch = nullptr; - - // std::static_pointer_cast(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage(); -} - void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { assert(renderContext->args); assert(renderContext->args->_context); @@ -538,3 +454,4 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer } }); } + diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 12ecd5ecaf..fd7c5eb23b 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -120,35 +120,6 @@ protected: bool _stateSort; }; -class DeferredFramebuffer; -class DrawStencilDeferred { -public: - using JobModel = render::Job::ModelI>; - - void run(const render::RenderContextPointer& renderContext, const std::shared_ptr& deferredFramebuffer); - -protected: - gpu::PipelinePointer _opaquePipeline; - - gpu::PipelinePointer getOpaquePipeline(); -}; - -using DrawBackgroundDeferredConfig = render::GPUJobConfig; - -class DrawBackgroundDeferred { -public: - using Inputs = render::VaryingSet2 ; - - using Config = DrawBackgroundDeferredConfig; - using JobModel = render::Job::ModelI; - - void configure(const Config& config) {} - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - -protected: - gpu::RangeTimerPointer _gpuTimer; -}; - class DrawOverlay3DConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged) diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index da264cbf7d..42ed0bdad9 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -15,6 +15,7 @@ #include #include +#include "StencilMaskPass.h" #include "DeferredLightingEffect.h" #include "TextureCache.h" #include "render/DrawTask.h" @@ -330,6 +331,7 @@ void addPlumberPipeline(ShapePlumber& plumber, bool isWireframed = (i & 4); auto state = std::make_shared(); + PrepareStencil::testMaskDrawShape(*state); // Depth test depends on transparency state->setDepthTest(true, !key.isTranslucent(), gpu::LESS_EQUAL); diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp new file mode 100644 index 0000000000..2374f24211 --- /dev/null +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -0,0 +1,126 @@ +// +// StencilMaskPass.cpp +// render-utils/src/ +// +// Created by Sam Gateau on 5/31/17. +// Copyright 2016 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 "StencilMaskPass.h" + +#include +#include +#include + + +#include + +#include "stencil_drawMask_frag.h" + +using namespace render; + +void PrepareStencil::configure(const Config& config) { + _maskMode = config.maskMode; + _forceDraw = config.forceDraw; +} + +model::MeshPointer PrepareStencil::getMesh() { + if (!_mesh) { + + std::vector vertices { + { -1.0f, -1.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, + { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, + { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, + { 1.0f, -1.0f, 0.0f }, { 0.0f, -1.0f, 0.0f } }; + + std::vector indices { 0, 7, 1, 1, 3, 2, 3, 5, 4, 5, 7, 6 }; + _mesh = model::Mesh::createIndexedTriangles_P3F((uint32_t) vertices.size(), (uint32_t) indices.size(), vertices.data(), indices.data()); + } + return _mesh; +} + +gpu::PipelinePointer PrepareStencil::getMeshStencilPipeline() { + if (!_meshStencilPipeline) { + auto vs = gpu::StandardShaderLib::getDrawVertexPositionVS(); + auto ps = gpu::StandardShaderLib::getDrawNadaPS(); + auto program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram((*program)); + + auto state = std::make_shared(); + drawMask(*state); + state->setColorWriteMask(0); + + _meshStencilPipeline = gpu::Pipeline::create(program, state); + } + return _meshStencilPipeline; +} + +gpu::PipelinePointer PrepareStencil::getPaintStencilPipeline() { + if (!_paintStencilPipeline) { + auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(stencil_drawMask_frag)); + auto program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram((*program)); + + auto state = std::make_shared(); + drawMask(*state); + state->setColorWriteMask(0); + + _paintStencilPipeline = gpu::Pipeline::create(program, state); + } + return _paintStencilPipeline; +} + +void PrepareStencil::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { + RenderArgs* args = renderContext->args; + + // Only draw the stencil mask if in HMD mode or not forced. + if (!_forceDraw && (args->_displayMode != RenderArgs::STEREO_HMD)) { + return; + } + + doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + + batch.setViewportTransform(args->_viewport); + + if (_maskMode < 0) { + batch.setPipeline(getMeshStencilPipeline()); + + auto mesh = getMesh(); + batch.setIndexBuffer(mesh->getIndexBuffer()); + batch.setInputFormat((mesh->getVertexFormat())); + batch.setInputStream(0, mesh->getVertexStream()); + + // Draw + auto part = mesh->getPartBuffer().get(0); + batch.drawIndexed(gpu::TRIANGLES, part._numIndices, part._startIndex); + } else { + batch.setPipeline(getPaintStencilPipeline()); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } + }); +} + +void PrepareStencil::drawMask(gpu::State& state) { + state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE)); +} + +void PrepareStencil::testMask(gpu::State& state) { + state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); +} + +void PrepareStencil::testBackground(gpu::State& state) { + state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_BACKGROUND, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); +} + +void PrepareStencil::testMaskDrawShape(gpu::State& state) { + state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO)); +} + +void PrepareStencil::testShape(gpu::State& state) { + state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_SHAPE, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); +} \ No newline at end of file diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h new file mode 100644 index 0000000000..01601d1ae6 --- /dev/null +++ b/libraries/render-utils/src/StencilMaskPass.h @@ -0,0 +1,71 @@ +// +// StencilMaskPass.h +// render-utils/src/ +// +// Created by Sam Gateau on 5/31/17. +// Copyright 20154 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 +// +#pragma once +#ifndef hifi_StencilMaskPass_h +#define hifi_StencilMaskPass_h + +#include +#include +#include + +class PrepareStencilConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(int maskMode MEMBER maskMode NOTIFY dirty) + Q_PROPERTY(bool forceDraw MEMBER forceDraw NOTIFY dirty) + +public: + PrepareStencilConfig(bool enabled = true) : JobConfig(enabled) {} + + int maskMode { 0 }; + bool forceDraw { false }; + +signals: + void dirty(); +}; + +class PrepareStencil { +public: + using Config = PrepareStencilConfig; + + using JobModel = render::Job::ModelI; + + void configure(const Config& config); + + void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& dstFramebuffer); + + static const gpu::int8 STENCIL_MASK = 2; + static const gpu::int8 STENCIL_BACKGROUND = 1; + static const gpu::int8 STENCIL_SHAPE = 0; + + + static void drawMask(gpu::State& state); + static void testMask(gpu::State& state); + static void testBackground(gpu::State& state); + static void testMaskDrawShape(gpu::State& state); + static void testShape(gpu::State& state); + + +private: + gpu::PipelinePointer _meshStencilPipeline; + gpu::PipelinePointer getMeshStencilPipeline(); + + gpu::PipelinePointer _paintStencilPipeline; + gpu::PipelinePointer getPaintStencilPipeline(); + + model::MeshPointer _mesh; + model::MeshPointer getMesh(); + + int _maskMode { 0 }; + bool _forceDraw { false }; +}; + + +#endif // hifi_StencilMaskPass_h diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index ef50960b7d..1941766353 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -14,7 +14,7 @@ #include #include - +#include "StencilMaskPass.h" const int DepthLinearPass_FrameTransformSlot = 0; const int DepthLinearPass_DepthMapSlot = 0; @@ -224,7 +224,7 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test the curvature pass for objects pixels only, not the background - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + PrepareStencil::testShape(*state); state->setColorWriteMask(true, false, false, false); @@ -250,6 +250,7 @@ const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testShape(*state); state->setColorWriteMask(true, true, true, false); @@ -554,7 +555,7 @@ const gpu::PipelinePointer& SurfaceGeometryPass::getCurvaturePipeline() { #ifdef USE_STENCIL_TEST // Stencil test the curvature pass for objects pixels only, not the background - state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); + PrepareStencil::testShape(*state); #endif // Good to go add the brand new pipeline _curvaturePipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render-utils/src/ToneMappingEffect.cpp b/libraries/render-utils/src/ToneMappingEffect.cpp index d54481d246..ce41cf16fa 100644 --- a/libraries/render-utils/src/ToneMappingEffect.cpp +++ b/libraries/render-utils/src/ToneMappingEffect.cpp @@ -15,7 +15,7 @@ #include #include - +#include "StencilMaskPass.h" #include "FramebufferCache.h" #include "toneMapping_frag.h" diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 3b4870fd3f..8f04265226 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -17,6 +17,7 @@ #include #include +#include "StencilMaskPass.h" #include "DeferredLightingEffect.h" #include "zone_drawKeyLight_frag.h" @@ -74,6 +75,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testMask(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); _keyLightPipeline = gpu::Pipeline::create(program, state); } @@ -95,6 +97,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testMask(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); _ambientPipeline = gpu::Pipeline::create(program, state); } @@ -115,6 +118,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getBackgroundPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + PrepareStencil::testMask(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); _backgroundPipeline = gpu::Pipeline::create(program, state); } @@ -169,7 +173,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I batch.setUniformBuffer(ZONE_DEFERRED_TRANSFORM_BUFFER, deferredTransform->getFrameTransformBuffer()); batch.setPipeline(getKeyLightPipeline()); - auto numKeys = keyLightStack.size(); + auto numKeys = (int) keyLightStack.size(); for (int i = numKeys - 1; i >= 0; i--) { model.setTranslation(glm::vec3(-4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); batch.setModelTransform(model); @@ -180,7 +184,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I } batch.setPipeline(getAmbientPipeline()); - auto numAmbients = ambientLightStack.size(); + auto numAmbients = (int) ambientLightStack.size(); for (int i = numAmbients - 1; i >= 0; i--) { model.setTranslation(glm::vec3(0.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); batch.setModelTransform(model); @@ -194,7 +198,7 @@ void DebugZoneLighting::run(const render::RenderContextPointer& context, const I } batch.setPipeline(getBackgroundPipeline()); - auto numBackgrounds = skyboxStack.size(); + auto numBackgrounds = (int) skyboxStack.size(); for (int i = numBackgrounds - 1; i >= 0; i--) { model.setTranslation(glm::vec3(4.0, -3.0 + (i * 1.0), -10.0 - (i * 3.0))); batch.setModelTransform(model); diff --git a/libraries/render-utils/src/directional_light.slf b/libraries/render-utils/src/directional_light.slf deleted file mode 100644 index cda03b0779..0000000000 --- a/libraries/render-utils/src/directional_light.slf +++ /dev/null @@ -1,47 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// directional_light.frag -// fragment shader -// -// Created by Andrzej Kapolka on 9/3/14. -// Copyright 2016 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 DeferredBufferRead.slh@> -<@include DeferredGlobalLight.slh@> - -<$declareEvalLightmappedColor()$> -<$declareEvalAmbientGlobalColor()$> - -in vec2 _texCoord0; -out vec4 _fragColor; - -void main(void) { - DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - - float shadowAttenuation = 1.0; - - if (frag.mode == FRAG_MODE_UNLIT) { - discard; - } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - discard; - } else { - vec3 color = evalAmbientGlobalColor( - getViewInverse(), - shadowAttenuation, - frag.obscurance, - frag.position.xyz, - frag.normal, - frag.albedo, - frag.fresnel, - frag.metallic, - frag.roughness); - _fragColor = vec4(color, 1.0); - } -} diff --git a/libraries/render-utils/src/directional_light_shadow.slf b/libraries/render-utils/src/directional_light_shadow.slf deleted file mode 100644 index 7f98330f84..0000000000 --- a/libraries/render-utils/src/directional_light_shadow.slf +++ /dev/null @@ -1,49 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// directional_light_shadow.frag -// fragment shader -// -// Created by Zach Pomerantz on 1/18/2016. -// Copyright 2016 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 Shadow.slh@> -<@include DeferredBufferRead.slh@> -<@include DeferredGlobalLight.slh@> - -<$declareEvalLightmappedColor()$> -<$declareEvalAmbientGlobalColor()$> - -in vec2 _texCoord0; -out vec4 _fragColor; - -void main(void) { - DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); - - vec4 worldPos = getViewInverse() * vec4(frag.position.xyz, 1.0); - float shadowAttenuation = evalShadowAttenuation(worldPos); - - if (frag.mode == FRAG_MODE_UNLIT) { - discard; - } else if (frag.mode == FRAG_MODE_LIGHTMAPPED) { - discard; - } else { - vec3 color = evalAmbientGlobalColor( - getViewInverse(), - shadowAttenuation, - frag.obscurance, - frag.position.xyz, - frag.normal, - frag.albedo, - frag.fresnel, - frag.metallic, - frag.roughness); - _fragColor = vec4(color, 1.0); - } -} diff --git a/libraries/render-utils/src/hit_effect.slf b/libraries/render-utils/src/hit_effect.slf deleted file mode 100644 index cc4484442f..0000000000 --- a/libraries/render-utils/src/hit_effect.slf +++ /dev/null @@ -1,27 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// - -// hit_effect.frag -// fragment shader -// -// Created by Eric Levin on 7/20 -// 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 DeferredBufferWrite.slh@> - -in vec2 varQuadPosition; -out vec4 outFragColor; - -void main(void) { - vec2 center = vec2(0.0, 0.0); - float distFromCenter = distance( vec2(0.0, 0.0), varQuadPosition); - float alpha = mix(0.0, 0.5, pow(distFromCenter,5.)); - outFragColor = vec4(1.0, 0.0, 0.0, alpha); -} \ No newline at end of file diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf deleted file mode 100644 index e646db5069..0000000000 --- a/libraries/render-utils/src/point_light.slf +++ /dev/null @@ -1,85 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// point_light.frag -// fragment shader -// -// Created by Sam Gateau on 9/18/15. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -<$declareDeferredCurvature()$> - -// Everything about light -<@include model/Light.slh@> -<$declareLightBuffer()$> - -<@include LightingModel.slh@> - -<@include LightPoint.slh@> -<$declareLightingPoint(supportScattering)$> - - -uniform vec4 texcoordFrameTransform; - -in vec4 _texCoord0;!> -out vec4 _fragColor; - -void main(void) { - _fragColor = vec4(1.0, 1.0, 1.0, 1.0); - - -} diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf deleted file mode 100644 index 4cacff86c4..0000000000 --- a/libraries/render-utils/src/spot_light.slf +++ /dev/null @@ -1,115 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// spot_light.frag -// fragment shader -// -// Created by Sam Gateau on 9/18/15. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -// Everything about deferred buffer - - -<$declareDeferredCurvature()$> - -// Everything about light -<@include model/Light.slh@> -<$declareLightBuffer(256)$> -uniform lightIndexBuffer { - int lightIndex[256]; -}; -<@include LightingModel.slh@> - -<@include LightPoint.slh@> -<$declareLightingPoint(supportScattering)$> -<@include LightSpot.slh@> -<$declareLightingSpot(supportScattering)$> - -//uniform vec4 texcoordFrameTransform; -!> - - -//in vec4 _texCoord0; -//flat in int instanceID; -out vec4 _fragColor; - -void main(void) { - _fragColor = vec4(1.0, 1.0, 1.0, 1.0); - -// DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - - // Grab the fragment data from the uv - //vec2 texCoord = _texCoord0.st;/* / _texCoord0.q; - /*texCoord *= texcoordFrameTransform.zw; - texCoord += texcoordFrameTransform.xy;*/ - /* - vec4 fragPosition = unpackDeferredPositionFromZeye(texCoord); - DeferredFragment frag = unpackDeferredFragmentNoPosition(texCoord); - - if (frag.mode == FRAG_MODE_UNLIT) { - discard; - } - - - // frag.depthVal = depthValue; - frag.position = fragPosition; - - vec4 midNormalCurvature; - vec4 lowNormalCurvature; - if (frag.mode == FRAG_MODE_SCATTERING) { - unpackMidLowNormalCurvature(texCoord, midNormalCurvature, lowNormalCurvature); - } - - // Frag pos in world - mat4 invViewMat = getViewInverse(); - vec4 fragPos = invViewMat * fragPosition; - - // Frag to eye vec - vec4 fragEyeVector = invViewMat * vec4(-frag.position.xyz, 0.0); - vec3 fragEyeDir = normalize(fragEyeVector.xyz); - - int numLights = lightIndex[0]; - for (int i = 0; i < numLights; i++) { - // Need the light now - Light light = getLight(lightIndex[i + 1]); - bool isSpot = light_isSpot(light); - // Clip againgst the light volume and Make the Light vector going from fragment to light center in world space - vec4 fragLightVecLen2; - vec4 fragLightDirLen; - float cosSpotAngle; - if (isSpot) { - if (!clipFragToLightVolumeSpot(light, fragPos.xyz, fragLightVecLen2, fragLightDirLen, cosSpotAngle)) { - continue; - } - } else { - if (!clipFragToLightVolumePoint(light, fragPos.xyz, fragLightVecLen2)) { - continue; - } - } - - vec3 diffuse; - vec3 specular; - - if (isSpot) { - evalLightingSpot(diffuse, specular, light, - fragLightDirLen.xyzw, cosSpotAngle, fragEyeDir, frag.normal, frag.roughness, - frag.metallic, frag.fresnel, frag.albedo, 1.0, - frag.scattering, midNormalCurvature, lowNormalCurvature); - } else { - evalLightingPoint(diffuse, specular, light, - fragLightVecLen2.xyz, fragEyeDir, frag.normal, frag.roughness, - frag.metallic, frag.fresnel, frag.albedo, 1.0, - frag.scattering, midNormalCurvature, lowNormalCurvature); - } - - _fragColor.rgb += diffuse; - _fragColor.rgb += specular; - } - */ -} - diff --git a/libraries/render-utils/src/stencil_drawMask.slf b/libraries/render-utils/src/stencil_drawMask.slf new file mode 100644 index 0000000000..3eedeecb82 --- /dev/null +++ b/libraries/render-utils/src/stencil_drawMask.slf @@ -0,0 +1,23 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// stencil_drawMask.slf +// fragment shader +// +// Created by Sam Gateau on 5/31/17. +// Copyright 2017 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 +// + +in vec2 varTexCoord0; + +float aspectRatio = 0.95; + +void main(void) { + vec2 pos = varTexCoord0 * 2.0 - vec2(1.0); + pos.x = aspectRatio * (pos.x * (pos.x > 0.0 ? 2.0 : -2.0) - 1.0); + if (1.0 - dot(pos.xy, pos.xy) > 0.0 ) discard; +} diff --git a/libraries/shared/src/Extents.h b/libraries/shared/src/Extents.h index 850735dd5d..444575b7d3 100644 --- a/libraries/shared/src/Extents.h +++ b/libraries/shared/src/Extents.h @@ -69,7 +69,7 @@ public: glm::vec3 size() const { return maximum - minimum; } float largestDimension() const { return glm::compMax(size()); } - /// \return new Extents which is original rotated around orign by rotation + /// \return new Extents which is original rotated around origin by rotation Extents getRotated(const glm::quat& rotation) const { Extents temp(minimum, maximum); temp.rotate(rotation); diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 70237e8ff6..394517aac4 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -49,7 +49,7 @@ const mat4 Matrices::Z_180 { createMatFromQuatAndPos(Quaternions::Z_180, Vectors glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) { float cosa = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; float ox = q2.x, oy = q2.y, oz = q2.z, ow = q2.w, s0, s1; - + // adjust signs if necessary if (cosa < 0.0f) { cosa = -cosa; @@ -58,19 +58,19 @@ glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float proportion) { oz = -oz; ow = -ow; } - + // calculate coefficients; if the angle is too close to zero, we must fall back // to linear interpolation if ((1.0f - cosa) > EPSILON) { float angle = acosf(cosa), sina = sinf(angle); s0 = sinf((1.0f - proportion) * angle) / sina; s1 = sinf(proportion * angle) / sina; - + } else { s0 = 1.0f - proportion; s1 = proportion; } - + return glm::normalize(glm::quat(s0 * q1.w + s1 * ow, s0 * q1.x + s1 * ox, s0 * q1.y + s1 * oy, s0 * q1.z + s1 * oz)); } @@ -105,10 +105,10 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm int packFloatAngleToTwoByte(unsigned char* buffer, float degrees) { const float ANGLE_CONVERSION_RATIO = (std::numeric_limits::max() / 360.0f); - + uint16_t angleHolder = floorf((degrees + 180.0f) * ANGLE_CONVERSION_RATIO); memcpy(buffer, &angleHolder, sizeof(uint16_t)); - + return sizeof(uint16_t); } @@ -125,7 +125,7 @@ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput quatParts[1] = floorf((quatNormalized.y + 1.0f) * QUAT_PART_CONVERSION_RATIO); quatParts[2] = floorf((quatNormalized.z + 1.0f) * QUAT_PART_CONVERSION_RATIO); quatParts[3] = floorf((quatNormalized.w + 1.0f) * QUAT_PART_CONVERSION_RATIO); - + memcpy(buffer, &quatParts, sizeof(quatParts)); return sizeof(quatParts); } @@ -133,12 +133,12 @@ int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput) { uint16_t quatParts[4]; memcpy(&quatParts, buffer, sizeof(quatParts)); - + quatOutput.x = ((quatParts[0] / (float) std::numeric_limits::max()) * 2.0f) - 1.0f; quatOutput.y = ((quatParts[1] / (float) std::numeric_limits::max()) * 2.0f) - 1.0f; quatOutput.z = ((quatParts[2] / (float) std::numeric_limits::max()) * 2.0f) - 1.0f; quatOutput.w = ((quatParts[3] / (float) std::numeric_limits::max()) * 2.0f) - 1.0f; - + return sizeof(quatParts); } @@ -235,7 +235,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { atan2f(q.y * q.z + q.x * q.w, 0.5f - (q.x * q.x + q.y * q.y)), asinf(sy), atan2f(q.x * q.y + q.z * q.w, 0.5f - (q.y * q.y + q.z * q.z))); - + } else { // not a unique solution; x + z = atan2(-m21, m11) eulers = glm::vec3( @@ -250,7 +250,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { PI_OVER_TWO, -atan2f(q.x * q.w - q.y * q.z, 0.5f - (q.x * q.x + q.z * q.z))); } - + // adjust so that z, rather than y, is in [-pi/2, pi/2] if (eulers.z < -PI_OVER_TWO) { if (eulers.x < 0.0f) { @@ -265,7 +265,7 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { eulers.y -= PI; } eulers.z += PI; - + } else if (eulers.z > PI_OVER_TWO) { if (eulers.x < 0.0f) { eulers.x += PI; @@ -320,7 +320,7 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { for (int i = 0; i < 10; i++) { // store the results of the previous iteration glm::mat3 previous = upper; - + // compute average of the matrix with its inverse transpose float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2]; float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2]; @@ -334,15 +334,15 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f; upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f; upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f; - + upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f; upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f; upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f; - + upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f; upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f; upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f; - + // compute the difference; if it's small enough, we're done glm::mat3 diff = upper - previous; if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] + @@ -352,7 +352,7 @@ glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { } } } - + // now that we have a nice orthogonal matrix, we can extract the rotation quaternion // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]); @@ -473,7 +473,7 @@ glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); } -// cancel out roll +// cancel out roll glm::quat cancelOutRoll(const glm::quat& q) { glm::vec3 forward = q * Vectors::FRONT; return glm::quat_cast(glm::inverse(glm::lookAt(Vectors::ZERO, forward, Vectors::UP))); @@ -538,17 +538,16 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda uAxisOut = glm::normalize(primaryAxis); glm::vec3 normSecondary = glm::normalize(secondaryAxis); - // if secondaryAxis is parallel with the primaryAxis, pick another axis. + // if normSecondary is parallel with the primaryAxis, pick another secondary. const float EPSILON = 1.0e-4f; - if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { - // pick a better secondaryAxis. - normSecondary = glm::vec3(1.0f, 0.0f, 0.0f); - if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { - normSecondary = glm::vec3(0.0f, 1.0f, 0.0f); + if (fabsf(fabsf(glm::dot(uAxisOut, normSecondary)) - 1.0f) < EPSILON) { + normSecondary = Vectors::UNIT_X; + if (fabsf(fabsf(glm::dot(uAxisOut, normSecondary)) - 1.0f) < EPSILON) { + normSecondary = Vectors::UNIT_Y; } } - wAxisOut = glm::normalize(glm::cross(uAxisOut, secondaryAxis)); + wAxisOut = glm::normalize(glm::cross(uAxisOut, normSecondary)); vAxisOut = glm::cross(wAxisOut, uAxisOut); } diff --git a/libraries/shared/src/NumericalConstants.h b/libraries/shared/src/NumericalConstants.h index d37e1e31c5..e2d2409d56 100644 --- a/libraries/shared/src/NumericalConstants.h +++ b/libraries/shared/src/NumericalConstants.h @@ -50,10 +50,13 @@ const int KILO_PER_MEGA = 1000; #define KB_TO_BYTES_SHIFT 10 #define MB_TO_BYTES_SHIFT 20 +#define GB_TO_BYTES_SHIFT 30 +#define GB_TO_BYTES(X) ((size_t)(X) << GB_TO_BYTES_SHIFT) #define MB_TO_BYTES(X) ((size_t)(X) << MB_TO_BYTES_SHIFT) #define KB_TO_BYTES(X) ((size_t)(X) << KB_TO_BYTES_SHIFT) +#define BYTES_TO_GB(X) (X >> GB_TO_BYTES_SHIFT) #define BYTES_TO_MB(X) (X >> MB_TO_BYTES_SHIFT) #define BYTES_TO_KB(X) (X >> KB_TO_BYTES_SHIFT) diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index f44d736e1a..d4d88c26a8 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -76,7 +76,7 @@ public: class RenderArgs { public: enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE }; - enum RenderSide { MONO, STEREO_LEFT, STEREO_RIGHT }; + enum DisplayMode { MONO, STEREO_MONITOR, STEREO_HMD }; enum DebugFlags { RENDER_DEBUG_NONE = 0, RENDER_DEBUG_HULLS = 1 @@ -87,7 +87,7 @@ public: float sizeScale = 1.0f, int boundaryLevelAdjust = 0, RenderMode renderMode = DEFAULT_RENDER_MODE, - RenderSide renderSide = MONO, + DisplayMode displayMode = MONO, DebugFlags debugFlags = RENDER_DEBUG_NONE, gpu::Batch* batch = nullptr) : _context(context), @@ -95,7 +95,7 @@ public: _sizeScale(sizeScale), _boundaryLevelAdjust(boundaryLevelAdjust), _renderMode(renderMode), - _renderSide(renderSide), + _displayMode(displayMode), _debugFlags(debugFlags), _batch(batch) { } @@ -121,7 +121,7 @@ public: float _sizeScale = 1.0f; int _boundaryLevelAdjust = 0; RenderMode _renderMode = DEFAULT_RENDER_MODE; - RenderSide _renderSide = MONO; + DisplayMode _displayMode = MONO; DebugFlags _debugFlags = RENDER_DEBUG_NONE; gpu::Batch* _batch = nullptr; diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp index 0c1fd06df8..cb7b39320c 100644 --- a/libraries/shared/src/RunningMarker.cpp +++ b/libraries/shared/src/RunningMarker.cpp @@ -13,44 +13,16 @@ #include #include -#include -#include -#include "NumericalConstants.h" #include "PathUtils.h" -RunningMarker::RunningMarker(QObject* parent, QString name) : - _parent(parent), +RunningMarker::RunningMarker(QString name) : _name(name) { } -void RunningMarker::startRunningMarker() { - static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND; - - // start the nodeThread so its event loop is running - _runningMarkerThread = new QThread(_parent); - _runningMarkerThread->setObjectName("Running Marker Thread"); - _runningMarkerThread->start(); - - writeRunningMarkerFile(); // write the first file, even before timer - - _runningMarkerTimer = new QTimer(); - QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){ - writeRunningMarkerFile(); - }); - _runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS); - - // put the time on the thread - _runningMarkerTimer->moveToThread(_runningMarkerThread); -} - RunningMarker::~RunningMarker() { deleteRunningMarkerFile(); - QMetaObject::invokeMethod(_runningMarkerTimer, "stop", Qt::BlockingQueuedConnection); - _runningMarkerThread->quit(); - _runningMarkerTimer->deleteLater(); - _runningMarkerThread->deleteLater(); } bool RunningMarker::fileExists() const { @@ -77,8 +49,3 @@ void RunningMarker::deleteRunningMarkerFile() { QString RunningMarker::getFilePath() const { return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; } - -QString RunningMarker::getMarkerFilePath(QString name) { - return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name; -} - diff --git a/libraries/shared/src/RunningMarker.h b/libraries/shared/src/RunningMarker.h index f9c8e72d37..ae7c550660 100644 --- a/libraries/shared/src/RunningMarker.h +++ b/libraries/shared/src/RunningMarker.h @@ -12,21 +12,14 @@ #ifndef hifi_RunningMarker_h #define hifi_RunningMarker_h -#include #include -class QThread; -class QTimer; - class RunningMarker { public: - RunningMarker(QObject* parent, QString name); + RunningMarker(QString name); ~RunningMarker(); - void startRunningMarker(); - QString getFilePath() const; - static QString getMarkerFilePath(QString name); bool fileExists() const; @@ -34,10 +27,7 @@ public: void deleteRunningMarkerFile(); private: - QObject* _parent { nullptr }; QString _name; - QThread* _runningMarkerThread { nullptr }; - QTimer* _runningMarkerTimer { nullptr }; }; #endif // hifi_RunningMarker_h diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index b759a06aee..80c8698bb6 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 53500a3353..5583ec6351 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -50,11 +50,15 @@ static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; static const int MIN_PUCK_COUNT = 2; static const int MIN_FEET_AND_HIPS = 3; static const int MIN_FEET_HIPS_CHEST = 4; +static const int MIN_FEET_HIPS_HEAD = 4; static const int MIN_FEET_HIPS_SHOULDERS = 5; +static const int MIN_FEET_HIPS_CHEST_HEAD = 5; static const int FIRST_FOOT = 0; static const int SECOND_FOOT = 1; static const int HIP = 2; static const int CHEST = 3; +static float HEAD_PUCK_Y_OFFSET = -0.0254f; +static float HEAD_PUCK_Z_OFFSET = -0.152f; const char* ViveControllerManager::NAME { "OpenVR" }; @@ -80,6 +84,28 @@ static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck) return (firstPuck.second.translation.x < secondPuck.second.translation.x); } +static bool determineFeetOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) { + glm::vec3 poseAPosition = poseA.getTranslation(); + glm::vec3 poseBPosition = poseB.getTranslation(); + + glm::vec3 poseAVector = poseAPosition - axisOrigin; + glm::vec3 poseBVector = poseBPosition - axisOrigin; + + float poseAProjection = glm::dot(poseAVector, axis); + float poseBProjection = glm::dot(poseBVector, axis); + return (poseAProjection > poseBProjection); +} + +static glm::vec3 getReferenceHeadXAxis(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) { + glm::mat4 finalHead = defaultToReferenceMat * defaultHead; + return glmExtractRotation(finalHead) * Vectors::UNIT_X; +} + +static glm::vec3 getReferenceHeadPosition(glm::mat4 defaultToReferenceMat, glm::mat4 defaultHead) { + glm::mat4 finalHead = defaultToReferenceMat * defaultHead; + return extractTranslation(finalHead); +} + static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) { QString result; auto iterator = TRACKING_RESULT_TO_STRING.find(trackingResult); @@ -175,6 +201,8 @@ ViveControllerManager::InputDevice::InputDevice(vr::IVRSystem*& system) : contro _configStringMap[Config::FeetAndHips] = QString("FeetAndHips"); _configStringMap[Config::FeetHipsAndChest] = QString("FeetHipsAndChest"); _configStringMap[Config::FeetHipsAndShoulders] = QString("FeetHipsAndShoulders"); + _configStringMap[Config::FeetHipsChestAndHead] = QString("FeetHipsChestAndHead"); + _configStringMap[Config::FeetHipsAndHead] = QString("FeetHipsAndHead"); if (openVrSupported()) { createPreferences(); @@ -355,6 +383,23 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr int firstShoulderIndex = 3; int secondShoulderIndex = 4; calibrateShoulders(defaultToReferenceMat, inputCalibration, firstShoulderIndex, secondShoulderIndex); + } else if (_config == Config::FeetHipsAndHead && puckCount == MIN_FEET_HIPS_HEAD) { + glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration); + glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); + glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); + calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition); + calibrateHips(headPuckDefaultToReferenceMat, inputCalibration); + calibrateHead(headPuckDefaultToReferenceMat, inputCalibration); + _overrideHead = true; + } else if (_config == Config::FeetHipsChestAndHead && puckCount == MIN_FEET_HIPS_CHEST_HEAD) { + glm::mat4 headPuckDefaultToReferenceMat = recalculateDefaultToReferenceForHeadPuck(inputCalibration); + glm::vec3 headXAxis = getReferenceHeadXAxis(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); + glm::vec3 headPosition = getReferenceHeadPosition(headPuckDefaultToReferenceMat, inputCalibration.defaultHeadMat); + calibrateFeet(headPuckDefaultToReferenceMat, inputCalibration, headXAxis, headPosition); + calibrateHips(headPuckDefaultToReferenceMat, inputCalibration); + calibrateChest(headPuckDefaultToReferenceMat, inputCalibration); + calibrateHead(headPuckDefaultToReferenceMat, inputCalibration); + _overrideHead = true; } else { qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks"; uncalibrate(); @@ -369,6 +414,7 @@ void ViveControllerManager::InputDevice::uncalibrate() { _pucksOffset.clear(); _jointToPuckMap.clear(); _calibrated = false; + _overrideHead = false; } void ViveControllerManager::InputDevice::updateCalibratedLimbs() { @@ -378,6 +424,10 @@ void ViveControllerManager::InputDevice::updateCalibratedLimbs() { _poseStateMap[controller::SPINE2] = addOffsetToPuckPose(controller::SPINE2); _poseStateMap[controller::RIGHT_ARM] = addOffsetToPuckPose(controller::RIGHT_ARM); _poseStateMap[controller::LEFT_ARM] = addOffsetToPuckPose(controller::LEFT_ARM); + + if (_overrideHead) { + _poseStateMap[controller::HEAD] = addOffsetToPuckPose(controller::HEAD); + } } controller::Pose ViveControllerManager::InputDevice::addOffsetToPuckPose(int joint) const { @@ -443,6 +493,43 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u } } +glm::mat4 ViveControllerManager::InputDevice::recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) { + glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat; + glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; + size_t headPuckIndex = _validTrackedObjects.size() - 1; + controller::Pose headPuckPose = _validTrackedObjects[headPuckIndex].second; + glm::mat4 headPuckAvatarMat = createMatFromQuatAndPos(headPuckPose.getRotation(), headPuckPose.getTranslation()) * Matrices::Y_180; + glm::vec3 headPuckTranslation = extractTranslation(headPuckAvatarMat); + glm::vec3 headPuckZAxis = cancelOutRollAndPitch(glmExtractRotation(headPuckAvatarMat)) * glm::vec3(0.0f, 0.0f, 1.0f); + glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f); + + // check that the head puck z axis is not parrallel to the world up + const float EPSILON = 1.0e-4f; + glm::vec3 zAxis = glmExtractRotation(headPuckAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f); + if (fabsf(fabsf(glm::dot(glm::normalize(worldUp), glm::normalize(zAxis))) - 1.0f) < EPSILON) { + headPuckZAxis = glm::vec3(1.0f, 0.0f, 0.0f); + } + + glm::vec3 yPrime = glm::vec3(0.0f, 1.0f, 0.0f); + glm::vec3 xPrime = glm::normalize(glm::cross(worldUp, headPuckZAxis)); + glm::vec3 zPrime = glm::normalize(glm::cross(xPrime, yPrime)); + glm::mat4 newHeadPuck = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f), + glm::vec4(zPrime, 0.0f), glm::vec4(headPuckTranslation, 1.0f)); + + glm::mat4 headPuckOffset = glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f), + glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, HEAD_PUCK_Y_OFFSET, HEAD_PUCK_Z_OFFSET, 1.0f)); + + glm::mat4 finalHeadPuck = newHeadPuck * headPuckOffset; + + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + + glm::mat4 currentHead = finalHeadPuck * defaultHeadOffset; + + // calculate the defaultToRefrenceXform + glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); + return defaultToReferenceMat; +} + void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. const float CENTER_DEADBAND = 0.6f; @@ -630,6 +717,26 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer } } + +void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition) { + auto& firstFoot = _validTrackedObjects[FIRST_FOOT]; + auto& secondFoot = _validTrackedObjects[SECOND_FOOT]; + controller::Pose& firstFootPose = firstFoot.second; + controller::Pose& secondFootPose = secondFoot.second; + + if (determineFeetOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) { + _jointToPuckMap[controller::LEFT_FOOT] = firstFoot.first; + _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, firstFootPose); + _jointToPuckMap[controller::RIGHT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, secondFootPose); + } else { + _jointToPuckMap[controller::LEFT_FOOT] = secondFoot.first; + _pucksOffset[secondFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultLeftFoot, secondFootPose); + _jointToPuckMap[controller::RIGHT_FOOT] = firstFoot.first; + _pucksOffset[firstFoot.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightFoot, firstFootPose); + } +} + void ViveControllerManager::InputDevice::calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { _jointToPuckMap[controller::HIPS] = _validTrackedObjects[HIP].first; _pucksOffset[_validTrackedObjects[HIP].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHips, _validTrackedObjects[HIP].second); @@ -658,7 +765,19 @@ void ViveControllerManager::InputDevice::calibrateShoulders(glm::mat4& defaultTo _jointToPuckMap[controller::RIGHT_ARM] = firstShoulder.first; _pucksOffset[firstShoulder.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultRightArm, firstShoulder.second); } -} +} + +void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { + size_t headIndex = _validTrackedObjects.size() - 1; + const PuckPosePair& head = _validTrackedObjects[headIndex]; + + // assume the person is wearing the head puck on his/her forehead + glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + controller::Pose newHead = head.second.postTransform(defaultHeadOffset); + + _jointToPuckMap[controller::HEAD] = head.first; + _pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead); +} void ViveControllerManager::InputDevice::loadSettings() { @@ -694,6 +813,10 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu _preferedConfig = Config::FeetHipsAndChest; } else if (value == "FeetHipsAndShoulders") { _preferedConfig = Config::FeetHipsAndShoulders; + } else if (value == "FeetHipsChestAndHead") { + _preferedConfig = Config::FeetHipsChestAndHead; + } else if (value == "FeetHipsAndHead") { + _preferedConfig = Config::FeetHipsAndHead; } } @@ -702,11 +825,43 @@ void ViveControllerManager::InputDevice::createPreferences() { auto preferences = DependencyManager::get(); static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration"; + { + static const float MIN_VALUE = -3.0f; + static const float MAX_VALUE = 3.0f; + static const float STEP = 0.01f; + + auto getter = [this]()->float { return HEAD_PUCK_Y_OFFSET; }; + auto setter = [this](const float& value) { HEAD_PUCK_Y_OFFSET = value; }; + + auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckYOffset", getter, setter); + preference->setMin(MIN_VALUE); + preference->setMax(MAX_VALUE); + preference->setDecimals(3); + preference->setStep(STEP); + preferences->addPreference(preference); + } + + { + static const float MIN_VALUE = -3.0f; + static const float MAX_VALUE = 3.0f; + static const float STEP = 0.01f; + + auto getter = [this]()->float { return HEAD_PUCK_Z_OFFSET; }; + auto setter = [this](const float& value) { HEAD_PUCK_Z_OFFSET = value; }; + + auto preference = new SpinnerPreference(VIVE_PUCKS_CONFIG, "HeadPuckXOffset", getter, setter); + preference->setMin(MIN_VALUE); + preference->setMax(MAX_VALUE); + preference->setStep(STEP); + preference->setDecimals(3); + preferences->addPreference(preference); + } + { auto getter = [this]()->QString { return _configStringMap[_preferedConfig]; }; auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); }; auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter); - QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders"}; + QStringList list = {"Auto", "Feet", "FeetAndHips", "FeetHipsAndChest", "FeetHipsAndShoulders", "FeetHipsAndHead"}; preference->setItems(list); preferences->addPreference(preference); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index a76adaa8f9..c32579b0d8 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -67,6 +67,7 @@ private: void calibrate(const controller::InputCalibrationData& inputCalibration); void uncalibrate(); controller::Pose addOffsetToPuckPose(int joint) const; + glm::mat4 recalculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration); void updateCalibratedLimbs(); bool checkForCalibrationEvent(); void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); @@ -84,11 +85,14 @@ private: void loadSettings(); void saveSettings() const; void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + void calibrateFeet(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, glm::vec3 headXAxis, glm::vec3 headPosition); void calibrateHips(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); void calibrateChest(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); void calibrateShoulders(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, int firstShoulderIndex, int secondShoulderIndex); + void calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration); + class FilteredStick { public: @@ -119,6 +123,8 @@ private: FeetAndHips, FeetHipsAndChest, FeetHipsAndShoulders, + FeetHipsChestAndHead, + FeetHipsAndHead }; Config _config { Config::Auto }; Config _preferedConfig { Config::Auto }; @@ -145,6 +151,7 @@ private: bool _triggersPressedHandled { false }; bool _calibrated { false }; bool _timeTilCalibrationSet { false }; + bool _overrideHead { false }; mutable std::recursive_mutex _lock; QString configToString(Config config); diff --git a/scripts/system/assets/sounds/snap.wav b/scripts/system/assets/sounds/snap.wav deleted file mode 100644 index bb562e1db9..0000000000 Binary files a/scripts/system/assets/sounds/snap.wav and /dev/null differ diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 8d103c93de..c2a2f7af40 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -10,7 +10,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ +/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ (function () { // BEGIN LOCAL_SCOPE var button; @@ -76,6 +76,7 @@ // Called from the C++ scripting interface to show the bubble overlay function enteredIgnoreRadius() { createOverlays(); + UserActivityLogger.bubbleActivated(); } // Used to set the state of the bubble HUD button @@ -139,10 +140,14 @@ } // When the space bubble is toggled... - function onBubbleToggled() { - var bubbleActive = Users.getIgnoreRadiusEnabled(); - writeButtonProperties(bubbleActive); - if (bubbleActive) { + // NOTE: the c++ calls this with just the first param -- we added a second + // just for not logging the initial state of the bubble when we startup. + function onBubbleToggled(enabled, doNotLog) { + writeButtonProperties(enabled); + if (doNotLog !== true) { + UserActivityLogger.bubbleToggled(enabled); + } + if (enabled) { createOverlays(); } else { hideOverlays(); @@ -163,7 +168,7 @@ sortOrder: 4 }); - onBubbleToggled(); + onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table button.clicked.connect(Users.toggleIgnoreRadius); Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index f39165f3df..1348734f53 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -217,6 +217,32 @@ function hideMarketplace() { // } // } +function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; +} + var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable"; @@ -234,6 +260,32 @@ var toolBar = (function () { var position = getPositionToCreateEntity(); var entityID = null; if (position !== null && position !== undefined) { + var direction; + if (Camera.mode === "entity" || Camera.mode === "independent") { + direction = Camera.orientation; + } else { + direction = MyAvatar.orientation; + } + direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); + + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // Adjust position of entity per bounding box prior to creating it. + var registration = properties.registration; + if (registration === undefined) { + var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; + registration = DEFAULT_REGISTRATION; + } + + var orientation = properties.orientation; + if (orientation === undefined) { + var DEFAULT_ORIENTATION = Quat.fromPitchYawRollDegrees(0, 0, 0); + orientation = DEFAULT_ORIENTATION; + } + + position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); + } + position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); properties.position = position; if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) { @@ -243,6 +295,32 @@ var toolBar = (function () { if (properties.type == "ParticleEffect") { selectParticleEntity(entityID); } + + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // Adjust position of entity per bounding box after it has been created and auto-resized. + var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; + var DIMENSIONS_CHECK_INTERVAL = 200; + var MAX_DIMENSIONS_CHECKS = 10; + var dimensionsCheckCount = 0; + var dimensionsCheckFunction = function () { + dimensionsCheckCount++; + var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); + if (!Vec3.equal(properties.dimensions, initialDimensions)) { + position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, + properties.dimensions, properties.rotation); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), + properties.dimensions); + Entities.editEntity(entityID, { + position: position + }); + selectionManager._update(); + } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + }; + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } } else { Window.notifyEditError("Can't create " + properties.type + ": " + properties.type + " would be out of bounds."); @@ -564,6 +642,8 @@ var toolBar = (function () { enabled: active })); isActive = active; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (!isActive) { entityListTool.setVisible(false); gridTool.setVisible(false); @@ -572,8 +652,8 @@ var toolBar = (function () { selectionManager.clearSelections(); cameraManager.disable(); selectionDisplay.triggerMapping.disable(); + tablet.landscape = false; } else { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet.loadQMLSource("Edit.qml"); UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); @@ -581,6 +661,8 @@ var toolBar = (function () { grid.setEnabled(true); propertiesTool.setVisible(true); selectionDisplay.triggerMapping.enable(); + print("starting tablet in landscape mode") + tablet.landscape = true; // Not sure what the following was meant to accomplish, but it currently causes // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); @@ -1407,40 +1489,26 @@ function handeMenuEvent(menuItem) { } tooltip.show(false); } -function getPositionToCreateEntity() { - var HALF_TREE_SCALE = 16384; - var direction = Quat.getForward(MyAvatar.orientation); - var distance = 1; - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance)); +var HALF_TREE_SCALE = 16384; + +function getPositionToCreateEntity(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; if (Camera.mode === "entity" || Camera.mode === "independent") { - position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), distance)); + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + position.y += 0.5; } - position.y += 0.5; + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { return null; } return position; } -function getPositionToImportEntity() { - var dimensions = Clipboard.getContentsDimensions(); - var HALF_TREE_SCALE = 16384; - var direction = Quat.getForward(MyAvatar.orientation); - var longest = 1; - longest = Math.sqrt(Math.pow(dimensions.x, 2) + Math.pow(dimensions.z, 2)); - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, longest)); - - if (Camera.mode === "entity" || Camera.mode === "independent") { - position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), longest)); - } - - if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { - return null; - } - - return position; -} function importSVO(importURL) { if (!Entities.canRez() && !Entities.canRezTmp()) { Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); @@ -1458,22 +1526,73 @@ function importSVO(importURL) { if (success) { var VERY_LARGE = 10000; - var position = { - x: 0, - y: 0, - z: 0 - }; - if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) { - position = getPositionToImportEntity(); + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; + if (!isLargeImport) { + position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); } if (position !== null && position !== undefined) { var pastedEntityIDs = Clipboard.pasteEntities(position); + if (!isLargeImport) { + // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move + // entities after they're imported so that they're all the correct distance in front of and with geometric mean + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + + var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]); + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(properties.type) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + var entityPositions = []; + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var properties = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + properties.registrationPoint, properties.dimensions, properties.rotation); + var delta = Vec3.subtract(adjustedPosition, properties.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = properties.position; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + } + + if (grid.getSnapToGrid()) { + var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var position = Vec3.sum(deltaPosition, properties.position); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions, + properties.registrationPoint), properties.dimensions, properties.registrationPoint); + deltaPosition = Vec3.subtract(position, properties.position); + } + + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + Entities.editEntity(pastedEntityIDs[i], { + position: Vec3.sum(deltaPosition, entityPositions[i]) + }); + } + } + } if (isActive) { selectionManager.setSelections(pastedEntityIDs); } } else { - Window.notifyEditError("Can't import objects: objects would be out of bounds."); + Window.notifyEditError("Can't import entities: entities would be out of bounds."); } } else { Window.notifyEditError("There was an error importing the entity file."); @@ -2100,7 +2219,13 @@ function selectParticleEntity(entityID) { } entityListTool.webView.webEventReceived.connect(function (data) { - data = JSON.parse(data); + try { + data = JSON.parse(data); + } catch(e) { + print("edit.js: Error parsing JSON: " + e.name + " data " + data) + return; + } + if (data.type === 'parent') { parentSelectedEntities(); } else if(data.type === 'unparent') { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 2a4d535fee..36f7124f93 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -21,6 +21,11 @@ var blastShareText = "Blast to my Connections", hifiAlreadySharedText = "Already Shared to Snaps Feed", facebookShareText = "Share to Facebook", twitterShareText = "Share to Twitter"; + +function fileExtensionMatches(filePath, extension) { + return filePath.split('.').pop().toLowerCase() === extension; +} + function showSetupInstructions() { var snapshotImagesDiv = document.getElementById("snapshot-images"); snapshotImagesDiv.className = "snapshotInstructions"; @@ -276,10 +281,10 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi if (!image_data.localPath) { return; } - var id = "p" + (idCounter++), - imageContainer = document.createElement("DIV"), + var imageContainer = document.createElement("DIV"), img = document.createElement("IMG"), - isGif; + isGif = fileExtensionMatches(image_data.localPath, "gif"), + id = "p" + (isGif ? "1" : "0"); imageContainer.id = id; imageContainer.style.width = "95%"; imageContainer.style.height = "240px"; @@ -290,18 +295,17 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi imageContainer.style.position = "relative"; img.id = id + "img"; img.src = image_data.localPath; - isGif = img.src.split('.').pop().toLowerCase() === "gif"; imageContainer.appendChild(img); document.getElementById("snapshot-images").appendChild(imageContainer); + paths.push(image_data.localPath); img.onload = function () { - paths.push(image_data.localPath); if (isGif) { imageContainer.innerHTML += 'GIF'; } if (!isGifLoading) { appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast); } - if (!isGifLoading || (isShowingPreviousImages && !image_data.story_id)) { + if ((!isShowingPreviousImages && ((isGif && !isGifLoading) || !isGif)) || (isShowingPreviousImages && !image_data.story_id)) { shareForUrl(id); } if (isShowingPreviousImages && isLoggedIn && image_data.story_id) { @@ -638,9 +642,8 @@ window.onload = function () { // The last element of the message contents list contains a bunch of options, // including whether or not we can share stuff // The other elements of the list contain image paths. - - if (messageOptions.containsGif) { - if (messageOptions.processingGif) { + if (messageOptions.containsGif === true) { + if (messageOptions.processingGif === true) { imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon message.image_data.push({ localPath: messageOptions.loadingGifPath }); message.image_data.forEach(function (element, idx) { @@ -669,7 +672,7 @@ window.onload = function () { handleCaptureSetting(message.setting); break; case 'snapshotUploadComplete': - var isGif = message.image_url.split('.').pop().toLowerCase() === "gif"; + var isGif = fileExtensionMatches(message.image_url, "gif"); updateShareInfo(isGif ? "p1" : "p0", message.story_id); break; default: diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index f2873954ed..777ef54085 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -20,7 +20,8 @@ var ICON_FOR_TYPE = { Light: "p", Zone: "o", PolyVox: "", - Multiple: "" + Multiple: "", + PolyLine: "" } var EDITOR_TIMEOUT_DURATION = 1500; diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 757743accc..8c3f5d03bb 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -22,7 +22,9 @@ var DEFAULT_WIDTH = 0.4375; var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees var SENSOR_TO_ROOM_MATRIX = -2; var CAMERA_MATRIX = -7; -var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0}; +var ROT_Y_180 = {x: 0.0, y: 1.0, z: 0, w: 0}; +var ROT_LANDSCAPE = {x: 1.0, y: 1.0, z: 0, w: 0}; +var ROT_LANDSCAPE_WINDOW = {x: 0.0, y: 0.0, z: 0.0, w: 0}; var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1}; var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 }; var INCHES_TO_METERS = 1 / 39.3701; @@ -243,29 +245,29 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { }; WebTablet.prototype.getDimensions = function() { - if (this.landscape) { - return { x: this.width * 2, y: this.height, z: this.depth }; - } else { - return { x: this.width, y: this.height, z: this.depth }; - } + return { x: this.width, y: this.height, z: this.depth }; }; WebTablet.prototype.getTabletTextureResolution = function() { if (this.landscape) { - return { x: TABLET_TEXTURE_RESOLUTION.x * 2, y: TABLET_TEXTURE_RESOLUTION.y }; + return { x: TABLET_TEXTURE_RESOLUTION.y , y: TABLET_TEXTURE_RESOLUTION.x }; } else { return TABLET_TEXTURE_RESOLUTION; } }; WebTablet.prototype.setLandscape = function(newLandscapeValue) { - if (this.landscape == newLandscapeValue) { + if (this.landscape === newLandscapeValue) { return; } + this.landscape = newLandscapeValue; - Overlays.editOverlay(this.tabletEntityID, { dimensions: this.getDimensions() }); + Overlays.editOverlay(this.tabletEntityID, + { rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : + Quat.multiply(Camera.orientation, ROT_Y_180) }); Overlays.editOverlay(this.webOverlayID, { - resolution: this.getTabletTextureResolution() + resolution: this.getTabletTextureResolution(), + rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW) }); }; @@ -407,7 +409,7 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos return { position: worldMousePosition, - rotation: Quat.multiply(Camera.orientation, ROT_Y_180) + rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : Quat.multiply(Camera.orientation, ROT_Y_180) }; }; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 3b6d32ec1c..64a05fcebf 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -109,7 +109,13 @@ EntityListTool = function(opts) { }; webView.webEventReceived.connect(function(data) { - data = JSON.parse(data); + try { + data = JSON.parse(data); + } catch(e) { + print("entityList.js: Error parsing JSON: " + e.name + " data " + data) + return; + } + if (data.type == "selectionUpdate") { var ids = data.entityIds; var entityIDs = []; diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 0290674a0f..2c417a9dde 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -79,7 +79,7 @@ Grid = function(opts) { } } - that.snapToSurface = function(position, dimensions) { + that.snapToSurface = function(position, dimensions, registration) { if (!snapToGrid) { return position; } @@ -88,14 +88,18 @@ Grid = function(opts) { dimensions = { x: 0, y: 0, z: 0 }; } + if (registration === undefined) { + registration = { x: 0.5, y: 0.5, z: 0.5 }; + } + return { x: position.x, - y: origin.y + (dimensions.y / 2), + y: origin.y + (registration.y * dimensions.y), z: position.z }; } - that.snapToGrid = function(position, majorOnly, dimensions) { + that.snapToGrid = function(position, majorOnly, dimensions, registration) { if (!snapToGrid) { return position; } @@ -104,6 +108,10 @@ Grid = function(opts) { dimensions = { x: 0, y: 0, z: 0 }; } + if (registration === undefined) { + registration = { x: 0.5, y: 0.5, z: 0.5 }; + } + var spacing = majorOnly ? majorGridEvery : minorGridEvery; position = Vec3.subtract(position, origin); @@ -112,7 +120,7 @@ Grid = function(opts) { position.y = Math.round(position.y / spacing) * spacing; position.z = Math.round(position.z / spacing) * spacing; - return Vec3.sum(Vec3.sum(position, Vec3.multiply(0.5, dimensions)), origin); + return Vec3.sum(Vec3.sum(position, Vec3.multiplyVbyV(registration, dimensions)), origin); } that.snapToSpacing = function(delta, majorOnly) { @@ -161,9 +169,9 @@ Grid = function(opts) { if (data.origin) { var pos = data.origin; - pos.x = pos.x === undefined ? origin.x : pos.x; - pos.y = pos.y === undefined ? origin.y : pos.y; - pos.z = pos.z === undefined ? origin.z : pos.z; + pos.x = pos.x === undefined ? origin.x : parseFloat(pos.x); + pos.y = pos.y === undefined ? origin.y : parseFloat(pos.y); + pos.z = pos.z === undefined ? origin.z : parseFloat(pos.z); that.setPosition(pos, true); } @@ -238,7 +246,13 @@ GridTool = function(opts) { }); webView.webEventReceived.connect(function(data) { - data = JSON.parse(data); + try { + data = JSON.parse(data); + } catch(e) { + print("gridTool.js: Error parsing JSON: " + e.name + " data " + data) + return; + } + if (data.type == "init") { horizontalGrid.emitUpdate(); } else if (data.type == "update") { diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 1c257cfed5..2d3aaffdbf 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -36,6 +36,8 @@ var shareAfterLogin = false; var snapshotToShareAfterLogin = []; var METAVERSE_BASE = location.metaverseServerUrl; var isLoggedIn; +var numGifSnapshotUploadsPending = 0; +var numStillSnapshotUploadsPending = 0; // It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, // POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS @@ -56,6 +58,10 @@ function removeFromStoryIDsToMaybeDelete(story_id) { print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); } +function fileExtensionMatches(filePath, extension) { + return filePath.split('.').pop().toLowerCase() === extension; +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -139,6 +145,12 @@ function onMessage(message) { if (isLoggedIn) { print('Sharing snapshot with audience "for_url":', message.data); Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref")); + var isGif = fileExtensionMatches(message.data, "gif"); + if (isGif) { + numGifSnapshotUploadsPending++; + } else { + numStillSnapshotUploadsPending++; + } } else { shareAfterLogin = true; snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") }); @@ -307,22 +319,39 @@ function onButtonClicked() { function snapshotUploaded(isError, reply) { if (!isError) { - var replyJson = JSON.parse(reply); - var storyID = replyJson.user_story.id; + var replyJson = JSON.parse(reply), + storyID = replyJson.user_story.id, + imageURL = replyJson.user_story.details.image_url, + isGif = fileExtensionMatches(imageURL, "gif"), + ignoreGifSnapshotData = false, + ignoreStillSnapshotData = false; storyIDsToMaybeDelete.push(storyID); - var imageURL = replyJson.user_story.details.image_url; - var isGif = imageURL.split('.').pop().toLowerCase() === "gif"; - print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "snapshotUploadComplete", - story_id: storyID, - image_url: imageURL, - })); if (isGif) { - Settings.setValue("previousAnimatedSnapStoryID", storyID); + numGifSnapshotUploadsPending--; + if (numGifSnapshotUploadsPending !== 0) { + ignoreGifSnapshotData = true; + } } else { - Settings.setValue("previousStillSnapStoryID", storyID); + numStillSnapshotUploadsPending--; + if (numStillSnapshotUploadsPending !== 0) { + ignoreStillSnapshotData = true; + } + } + if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) { + print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "snapshotUploadComplete", + story_id: storyID, + image_url: imageURL, + })); + if (isGif) { + Settings.setValue("previousAnimatedSnapStoryID", storyID); + } else { + Settings.setValue("previousStillSnapStoryID", storyID); + } + } else { + print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID); } } else { print(reply); @@ -381,8 +410,15 @@ function takeSnapshot() { Menu.setIsOptionChecked("Overlays", false); } + var snapActivateSound = SoundCache.getSound(Script.resolvePath("../../resources/sounds/snap.wav")); + // take snapshot (with no notification) Script.setTimeout(function () { + Audio.playSound(snapActivateSound, { + position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, + localOnly: true, + volume: 1.0 + }); HMD.closeTablet(); Script.setTimeout(function () { Window.takeSnapshot(false, includeAnimated, 1.91); @@ -568,6 +604,12 @@ function onUsernameChanged() { snapshotToShareAfterLogin.forEach(function (element) { print('Uploading snapshot after login:', element.path); Window.shareSnapshot(element.path, element.href); + var isGif = fileExtensionMatches(element.path, "gif"); + if (isGif) { + numGifSnapshotUploadsPending++; + } else { + numStillSnapshotUploadsPending++; + } }); } }); diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index f83e8d9550..e45fc8d87b 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -191,16 +191,12 @@ gTablet.updateAudioBar(currentMicLevel); } - if (validCheckTime - now > MSECS_PER_SEC/4) { - //each 250ms should be just fine + if (now - validCheckTime > MSECS_PER_SEC) { + validCheckTime = now; updateTabletWidthFromSettings(); if (UIWebTablet) { UIWebTablet.setLandscape(landscape); } - } - - if (validCheckTime - now > MSECS_PER_SEC) { - validCheckTime = now; if (tabletRezzed && UIWebTablet && !tabletIsValid()) { // when we switch domains, the tablet entity gets destroyed and recreated. this causes // the overlay to be deleted, but not recreated. If the overlay is deleted for this or any diff --git a/server-console/src/main.js b/server-console/src/main.js index 98bda9a10f..408a17bd56 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -821,6 +821,17 @@ for (var key in trayIcons) { const notificationIcon = path.join(__dirname, '../resources/console-notification.png'); +function isProcessRunning(pid) { + try { + // Sending a signal of 0 is effectively a NOOP. + // If sending the signal is successful, kill will return true. + // If the process is not running, an exception will be thrown. + return process.kill(pid, 0); + } catch (e) { + } + return false; +} + function onContentLoaded() { // Disable splash window for now. // maybeShowSplash(); @@ -882,31 +893,18 @@ function onContentLoaded() { startInterface(); } - // If we were launched with the shutdownWatcher option, then we need to watch for the interface app - // shutting down. The interface app will regularly update a running state file which we will check. - // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. - if (argv.shutdownWatcher) { - log.debug("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher); - var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates - var firstAttemptToCheck = new Date().getTime(); - var shutdownWatchInterval = setInterval(function(){ - var stats = fs.stat(argv.shutdownWatcher, function(err, stats) { - if (err) { - var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck; - if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) { - log.debug("Running state file is missing, assume interface has shutdown... shutting down snadbox."); - forcedShutdown(); - clearTimeout(shutdownWatchInterval); - } - } else { - var sinceEdit = new Date().getTime() - stats.mtime.getTime(); - if (sinceEdit > MAX_TIME_SINCE_EDIT) { - log.debug("Running state of interface hasn't updated in MAX time... shutting down."); - forcedShutdown(); - clearTimeout(shutdownWatchInterval); - } - } - }); + // If we were launched with the shutdownWith option, then we need to shutdown when that process (pid) + // is no longer running. + if (argv.shutdownWith) { + let pid = argv.shutdownWith; + console.log("Shutting down with process: ", pid); + let checkProcessInterval = setInterval(function() { + let isRunning = isProcessRunning(pid); + if (!isRunning) { + log.debug("Watched process is no longer running, shutting down"); + clearTimeout(checkProcessInterval); + forcedShutdown(); + } }, 1000); } } diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index e697bd501f..81f2f8d581 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -122,6 +122,10 @@ int main(int argc, char** argv) { glm::mat4(), glm::mat4(), glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), glm::mat4() }; @@ -144,6 +148,10 @@ int main(int argc, char** argv) { glm::mat4(), glm::mat4(), glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), + glm::mat4(), glm::mat4() }; diff --git a/tests/ktx/CMakeLists.txt b/tests/ktx/CMakeLists.txt index 7133c30898..599435a90b 100644 --- a/tests/ktx/CMakeLists.txt +++ b/tests/ktx/CMakeLists.txt @@ -1,15 +1,9 @@ +# Declare dependencies +macro (SETUP_TESTCASE_DEPENDENCIES) + # link in the shared libraries + link_hifi_libraries(shared ktx gpu image) -set(TARGET_NAME ktx-test) - -if (WIN32) - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") -endif() + package_libraries_for_deployment() +endmacro () -# This is not a testcase -- just set it up as a regular hifi project -setup_hifi_project(Quick Gui OpenGL) -set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") - -# link in the shared libraries -link_hifi_libraries(shared octree ktx gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics image) - -package_libraries_for_deployment() +setup_hifi_testcase() diff --git a/tests/ktx/src/KtxTests.cpp b/tests/ktx/src/KtxTests.cpp new file mode 100644 index 0000000000..94e5d7e8e7 --- /dev/null +++ b/tests/ktx/src/KtxTests.cpp @@ -0,0 +1,195 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "KtxTests.h" + +#include + +#include + +#include +#include +#include + + +QTEST_GUILESS_MAIN(KtxTests) + +QString getRootPath() { + static std::once_flag once; + static QString result; + std::call_once(once, [&] { + QFileInfo file(__FILE__); + QDir parent = file.absolutePath(); + result = QDir::cleanPath(parent.currentPath() + "/../../.."); + }); + return result; +} + +void KtxTests::initTestCase() { +} + +void KtxTests::cleanupTestCase() { +} + +void KtxTests::testKhronosCompressionFunctions() { + using namespace khronos::gl::texture; + QCOMPARE(evalAlignedCompressedBlockCount<4>(0), (uint32_t)0x0); + QCOMPARE(evalAlignedCompressedBlockCount<4>(1), (uint32_t)0x1); + QCOMPARE(evalAlignedCompressedBlockCount<4>(4), (uint32_t)0x1); + QCOMPARE(evalAlignedCompressedBlockCount<4>(5), (uint32_t)0x2); + QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x00), (uint32_t)0x00); + QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x01), (uint32_t)0x01); + QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x04), (uint32_t)0x01); + QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x05), (uint32_t)0x02); + QCOMPARE(evalCompressedBlockCount(InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, 0x1000), (uint32_t)0x400); + + QVERIFY_EXCEPTION_THROWN(evalCompressedBlockCount(InternalFormat::RGBA8, 0x00), std::runtime_error); +} + +void KtxTests::testKtxEvalFunctions() { + QCOMPARE(sizeof(ktx::Header), (size_t)64); + QCOMPARE(ktx::evalPadding(0x0), (uint8_t)0); + QCOMPARE(ktx::evalPadding(0x1), (uint8_t)3); + QCOMPARE(ktx::evalPadding(0x2), (uint8_t)2); + QCOMPARE(ktx::evalPadding(0x3), (uint8_t)1); + QCOMPARE(ktx::evalPadding(0x4), (uint8_t)0); + QCOMPARE(ktx::evalPadding(0x400), (uint8_t)0); + QCOMPARE(ktx::evalPadding(0x401), (uint8_t)3); + QCOMPARE(ktx::evalPaddedSize(0x0), 0x0); + QCOMPARE(ktx::evalPaddedSize(0x1), 0x4); + QCOMPARE(ktx::evalPaddedSize(0x2), 0x4); + QCOMPARE(ktx::evalPaddedSize(0x3), 0x4); + QCOMPARE(ktx::evalPaddedSize(0x4), 0x4); + QCOMPARE(ktx::evalPaddedSize(0x400), 0x400); + QCOMPARE(ktx::evalPaddedSize(0x401), 0x404); + QCOMPARE(ktx::evalAlignedCount((uint32_t)0x0), (uint32_t)0x0); + QCOMPARE(ktx::evalAlignedCount((uint32_t)0x1), (uint32_t)0x1); + QCOMPARE(ktx::evalAlignedCount((uint32_t)0x4), (uint32_t)0x1); + QCOMPARE(ktx::evalAlignedCount((uint32_t)0x5), (uint32_t)0x2); +} + +void KtxTests::testKtxSerialization() { + const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png"; + QImage image(TEST_IMAGE); + gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true); + auto ktxMemory = gpu::Texture::serialize(*testTexture); + QVERIFY(ktxMemory.get()); + + // Serialize the image to a file + QTemporaryFile TEST_IMAGE_KTX; + { + const auto& ktxStorage = ktxMemory->getStorage(); + QVERIFY(ktx::KTX::validate(ktxStorage)); + QVERIFY(ktxMemory->isValid()); + + auto& outFile = TEST_IMAGE_KTX; + if (!outFile.open()) { + QFAIL("Unable to open file"); + } + auto ktxSize = ktxStorage->size(); + outFile.resize(ktxSize); + auto dest = outFile.map(0, ktxSize); + memcpy(dest, ktxStorage->data(), ktxSize); + outFile.unmap(dest); + outFile.close(); + } + + + { + auto ktxStorage = std::make_shared(TEST_IMAGE_KTX.fileName()); + QVERIFY(ktx::KTX::validate(ktxStorage)); + auto ktxFile = ktx::KTX::create(ktxStorage); + QVERIFY(ktxFile.get()); + QVERIFY(ktxFile->isValid()); + { + const auto& memStorage = ktxMemory->getStorage(); + const auto& fileStorage = ktxFile->getStorage(); + QVERIFY(memStorage->size() == fileStorage->size()); + QVERIFY(memStorage->data() != fileStorage->data()); + QVERIFY(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size())); + QVERIFY(ktxFile->_images.size() == ktxMemory->_images.size()); + auto imageCount = ktxFile->_images.size(); + auto startMemory = ktxMemory->_storage->data(); + auto startFile = ktxFile->_storage->data(); + for (size_t i = 0; i < imageCount; ++i) { + auto memImages = ktxMemory->_images[i]; + auto fileImages = ktxFile->_images[i]; + QVERIFY(memImages._padding == fileImages._padding); + QVERIFY(memImages._numFaces == fileImages._numFaces); + QVERIFY(memImages._imageSize == fileImages._imageSize); + QVERIFY(memImages._faceSize == fileImages._faceSize); + QVERIFY(memImages._faceBytes.size() == memImages._numFaces); + QVERIFY(fileImages._faceBytes.size() == fileImages._numFaces); + auto faceCount = fileImages._numFaces; + for (uint32_t face = 0; face < faceCount; ++face) { + auto memFace = memImages._faceBytes[face]; + auto memOffset = memFace - startMemory; + auto fileFace = fileImages._faceBytes[face]; + auto fileOffset = fileFace - startFile; + QVERIFY(memOffset % 4 == 0); + QVERIFY(memOffset == fileOffset); + } + } + } + } + testTexture->setKtxBacking(TEST_IMAGE_KTX.fileName().toStdString()); +} + +#if 0 + +static const QString TEST_FOLDER { "H:/ktx_cacheold" }; +//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" }; + +//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" }; +static const QString EXTENSIONS { "*.ktx" }; + +int mainTemp(int, char**) { + qInstallMessageHandler(messageHandler); + auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS }); + for (auto fileInfo : fileInfoList) { + qDebug() << fileInfo.filePath(); + std::shared_ptr storage { new storage::FileStorage { fileInfo.filePath() } }; + + if (!ktx::KTX::validate(storage)) { + qDebug() << "KTX invalid"; + } + + auto ktxFile = ktx::KTX::create(storage); + ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor(); + + qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs"; + for (const auto& kv : ktxDescriptor.keyValues) { + qDebug() << "\t" << kv._key.c_str(); + } + + auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY); + if (offsetToMinMipKV) { + auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV; + auto minMipLevelAvailable = *data; + qDebug() << "\tMin mip available " << minMipLevelAvailable; + assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels); + } + auto storageSize = storage->size(); + for (const auto& faceImageDesc : ktxDescriptor.images) { + //assert(0 == (faceImageDesc._faceSize % 4)); + for (const auto& faceOffset : faceImageDesc._faceOffsets) { + assert(0 == (faceOffset % 4)); + auto faceEndOffset = faceOffset + faceImageDesc._faceSize; + assert(faceEndOffset <= storageSize); + } + } + + for (const auto& faceImage : ktxFile->_images) { + for (const ktx::Byte* faceBytes : faceImage._faceBytes) { + assert(0 == (reinterpret_cast(faceBytes) % 4)); + } + } + } + return 0; +} +#endif diff --git a/tests/ktx/src/KtxTests.h b/tests/ktx/src/KtxTests.h new file mode 100644 index 0000000000..5627dc313d --- /dev/null +++ b/tests/ktx/src/KtxTests.h @@ -0,0 +1,21 @@ +// +// Created by Bradley Austin Davis on 2016/07/01 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +class KtxTests : public QObject { + Q_OBJECT +private slots: + void initTestCase(); + void cleanupTestCase(); + void testKtxEvalFunctions(); + void testKhronosCompressionFunctions(); + void testKtxSerialization(); +}; + + diff --git a/tests/ktx/src/main.cpp b/tests/ktx/src/main.cpp deleted file mode 100644 index 3b62b89948..0000000000 --- a/tests/ktx/src/main.cpp +++ /dev/null @@ -1,223 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/07/01 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -QSharedPointer logger; - -gpu::Texture* cacheTexture(const std::string& name, gpu::Texture* srcTexture, bool write = true, bool read = true); - - -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message); - - if (!logMessage.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(logMessage.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#endif - if (logger) { - logger->addMessage(qPrintable(logMessage + "\n")); - } - } -} - -const char * LOG_FILTER_RULES = R"V0G0N( -hifi.gpu=true -)V0G0N"; - -QString getRootPath() { - static std::once_flag once; - static QString result; - std::call_once(once, [&] { - QFileInfo file(__FILE__); - QDir parent = file.absolutePath(); - result = QDir::cleanPath(parent.currentPath() + "/../../.."); - }); - return result; -} - -const QString TEST_IMAGE = getRootPath() + "/scripts/developer/tests/cube_texture.png"; -const QString TEST_IMAGE_KTX = getRootPath() + "/scripts/developer/tests/cube_texture.ktx"; - -int main(int argc, char** argv) { - QApplication app(argc, argv); - QCoreApplication::setApplicationName("KTX"); - QCoreApplication::setOrganizationName("High Fidelity"); - QCoreApplication::setOrganizationDomain("highfidelity.com"); - logger.reset(new FileLogger()); - - Q_ASSERT(ktx::evalPadding(0) == 0); - Q_ASSERT(ktx::evalPadding(1) == 3); - Q_ASSERT(ktx::evalPadding(2) == 2); - Q_ASSERT(ktx::evalPadding(3) == 1); - Q_ASSERT(ktx::evalPadding(4) == 0); - Q_ASSERT(ktx::evalPadding(1024) == 0); - Q_ASSERT(ktx::evalPadding(1025) == 3); - Q_ASSERT(ktx::evalPaddedSize(0) == 0); - Q_ASSERT(ktx::evalPaddedSize(1) == 4); - Q_ASSERT(ktx::evalPaddedSize(2) == 4); - Q_ASSERT(ktx::evalPaddedSize(3) == 4); - Q_ASSERT(ktx::evalPaddedSize(4) == 4); - Q_ASSERT(ktx::evalPaddedSize(1024) == 1024); - Q_ASSERT(ktx::evalPaddedSize(1025) == 1028); - Q_ASSERT(sizeof(ktx::Header) == 12 + (sizeof(uint32_t) * 13)); - - DependencyManager::set(); - qInstallMessageHandler(messageHandler); - QLoggingCategory::setFilterRules(LOG_FILTER_RULES); - - QImage image(TEST_IMAGE); - gpu::TexturePointer testTexture = image::TextureUsage::process2DTextureColorFromImage(image, TEST_IMAGE.toStdString(), true); - - auto ktxMemory = gpu::Texture::serialize(*testTexture); - { - const auto& ktxStorage = ktxMemory->getStorage(); - Q_ASSERT_X(ktx::KTX::validate(ktxStorage), __FUNCTION__, "KTX storage validation failed"); - Q_ASSERT_X(ktxMemory->isValid(), __FUNCTION__, "KTX self-validation failed"); - QSaveFile outFile(TEST_IMAGE_KTX); - if (!outFile.open(QFile::WriteOnly)) { - throw std::runtime_error("Unable to open file"); - } - auto ktxSize = ktxStorage->size(); - outFile.resize(ktxSize); - auto dest = outFile.map(0, ktxSize); - memcpy(dest, ktxStorage->data(), ktxSize); - outFile.unmap(dest); - outFile.commit(); - } - - { - auto ktxFile = ktx::KTX::create(std::shared_ptr(new storage::FileStorage(TEST_IMAGE_KTX))); - { - const auto& memStorage = ktxMemory->getStorage(); - const auto& fileStorage = ktxFile->getStorage(); - Q_ASSERT(memStorage->size() == fileStorage->size()); - Q_ASSERT(memStorage->data() != fileStorage->data()); - Q_ASSERT(0 == memcmp(memStorage->data(), fileStorage->data(), memStorage->size())); - Q_ASSERT(ktxFile->_images.size() == ktxMemory->_images.size()); - auto imageCount = ktxFile->_images.size(); - auto startMemory = ktxMemory->_storage->data(); - auto startFile = ktxFile->_storage->data(); - for (size_t i = 0; i < imageCount; ++i) { - auto memImages = ktxMemory->_images[i]; - auto fileImages = ktxFile->_images[i]; - Q_ASSERT(memImages._padding == fileImages._padding); - Q_ASSERT(memImages._numFaces == fileImages._numFaces); - Q_ASSERT(memImages._imageSize == fileImages._imageSize); - Q_ASSERT(memImages._faceSize == fileImages._faceSize); - Q_ASSERT(memImages._faceBytes.size() == memImages._numFaces); - Q_ASSERT(fileImages._faceBytes.size() == fileImages._numFaces); - auto faceCount = fileImages._numFaces; - for (uint32_t face = 0; face < faceCount; ++face) { - auto memFace = memImages._faceBytes[face]; - auto memOffset = memFace - startMemory; - auto fileFace = fileImages._faceBytes[face]; - auto fileOffset = fileFace - startFile; - Q_ASSERT(memOffset % 4 == 0); - Q_ASSERT(memOffset == fileOffset); - } - } - } - } - testTexture->setKtxBacking(TEST_IMAGE_KTX.toStdString()); - return 0; -} - -#if 0 -static const QString TEST_FOLDER { "H:/ktx_cacheold" }; -//static const QString TEST_FOLDER { "C:/Users/bdavis/Git/KTX/testimages" }; - -//static const QString EXTENSIONS { "4bbdf8f786470e4ab3e672d44b8e8df2.ktx" }; -static const QString EXTENSIONS { "*.ktx" }; - -int mainTemp(int, char**) { - qInstallMessageHandler(messageHandler); - auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS }); - for (auto fileInfo : fileInfoList) { - qDebug() << fileInfo.filePath(); - std::shared_ptr storage { new storage::FileStorage { fileInfo.filePath() } }; - - if (!ktx::KTX::validate(storage)) { - qDebug() << "KTX invalid"; - } - - auto ktxFile = ktx::KTX::create(storage); - ktx::KTXDescriptor ktxDescriptor = ktxFile->toDescriptor(); - - qDebug() << "Contains " << ktxDescriptor.keyValues.size() << " key value pairs"; - for (const auto& kv : ktxDescriptor.keyValues) { - qDebug() << "\t" << kv._key.c_str(); - } - - auto offsetToMinMipKV = ktxDescriptor.getValueOffsetForKey(ktx::HIFI_MIN_POPULATED_MIP_KEY); - if (offsetToMinMipKV) { - auto data = storage->data() + ktx::KTX_HEADER_SIZE + offsetToMinMipKV; - auto minMipLevelAvailable = *data; - qDebug() << "\tMin mip available " << minMipLevelAvailable; - assert(minMipLevelAvailable < ktxDescriptor.header.numberOfMipmapLevels); - } - auto storageSize = storage->size(); - for (const auto& faceImageDesc : ktxDescriptor.images) { - //assert(0 == (faceImageDesc._faceSize % 4)); - for (const auto& faceOffset : faceImageDesc._faceOffsets) { - assert(0 == (faceOffset % 4)); - auto faceEndOffset = faceOffset + faceImageDesc._faceSize; - assert(faceEndOffset <= storageSize); - } - } - - for (const auto& faceImage : ktxFile->_images) { - for (const ktx::Byte* faceBytes : faceImage._faceBytes) { - assert(0 == (reinterpret_cast(faceBytes) % 4)); - } - } - } - return 0; -} -#endif - -#include "main.moc" - diff --git a/tests/networking/src/FileCacheTests.cpp b/tests/networking/src/FileCacheTests.cpp new file mode 100644 index 0000000000..79fe9dee54 --- /dev/null +++ b/tests/networking/src/FileCacheTests.cpp @@ -0,0 +1,187 @@ +// +// ResoruceTests.cpp +// +// 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 "FileCacheTests.h" + +#include + +QTEST_GUILESS_MAIN(FileCacheTests) + +using namespace cache; + +// Limit the file size to 10 MB +static const size_t MAX_UNUSED_SIZE { 1024 * 1024 * 10 }; +static const QByteArray TEST_DATA { 1024 * 1024, '0' }; + +static std::string getFileKey(int i) { + return QString(QByteArray { 1, (char)i }.toHex()).toStdString(); +} + +class TestFile : public File { + using Parent = File; +public: + TestFile(Metadata&& metadata, const std::string& filepath) + : Parent(std::move(metadata), filepath) { + } +}; + +class TestFileCache : public FileCache { + using Parent = FileCache; + +public: + TestFileCache(const std::string& dirname, const std::string& ext, QObject* parent = nullptr) : Parent(dirname, ext, nullptr) { + initialize(); + } + + std::unique_ptr createFile(Metadata&& metadata, const std::string& filepath) override { + qCInfo(file_cache) << "Wrote KTX" << metadata.key.c_str(); + return std::unique_ptr(new TestFile(std::move(metadata), filepath)); + } +}; + +using CachePointer = std::shared_ptr; + +// The FileCache relies on deleteLater to clear unused files, but QTest classes don't run a conventional event loop +// so we need to call this function to force any pending deletes to occur in the File destructor +static void forceDeletes() { + while (QCoreApplication::hasPendingEvents()) { + QCoreApplication::sendPostedEvents(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } +} + +size_t FileCacheTests::getCacheDirectorySize() const { + size_t result = 0; + QDir dir(_testDir.path()); + for (const auto& file : dir.entryList({ "*.tmp" })) { + result += QFileInfo(dir.absoluteFilePath(file)).size(); + } + return result; +} + +CachePointer makeFileCache(QString& location) { + auto result = std::make_shared(location.toStdString(), "tmp"); + result->setMaxSize(MAX_UNUSED_SIZE); + return result; +} + +void FileCacheTests::initTestCase() { +} + +void FileCacheTests::testUnusedFiles() { + auto cache = makeFileCache(_testDir.path()); + std::list inUseFiles; + + { + for (int i = 0; i < 100; ++i) { + std::string key = getFileKey(i); + auto file = cache->writeFile(TEST_DATA.data(), TestFileCache::Metadata(key, TEST_DATA.size())); + inUseFiles.push_back(file); + forceDeletes(); + QThread::msleep(10); + } + QCOMPARE(cache->getNumCachedFiles(), (size_t)0); + QCOMPARE(cache->getNumTotalFiles(), (size_t)100); + // Release the in-use files + inUseFiles.clear(); + // Cache state is updated, but the directory state is unchanged, + // because the file deletes are triggered by an event loop + QCOMPARE(cache->getNumCachedFiles(), (size_t)10); + QCOMPARE(cache->getNumTotalFiles(), (size_t)10); + QVERIFY(getCacheDirectorySize() > MAX_UNUSED_SIZE); + forceDeletes(); + QCOMPARE(cache->getNumCachedFiles(), (size_t)10); + QCOMPARE(cache->getNumTotalFiles(), (size_t)10); + QVERIFY(getCacheDirectorySize() <= MAX_UNUSED_SIZE); + } + + // Reset the cache + cache = makeFileCache(_testDir.path()); + { + // Test files 0 to 89 are missing, because the LRU algorithm deleted them when we released the files + for (int i = 0; i < 90; ++i) { + std::string key = getFileKey(i); + auto file = cache->getFile(key); + QVERIFY(!file.get()); + } + + // Test files 90 to 99 are present + for (int i = 90; i < 100; ++i) { + std::string key = getFileKey(i); + auto file = cache->getFile(key); + QVERIFY(file.get()); + inUseFiles.push_back(file); + + if (i == 94) { + // Each access touches the file, so we need to sleep here to ensure that the the last 5 files + // have later times for cache ejection priority, otherwise the test runs too fast to reliably + // differentiate + QThread::msleep(1000); + } + } + + QCOMPARE(cache->getNumCachedFiles(), (size_t)0); + QCOMPARE(cache->getNumTotalFiles(), (size_t)10); + inUseFiles.clear(); + QCOMPARE(cache->getNumCachedFiles(), (size_t)10); + QCOMPARE(cache->getNumTotalFiles(), (size_t)10); + } +} + +size_t FileCacheTests::getFreeSpace() const { + return QStorageInfo(_testDir.path()).bytesFree(); +} + +// FIXME if something else is changing the amount of free space on the target drive concurrently with this test +// running, then it may fail +void FileCacheTests::testFreeSpacePreservation() { + QCOMPARE(getCacheDirectorySize(), MAX_UNUSED_SIZE); + // Set the target free space to slightly above whatever the current free space is... + size_t targetFreeSpace = getFreeSpace() + MAX_UNUSED_SIZE / 2; + + // Reset the cache + auto cache = makeFileCache(_testDir.path()); + // Setting the min free space causes it to eject the oldest files that cause the cache to exceed the minimum space + cache->setMinFreeSize(targetFreeSpace); + QVERIFY(getFreeSpace() < targetFreeSpace); + forceDeletes(); + QCOMPARE(cache->getNumCachedFiles(), (size_t)5); + QCOMPARE(cache->getNumTotalFiles(), (size_t)5); + QVERIFY(getFreeSpace() >= targetFreeSpace); + for (int i = 0; i < 95; ++i) { + std::string key = getFileKey(i); + auto file = cache->getFile(key); + QVERIFY(!file.get()); + } + for (int i = 95; i < 100; ++i) { + std::string key = getFileKey(i); + auto file = cache->getFile(key); + QVERIFY(file.get()); + } +} + +void FileCacheTests::testWipe() { + // Reset the cache + auto cache = makeFileCache(_testDir.path()); + QCOMPARE(cache->getNumCachedFiles(), (size_t)5); + QCOMPARE(cache->getNumTotalFiles(), (size_t)5); + cache->wipe(); + QCOMPARE(cache->getNumCachedFiles(), (size_t)0); + QCOMPARE(cache->getNumTotalFiles(), (size_t)0); + QVERIFY(getCacheDirectorySize() > 0); + forceDeletes(); + QCOMPARE(getCacheDirectorySize(), (size_t)0); +} + + +void FileCacheTests::cleanupTestCase() { +} + diff --git a/tests/networking/src/FileCacheTests.h b/tests/networking/src/FileCacheTests.h new file mode 100644 index 0000000000..b34b384855 --- /dev/null +++ b/tests/networking/src/FileCacheTests.h @@ -0,0 +1,31 @@ +// +// ResourceTests.h +// +// 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_ResourceTests_h +#define hifi_ResourceTests_h + +#include +#include + +class FileCacheTests : public QObject { + Q_OBJECT +private slots: + void initTestCase(); + void testUnusedFiles(); + void testFreeSpacePreservation(); + void cleanupTestCase(); + void testWipe(); + +private: + size_t getFreeSpace() const; + size_t getCacheDirectorySize() const; + QTemporaryDir _testDir; +}; + +#endif // hifi_ResourceTests_h diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index 8a239f0728..d10ab1ddbe 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -31,13 +31,9 @@ #include #include -#include #include #include -#include -#include - #include #include @@ -66,9 +62,6 @@ #include #include -#include -#include - #include #include @@ -155,11 +148,8 @@ void QTestWindow::draw() { testShaderBuild(simple_vert, simple_frag); testShaderBuild(simple_vert, simple_textured_frag); testShaderBuild(simple_vert, simple_textured_unlit_frag); - testShaderBuild(deferred_light_vert, directional_light_frag); testShaderBuild(deferred_light_vert, directional_ambient_light_frag); testShaderBuild(deferred_light_vert, directional_skybox_light_frag); - testShaderBuild(deferred_light_point_vert, point_light_frag); - testShaderBuild(deferred_light_spot_vert, spot_light_frag); testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag); testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag); @@ -190,7 +180,6 @@ void QTestWindow::draw() { testShaderBuild(ambient_occlusion_vert, ambient_occlusion_frag); testShaderBuild(ambient_occlusion_vert, occlusion_blend_frag); */ - testShaderBuild(hit_effect_vert, hit_effect_frag); testShaderBuild(overlay3D_vert, overlay3D_frag); diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index b4af4729a3..83a294ee1d 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -140,3 +140,77 @@ void GLMHelpersTests::testSimd() { } qDebug() << "Done "; } + +void GLMHelpersTests::testGenerateBasisVectors() { + { // very simple case: primary along X, secondary is linear combination of X and Y + glm::vec3 u(1.0f, 0.0f, 0.0f); + glm::vec3 v(1.0f, 1.0f, 0.0f); + glm::vec3 w; + + generateBasisVectors(u, v, u, v, w); + + QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_X, EPSILON); + QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_Y, EPSILON); + QCOMPARE_WITH_ABS_ERROR(w, Vectors::UNIT_Z, EPSILON); + } + + { // point primary along Y instead of X + glm::vec3 u(0.0f, 1.0f, 0.0f); + glm::vec3 v(1.0f, 1.0f, 0.0f); + glm::vec3 w; + + generateBasisVectors(u, v, u, v, w); + + QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_Y, EPSILON); + QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_X, EPSILON); + QCOMPARE_WITH_ABS_ERROR(w, -Vectors::UNIT_Z, EPSILON); + } + + { // pass bad data (both vectors along Y). The helper will guess X for secondary. + glm::vec3 u(0.0f, 1.0f, 0.0f); + glm::vec3 v(0.0f, 1.0f, 0.0f); + glm::vec3 w; + + generateBasisVectors(u, v, u, v, w); + + QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_Y, EPSILON); + QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_X, EPSILON); + QCOMPARE_WITH_ABS_ERROR(w, -Vectors::UNIT_Z, EPSILON); + } + + { // pass bad data (both vectors along X). The helper will guess X for secondary, fail, then guess Y. + glm::vec3 u(1.0f, 0.0f, 0.0f); + glm::vec3 v(1.0f, 0.0f, 0.0f); + glm::vec3 w; + + generateBasisVectors(u, v, u, v, w); + + QCOMPARE_WITH_ABS_ERROR(u, Vectors::UNIT_X, EPSILON); + QCOMPARE_WITH_ABS_ERROR(v, Vectors::UNIT_Y, EPSILON); + QCOMPARE_WITH_ABS_ERROR(w, Vectors::UNIT_Z, EPSILON); + } + + { // general case for arbitrary rotation + float angle = 1.234f; + glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + glm::quat rotation = glm::angleAxis(angle, axis); + + // expected values + glm::vec3 x = rotation * Vectors::UNIT_X; + glm::vec3 y = rotation * Vectors::UNIT_Y; + glm::vec3 z = rotation * Vectors::UNIT_Z; + + // primary is along x + // secondary is linear combination of x and y + // tertiary is unknown + glm::vec3 u = 1.23f * x; + glm::vec3 v = 2.34f * x + 3.45f * y; + glm::vec3 w; + + generateBasisVectors(u, v, u, v, w); + + QCOMPARE_WITH_ABS_ERROR(u, x, EPSILON); + QCOMPARE_WITH_ABS_ERROR(v, y, EPSILON); + QCOMPARE_WITH_ABS_ERROR(w, z, EPSILON); + } +} diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h index acc7b533f5..030f2d477f 100644 --- a/tests/shared/src/GLMHelpersTests.h +++ b/tests/shared/src/GLMHelpersTests.h @@ -21,6 +21,7 @@ private slots: void testEulerDecomposition(); void testSixByteOrientationCompression(); void testSimd(); + void testGenerateBasisVectors(); }; float getErrorDifference(const float& a, const float& b); diff --git a/tests/shared/src/StorageTests.cpp b/tests/shared/src/StorageTests.cpp index fa538f6911..48e6b91900 100644 --- a/tests/shared/src/StorageTests.cpp +++ b/tests/shared/src/StorageTests.cpp @@ -8,6 +8,8 @@ #include "StorageTests.h" +#include + QTEST_MAIN(StorageTests) using namespace storage; @@ -32,8 +34,8 @@ void StorageTests::testConversion() { QFileInfo fileInfo(_testFile); QCOMPARE(fileInfo.exists(), false); } - StoragePointer storagePointer = std::make_unique(_testData.size(), _testData.data()); - QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + StoragePointer storagePointer = std::unique_ptr(new MemoryStorage(_testData.size(), _testData.data())); + QCOMPARE(storagePointer->size(), _testData.size()); QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); // Convert to a file storagePointer = storagePointer->toFileStorage(_testFile); @@ -42,12 +44,12 @@ void StorageTests::testConversion() { QCOMPARE(fileInfo.exists(), true); QCOMPARE(fileInfo.size(), (qint64)_testData.size()); } - QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(storagePointer->size(), _testData.size()); QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); // Convert to memory storagePointer = storagePointer->toMemoryStorage(); - QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(storagePointer->size(), _testData.size()); QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); { // ensure the file is unaffected @@ -58,13 +60,13 @@ void StorageTests::testConversion() { // truncate the data as a new memory object auto newSize = _testData.size() / 2; - storagePointer = std::make_unique(newSize, storagePointer->data()); - QCOMPARE(storagePointer->size(), (quint64)newSize); + storagePointer = std::unique_ptr(new MemoryStorage(newSize, storagePointer->data())); + QCOMPARE(storagePointer->size(), newSize); QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); // Convert back to file storagePointer = storagePointer->toFileStorage(_testFile); - QCOMPARE(storagePointer->size(), (quint64)newSize); + QCOMPARE(storagePointer->size(), newSize); QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); { // ensure the file is truncated diff --git a/scripts/system/assets/sounds/countdown-tick.wav b/unpublishedScripts/marketplace/record/assets/sounds/countdown-tick.wav similarity index 100% rename from scripts/system/assets/sounds/countdown-tick.wav rename to unpublishedScripts/marketplace/record/assets/sounds/countdown-tick.wav diff --git a/scripts/system/assets/sounds/finish-recording.wav b/unpublishedScripts/marketplace/record/assets/sounds/finish-recording.wav similarity index 100% rename from scripts/system/assets/sounds/finish-recording.wav rename to unpublishedScripts/marketplace/record/assets/sounds/finish-recording.wav diff --git a/scripts/system/assets/sounds/start-recording.wav b/unpublishedScripts/marketplace/record/assets/sounds/start-recording.wav similarity index 100% rename from scripts/system/assets/sounds/start-recording.wav rename to unpublishedScripts/marketplace/record/assets/sounds/start-recording.wav diff --git a/unpublishedScripts/marketplace/record/html/css/edit-style.css b/unpublishedScripts/marketplace/record/html/css/edit-style.css new file mode 100644 index 0000000000..fcb1815ca4 --- /dev/null +++ b/unpublishedScripts/marketplace/record/html/css/edit-style.css @@ -0,0 +1,1262 @@ +/* +// edit-style.css +// +// Created by Ryan Huffman on 13 Nov 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf), /* Development, running script in /HiFi/examples */ + url(../fonts/Raleway-Regular.ttf); /* Marketplace script */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf), + url(../fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf), + url(../fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf), + url(../fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf), + url(../fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf), + url(../fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf), + url(../fonts/hifi-glyphs.ttf); +} + +* { + margin: 0; + padding: 0; +} + +body { + padding: 21px 21px 21px 21px; + + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +table { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + border-collapse: collapse; + width: 100%; + border: 2px solid #575757; + border-radius: 7px; +} + +thead { + font-family: Raleway-Regular; + font-size: 12px; + text-transform: uppercase; + background-color: #1c1c1c; + padding: 1px 0px; + border-bottom: 1px solid #575757; + width: 100%; +} + +tbody { + width: 100%; +} + +tfoot { + font-family: Raleway-Light; + font-size: 13px; + background-color: #1c1c1c; + border-top: 1px solid #575757; + width: 100%; +} + +tfoot tr { + background-color: #1c1cff; +} + +thead tr { + height: 26px; /* 28px with thead padding */ +} + +thead th { + height: 26px; + background-color: #1c1c1c; + border-right: 1px solid #575757; +} + +thead th:last-child { + border: none; +} + +tbody td { + height: 26px; +} + +tfoot td { + height: 18px; + width: 100%; + background-color: #1c1c1c; + margin-left: 12px; +} + +tr { + width: 100%; + cursor: pointer; +} + +tr:nth-child(odd) { + background-color: #2e2e2e; +} + +tr:nth-child(even) { + background-color: #1c1c1c; +} + +tr:focus { + outline: none; +} + +tr.selected { + color: #000000; + background-color: #00b4ef; +} + +tr.selected + tr.selected { + border-top: 1px solid #2e2e2e; +} + +th { + text-align: center; + word-wrap: nowrap; + white-space: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-wrap: nowrap; + padding-left: 12px; + padding-right: 12px; +} + +td.url { + white-space: nowrap; + overflow: hidden; +} + + +input[type="text"], input[type="number"], textarea { + margin: 0; + padding: 0 12px; + color: #afafaf; + background-color: #252525; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +textarea { + font-family: AnonymousPro-Regular; + font-size: 16px; + padding-top: 5px; + padding-bottom: 5px; + min-height: 64px; + width: 100%; + resize: vertical; +} + +input::-webkit-input-placeholder { + font-style: italic; +} + +input:focus, textarea:focus { + color: #fff; + background-color: #000; + outline: 1px solid #00b4ef; + outline-offset: -1px; +} + +input::selection, textarea::selection { + color: #000000; + background-color: #00b4ef; +} + +input.search { + border-radius: 14px; +} + +input.search:focus { + outline: none; + box-sizing: border-box; + height: 26px; + margin-top: 1px; + margin-bottom: 1px; + box-shadow: 0 0 0px 1px #00b4ef; +} + +input:disabled, textarea:disabled { + background-color: #383838; + color: #afafaf; +} + +input[type="text"] { + height: 28px; + width: 100%; +} + +input[type="number"] { + position: relative; + height: 28px; + width: 124px; +} + +input[type=number] { + padding-right: 3px; +} +input[type=number]::-webkit-inner-spin-button { + -webkit-appearance: none; + opacity: 1.0; + display: block; + position: relative; + width: 10px; + height: 100%; + overflow: hidden; + font-family: hifi-glyphs; + font-size: 46px; + color: #afafaf; + cursor: pointer; + /*background-color: #000000;*/ +} +input[type=number]::-webkit-inner-spin-button:before, +input[type=number]::-webkit-inner-spin-button:after { + position:absolute; + left: -19px; + line-height: 8px; + text-align: center; +} +input[type=number]::-webkit-inner-spin-button:before { + content: "6"; + top: 4px; +} +input[type=number]::-webkit-inner-spin-button:after { + content: "5"; + bottom: 4px; +} + +input[type=number].hover-up::-webkit-inner-spin-button:before, +input[type=number].hover-down::-webkit-inner-spin-button:after { + color: #ffffff; +} + +input.no-spin::-webkit-outer-spin-button, +input.no-spin::-webkit-inner-spin-button { + display: none; + -webkit-appearance: none; + margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ + padding-right: 12px; +} + +input[type=button] { + font-family: Raleway-Bold; + font-size: 13px; + text-transform: uppercase; + vertical-align: top; + height: 28px; + min-width: 120px; + padding: 0px 18px; + margin-right: 6px; + border-radius: 5px; + border: none; + color: #fff; + background-color: #000; + background: linear-gradient(#343434 20%, #000 100%); + cursor: pointer; +} + +input[type=button].glyph { + font-family: HiFi-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding: 0; +} + +input[type=button].red { + color: #fff; + background-color: #94132e; + background: linear-gradient(#d42043 20%, #94132e 100%); +} +input[type=button].blue { + color: #fff; + background-color: #1080b8; + background: linear-gradient(#00b4ef 20%, #1080b8 100%); +} +input[type=button].white { + color: #121212; + background-color: #afafaf; + background: linear-gradient(#fff 20%, #afafaf 100%); +} + +input[type=button]:enabled:hover { + background: linear-gradient(#000, #000); + border: none; +} +input[type=button].red:enabled:hover { + background: linear-gradient(#d42043, #d42043); + border: none; +} +input[type=button].blue:enabled:hover { + background: linear-gradient(#00b4ef, #00b4ef); + border: none; +} +input[type=button].white:enabled:hover { + background: linear-gradient(#fff, #fff); + border: none; +} + +input[type=button]:active { + background: linear-gradient(#343434, #343434); +} +input[type=button].red:active { + background: linear-gradient(#94132e, #94132e); +} +input[type=button].blue:active { + background: linear-gradient(#1080b8, #1080b8); +} +input[type=button].white:active { + background: linear-gradient(#afafaf, #afafaf); +} + +input[type=button]:disabled { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} + +input[type=button][pressed=pressed] { + color: #00b4ef; +} + +input[type=checkbox] { + display: none; +} +input[type=checkbox] + label { + padding-left: 24px; + background-position-y: 6px; + background-repeat: no-repeat; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACpSURBVDhPY2xoaGD68+dPMSMjY9L////VgTQjAw4AlH8PxLOPHj1azWxjY1MBVNsBFBfBpwkEgNKcQGwtJyfHyATkF0KEiQdAzYlMQEIUyicFyDD9+/ePgRxMvsb///4zkIOZ/v0HmkAGHginYjGNGAzS+BpdkAj8mun/3//92DyPD//993cG88nTJ4+Zm5p/BSZeJYb/DEJADEzNOPF7hn8Mk69cvVIPAHN5pyfo70F5AAAAAElFTkSuQmCC); +} +input[type=checkbox]:enabled + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAClSURBVDhPY2hoaGD6//9/6b9//64D8T8gGycASr/7+/dv5/79+1kYgIxKqDjRAKiniRFIv2JgYBAFYlLAE0aQ66AckgDjjx8/yNP44cMH8jS+fPmSPI0PHz4kT+PNmzfJ03jp0iXyNJ46dYo8jYcPHyYnAbxm+vnzZz8wLhlIwd+/f5/BrKSkdExCQuLrnz9/lIBpUAiIQekXF34PTGmTT548WQ8AokXg+rhVtPYAAAAASUVORK5CYII=); +} +input[type=checkbox]:checked + label { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFLSURBVDhPjZK9SgNBFIXvbCaQRDQq7mIhQRPBRisJKcwLWOobaCsExEaxcEEQe0trGysfwg0EwWoDsbFIJUaIBJOwus547saNP3FlPzgzzJxzL5edFbZtG77v7wkhtrXWS9gFRQC/DZ07jnOYKJfL+8ie4n7mvyIGdhpay+VyQuK8y5dPZoHuVtbpZcLi4wjJ1x4t316R9dDgBlsSi8mGu7pJjyJFzVaH+r7iqyHSELSQzVADjS0UgjlDKUUsLzVO98+9kSLGV5qaHXhjU0GWNSxk3hCIwnsfeMNCjTArLmHeUBodoLiE+R+jxuHPUZP4elGE3teonx2S/Q7lJzOUlkYQ+A4/xzyegzNhXmJpwTMXry9IFjcoa84O0r+QXpcK1cugCLREZadyoA19Ergxwf96nKjd1KqlYqmLQ540TUNwItUmRWdu3T36AODjwgpY9xqqAAAAAElFTkSuQmCC); +} +input[type=checkbox]:checked + label:hover { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEySURBVDhPnZLPSsNAEMa/XVPBCE0RhNy0OarP4Av4AD6JB0GwVBA8efBBxHsgh4CQswcRoUIpiIpVAm3zZ5M4szFSbQPBH3xkJvNNZskOer2eLIriKM/ze1JOcS1UHmdZduF5ngEKjr/fN4Z6+oKerwA2gxC4HAFPEWVLsAzgZAvYt3Q6Enw6jg7uBAaTFMNwhpnKdbXCkAJdy8ROu4XrXW2HTJIErHcFDD6nC02Mom8PwymeE2gvS0ZRBBaTlsOXEmdlrfLLOI7Bakrl/zWxCT8T/904f9QW/b06qtrCUdtFCqdjYs2Q2jAPX8c2XQd7Kr/wfV8vwIPs4Ga1ixe5Xrr/YFLTYfKIvWzM6ZtwXZdX7lxXG0L+sxXHcW5t254opRzawQ0S72+dPmjTroIgOP0CQSMt5LDn1T8AAAAASUVORK5CYII=); +} + +.icon-input input { + position: relative; + padding-left: 36px; +} +.icon-input span { + position: absolute; + left: 6px; + top: -2px; + font-family: hifi-glyphs; + font-size: 30px; + color: #afafaf; +} +.icon-input input:focus + span { + color: #ffffff; +} + +.selectable { + -webkit-touch-callout: text; + -webkit-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + cursor: text; +} + +.color-box { + display: inline-block; + width: 15pt; + height: 15pt; + border: 0.75pt solid black; + margin: 1.5pt; + cursor: pointer; +} + +.color-box.highlight { + width: 13.5pt; + height: 13.5pt; + border: 1.5pt solid black; +} + + +.section-header, .sub-section-header, hr { + display: table; + width: 100%; + margin: 21px -21px 0 -21px; + padding: 14px 21px 0 21px; + font-family: Raleway-Regular; + font-size: 12px; + color: #afafaf; + height: 28px; + text-transform: uppercase; + outline: none; +} + +.section-header { + position: relative; + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAqCAIAAAAbNW1vAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAmSURBVChTY1BFAgzhSIDBAQmMcoYHRwIJMCgjAQZ9JMBgBQdWVgBh5XmBV5A2FQAAAABJRU5ErkJggg==) repeat-x top left; +} + +.sub-section-header, .no-collapse, hr { + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; +} + +.section-header:first-child { + margin-top: -2px; + padding-top: 0; + background: none; + height: auto; +} + +.sub-section-header { + margin-bottom: -10px; +} + +.section-header span { + font-family: HiFi-Glyphs; + font-size: 30px; + float: right; + position: absolute; + top: 4px; + right: 13px; +} + +.section-header[collapsed="true"] { + margin-bottom: -21px; +} + +hr { + border: none; + padding-top: 2px; +} + +.text-group[collapsed="true"] ~ .text-group, +.zone-group[collapsed="true"] ~ .zone-group, +.web-group[collapsed="true"] ~ .web-group, +.hyperlink-group[collapsed="true"] ~ .hyperlink-group, +.spatial-group[collapsed="true"] ~ .spatial-group, +.physical-group[collapsed="true"] ~ .physical-group, +.behavior-group[collapsed="true"] ~ .behavior-group, +.model-group[collapsed="true"] ~ .model-group, +.light-group[collapsed="true"] ~ .light-group { + display: none !important; +} + + +.property { + display: table; + width: 100%; + margin-top: 21px; + min-height: 28px; +} + +.property.checkbox { + width: auto; +} + +.property label, .number label { + display: table-cell; + vertical-align: middle; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.property label .unit, .number label .unit { + margin-left: 8px; + font-family: Raleway-Light; + font-size: 13px; +} + +.value { + display: block; + min-height: 18px; +} +.value label { + display: inline-block; + vertical-align: top; + width: 48px; +} +.value span { + font-family: FiraSans-SemiBold; + font-size: 15px; +} + +.checkbox + .checkbox { + margin-top: 0; +} + +.checkbox-sub-props { + margin-top: 18px; +} + +.property .number { + float: left; +} +.property .number + .number { + margin-left: 10px; +} + +.text label, .url label, .number label, .textarea label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label { + float: left; + margin-left: 1px; + margin-bottom: 3px; + margin-top: -2px; +} + +.number > input { + clear: both; + float: left; +} +.number > span { + clear: both; + float: left; +} +.xyz > div, .pyr > div, .gen > div { + clear: both; +} + +.dropdown { + position: relative; + margin-bottom: -17px; +} + +.dropdown select { + clear: both; +} + +.dropdown dl { + clear: both; +} +.dropdown dl { + font-family: FiraSans-SemiBold; + font-size: 15px; + width: 292px; + height: 28px; + padding: 0 28px 0 12px; + color: #afafaf; + background: linear-gradient(#7d7d7d 20%, #686a68 100%); + position: relative; +} +.dropdown dl[dropped="true"] { + color: #404040; + background: linear-gradient(#afafaf, #afafaf); +} + +.dropdown dt { + height: 100%; + box-sizing: border-box; + border-right: 1px solid #121212; + width: 100%; +} +.dropdown dt:hover { + color: #404040; +} +.dropdown dt:focus { + outline: none; +} +.dropdown dt span:first-child { + display: inline-block; + position: relative; + top: 5px; +} +.dropdown dt span:last-child { + font-family: HiFi-Glyphs; + font-size: 42px; + float: right; + margin-right: -48px; + position: relative; + left: -12px; + top: -9px; +} + +.dropdown dd { + position: absolute; + top: 28px; + left: 3px; + display: none; +} +.dropdown dl[dropped="true"] dd { + display: block; +} + +.dropdown li { + list-style-type: none; + padding: 3px 0 1px 12px; + width: 320px; + height: auto; + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #404040; + background-color: #afafaf +} +.dropdown li:hover { + background-color: #00b4ef; +} + +.dropdown dl[disabled="disabled"], .dropdown dl[disabled="disabled"][dropped="true"] { + color: #252525; + background: linear-gradient(#575757 20%, #252525 100%); +} +.dropdown dl[disabled="disabled"] dd { + display: none; +} +.dropdown dl[disabled="disabled"] dt:hover { + color: #252525; +} + + +div.refresh { + box-sizing: border-box; + padding-right: 44px; +} +div.refresh input[type="button"] { + float: right; + margin-right: -44px; +} + +.color-picker { + box-sizing: border-box; + float: left; + margin-bottom: 21px; + width: 36px; + height: 36px; + border: 4px solid #afafaf; + border-radius: 4px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABTSURBVChTjcxBDoAwCETRwTs33sFjwB6uaCE1Ggvav5qQF7CSqu40dllHjYiOT3gh3yV8Ii+Fb+RNMEP9hm3sKENmBhG5P1aImWMH/EMerSAAOAFgTC/R8ZXSXAAAAABJRU5ErkJggg==); + background-position: bottom right; + background-repeat: no-repeat; +} +.color-picker:focus { + outline: none; +} +.color-picker[active="true"] { + border-color: #000; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABDSURBVChTjcoLCkAhCETRNq0tf97Y5xGZ1gVJ45TH6njThIO+xk2UwhWFcEdH6JCqOuiQiMDi/hcii3crRRb/7ggAPvIMVihQwvSXAAAAAElFTkSuQmCC); +} + +.color-picker[disabled="disabled"] { + border-color: #afafaf; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABTSURBVChTjcxBDoAwCETRwTs33sFjwB6uaCE1Ggvav5qQF7CSqu40dllHjYiOT3gh3yV8Ii+Fb+RNMEP9hm3sKENmBhG5P1aImWMH/EMerSAAOAFgTC/R8ZXSXAAAAABJRU5ErkJggg==); +} + +.colpick[disabled="disabled"] { + display: none !important; +} + + +.rgb label { + float: left; + margin-top: 10px; + margin-left: 21px; +} +.rgb label + * { + clear: both; +} + +.tuple div { + display: inline-block; + position: relative; + margin-right: 6px; +} +.tuple div:last-child { + margin-right: 0; +} + +.tuple label { + margin-right: -6px; +} + +.rgb .tuple input { + padding-left: 65px; +} +.xyz .tuple input { + padding-left: 25px; +} +.pyr .tuple input { + padding-left: 40px; +} + +.tuple div > label:first-child { + float: left; +} +.tuple div > label + input { + clear: both; + float: left; +} +.tuple div input + label { + display: inline !important; + float: none !important; + position: absolute; + margin-top: 8px; + margin-left: 6px; + left: 0; + font-family: FiraSans-SemiBold; + font-size: 12px; +} +.tuple .red + label, .tuple .x + label, .tuple .pitch + label { + color: #e2334d; +} +.tuple .green + label, .tuple .y + label, .tuple .yaw + label { + color: #1ac567; +} +.tuple .blue + label, .tuple .z + label, .tuple .roll + label { + color: #1080b8; +} + +.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus { + outline-color: #e2334d; +} +.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus { + outline-color: #1ac567; +} +tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { + outline-color: #1080b8; +} + +.xyz .buttons input { + margin-top: 14px; +} +.xyz .buttons span { + word-wrap: nowrap; + white-space: nowrap; +} + +.row .property { + width: auto; + display: inline-block; + margin-right: 6px; +} +.row .property:last-child { + margin-right: 0; +} +.row .property input { + clear: both; + float: left; +} + +.two-column { + display: table; + width: 100%; +} +.two-column > div { + display: table-cell; + width: 50%; +} +.column { + vertical-align: top; +} + +.indent { + margin-left: 24px; +} + +::-webkit-scrollbar { + width: 20px; + height: 10px; +} +::-webkit-scrollbar-track { + background-color: #2e2e2e; +} +::-webkit-scrollbar-thumb { + background-color: #696969; + border: 2px solid #2e2e2e; + border-radius: 8px; +} + +/* FIXME: Revisit textarea resizer/corner when move to Qt 5.6 or later: see if can get resizer/corner to always be visible and +have correct background color with and without scrollbars. */ +textarea:enabled::-webkit-resizer { + background-size: 10px 10px; + background: #252525 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAXSURBVChTY1RVVWXADZigNA4wMqUZGACS3gCD5UUtKAAAAABJRU5ErkJggg==) no-repeat bottom right; +} +textarea:focus::-webkit-resizer { + background-size: 10px 10px; + background: #000000 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACOSURBVChThdC5DQQhDAXQvyRI5LQxFdABARWQElAPogYkiqEWQhLYGe8xxzJaS5a/8AuQHwDG2n+Lvee0hBDQWlO+hRvy3mNZFjDG5vCDOOeIMaL3/guPKISAWiu9n+AVSSlhraXdF86Qcw6tNdoTvEOlFOScd6iUOv3JGEMopYQx9jNvaawnoHnNr8Z4AuRLPOq2gPgnAAAAAElFTkSuQmCC) no-repeat bottom right; +} +textarea:enabled[scrolling="true"]::-webkit-resizer { + background-size: 10px 10px; + background: #2e2e2e url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACKSURBVChTjdAxDsMgDAXQT4UYuQIzCwsSKxsSJ4YDoByDY7AwUOG2aZMQqX+xhd9gzIwxA3/k8a7LCCFgraX+Fk4UY4RSCoyxNfwgzjlyzhhjXOEvSimhtUbvB3hGUkp472m2wxUKIaD3TnOCd6jWim3bvlBrfdjJOUeolEJoZj/4PMH83bl/BXgCWSs2Z09IjgoAAAAASUVORK5CYII=) no-repeat bottom right; +} + + +#entity-list-header { + margin-bottom: 36px; +} + +#entity-list-header div { + display: inline-block; + width: 65px; + margin-right: 6px; +} + +#entity-list-header div input:first-child { + margin-right: 0; + float: left; + width: 33px; + border-right: 1px solid #808080; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +#entity-list-header div input:last-child { + margin-right: 0; + float: right; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +#delete { + float: right; + margin-right: 0; + background-color: #ff0000; + min-width: 90px; +} + +#entity-list { + position: relative; /* New positioning context. */ +} + +#search-area { + padding-right: 168px; + padding-bottom: 24px; +} + +#filter { + width: 98%; +} + +#in-view { + position: absolute; + right: 126px; +} + +#radius-and-unit { + float: right; + margin-right: -168px; + position: relative; + top: -17px; +} +#radius-and-unit label { + margin-left: 2px; +} +#radius-and-unit input { + width: 120px; +} + +#entity-table-scroll { + /* Height is set by JavaScript. */ + width: 100%; + overflow-x: hidden; + overflow-y: auto; + box-sizing: border-box; + padding-top: 28px; /* Space for header and footer outside of scroll region. */ + margin-top: 28px; + border-left: 2px solid #575757; + border-right: 2px solid #575757; + background-color: #1c1c1c; +} + +#entity-table-scroll .glyph { + font-family: HiFi-Glyphs; + font-size: 15px; +} + +#entity-table { + margin-top: -28px; + margin-bottom: -18px; + table-layout: fixed; + border: none; + background-color: #1c1c1c; +} + +#entity-table thead tr, #entity-table thead tr th, +#entity-table tfoot tr, #entity-table tfoot tr td { + background: none; +} + +#entity-table .glyph { + margin: 0 -2px 0 -2px; + vertical-align: middle; +} + +#entity-table thead { + box-sizing: border-box; + border: 2px solid #575757; + border-top-left-radius: 7px; + border-top-right-radius: 7px; + border-bottom: 1px solid #575757; + position: absolute; + top: 49px; + left: 0; + width: 100%; + word-wrap: nowrap; + white-space: nowrap; + overflow: hidden; +} + +.verticesCount, .texturesCount, .texturesSize, .drawCalls { + text-align: right; +} + +#entity-table th { + display: inline-block; + box-sizing: border-box; + padding: 5px 0 0 0; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; +} + +#entity-table th:focus { + outline: none; +} + +#entity-table th .glyph { + position: relative; + left: 4px; +} +#entity-table th .glyph + .sort-order { + position: relative; + left: 4px; +} + +#entity-table th#entity-hasScript { + overflow: visible; +} + +#entity-table th#entity-hasScript .glyph { + text-transform: none; +} + +#entity-table thead .sort-order { + display: inline-block; + width: 8px; + margin: -5px 0 -3px 0; + vertical-align: middle; +} + +#entity-table th #info-toggle { + display: inline-block; + position: absolute; + left: initial; + right: 0; + width: 11px; + background-color: #1c1c1c; + z-index: 100; +} +#entity-table th #info-toggle span { + position: relative; + left: -2px; +} + +th#entity-hasTransparent .glyph { + font-weight: normal; + font-size: 24px !important; + margin: -6px; + position: relative; + top: -6px; +} +th#entity-hasTransparent .sort-order { + position: relative; + top: -4px; +} + +#entity-table td { + box-sizing: border-box; +} + +#entity-table td.glyph { + text-align: center; + padding: 0; +} +#entity-table td.hasTransparent.glyph { + font-size: 22px; + position: relative; + top: -1px; +} + +#entity-table td.isBaked.glyph { + font-size: 22px; + position: relative; + top: -1px; +} + +#entity-table tfoot { + box-sizing: border-box; + border: 2px solid #575757; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; + border-top: 1px solid #575757; + position: absolute; + bottom: -21px; + left: 0; + width: 100%; +} + + +#col-type { + width: 16%; +} +#col-name { + width: 34%; +} +#col-url { + width: 34%; +} +#col-locked, #col-visible { + width: 9%; +} +#col-verticesCount, #col-texturesCount, #col-texturesSize, #col-hasTransparent, #col-isBaked, #col-drawCalls, #col-hasScript { + width: 0; +} + +.showExtraInfo #col-type { + width: 10%; +} +.showExtraInfo #col-name { + width: 19%; +} +.showExtraInfo #col-url { + width: 19%; +} +.showExtraInfo #col-locked, .showExtraInfo #col-visible { + width: 4%; +} +.showExtraInfo #col-verticesCount { + width: 8%; +} +.showExtraInfo #col-texturesCount { + width: 8%; +} +.showExtraInfo #col-texturesSize { + width: 10%; +} +.showExtraInfo #col-hasTransparent { + width: 4%; +} +.showExtraInfo #col-isBaked { + width: 8%; +} +.showExtraInfo #col-drawCalls { + width: 8%; +} +.showExtraInfo #col-hasScript { + width: 6%; +} + +th#entity-verticesCount, th#entity-texturesCount, th#entity-texturesSize, th#entity-hasTransparent, th#entity-isBaked, th#entity-drawCalls, +th#entity-hasScript { + display: none; +} + +.verticesCount, .texturesCount, .texturesSize, .hasTransparent, .isBaked, .drawCalls, .hasScript { + display: none; +} + +#entity-visible { + border: none; +} + +.showExtraInfo #entity-verticesCount, .showExtraInfo #entity-texturesCount, .showExtraInfo #entity-texturesSize, +.showExtraInfo #entity-hasTransparent, .showExtraInfo #entity-isBaked, .showExtraInfo #entity-drawCalls, .showExtraInfo #entity-hasScript { + display: inline-block; +} + +.showExtraInfo .verticesCount, .showExtraInfo .texturesCount, .showExtraInfo .texturesSize, .showExtraInfo .hasTransparent, +.showExtraInfo .isBaked, .showExtraInfo .drawCalls, .showExtraInfo .hasScript { + display: table-cell; +} + +.showExtraInfo #entity-visible { + border-right: 1px solid #575757; +} + + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + + +#properties-header { + display: table-row; + height: 28px; +} + +#properties-header .property { + display: table-cell; + vertical-align: middle; +} +#properties-header .checkbox { + position: relative; + top: -1px; +} + +#properties-header #type-icon { + font-family: hifi-glyphs; + font-size: 31px; + color: #00b4ef; + margin: -4px 12px -4px -2px; + width: auto; + display: none; + vertical-align: middle; +} + +#properties-header #property-type { + padding: 5px 24px 5px 0; + border-right: 1px solid #808080; + height: 100%; + width: auto; + display: inline-block; + vertical-align: middle; +} + +#properties-header .checkbox:last-child { + padding-left: 24px; +} + +#properties-header .checkbox label { + background-position-y: 1px; +} + +#properties-header .checkbox label span { + font-family: HiFi-Glyphs; + font-size: 20px; + padding-right: 6px; + vertical-align: top; + position: relative; + top: -4px; +} + +#properties-header input[type=checkbox]:checked + label span { + color: #ffffff; +} + +#properties-header + hr { + margin-top: 12px; +} + + +#id label { + width: 24px; +} +#property-id { + display: inline-block; +} +#property-id::selection { + color: #000000; + background-color: #00b4ef; +} + +input#property-parent-id { + width: 340px; +} + +input#dimension-rescale-button { + min-width: 50px; + margin-left: 6px; +} +input#reset-to-natural-dimensions { + margin-right: 0; +} + +#animation-fps { + margin-top: 48px; +} + +#userdata-clear{ + margin-bottom: 10px; +} + + +#static-userdata{ + display: none; + z-index: 99; + position: absolute; + width: 96%; + padding-left:1%; + margin-top:5px; + margin-bottom:10px; + background-color: #2e2e2e; +} + +#userdata-saved{ + margin-top:5px; + font-size:16px; + display:none; +} diff --git a/scripts/system/html/css/record.css b/unpublishedScripts/marketplace/record/html/css/record.css similarity index 100% rename from scripts/system/html/css/record.css rename to unpublishedScripts/marketplace/record/html/css/record.css diff --git a/unpublishedScripts/marketplace/record/html/fonts/FiraSans-SemiBold.ttf b/unpublishedScripts/marketplace/record/html/fonts/FiraSans-SemiBold.ttf new file mode 100644 index 0000000000..821a43d7fd Binary files /dev/null and b/unpublishedScripts/marketplace/record/html/fonts/FiraSans-SemiBold.ttf differ diff --git a/unpublishedScripts/marketplace/record/html/fonts/FiraSans.license b/unpublishedScripts/marketplace/record/html/fonts/FiraSans.license new file mode 100644 index 0000000000..d444ea92b6 --- /dev/null +++ b/unpublishedScripts/marketplace/record/html/fonts/FiraSans.license @@ -0,0 +1,94 @@ +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. +with Reserved Font Name < Fira >, + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/unpublishedScripts/marketplace/record/html/fonts/Raleway-Bold.ttf b/unpublishedScripts/marketplace/record/html/fonts/Raleway-Bold.ttf new file mode 100644 index 0000000000..38c099cc85 Binary files /dev/null and b/unpublishedScripts/marketplace/record/html/fonts/Raleway-Bold.ttf differ diff --git a/unpublishedScripts/marketplace/record/html/fonts/Raleway-Light.ttf b/unpublishedScripts/marketplace/record/html/fonts/Raleway-Light.ttf new file mode 100644 index 0000000000..91aa0c701f Binary files /dev/null and b/unpublishedScripts/marketplace/record/html/fonts/Raleway-Light.ttf differ diff --git a/unpublishedScripts/marketplace/record/html/fonts/Raleway-Regular.ttf b/unpublishedScripts/marketplace/record/html/fonts/Raleway-Regular.ttf new file mode 100644 index 0000000000..e570a2d5c3 Binary files /dev/null and b/unpublishedScripts/marketplace/record/html/fonts/Raleway-Regular.ttf differ diff --git a/unpublishedScripts/marketplace/record/html/fonts/Raleway-SemiBold.ttf b/unpublishedScripts/marketplace/record/html/fonts/Raleway-SemiBold.ttf new file mode 100644 index 0000000000..ed0a8b9941 Binary files /dev/null and b/unpublishedScripts/marketplace/record/html/fonts/Raleway-SemiBold.ttf differ diff --git a/unpublishedScripts/marketplace/record/html/fonts/Raleway.license b/unpublishedScripts/marketplace/record/html/fonts/Raleway.license new file mode 100644 index 0000000000..1c9779ddcd --- /dev/null +++ b/unpublishedScripts/marketplace/record/html/fonts/Raleway.license @@ -0,0 +1,94 @@ +Copyright (c) 2010, Matt McInerney (matt@pixelspread.com), +Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com), +Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Raleway +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/unpublishedScripts/marketplace/record/html/fonts/hifi-glyphs.ttf b/unpublishedScripts/marketplace/record/html/fonts/hifi-glyphs.ttf new file mode 100644 index 0000000000..93f6fe6d13 Binary files /dev/null and b/unpublishedScripts/marketplace/record/html/fonts/hifi-glyphs.ttf differ diff --git a/scripts/system/html/img/loader-red-countdown-ring.gif b/unpublishedScripts/marketplace/record/html/img/loader-red-countdown-ring.gif similarity index 100% rename from scripts/system/html/img/loader-red-countdown-ring.gif rename to unpublishedScripts/marketplace/record/html/img/loader-red-countdown-ring.gif diff --git a/scripts/system/html/js/record.js b/unpublishedScripts/marketplace/record/html/js/record.js similarity index 100% rename from scripts/system/html/js/record.js rename to unpublishedScripts/marketplace/record/html/js/record.js diff --git a/scripts/system/html/record.html b/unpublishedScripts/marketplace/record/html/record.html similarity index 100% rename from scripts/system/html/record.html rename to unpublishedScripts/marketplace/record/html/record.html diff --git a/scripts/system/playRecordingAC.js b/unpublishedScripts/marketplace/record/playRecordingAC.js similarity index 100% rename from scripts/system/playRecordingAC.js rename to unpublishedScripts/marketplace/record/playRecordingAC.js diff --git a/scripts/system/record.js b/unpublishedScripts/marketplace/record/record.js similarity index 100% rename from scripts/system/record.js rename to unpublishedScripts/marketplace/record/record.js diff --git a/unpublishedScripts/marketplace/stopwatch/models/transparent-box.fbx b/unpublishedScripts/marketplace/stopwatch/models/transparent-box.fbx new file mode 100644 index 0000000000..b1df7d962c Binary files /dev/null and b/unpublishedScripts/marketplace/stopwatch/models/transparent-box.fbx differ diff --git a/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js b/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js index e72f949163..3a0a8a506b 100644 --- a/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js +++ b/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js @@ -43,13 +43,47 @@ var minuteHandID = Entities.addEntity({ modelURL: Script.resolvePath("models/Stopwatch-min-hand.fbx"), }); +var startStopButtonID = Entities.addEntity({ + type: "Model", + name: "stopwatch/startStop", + parentID: stopwatchID, + dimensions: Vec3.multiply(scale, { x: 0.8, y: 0.8, z: 1.0 }), + localPosition: Vec3.multiply(scale, { x: 0, y: -0.1, z: -2.06 }), + modelURL: Script.resolvePath("models/transparent-box.fbx") +}); + +var resetButtonID = Entities.addEntity({ + type: "Model", + name: "stopwatch/startStop", + parentID: stopwatchID, + dimensions: Vec3.multiply(scale, { x: 0.6, y: 0.6, z: 0.8 }), + localPosition: Vec3.multiply(scale, { x: -1.5, y: -0.1, z: -1.2 }), + localRotation: Quat.fromVec3Degrees({ x: 0, y: 36, z: 0 }), + modelURL: Script.resolvePath("models/transparent-box.fbx") +}); + Entities.editEntity(stopwatchID, { userData: JSON.stringify({ secondHandID: secondHandID, - minuteHandID: minuteHandID, + minuteHandID: minuteHandID }), - script: Script.resolvePath("stopwatchClient.js"), serverScripts: Script.resolvePath("stopwatchServer.js") }); +Entities.editEntity(startStopButtonID, { + userData: JSON.stringify({ + stopwatchID: stopwatchID, + grabbableKey: { wantsTrigger: true } + }), + script: Script.resolvePath("stopwatchStartStop.js") +}); + +Entities.editEntity(resetButtonID, { + userData: JSON.stringify({ + stopwatchID: stopwatchID, + grabbableKey: { wantsTrigger: true } + }), + script: Script.resolvePath("stopwatchReset.js") +}); + Script.stop() diff --git a/unpublishedScripts/marketplace/stopwatch/stopwatchReset.js b/unpublishedScripts/marketplace/stopwatch/stopwatchReset.js new file mode 100644 index 0000000000..b65c1e7340 --- /dev/null +++ b/unpublishedScripts/marketplace/stopwatch/stopwatchReset.js @@ -0,0 +1,22 @@ +// +// stopwatchReset.js +// +// Created by David Rowe on 26 May 2017. +// Copyright 2017 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 +// + +(function () { + this.preload = function (entityID) { + var properties = Entities.getEntityProperties(entityID, "userData"); + this.messageChannel = "STOPWATCH-" + JSON.parse(properties.userData).stopwatchID; + }; + function click() { + Messages.sendMessage(this.messageChannel, "reset"); + } + this.startNearTrigger = click; + this.startFarTrigger = click; + this.clickDownOnEntity = click; +}); diff --git a/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js index 925db565c3..6ae1b69087 100644 --- a/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js +++ b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js @@ -13,6 +13,7 @@ self.equipped = false; self.isActive = false; + self.seconds = 0; self.secondHandID = null; self.minuteHandID = null; @@ -46,11 +47,19 @@ }; self.messageReceived = function(channel, message, sender) { print("Message received", channel, sender, message); - if (channel === self.messageChannel && message === 'click') { - if (self.isActive) { - self.resetTimer(); - } else { - self.startTimer(); + if (channel === self.messageChannel) { + switch (message) { + case "startStop": + if (self.isActive) { + self.stopTimer(); + } else { + self.startTimer(); + } + break; + case "reset": + self.stopTimer(); + self.resetTimer(); + break; } } }; @@ -58,14 +67,7 @@ return Entities.getEntityProperties(self.entityID, "position").position; }; self.resetTimer = function() { - print("Stopping stopwatch"); - if (self.tickInjector) { - self.tickInjector.stop(); - } - if (self.tickIntervalID !== null) { - Script.clearInterval(self.tickIntervalID); - self.tickIntervalID = null; - } + print("Resetting stopwatch"); Entities.editEntity(self.secondHandID, { localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0), angularVelocity: { x: 0, y: 0, z: 0 }, @@ -74,7 +76,7 @@ localRotation: Quat.fromPitchYawRollDegrees(0, 0, 0), angularVelocity: { x: 0, y: 0, z: 0 }, }); - self.isActive = false; + self.seconds = 0; }; self.startTimer = function() { print("Starting stopwatch"); @@ -88,7 +90,6 @@ self.tickInjector.restart(); } - var seconds = 0; self.tickIntervalID = Script.setInterval(function() { if (self.tickInjector) { self.tickInjector.setOptions({ @@ -97,15 +98,15 @@ loop: true }); } - seconds++; + self.seconds++; const degreesPerTick = -360 / 60; Entities.editEntity(self.secondHandID, { - localRotation: Quat.fromPitchYawRollDegrees(0, seconds * degreesPerTick, 0), + localRotation: Quat.fromPitchYawRollDegrees(0, self.seconds * degreesPerTick, 0), }); - if (seconds % 60 == 0) { + if (self.seconds % 60 == 0) { Entities.editEntity(self.minuteHandID, { - localRotation: Quat.fromPitchYawRollDegrees(0, (seconds / 60) * degreesPerTick, 0), + localRotation: Quat.fromPitchYawRollDegrees(0, (self.seconds / 60) * degreesPerTick, 0), }); Audio.playSound(self.chimeSound, { position: self.getStopwatchPosition(), @@ -117,4 +118,15 @@ self.isActive = true; }; + self.stopTimer = function () { + print("Stopping stopwatch"); + if (self.tickInjector) { + self.tickInjector.stop(); + } + if (self.tickIntervalID !== null) { + Script.clearInterval(self.tickIntervalID); + self.tickIntervalID = null; + } + self.isActive = false; + }; }); diff --git a/unpublishedScripts/marketplace/stopwatch/stopwatchClient.js b/unpublishedScripts/marketplace/stopwatch/stopwatchStartStop.js similarity index 50% rename from unpublishedScripts/marketplace/stopwatch/stopwatchClient.js rename to unpublishedScripts/marketplace/stopwatch/stopwatchStartStop.js index 6284b86102..88c037ee36 100644 --- a/unpublishedScripts/marketplace/stopwatch/stopwatchClient.js +++ b/unpublishedScripts/marketplace/stopwatch/stopwatchStartStop.js @@ -1,20 +1,21 @@ // -// stopwatchServer.js +// stopwatchStartStop.js // -// Created by Ryan Huffman on 1/20/17. +// Created by David Rowe on 26 May 2017. // Copyright 2017 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 // -(function() { +(function () { var messageChannel; - this.preload = function(entityID) { - this.messageChannel = "STOPWATCH-" + entityID; + this.preload = function (entityID) { + var properties = Entities.getEntityProperties(entityID, "userData"); + this.messageChannel = "STOPWATCH-" + JSON.parse(properties.userData).stopwatchID; }; function click() { - Messages.sendMessage(this.messageChannel, 'click'); + Messages.sendMessage(this.messageChannel, "startStop"); } this.startNearTrigger = click; this.startFarTrigger = click;