diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index bf46ba121c..eb47afc951 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -177,7 +177,7 @@ ScrollingWindow { SHAPE_TYPE_STATIC_MESH ], checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic" + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } }); diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 85f8a2f59e..6e0263787b 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -179,7 +179,7 @@ Rectangle { SHAPE_TYPE_STATIC_MESH ], checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic" + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } }); diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 5dbb733872..2d9d121209 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -118,7 +118,7 @@ Rectangle { id: text2 width: 160 color: "#ffffff" - text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic") + text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") wrapMode: Text.WordWrap font.pixelSize: 12 } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8c6bea0905..851c07c501 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; } @@ -1570,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"); @@ -1683,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; @@ -1734,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); } @@ -2384,15 +2410,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; @@ -2406,10 +2433,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()) { @@ -2417,8 +2452,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 { @@ -2431,23 +2468,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. @@ -2776,6 +2830,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; @@ -4490,12 +4555,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) { @@ -6307,21 +6373,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 5a3c00b1ec..0370acaf05 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -435,11 +435,13 @@ void MyAvatar::update(float deltaTime) { // so we update now. It's ok if it updates again in the normal way. updateSensorToWorldMatrix(); emit positionGoneTo(); - _physicsSafetyPending = getCollisionsEnabled(); // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. + // Run safety tests as soon as we can after goToLocation, or clear if we're not colliding. + _physicsSafetyPending = getCollisionsEnabled(); } - if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // fix only when needed and ready + if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { + // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; - safeLanding(_goToPosition, _characterController.isStuck()); // no-op if already safe + safeLanding(_goToPosition); // no-op if already safe } Head* head = getHead(); @@ -1558,7 +1560,6 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { if (_characterController.isStuck()) { _physicsSafetyPending = true; _goToPosition = getPosition(); - qDebug() << "FIXME setting safety test at:" << _goToPosition; } } else { setVelocity(getVelocity() + _characterController.getFollowVelocity()); @@ -2232,11 +2233,10 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, } void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding. - // FIXME: Doesn't work 100% of time. Need to figure out what isn't happening fast enough. E.g., don't goToLocation until confirmed removed from physics? goToLocation(position); QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); } -bool MyAvatar::safeLanding(const glm::vec3& position, bool force) { +bool MyAvatar::safeLanding(const glm::vec3& position) { // Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point. // There needs to be a "landing" if: // a) the closest above and the closest below are less than the avatar capsule height apart, or @@ -2253,39 +2253,33 @@ bool MyAvatar::safeLanding(const glm::vec3& position, bool force) { return result; } glm::vec3 better; - if (!requiresSafeLanding(position, better, force)) { + if (!requiresSafeLanding(position, better)) { return false; } qDebug() << "rechecking" << position << " => " << better << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); if (!getCollisionsEnabled()) { - goToLocation(better); // recurses + goToLocation(better); // recurses on next update } else { // If you try to go while stuck, physics will keep you stuck. setCollisionsEnabled(false); // Don't goToLocation just yet. Yield so that physics can act on the above. QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick - Q_ARG(glm::vec3, better)); // I.e., capsuleCenter - offset + Q_ARG(glm::vec3, better)); } return true; } // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. -bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut, bool force) { - // We could repeat this whole test for each of the four corners of our bounding box, in case the surface is uneven. However: - // 1) This is only meant to cover the most important cases, and even the four corners won't handle random spikes in the surfaces or avatar. - // 2) My feeling is that this code is already at the limit of what can realistically be reviewed and maintained. - auto ok = [&](const char* label) { // position is good to go, or at least, we cannot do better - qDebug() << "Already safe" << label << positionIn << " collisions:" << getCollisionsEnabled() << " physics:" << qApp->isPhysicsEnabled(); - return false; - }; +bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { + // We begin with utilities and tests. The Algorithm in four parts is below. auto halfHeight = _characterController.getCapsuleHalfHeight(); if (halfHeight == 0) { - return ok("zero height avatar"); + return false; // zero height avatar } auto entityTree = DependencyManager::get()->getTree(); if (!entityTree) { - return ok("no entity tree"); + return false; // no entity tree } - + // More utilities. const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset(); const auto capsuleCenter = positionIn + offset; const auto up = _worldUpDirection, down = -up; @@ -2302,7 +2296,8 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette float distance; BoxFace face; const bool visibleOnly = false; - // FIXME: this doesn't do what we want here. findRayIntersection always works on mesh, skipping entirely based on collidable. What we really want is to use the collision hull! See CharacterGhostObject::rayTest? + // This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable. + // What we really want is to use the collision hull! // See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders const bool collidableOnly = true; const bool precisionPicking = true; @@ -2324,17 +2319,13 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) { // We currently believe that physics will reliably push us out if our feet are embedded, // as long as our capsule center is out and there's room above us. Here we have those - // conditions, so no need to check our feet below, unless forced. - if (force) { - upperIntersection = capsuleCenter; - return mustMove(); - } - return ok("nothing above"); + // conditions, so no need to check our feet below. + return false; // nothing above } if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) { // Our head may be embedded, but our center is out and there's room below. See corresponding comment above. - return ok("nothing below"); + return false; // nothing below } // See if we have room between entities above and below, but that we are not contained. @@ -2342,7 +2333,8 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // The surface above us is the bottom of something, and the surface below us it the top of something. // I.e., we are in a clearing between two objects. auto delta = glm::distance(upperIntersection, lowerIntersection); - if (delta > (2 * halfHeight)) { + const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. + if (delta > (halfHeightFactor * halfHeight)) { // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. // There will be one of two outcomes: @@ -2352,14 +2344,12 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette ignore.push_back(upperId); if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) { // We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good! - //qDebug() << "FIXME upper:" << upperId << upperIntersection << " n:" << upperNormal.y << " lower:" << lowerId << lowerIntersection << " n:" << lowerNormal.y << " delta:" << delta << " halfHeight:" << halfHeight; - return ok("enough room"); + return false; // enough room } if (isUp(upperNormal)) { // This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it. // We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top // of the enclosing entity, let's put our feet at upperIntersection and start over. - qDebug() << "FIXME inside above:" << upperId << " below:" << lowerId; return mustMove(); } // We found a new bottom surface, which we're not interested in. @@ -2368,13 +2358,12 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette } } - qDebug() << "FIXME need to compute safe landing for" << capsuleCenter << " based on " << (isDown(upperNormal) ? "down " : "up ") << upperIntersection << "@" << upperId << " and " << (isUp(lowerNormal) ? "up " : "down ") << lowerIntersection << "@" << lowerId; + include.push_back(upperId); // We're now looking for the intersection from above onto this entity. const float big = (float)TREE_SCALE; const auto skyHigh = up * big; auto fromAbove = capsuleCenter + skyHigh; - include.push_back(upperId); // We're looking for the intersection from above onto this entity. if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) { - return ok("Unable to find a landing"); + return false; // Unable to find a landing } // Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate. return mustMove(); @@ -2398,7 +2387,6 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } - qDebug() << "FIXME updateMotionBehaviorFromMenu collisions:" << menu->isOptionChecked(MenuOption::EnableAvatarCollisions) << "physics:" << qApp->isPhysicsEnabled(); setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d9a2c03c38..efab177a04 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -523,7 +523,7 @@ public slots: bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); void goToLocationAndEnableCollisions(const glm::vec3& newPosition); - bool safeLanding(const glm::vec3& position, bool force = false); + bool safeLanding(const glm::vec3& position); void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); void clearScaleRestriction(); @@ -571,7 +571,7 @@ private: glm::vec3 getWorldBodyPosition() const; glm::quat getWorldBodyOrientation() const; - bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut, bool force = false); + bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; 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/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 36273c1f07..eab0e3011e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori QString extraInfo; return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, - face, surfaceNormal, extraInfo, precisionPicking); + face, surfaceNormal, extraInfo, precisionPicking, precisionPicking); } void RenderableModelEntityItem::getCollisionGeometryResource() { 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/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/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index c6fffbfdbd..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, diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 9a456ca7e8..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; diff --git a/libraries/networking/src/FileCache.cpp b/libraries/networking/src/FileCache.cpp index 6cb8cd8f7c..95304e3866 100644 --- a/libraries/networking/src/FileCache.cpp +++ b/libraries/networking/src/FileCache.cpp @@ -236,6 +236,28 @@ namespace cache { }; } +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(); @@ -250,28 +272,23 @@ void FileCache::clean() { 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(); - - unusedLock.unlock(); - { - file->_cache = nullptr; - Lock lock(_filesMutex); - _files.erase(file->getKey()); - } - unusedLock.lock(); - - _unusedFiles.erase(file); - _numTotalFiles -= 1; - _numUnusedFiles -= 1; - _totalFilesSize -= length; - _unusedFilesSize -= length; overbudgetAmount -= std::min(length, overbudgetAmount); } } +void FileCache::wipe() { + Lock unusedFilesLock(_unusedFilesMutex); + while (!_unusedFiles.empty()) { + eject(*_unusedFiles.begin()); + } +} + void FileCache::clear() { // Eliminate any overbudget files clean(); diff --git a/libraries/networking/src/FileCache.h b/libraries/networking/src/FileCache.h index 040e1ab592..f29d75f779 100644 --- a/libraries/networking/src/FileCache.h +++ b/libraries/networking/src/FileCache.h @@ -46,6 +46,9 @@ public: 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; } @@ -95,6 +98,9 @@ public: 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; @@ -105,6 +111,8 @@ private: void removeUnusedFile(const FilePointer& file); void clean(); void clear(); + // Remove a file from the cache + void eject(const FilePointer& file); size_t getOverbudgetAmount() const; @@ -122,10 +130,10 @@ private: std::string _dirpath; bool _initialized { false }; - std::unordered_map> _files; + Map _files; Mutex _filesMutex; - std::unordered_set _unusedFiles; + Set _unusedFiles; Mutex _unusedFilesMutex; }; @@ -136,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(); 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/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 06c211e2f3..bd4d1201c7 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -175,9 +175,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { const float STUCK_PENETRATION = -0.05f; // always negative into the object. const float STUCK_IMPULSE = 500.0f; const int STUCK_LIFETIME = 3; - //if (contact.getDistance() < STUCK_PENETRATION) qDebug() << "FIXME checking contact:" << contact.getDistance() << " impulse:" << contact.getAppliedImpulse() << " lifetime:" << contact.getLifeTime(); if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) { - qDebug() << "FIXME stuck contact:" << contact.getDistance() << " impulse:" << contact.getAppliedImpulse() << " lifetime:" << contact.getLifeTime(); isStuck = true; // latch on } if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) { @@ -193,7 +191,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) { if (!_stepUpEnabled || hitHeight > _maxStepHeight) { // this manifold is invalidated by point that is too high stepContactIndex = -1; - qDebug() << "FIXME breaking early"; break; + break; } else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) { highestStep = hitHeight; stepContactIndex = j; 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/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index e5a25d733e..447d0e37bd 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -332,7 +332,7 @@ void Model::initJointStates() { bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QString& extraInfo, bool pickAgainstTriangles) { + QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) { bool intersectedSomething = false; @@ -381,7 +381,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g float triangleSetDistance = 0.0f; BoxFace triangleSetFace; glm::vec3 triangleSetNormal; - if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles)) { + if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) { glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index d718145d66..53d446d306 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -156,7 +156,7 @@ public: bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QString& extraInfo, bool pickAgainstTriangles = false); + QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } 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/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index c137ebd438..f853240fe3 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -290,12 +290,12 @@ glm::vec3 Triangle::getNormal() const { } bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) { + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) { glm::vec3 firstSide = v0 - v1; glm::vec3 secondSide = v2 - v1; glm::vec3 normal = glm::cross(secondSide, firstSide); float dividend = glm::dot(normal, v1) - glm::dot(origin, normal); - if (dividend > 0.0f) { + if (!allowBackface && dividend > 0.0f) { return false; // origin below plane } float divisor = glm::dot(normal, direction); diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 2fdc1aa25f..857d423896 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -83,7 +83,7 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire const glm::vec3& position, const glm::vec2& dimensions, float& distance); bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance); + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false); /// \brief decomposes rotation into its components such that: rotation = swing * twist /// \param rotation[in] rotation to decompose @@ -104,8 +104,8 @@ public: }; inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, - const Triangle& triangle, float& distance) { - return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance); + const Triangle& triangle, float& distance, bool allowBackface = false) { + return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface); } 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/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index aa21aa5cc0..68c99a9753 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -31,7 +31,7 @@ void TriangleSet::clear() { } bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface) { // reset our distance to be the max possible, lower level tests will store best distance here distance = std::numeric_limits::max(); @@ -41,7 +41,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& } int trianglesTouched = 0; - auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); + auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched, allowBackface); #if WANT_DEBUGGING if (precision) { @@ -95,7 +95,7 @@ void TriangleSet::balanceOctree() { // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; float boxDistance = distance; @@ -114,7 +114,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec const auto& triangle = _allTriangles[triangleIndex]; float thisTriangleDistance; trianglesTouched++; - if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { + if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance, allowBackface)) { if (thisTriangleDistance < bestDistance) { bestDistance = thisTriangleDistance; intersectedSomething = true; @@ -203,7 +203,7 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { } bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect @@ -247,7 +247,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } } // also check our local triangle set - if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched, allowBackface)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; bestLocalFace = childFace; diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 6cedc4da7e..3b0b33d7d5 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -27,7 +27,7 @@ class TriangleSet { void clear(); bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); const AABox& getBounds() const { return _bounds; } @@ -38,7 +38,7 @@ class TriangleSet { // checks our internal list of triangles bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false); std::vector& _allTriangles; std::map _children; @@ -60,7 +60,7 @@ public: void insert(const Triangle& t); bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface = false); void balanceOctree(); 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/edit.js b/scripts/system/edit.js index 6a1ede88c6..3c687f2f29 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."); @@ -1407,40 +1485,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 +1522,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."); diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index de9596e9be..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); } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 494ab245b1..2d3aaffdbf 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -410,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); 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/networking/src/FileCacheTests.cpp b/tests/networking/src/FileCacheTests.cpp index 0813d05a54..79fe9dee54 100644 --- a/tests/networking/src/FileCacheTests.cpp +++ b/tests/networking/src/FileCacheTests.cpp @@ -113,18 +113,21 @@ void FileCacheTests::testUnusedFiles() { QVERIFY(!file.get()); } - QThread::msleep(1000); // 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); - // Each access touches the file, so we need to sleep here to ensure that the files are - // spaced out in numeric order, otherwise later tests can't reliably determine the order - // for cache ejection - QThread::msleep(1000); + + 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(); @@ -165,6 +168,20 @@ void FileCacheTests::testFreeSpacePreservation() { } } +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 index 838c15afb8..b34b384855 100644 --- a/tests/networking/src/FileCacheTests.h +++ b/tests/networking/src/FileCacheTests.h @@ -20,6 +20,7 @@ private slots: void testUnusedFiles(); void testFreeSpacePreservation(); void cleanupTestCase(); + void testWipe(); private: size_t getFreeSpace() const; 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;