diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ef20dc82e2..f8f0f7904a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -386,8 +386,7 @@ void Agent::sendAvatarBillboardPacket() { void Agent::processAgentAvatarAndAudio(float deltaTime) { if (!_scriptEngine->isFinished() && _isAvatar) { auto scriptedAvatar = DependencyManager::get(); - const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE) - / (1000 * 1000)) + 0.5); + const int SCRIPT_AUDIO_BUFFER_SAMPLES = AudioConstants::SAMPLE_RATE / SCRIPT_FPS + 0.5; const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t); QByteArray avatarByteArray = scriptedAvatar->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); diff --git a/assignment-client/src/AssignmentAction.cpp b/assignment-client/src/AssignmentAction.cpp index 388f4123f6..8d296cd6ab 100644 --- a/assignment-client/src/AssignmentAction.cpp +++ b/assignment-client/src/AssignmentAction.cpp @@ -23,7 +23,7 @@ AssignmentAction::AssignmentAction(EntityActionType type, const QUuid& id, Entit AssignmentAction::~AssignmentAction() { } -void AssignmentAction::removeFromSimulation(EntitySimulation* simulation) const { +void AssignmentAction::removeFromSimulation(EntitySimulationPointer simulation) const { withReadLock([&]{ simulation->removeAction(_id); simulation->applyActionChanges(); diff --git a/assignment-client/src/AssignmentAction.h b/assignment-client/src/AssignmentAction.h index 6b901c9766..6b55b280bb 100644 --- a/assignment-client/src/AssignmentAction.h +++ b/assignment-client/src/AssignmentAction.h @@ -24,7 +24,7 @@ public: AssignmentAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity); virtual ~AssignmentAction(); - virtual void removeFromSimulation(EntitySimulation* simulation) const; + virtual void removeFromSimulation(EntitySimulationPointer simulation) const; virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; } virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; } virtual bool updateArguments(QVariantMap arguments); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 867412f6c0..0555f95c65 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -56,7 +56,7 @@ OctreePointer EntityServer::createTree() { tree->createRootElement(); tree->addNewlyCreatedHook(this); if (!_entitySimulation) { - SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation(); + SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; simpleSimulation->setEntityTree(tree); tree->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index ec58cda6cd..1685f08e01 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -28,6 +28,8 @@ struct ViewerSendingStats { }; class SimpleEntitySimulation; +using SimpleEntitySimulationPointer = std::shared_ptr; + class EntityServer : public OctreeServer, public NewlyCreatedEntityHook { Q_OBJECT @@ -69,7 +71,7 @@ private slots: void handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode); private: - SimpleEntitySimulation* _entitySimulation; + SimpleEntitySimulationPointer _entitySimulation; QTimer* _pruneDeletedEntitiesTimer = nullptr; QReadWriteLock _viewerSendingStatsLock; diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index 55eddf9e13..fa844dc0f8 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -141,6 +141,16 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by } } +void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const { + QMutexLocker viewLocker(&_viewMutex); + viewOut = _currentViewFrustum; +} + +void OctreeQueryNode::copyLastKnownViewFrustum(ViewFrustum& viewOut) const { + QMutexLocker viewLocker(&_viewMutex); + viewOut = _lastKnownViewFrustum; +} + bool OctreeQueryNode::updateCurrentViewFrustum() { // if shutting down, return immediately if (_isShuttingDown) { @@ -171,11 +181,13 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { } - // if there has been a change, then recalculate - if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) { - _currentViewFrustum = newestViewFrustum; - _currentViewFrustum.calculate(); - currentViewFrustumChanged = true; + { // if there has been a change, then recalculate + QMutexLocker viewLocker(&_viewMutex); + if (!newestViewFrustum.isVerySimilar(_currentViewFrustum)) { + _currentViewFrustum = newestViewFrustum; + _currentViewFrustum.calculate(); + currentViewFrustumChanged = true; + } } // Also check for LOD changes from the client @@ -219,11 +231,14 @@ void OctreeQueryNode::updateLastKnownViewFrustum() { return; } - bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum); + { + QMutexLocker viewLocker(&_viewMutex); + bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum); - if (frustumChanges) { - // save our currentViewFrustum into our lastKnownViewFrustum - _lastKnownViewFrustum = _currentViewFrustum; + if (frustumChanges) { + // save our currentViewFrustum into our lastKnownViewFrustum + _lastKnownViewFrustum = _currentViewFrustum; + } } // save that we know the view has been sent. @@ -237,15 +252,13 @@ bool OctreeQueryNode::moveShouldDump() const { return false; } + QMutexLocker viewLocker(&_viewMutex); glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition(); glm::vec3 newPosition = _currentViewFrustum.getPosition(); // theoretically we could make this slightly larger but relative to avatar scale. const float MAXIMUM_MOVE_WITHOUT_DUMP = 0.0f; - if (glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP) { - return true; - } - return false; + return glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP; } void OctreeQueryNode::dumpOutOfView() { @@ -257,8 +270,10 @@ void OctreeQueryNode::dumpOutOfView() { int stillInView = 0; int outOfView = 0; OctreeElementBag tempBag; + ViewFrustum viewCopy; + copyCurrentViewFrustum(viewCopy); while (OctreeElementPointer elementToCheck = elementBag.extract()) { - if (elementToCheck->isInView(_currentViewFrustum)) { + if (elementToCheck->isInView(viewCopy)) { tempBag.insert(elementToCheck); stillInView++; } else { @@ -267,7 +282,7 @@ void OctreeQueryNode::dumpOutOfView() { } if (stillInView > 0) { while (OctreeElementPointer elementToKeepInBag = tempBag.extract()) { - if (elementToKeepInBag->isInView(_currentViewFrustum)) { + if (elementToKeepInBag->isInView(viewCopy)) { elementBag.insert(elementToKeepInBag); } } diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 0ec876e674..96f46cb2fa 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -44,7 +44,7 @@ public: bool packetIsDuplicate() const; bool shouldSuppressDuplicatePacket(); - + unsigned int getAvailable() const { return _octreePacket->bytesAvailableForWrite(); } int getMaxSearchLevel() const { return _maxSearchLevel; } void resetMaxSearchLevel() { _maxSearchLevel = 1; } @@ -56,8 +56,8 @@ public: OctreeElementBag elementBag; OctreeElementExtraEncodeData extraEncodeData; - ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; } - ViewFrustum& getLastKnownViewFrustum() { return _lastKnownViewFrustum; } + void copyCurrentViewFrustum(ViewFrustum& viewOut) const; + void copyLastKnownViewFrustum(ViewFrustum& viewOut) const; // These are not classic setters because they are calculating and maintaining state // which is set asynchronously through the network receive @@ -114,6 +114,8 @@ private: int _maxSearchLevel { 1 }; int _maxLevelReachedInLastSearch { 1 }; + + mutable QMutex _viewMutex { QMutex::Recursive }; ViewFrustum _currentViewFrustum; ViewFrustum _lastKnownViewFrustum; quint64 _lastTimeBagEmpty { 0 }; @@ -139,7 +141,7 @@ private: QQueue _nackedSequenceNumbers; quint64 _sceneSendStartTime = 0; - + std::array _lastOctreePayload; }; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 94349bd3bc..0fbaf978e2 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -338,8 +338,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* _packetData.changeSettings(true, targetSize); // FIXME - eventually support only compressed packets - const ViewFrustum* lastViewFrustum = viewFrustumChanged ? &nodeData->getLastKnownViewFrustum() : NULL; - // If the current view frustum has changed OR we have nothing to send, then search against // the current view frustum for things to send. if (viewFrustumChanged || nodeData->elementBag.isEmpty()) { @@ -417,7 +415,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* quint64 lockWaitEnd = usecTimestampNow(); lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); quint64 encodeStart = usecTimestampNow(); - + OctreeElementPointer subTree = nodeData->elementBag.extract(); if (!subTree) { return; @@ -426,18 +424,22 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* float octreeSizeScale = nodeData->getOctreeSizeScale(); int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); - int boundaryLevelAdjust = boundaryLevelAdjustClient + + int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), - WANT_EXISTS_BITS, DONT_CHOP, viewFrustumChanged, lastViewFrustum, + EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, + viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, nodeData->getLastTimeBagEmpty(), isFullScene, &nodeData->stats, _myServer->getJurisdiction(), &nodeData->extraEncodeData); + nodeData->copyCurrentViewFrustum(params.viewFrustum); + if (viewFrustumChanged) { + nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); + } // Our trackSend() function is implemented by the server subclass, and will be called back - // during the encodeTreeBitstream() as new entities/data elements are sent + // during the encodeTreeBitstream() as new entities/data elements are sent params.trackSend = [this, node](const QUuid& dataID, quint64 dataEdited) { _myServer->trackSend(dataID, dataEdited, node->getUUID()); }; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 800cadf72f..84aa0328b0 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -44,7 +44,7 @@ else () endif () find_package(Qt5 COMPONENTS - Gui Multimedia Network OpenGL Qml Quick Script Svg + Gui Multimedia Network OpenGL Qml Quick Script ScriptTools Svg WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets) # grab the ui files in resources/ui @@ -201,7 +201,7 @@ include_directories("${PROJECT_SOURCE_DIR}/src") target_link_libraries( ${TARGET_NAME} Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL - Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg + Qt5::Qml Qt5::Quick Qt5::Script Qt5::ScriptTools Qt5::Svg Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets ) diff --git a/interface/resources/avatar/animations/fly.fbx b/interface/resources/avatar/animations/fly.fbx index a05a1e9a37..4a855032f9 100644 Binary files a/interface/resources/avatar/animations/fly.fbx and b/interface/resources/avatar/animations/fly.fbx differ diff --git a/interface/resources/avatar/animations/idle.fbx b/interface/resources/avatar/animations/idle.fbx index 6e4b3f4155..67e2be8735 100644 Binary files a/interface/resources/avatar/animations/idle.fbx and b/interface/resources/avatar/animations/idle.fbx differ diff --git a/interface/resources/avatar/animations/idle_to_walk.fbx b/interface/resources/avatar/animations/idle_to_walk.fbx index 9c2b03a4d6..858f35596f 100644 Binary files a/interface/resources/avatar/animations/idle_to_walk.fbx and b/interface/resources/avatar/animations/idle_to_walk.fbx differ diff --git a/interface/resources/avatar/animations/talk.fbx b/interface/resources/avatar/animations/talk.fbx index 4c27deadd8..3fd470befd 100644 Binary files a/interface/resources/avatar/animations/talk.fbx and b/interface/resources/avatar/animations/talk.fbx differ diff --git a/interface/resources/avatar/animations/turn_left.fbx b/interface/resources/avatar/animations/turn_left.fbx index 8856d159b9..de6d68b6c0 100644 Binary files a/interface/resources/avatar/animations/turn_left.fbx and b/interface/resources/avatar/animations/turn_left.fbx differ diff --git a/interface/resources/avatar/animations/turn_right.fbx b/interface/resources/avatar/animations/turn_right.fbx deleted file mode 100644 index 8af7213d22..0000000000 Binary files a/interface/resources/avatar/animations/turn_right.fbx and /dev/null differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 9bdbbec3da..834a3fc277 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -274,7 +274,7 @@ }, { "id": "walkFwd", - "interpTarget": 15, + "interpTarget": 16, "interpDuration": 6, "transitions": [ { "var": "isNotMoving", "state": "idle" }, @@ -497,7 +497,7 @@ "data": { "url": "animations/idle.fbx", "startFrame": 0.0, - "endFrame": 90.0, + "endFrame": 300.0, "timeScale": 1.0, "loopFlag": true }, @@ -509,7 +509,7 @@ "data": { "url": "animations/talk.fbx", "startFrame": 0.0, - "endFrame": 801.0, + "endFrame": 800.0, "timeScale": 1.0, "loopFlag": true }, @@ -572,7 +572,7 @@ "data": { "url": "animations/idle_to_walk.fbx", "startFrame": 1.0, - "endFrame": 19.0, + "endFrame": 13.0, "timeScale": 1.0, "loopFlag": false }, @@ -631,11 +631,12 @@ "id": "turnRight", "type": "clip", "data": { - "url": "animations/turn_right.fbx", + "url": "animations/turn_left.fbx", "startFrame": 0.0, "endFrame": 30.0, "timeScale": 1.0, - "loopFlag": true + "loopFlag": true, + "mirrorFlag": true }, "children": [] }, diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 1f71658946..4085d71c27 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,19 +1,21 @@ { "name": "Vive to Standard", "channels": [ - { "from": "Vive.LY", "when": "Vive.LS", "filters": "invert", "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LS", "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LS", "filters": ["invert" ,{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": "Vive.LS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT" }, - { "from": "Vive.LB", "to": "Standard.LB" }, + { "from": "Vive.LeftGrip", "to": "Standard.LB" }, { "from": "Vive.LS", "to": "Standard.LS" }, + { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RS", "filters": "invert", "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RS", "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RS", "filters": ["invert", { "type": "deadZone", "min": 0.6 }], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": "Vive.RS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT" }, - { "from": "Vive.RB", "to": "Standard.RB" }, + { "from": "Vive.RightGrip", "to": "Standard.RB" }, { "from": "Vive.RS", "to": "Standard.RS" }, + { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, { "from": "Vive.LeftApplicationMenu", "to": "Standard.Back" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.Start" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4a829b3191..36a12e7b6a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -45,11 +45,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -101,7 +103,7 @@ #include #include -#include "AnimDebugDraw.h" + #include "AudioClient.h" #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" @@ -210,9 +212,7 @@ const QHash Application::_acceptedExtensi class DeadlockWatchdogThread : public QThread { public: - static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1; static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; - static const unsigned long HEARTBEAT_REPORT_INTERVAL_USECS = 5 * USECS_PER_SECOND; static const unsigned long MAX_HEARTBEAT_AGE_USECS = 30 * USECS_PER_SECOND; static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples @@ -239,8 +239,6 @@ public: *crashTrigger = 0xDEAD10CC; } - static void setSuppressStatus(bool suppress) { _suppressStatus = suppress; } - void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); @@ -248,7 +246,6 @@ public: uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us uint64_t now = usecTimestampNow(); auto lastHeartbeatAge = (now > lastHeartbeat) ? now - lastHeartbeat : 0; - auto sinceLastReport = (now > _lastReport) ? now - _lastReport : 0; auto elapsedMovingAverage = _movingAverage.getAverage(); if (elapsedMovingAverage > _maxElapsedAverage) { @@ -274,21 +271,10 @@ public: if (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT) { qDebug() << "DEADLOCK WATCHDOG WARNING:" << "lastHeartbeatAge:" << lastHeartbeatAge - << "elapsedMovingAverage:" << elapsedMovingAverage << "** OVER EXPECTED VALUE**" + << "elapsedMovingAverage:" << elapsedMovingAverage << "** OVER EXPECTED VALUE **" << "maxElapsed:" << _maxElapsed << "maxElapsedAverage:" << _maxElapsedAverage << "samples:" << _movingAverage.getSamples(); - _lastReport = now; - } - - if (!_suppressStatus && sinceLastReport > HEARTBEAT_REPORT_INTERVAL_USECS) { - qDebug() << "DEADLOCK WATCHDOG STATUS:" - << "lastHeartbeatAge:" << lastHeartbeatAge - << "elapsedMovingAverage:" << elapsedMovingAverage - << "maxElapsed:" << _maxElapsed - << "maxElapsedAverage:" << _maxElapsedAverage - << "samples:" << _movingAverage.getSamples(); - _lastReport = now; } if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) { @@ -310,9 +296,7 @@ public: } } - static std::atomic _suppressStatus; static std::atomic _heartbeat; - static std::atomic _lastReport; static std::atomic _maxElapsed; static std::atomic _maxElapsedAverage; static ThreadSafeMovingAverage _movingAverage; @@ -320,17 +304,11 @@ public: bool _quit { false }; }; -std::atomic DeadlockWatchdogThread::_suppressStatus; std::atomic DeadlockWatchdogThread::_heartbeat; -std::atomic DeadlockWatchdogThread::_lastReport; std::atomic DeadlockWatchdogThread::_maxElapsed; std::atomic DeadlockWatchdogThread::_maxElapsedAverage; ThreadSafeMovingAverage DeadlockWatchdogThread::_movingAverage; -void Application::toggleSuppressDeadlockWatchdogStatus(bool checked) { - DeadlockWatchdogThread::setSuppressStatus(checked); -} - #ifdef Q_OS_WIN class MyNativeEventFilter : public QAbstractNativeEventFilter { public: @@ -509,6 +487,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _sessionRunTimer(startupTimer), _previousSessionCrashed(setupEssentials(argc, argv)), _undoStackScriptingInterface(&_undoStack), + _entitySimulation(new PhysicalEntitySimulation()), _physicsEngine(new PhysicsEngine(Vectors::ZERO)), _entityClipboardRenderer(false, this, this), _entityClipboard(new EntityTree()), @@ -584,12 +563,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // put the NodeList and datagram processing on the node thread nodeList->moveToThread(nodeThread); - // Model background downloads need to happen on the Datagram Processor Thread. The idle loop will - // emit checkBackgroundDownloads to cause the ModelCache to check it's queue for requested background - // downloads. - auto modelCache = DependencyManager::get(); - connect(this, &Application::checkBackgroundDownloads, modelCache.data(), &ModelCache::checkAsynchronousGets); - // put the audio processing on a separate thread QThread* audioThread = new QThread(); audioThread->setObjectName("Audio Thread"); @@ -702,9 +675,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get()); - // once the event loop has started, check and signal for an access token - QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); - auto addressManager = DependencyManager::get(); // use our MyAvatar position and quat for address manager path @@ -1118,6 +1088,11 @@ void Application::checkChangeCursor() { _cursorNeedsChanging = false; } + + + // After all of the constructor is completed, then set firstRun to false. + Setting::Handle firstRun{ Settings::firstRun, true }; + firstRun.set(false); } void Application::showCursor(const QCursor& cursor) { @@ -1327,8 +1302,6 @@ void Application::initializeGL() { // update before the first render update(0); - - InfoView::show(INFO_HELP_PATH, true); } FrameTimingsScriptingInterface _frameTimingsScriptingInterface; @@ -1505,10 +1478,17 @@ void Application::paintGL() { auto lodManager = DependencyManager::get(); - - RenderArgs renderArgs(_gpuContext, getEntities(), getViewFrustum(), lodManager->getOctreeSizeScale(), + { + QMutexLocker viewLocker(&_viewMutex); + _viewFrustum.calculate(); + } + RenderArgs renderArgs(_gpuContext, getEntities(), lodManager->getOctreeSizeScale(), lodManager->getBoundaryLevelAdjust(), RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); + { + QMutexLocker viewLocker(&_viewMutex); + renderArgs.setViewFrustum(_viewFrustum); + } PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings)); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); @@ -1675,7 +1655,7 @@ void Application::paintGL() { renderArgs._context->enableStereo(true); mat4 eyeOffsets[2]; mat4 eyeProjections[2]; - auto baseProjection = renderArgs._viewFrustum->getProjection(); + auto baseProjection = renderArgs.getViewFrustum().getProjection(); auto hmdInterface = DependencyManager::get(); float IPDScale = hmdInterface->getIPDScale(); mat4 headPose = displayPlugin->getHeadPose(); @@ -1805,7 +1785,10 @@ void Application::resizeGL() { _myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); // Possible change in aspect ratio - loadViewFrustum(_myCamera, _viewFrustum); + { + QMutexLocker viewLocker(&_viewMutex); + loadViewFrustum(_myCamera, _viewFrustum); + } auto offscreenUi = DependencyManager::get(); auto uiSize = displayPlugin->getRecommendedUiSize(); @@ -2132,6 +2115,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_J: if (isShifted) { + QMutexLocker viewLocker(&_viewMutex); _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() - 0.1f); } else { _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(-0.001, 0, 0)); @@ -2141,6 +2125,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_M: if (isShifted) { + QMutexLocker viewLocker(&_viewMutex); _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() + 0.1f); } else { _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0.001, 0, 0)); @@ -2746,9 +2731,6 @@ void Application::idle(uint64_t now) { } _overlayConductor.update(secondsSinceLastUpdate); - - // check for any requested background downloads. - emit checkBackgroundDownloads(); } void Application::setLowVelocityFilter(bool lowVelocityFilter) { @@ -2979,7 +2961,21 @@ void Application::init() { addressLookupString = arguments().value(urlIndex + 1); } - DependencyManager::get()->loadSettings(addressLookupString); + Setting::Handle firstRun { Settings::firstRun, true }; + if (addressLookupString.isEmpty() && firstRun.get()) { + qDebug() << "First run and no URL passed... attempting to go to Home or Entry..."; + DependencyManager::get()->ifLocalSandboxRunningElse([](){ + qDebug() << "Home sandbox appears to be running, going to Home."; + DependencyManager::get()->goToLocalSandbox(); + }, + [](){ + qDebug() << "Home sandbox does not appear to be running, going to Entry."; + DependencyManager::get()->goToEntry(); + }); + } else { + qDebug() << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); + DependencyManager::get()->loadSettings(addressLookupString); + } qCDebug(interfaceapp) << "Loaded settings"; @@ -2989,19 +2985,22 @@ void Application::init() { DependencyManager::get()->sendDomainServerCheckIn(); getEntities()->init(); - getEntities()->setViewFrustum(getViewFrustum()); + { + QMutexLocker viewLocker(&_viewMutex); + getEntities()->setViewFrustum(_viewFrustum); + } ObjectMotionState::setShapeManager(&_shapeManager); _physicsEngine->init(); EntityTreePointer tree = getEntities()->getTree(); - _entitySimulation.init(tree, _physicsEngine, &_entityEditSender); - tree->setSimulation(&_entitySimulation); + _entitySimulation->init(tree, _physicsEngine, &_entityEditSender); + tree->setSimulation(_entitySimulation); auto entityScriptingInterface = DependencyManager::get(); // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts - connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, + connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity, getEntities(), &EntityTreeRenderer::entityCollisionWithEntity); // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing @@ -3009,12 +3008,26 @@ void Application::init() { getEntities()->connectSignalsToSlots(entityScriptingInterface.data()); _entityClipboardRenderer.init(); - _entityClipboardRenderer.setViewFrustum(getViewFrustum()); + { + QMutexLocker viewLocker(&_viewMutex); + _entityClipboardRenderer.setViewFrustum(_viewFrustum); + } _entityClipboardRenderer.setTree(_entityClipboard); // Make sure any new sounds are loaded as soon as know about them. - connect(tree.get(), &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); - connect(getMyAvatar(), &MyAvatar::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); + connect(tree.get(), &EntityTree::newCollisionSoundURL, this, [this](QUrl newURL, EntityItemID id) { + EntityTreePointer tree = getEntities()->getTree(); + if (auto entity = tree->findEntityByEntityItemID(id)) { + auto sound = DependencyManager::get()->getSound(newURL); + entity->setCollisionSound(sound); + } + }, Qt::QueuedConnection); + connect(getMyAvatar(), &MyAvatar::newCollisionSoundURL, this, [this](QUrl newURL) { + if (auto avatar = getMyAvatar()) { + auto sound = DependencyManager::get()->getSound(newURL); + avatar->setCollisionSound(sound); + } + }, Qt::QueuedConnection); } void Application::updateLOD() const { @@ -3027,9 +3040,9 @@ void Application::updateLOD() const { } } -void Application::pushPreRenderLambda(void* key, std::function func) { - std::unique_lock guard(_preRenderLambdasLock); - _preRenderLambdas[key] = func; +void Application::pushPostUpdateLambda(void* key, std::function func) { + std::unique_lock guard(_postUpdateLambdasLock); + _postUpdateLambdas[key] = func; } // Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone. @@ -3212,9 +3225,12 @@ void Application::resetPhysicsReadyInformation() { void Application::reloadResourceCaches() { resetPhysicsReadyInformation(); + { + QMutexLocker viewLocker(&_viewMutex); + _viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE)); + _viewFrustum.setOrientation(glm::quat()); + } // Clear entities out of view frustum - _viewFrustum.setPosition(glm::vec3(0.0f, 0.0f, TREE_SCALE)); - _viewFrustum.setOrientation(glm::quat()); queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); DependencyManager::get()->clearCache(); @@ -3404,22 +3420,22 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("updateStates)"); static VectorOfMotionStates motionStates; - _entitySimulation.getObjectsToRemoveFromPhysics(motionStates); + _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); - _entitySimulation.deleteObjectsRemovedFromPhysics(); + _entitySimulation->deleteObjectsRemovedFromPhysics(); getEntities()->getTree()->withReadLock([&] { - _entitySimulation.getObjectsToAddToPhysics(motionStates); + _entitySimulation->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); }); getEntities()->getTree()->withReadLock([&] { - _entitySimulation.getObjectsToChange(motionStates); + _entitySimulation->getObjectsToChange(motionStates); VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates); - _entitySimulation.setObjectsToChange(stillNeedChange); + _entitySimulation->setObjectsToChange(stillNeedChange); }); - _entitySimulation.applyActionChanges(); + _entitySimulation->applyActionChanges(); avatarManager->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); @@ -3447,7 +3463,7 @@ void Application::update(float deltaTime) { getEntities()->getTree()->withWriteLock([&] { PerformanceTimer perfTimer("handleOutgoingChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges(); - _entitySimulation.handleOutgoingChanges(outgoingChanges); + _entitySimulation->handleOutgoingChanges(outgoingChanges); avatarManager->handleOutgoingChanges(outgoingChanges); }); @@ -3460,7 +3476,7 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("entities"); // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) - _entitySimulation.handleCollisionEvents(collisionEvents); + _entitySimulation->handleCollisionEvents(collisionEvents); // NOTE: the getEntities()->update() call below will wait for lock // and will simulate entity motion (the EntityTree has been given an EntitySimulation). @@ -3503,7 +3519,7 @@ void Application::update(float deltaTime) { // actually need to calculate the view frustum planes to send these details // to the server. { - PerformanceTimer perfTimer("loadViewFrustum"); + QMutexLocker viewLocker(&_viewMutex); loadViewFrustum(_myCamera, _viewFrustum); } @@ -3512,6 +3528,7 @@ void Application::update(float deltaTime) { // Update my voxel servers with my current voxel query... { PROFILE_RANGE_EX("QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); + QMutexLocker viewLocker(&_viewMutex); PerformanceTimer perfTimer("queryOctree"); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; @@ -3547,15 +3564,19 @@ void Application::update(float deltaTime) { } } + avatarManager->postUpdate(deltaTime); + { PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0); - std::unique_lock guard(_preRenderLambdasLock); - for (auto& iter : _preRenderLambdas) { + std::unique_lock guard(_postUpdateLambdasLock); + for (auto& iter : _postUpdateLambdas) { iter.second(); } - _preRenderLambdas.clear(); + _postUpdateLambdas.clear(); } + + AnimDebugDraw::getInstance().update(); } @@ -3623,14 +3644,16 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node //qCDebug(interfaceapp) << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView(); bool wantExtraDebugging = getLogger()->extraDebugging(); - _octreeQuery.setCameraPosition(_viewFrustum.getPosition()); - _octreeQuery.setCameraOrientation(_viewFrustum.getOrientation()); - _octreeQuery.setCameraFov(_viewFrustum.getFieldOfView()); - _octreeQuery.setCameraAspectRatio(_viewFrustum.getAspectRatio()); - _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip()); - _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip()); + ViewFrustum viewFrustum; + copyViewFrustum(viewFrustum); + _octreeQuery.setCameraPosition(viewFrustum.getPosition()); + _octreeQuery.setCameraOrientation(viewFrustum.getOrientation()); + _octreeQuery.setCameraFov(viewFrustum.getFieldOfView()); + _octreeQuery.setCameraAspectRatio(viewFrustum.getAspectRatio()); + _octreeQuery.setCameraNearClip(viewFrustum.getNearClip()); + _octreeQuery.setCameraFarClip(viewFrustum.getFarClip()); _octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); - _octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius()); + _octreeQuery.setCameraCenterRadius(viewFrustum.getCenterRadius()); auto lodManager = DependencyManager::get(); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); @@ -3666,7 +3689,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), rootDetails.s * TREE_SCALE); - if (_viewFrustum.cubeIntersectsKeyhole(serverBounds)) { + if (viewFrustum.cubeIntersectsKeyhole(serverBounds)) { inViewServers++; } } @@ -3732,11 +3755,9 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node rootDetails.s * TREE_SCALE); - inView = _viewFrustum.cubeIntersectsKeyhole(serverBounds); - } else { - if (wantExtraDebugging) { - qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; - } + inView = viewFrustum.cubeIntersectsKeyhole(serverBounds); + } else if (wantExtraDebugging) { + qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; } } @@ -3827,6 +3848,7 @@ QRect Application::getDesirableApplicationGeometry() const { // or the "myCamera". // void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) { + PerformanceTimer perfTimer("loadViewFrustum"); PROFILE_RANGE(__FUNCTION__); // We will use these below, from either the camera or head vectors calculated above viewFrustum.setProjection(camera.getProjection()); @@ -3855,7 +3877,8 @@ PickRay Application::computePickRay(float x, float y) const { getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction); } else { pickPoint /= getCanvasSize(); - getViewFrustum()->computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction); + QMutexLocker viewLocker(&_viewMutex); + _viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction); } return result; } @@ -3868,44 +3891,19 @@ glm::vec3 Application::getAvatarPosition() const { return getMyAvatar()->getPosition(); } -ViewFrustum* Application::getViewFrustum() { -#ifdef DEBUG - if (QThread::currentThread() == activeRenderingThread) { - // FIXME, figure out a better way to do this - //qWarning() << "Calling Application::getViewFrustum() from the active rendering thread, did you mean Application::getDisplayViewFrustum()?"; - } -#endif - return &_viewFrustum; +void Application::copyViewFrustum(ViewFrustum& viewOut) const { + QMutexLocker viewLocker(&_viewMutex); + viewOut = _viewFrustum; } -const ViewFrustum* Application::getViewFrustum() const { -#ifdef DEBUG - if (QThread::currentThread() == activeRenderingThread) { - // FIXME, figure out a better way to do this - //qWarning() << "Calling Application::getViewFrustum() from the active rendering thread, did you mean Application::getDisplayViewFrustum()?"; - } -#endif - return &_viewFrustum; +void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const { + QMutexLocker viewLocker(&_viewMutex); + viewOut = _displayViewFrustum; } -ViewFrustum* Application::getDisplayViewFrustum() { -#ifdef DEBUG - if (QThread::currentThread() != activeRenderingThread) { - // FIXME, figure out a better way to do this - // qWarning() << "Calling Application::getDisplayViewFrustum() from outside the active rendering thread or outside rendering, did you mean Application::getViewFrustum()?"; - } -#endif - return &_displayViewFrustum; -} - -const ViewFrustum* Application::getDisplayViewFrustum() const { -#ifdef DEBUG - if (QThread::currentThread() != activeRenderingThread) { - // FIXME, figure out a better way to do this - // qWarning() << "Calling Application::getDisplayViewFrustum() from outside the active rendering thread or outside rendering, did you mean Application::getViewFrustum()?"; - } -#endif - return &_displayViewFrustum; +void Application::copyShadowViewFrustum(ViewFrustum& viewOut) const { + QMutexLocker viewLocker(&_viewMutex); + viewOut = _shadowViewFrustum; } // WorldBox Render Data & rendering functions @@ -3970,7 +3968,7 @@ namespace render { auto skybox = skyStage->getSkybox(); if (skybox) { PerformanceTimer perfTimer("skybox"); - skybox->render(batch, *(args->_viewFrustum)); + skybox->render(batch, args->getViewFrustum()); break; } } @@ -4000,13 +3998,10 @@ namespace render { void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly) { - // FIXME: This preRender call is temporary until we create a separate render::scene for the mirror rendering. + // FIXME: This preDisplayRender call is temporary until we create a separate render::scene for the mirror rendering. // Then we can move this logic into the Avatar::simulate call. auto myAvatar = getMyAvatar(); - myAvatar->preRender(renderArgs); - - // Update animation debug draw renderer - AnimDebugDraw::getInstance().update(); + myAvatar->preDisplaySide(renderArgs); activeRenderingThread = QThread::currentThread(); PROFILE_RANGE(__FUNCTION__); @@ -4014,7 +4009,10 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); // load the view frustum - loadViewFrustum(theCamera, _displayViewFrustum); + { + QMutexLocker viewLocker(&_viewMutex); + loadViewFrustum(theCamera, _displayViewFrustum); + } // TODO fix shadows and make them use the GPU library @@ -4082,7 +4080,10 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se { PerformanceTimer perfTimer("EngineRun"); - renderArgs->_viewFrustum = getDisplayViewFrustum(); + { + QMutexLocker viewLocker(&_viewMutex); + renderArgs->setViewFrustum(_displayViewFrustum); + } _renderEngine->getRenderContext()->args = renderArgs; // Before the deferred pass, let's try to use the render engine @@ -4951,24 +4952,19 @@ qreal Application::getDevicePixelRatio() { return (_window && _window->windowHandle()) ? _window->windowHandle()->devicePixelRatio() : 1.0; } -DisplayPlugin* Application::getActiveDisplayPlugin() { - DisplayPlugin* result = nullptr; - if (QThread::currentThread() == thread()) { - if (nullptr == _displayPlugin) { - updateDisplayMode(); - Q_ASSERT(_displayPlugin); - } - result = _displayPlugin.get(); - } else { +DisplayPluginPointer Application::getActiveDisplayPlugin() const { + if (QThread::currentThread() != thread()) { std::unique_lock lock(_displayPluginLock); - result = _displayPlugin.get(); + return _displayPlugin; } - return result; + + if (!_displayPlugin) { + const_cast(this)->updateDisplayMode(); + Q_ASSERT(_displayPlugin); + } + return _displayPlugin; } -const DisplayPlugin* Application::getActiveDisplayPlugin() const { - return const_cast(this)->getActiveDisplayPlugin(); -} static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); @@ -5212,10 +5208,10 @@ void Application::updateInputModes() { } mat4 Application::getEyeProjection(int eye) const { + QMutexLocker viewLocker(&_viewMutex); if (isHMDMode()) { return getActiveDisplayPlugin()->getEyeProjection((Eye)eye, _viewFrustum.getProjection()); } - return _viewFrustum.getProjection(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 2911d42b65..a4bbdf4326 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -128,14 +128,12 @@ public: Camera* getCamera() { return &_myCamera; } const Camera* getCamera() const { return &_myCamera; } // Represents the current view frustum of the avatar. - ViewFrustum* getViewFrustum(); - const ViewFrustum* getViewFrustum() const; + void copyViewFrustum(ViewFrustum& viewOut) const; // Represents the view frustum of the current rendering pass, // which might be different from the viewFrustum, i.e. shadowmap // passes, mirror window passes, etc - ViewFrustum* getDisplayViewFrustum(); - const ViewFrustum* getDisplayViewFrustum() const; - ViewFrustum* getShadowViewFrustum() override { return &_shadowViewFrustum; } + void copyDisplayViewFrustum(ViewFrustum& viewOut) const; + void copyShadowViewFrustum(ViewFrustum& viewOut) const override; const OctreePacketProcessor& getOctreePacketProcessor() const { return _octreeProcessor; } EntityTreeRenderer* getEntities() const { return DependencyManager::get().data(); } QUndoStack* getUndoStack() { return &_undoStack; } @@ -169,7 +167,7 @@ public: virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) override; - virtual ViewFrustum* getCurrentViewFrustum() override { return getDisplayViewFrustum(); } + virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { copyDisplayViewFrustum(viewOut); } virtual QThread* getMainThread() override { return thread(); } virtual PickRay computePickRay(float x, float y) const override; virtual glm::vec3 getAvatarPosition() const override; @@ -177,8 +175,7 @@ public: void setActiveDisplayPlugin(const QString& pluginName); - DisplayPlugin* getActiveDisplayPlugin(); - const DisplayPlugin* getActiveDisplayPlugin() const; + DisplayPluginPointer getActiveDisplayPlugin() const; FileLogger* getLogger() const { return _logger; } @@ -212,7 +209,7 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } - virtual void pushPreRenderLambda(void* key, std::function func) override; + virtual void pushPostUpdateLambda(void* key, std::function func) override; const QRect& getMirrorViewRect() const { return _mirrorViewRect; } @@ -224,8 +221,6 @@ public: signals: void svoImportRequested(const QString& url); - void checkBackgroundDownloads(); - void fullAvatarURLChanged(const QString& newValue, const QString& modelName); void beforeAboutToQuit(); @@ -255,7 +250,6 @@ public slots: void resetSensors(bool andReload = false); void setActiveFaceTracker() const; - void toggleSuppressDeadlockWatchdogStatus(bool checked); #ifdef HAVE_IVIEWHMD void setActiveEyeTracker(); @@ -388,7 +382,7 @@ private: OffscreenGLCanvas* _offscreenContext { nullptr }; DisplayPluginPointer _displayPlugin; - std::mutex _displayPluginLock; + mutable std::mutex _displayPluginLock; InputPluginList _activeInputPlugins; bool _activatingDisplayPlugin { false }; @@ -408,12 +402,13 @@ private: QElapsedTimer _lastTimeUpdated; ShapeManager _shapeManager; - PhysicalEntitySimulation _entitySimulation; + PhysicalEntitySimulationPointer _entitySimulation; PhysicsEnginePointer _physicsEngine; EntityTreeRenderer _entityClipboardRenderer; EntityTreePointer _entityClipboard; + mutable QMutex _viewMutex { QMutex::Recursive }; ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels) ViewFrustum _displayViewFrustum; @@ -513,8 +508,8 @@ private: QThread* _deadlockWatchdogThread; - std::map> _preRenderLambdas; - std::mutex _preRenderLambdasLock; + std::map> _postUpdateLambdas; + std::mutex _postUpdateLambdasLock; std::atomic _fullSceneReceivedCounter { 0 }; // how many times have we received a full-scene octree stats packet uint32_t _fullSceneCounterAtLastPhysicsCheck { 0 }; // _fullSceneReceivedCounter last time we checked physics ready diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 64b506059c..bf756a55a5 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include "Application.h" @@ -216,7 +217,7 @@ QString LODManager::getLODFeedbackText() { bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { // FIXME - eventually we want to use the render accuracy as an indicator for the level of detail // to use in rendering. - float renderAccuracy = args->_viewFrustum->calculateRenderAccuracy(bounds, args->_sizeScale, args->_boundaryLevelAdjust); + float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); return (renderAccuracy > 0.0f); }; @@ -228,7 +229,6 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } - void LODManager::loadSettings() { setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); @@ -239,4 +239,3 @@ void LODManager::saveSettings() { hmdLODDecreaseFPS.set(getHMDLODDecreaseFPS()); } - diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c5fa52e49d..25e94f4e92 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -480,10 +480,8 @@ Menu::Menu() { avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtTargets, 0, false); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderMyLookAtVectors, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderOtherLookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, avatar, SLOT(setEnableDebugDrawDefaultPose(bool))); @@ -568,8 +566,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::PipelineWarnings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::LogExtraTimings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); - addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SupressDeadlockWatchdogStatus, 0, false, - qApp, SLOT(toggleSuppressDeadlockWatchdogStatus(bool))); // Developer > Audio >>> diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 5796575780..fdb136b900 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -147,9 +147,8 @@ namespace MenuOption { const QString ReloadAllScripts = "Reload All Scripts"; const QString ReloadContent = "Reload Content (Clears all caches)"; const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; - const QString RenderFocusIndicator = "Show Eye Focus"; - const QString RenderLookAtTargets = "Show Look-at Targets"; - const QString RenderLookAtVectors = "Show Look-at Vectors"; + const QString RenderMyLookAtVectors = "Show My Eye Vectors"; + const QString RenderOtherLookAtVectors = "Show Other Eye Vectors"; const QString RenderMaxTextureMemory = "Maximum Texture Memory"; const QString RenderMaxTextureAutomatic = "Automatic Texture Memory"; const QString RenderMaxTexture64MB = "64 MB"; @@ -174,7 +173,6 @@ namespace MenuOption { const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats"; - const QString ShowWhosLookingAtMe = "Show Who's Looking at Me"; const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; const QString SimulateEyeTracking = "Simulate"; const QString SMIEyeTracking = "SMI Eye Tracking"; @@ -182,7 +180,6 @@ namespace MenuOption { const QString Stats = "Stats"; const QString StopAllScripts = "Stop All Scripts"; const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; - const QString SupressDeadlockWatchdogStatus = "Supress Deadlock Watchdog Status"; const QString ThirdPerson = "Third Person"; const QString ThreePointCalibration = "3 Point Calibration"; const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index c3d186f190..b651a1520d 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -54,7 +54,7 @@ QOpenGLContext* PluginContainerProxy::getPrimaryContext() { return qApp->_glWidget->context()->contextHandle(); } -const DisplayPlugin* PluginContainerProxy::getActiveDisplayPlugin() const { +const DisplayPluginPointer PluginContainerProxy::getActiveDisplayPlugin() const { return qApp->getActiveDisplayPlugin(); } diff --git a/interface/src/PluginContainerProxy.h b/interface/src/PluginContainerProxy.h index 3ec2489c54..a04a1b2977 100644 --- a/interface/src/PluginContainerProxy.h +++ b/interface/src/PluginContainerProxy.h @@ -24,7 +24,7 @@ class PluginContainerProxy : public QObject, PluginContainer { virtual ui::Menu* getPrimaryMenu() override; virtual QOpenGLContext* getPrimaryContext() override; virtual bool isForeground() override; - virtual const DisplayPlugin* getActiveDisplayPlugin() const override; + virtual const DisplayPluginPointer getActiveDisplayPlugin() const override; friend class Application; diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index 7e2deef494..9510710eb3 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -194,8 +194,8 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { gpu::Batch& batch = *renderArgs->_batch; batch.setViewTransform(Transform()); - batch.setProjectionTransform(renderArgs->_viewFrustum->getProjection()); - batch.setModelTransform(Transform().setRotation(glm::inverse(renderArgs->_viewFrustum->getOrientation()) * + batch.setProjectionTransform(renderArgs->getViewFrustum().getProjection()); + batch.setModelTransform(Transform().setRotation(glm::inverse(renderArgs->getViewFrustum().getOrientation()) * quat(vec3(TILT, 0, 0)))); batch.setResourceTexture(0, textureCache->getWhiteTexture()); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 2cacb81ce4..9ae636af36 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -25,11 +25,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "Application.h" #include "Avatar.h" @@ -65,11 +67,6 @@ namespace render { } template <> void payloadRender(const AvatarSharedPointer& avatar, RenderArgs* args) { auto avatarPtr = static_pointer_cast(avatar); - bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderLookAtVectors); - avatarPtr->setDisplayingLookatVectors(renderLookAtVectors); - bool renderLookAtTarget = Menu::getInstance()->isOptionChecked(MenuOption::RenderLookAtTargets); - avatarPtr->setDisplayingLookatTarget(renderLookAtTarget); - if (avatarPtr->isInitialized() && args) { PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload"); avatarPtr->render(args, qApp->getCamera()->getPosition()); @@ -171,7 +168,10 @@ void Avatar::simulate(float deltaTime) { // update the shouldAnimate flag to match whether or not we will render the avatar. const float MINIMUM_VISIBILITY_FOR_ON = 0.4f; const float MAXIMUM_VISIBILITY_FOR_OFF = 0.6f; - float visibility = qApp->getViewFrustum()->calculateRenderAccuracy(getBounds(), DependencyManager::get()->getOctreeSizeScale()); + ViewFrustum viewFrustum; + qApp->copyViewFrustum(viewFrustum); + float visibility = calculateRenderAccuracy(viewFrustum.getPosition(), + getBounds(), DependencyManager::get()->getOctreeSizeScale()); if (!_shouldAnimate) { if (visibility > MINIMUM_VISIBILITY_FOR_ON) { _shouldAnimate = true; @@ -184,8 +184,9 @@ void Avatar::simulate(float deltaTime) { // simple frustum check float boundingRadius = getBoundingRadius(); - bool avatarPositionInView = qApp->getDisplayViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius); - bool avatarMeshInView = qApp->getDisplayViewFrustum()->boxIntersectsFrustum(_skeletonModel->getRenderableMeshBound()); + qApp->copyDisplayViewFrustum(viewFrustum); + bool avatarPositionInView = viewFrustum.sphereIntersectsFrustum(getPosition(), boundingRadius); + bool avatarMeshInView = viewFrustum.boxIntersectsFrustum(_skeletonModel->getRenderableMeshBound()); if (_shouldAnimate && !_shouldSkipRender && (avatarPositionInView || avatarMeshInView)) { { @@ -318,6 +319,39 @@ void Avatar::updateRenderItem(render::PendingChanges& pendingChanges) { } } +void Avatar::postUpdate(float deltaTime) { + + bool renderLookAtVectors; + if (isMyAvatar()) { + renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderMyLookAtVectors); + } else { + renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderOtherLookAtVectors); + } + + if (renderLookAtVectors) { + const float EYE_RAY_LENGTH = 10.0; + const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + + int leftEyeJoint = getJointIndex("LeftEye"); + glm::vec3 leftEyePosition; + glm::quat leftEyeRotation; + + if (_skeletonModel->getJointPositionInWorldFrame(leftEyeJoint, leftEyePosition) && + _skeletonModel->getJointRotationInWorldFrame(leftEyeJoint, leftEyeRotation)) { + DebugDraw::getInstance().drawRay(leftEyePosition, leftEyePosition + leftEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, BLUE); + } + + int rightEyeJoint = getJointIndex("RightEye"); + glm::vec3 rightEyePosition; + glm::quat rightEyeRotation; + if (_skeletonModel->getJointPositionInWorldFrame(rightEyeJoint, rightEyePosition) && + _skeletonModel->getJointRotationInWorldFrame(rightEyeJoint, rightEyeRotation)) { + DebugDraw::getInstance().drawRay(rightEyePosition, rightEyePosition + rightEyeRotation * Vectors::UNIT_Z * EYE_RAY_LENGTH, RED); + } + } +} + void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { auto& batch = *renderArgs->_batch; PROFILE_RANGE_BATCH(batch, __FUNCTION__); @@ -381,39 +415,23 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } } - // simple frustum check - float boundingRadius = getBoundingRadius(); - ViewFrustum* frustum = nullptr; - if (renderArgs->_renderMode == RenderArgs::SHADOW_RENDER_MODE) { - frustum = qApp->getShadowViewFrustum(); - } else { - frustum = qApp->getDisplayViewFrustum(); - } - - if (!frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) { - return; + { // simple frustum check + ViewFrustum frustum; + if (renderArgs->_renderMode == RenderArgs::SHADOW_RENDER_MODE) { + qApp->copyShadowViewFrustum(frustum); + } else { + qApp->copyDisplayViewFrustum(frustum); + } + if (!frustum.sphereIntersectsFrustum(getPosition(), getBoundingRadius())) { + return; + } } glm::vec3 toTarget = cameraPosition - getPosition(); float distanceToTarget = glm::length(toTarget); { - // glow when moving far away - const float GLOW_DISTANCE = 20.0f; - const float GLOW_MAX_LOUDNESS = 2500.0f; - const float MAX_GLOW = 0.5f; - - float GLOW_FROM_AVERAGE_LOUDNESS = ((this == DependencyManager::get()->getMyAvatar()) - ? 0.0f - : MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS); - GLOW_FROM_AVERAGE_LOUDNESS = 0.0f; - - float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderArgs->_renderMode == RenderArgs::NORMAL_RENDER_MODE - ? 1.0f - : GLOW_FROM_AVERAGE_LOUDNESS; - - // render body - renderBody(renderArgs, frustum, glowLevel); + fixupModelsInScene(); if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) { // add local lights @@ -437,64 +455,6 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { const float BOUNDING_SHAPE_ALPHA = 0.7f; _skeletonModel->renderBoundingCollisionShapes(*renderArgs->_batch, getUniformScale(), BOUNDING_SHAPE_ALPHA); } - - // If this is the avatar being looked at, render a little ball above their head - if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) { - static const float INDICATOR_OFFSET = 0.22f; - static const float INDICATOR_RADIUS = 0.03f; - static const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; - glm::vec3 avatarPosition = getPosition(); - glm::vec3 position = glm::vec3(avatarPosition.x, getDisplayNamePosition().y + INDICATOR_OFFSET, avatarPosition.z); - PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderFocusIndicator"); - Transform transform; - transform.setTranslation(position); - transform.postScale(INDICATOR_RADIUS); - batch.setModelTransform(transform); - DependencyManager::get()->renderSolidSphereInstance(batch, LOOK_AT_INDICATOR_COLOR); - } - - // If the avatar is looking at me, indicate that they are - if (getHead()->isLookingAtMe() && Menu::getInstance()->isOptionChecked(MenuOption::ShowWhosLookingAtMe)) { - PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderLookingAtMe"); - const glm::vec3 LOOKING_AT_ME_COLOR = { 1.0f, 1.0f, 1.0f }; - const float LOOKING_AT_ME_ALPHA_START = 0.8f; - const float LOOKING_AT_ME_DURATION = 0.5f; // seconds - quint64 now = usecTimestampNow(); - float alpha = LOOKING_AT_ME_ALPHA_START - * (1.0f - ((float)(now - getHead()->getLookingAtMeStarted())) - / (LOOKING_AT_ME_DURATION * (float)USECS_PER_SECOND)); - if (alpha > 0.0f) { - if (_skeletonModel->isLoaded()) { - const auto& geometry = _skeletonModel->getFBXGeometry(); - const float DEFAULT_EYE_DIAMETER = 0.048f; // Typical human eye - const float RADIUS_INCREMENT = 0.005f; - batch.setModelTransform(Transform()); - - glm::vec3 position = getHead()->getLeftEyePosition(); - Transform transform; - transform.setTranslation(position); - float eyeDiameter = geometry.leftEyeSize; - if (eyeDiameter == 0.0f) { - eyeDiameter = DEFAULT_EYE_DIAMETER; - } - - batch.setModelTransform(Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT)); - DependencyManager::get()->renderSolidSphereInstance(batch, - glm::vec4(LOOKING_AT_ME_COLOR, alpha)); - - position = getHead()->getRightEyePosition(); - transform.setTranslation(position); - eyeDiameter = geometry.rightEyeSize; - if (eyeDiameter == 0.0f) { - eyeDiameter = DEFAULT_EYE_DIAMETER; - } - batch.setModelTransform(Transform(transform).postScale(eyeDiameter * getUniformScale() / 2.0f + RADIUS_INCREMENT)); - DependencyManager::get()->renderSolidSphereInstance(batch, - glm::vec4(LOOKING_AT_ME_COLOR, alpha)); - - } - } - } } const float DISPLAYNAME_DISTANCE = 20.0f; @@ -502,9 +462,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { auto cameraMode = qApp->getCamera()->getMode(); if (!isMyAvatar() || cameraMode != CAMERA_MODE_FIRST_PERSON) { - auto& frustum = *renderArgs->_viewFrustum; + auto& frustum = renderArgs->getViewFrustum(); auto textPosition = getDisplayNamePosition(); - if (frustum.pointIntersectsFrustum(textPosition)) { renderDisplayName(batch, frustum, textPosition); } @@ -553,11 +512,6 @@ void Avatar::fixupModelsInScene() { scene->enqueuePendingChanges(pendingChanges); } -void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel) { - fixupModelsInScene(); - getHead()->renderLookAts(renderArgs); -} - bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { return true; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index cb35fbb5eb..2580ac1d37 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -77,9 +77,9 @@ public: void updateRenderItem(render::PendingChanges& pendingChanges); + virtual void postUpdate(float deltaTime); + //setters - void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); } - void setDisplayingLookatTarget(bool displayingLookatTarget) { getHead()->setRenderLookatTarget(displayingLookatTarget); } void setIsLookAtTarget(const bool isLookAtTarget) { _isLookAtTarget = isLookAtTarget; } bool getIsLookAtTarget() const { return _isLookAtTarget; } //getters @@ -232,7 +232,6 @@ protected: Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const; void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const; - virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f); virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual void fixupModelsInScene(); @@ -251,7 +250,7 @@ private: bool _initialized; bool _shouldAnimate { true }; bool _shouldSkipRender { false }; - bool _isLookAtTarget; + bool _isLookAtTarget { false }; float getBoundingRadius() const; diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 629b3aac12..c84cfecb40 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -93,13 +93,13 @@ void AvatarActionHold::prepareForPhysicsSimulation() { activateBody(true); } -std::shared_ptr AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity) { +bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, + glm::vec3& linearVelocity, glm::vec3& angularVelocity) { auto avatarManager = DependencyManager::get(); auto holdingAvatar = std::static_pointer_cast(avatarManager->getAvatarBySessionID(_holderID)); if (!holdingAvatar) { - return holdingAvatar; + return false;; } withReadLock([&]{ @@ -171,62 +171,17 @@ std::shared_ptr AvatarActionHold::getTarget(float deltaTimeStep, glm::qu linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition); }); - return holdingAvatar; + return true; } void AvatarActionHold::updateActionWorker(float deltaTimeStep) { - glm::quat rotation; - glm::vec3 position; - glm::vec3 linearVelocity; - glm::vec3 angularVelocity; - bool valid = false; - int holdCount = 0; - - auto ownerEntity = _ownerEntity.lock(); - if (!ownerEntity) { - return; - } - QList holdActions = ownerEntity->getActionsOfType(ACTION_TYPE_HOLD); - foreach (EntityActionPointer action, holdActions) { - std::shared_ptr holdAction = std::static_pointer_cast(action); - glm::quat rotationForAction; - glm::vec3 positionForAction; - glm::vec3 linearVelocityForAction, angularVelocityForAction; - std::shared_ptr holdingAvatar = holdAction->getTarget(deltaTimeStep, rotationForAction, positionForAction, linearVelocityForAction, angularVelocityForAction); - if (holdingAvatar) { - holdCount ++; - if (holdAction.get() == this) { - // only use the rotation for this action - valid = true; - rotation = rotationForAction; - } - - position += positionForAction; - linearVelocity += linearVelocityForAction; - angularVelocity += angularVelocityForAction; - } - } - - if (valid && holdCount > 0) { - position /= holdCount; - linearVelocity /= holdCount; - angularVelocity /= holdCount; - - withWriteLock([&]{ - _positionalTarget = position; - _rotationalTarget = rotation; - _linearVelocityTarget = linearVelocity; - _angularVelocityTarget = angularVelocity; - _positionalTargetSet = true; - _rotationalTargetSet = true; - _active = true; - }); - if (_kinematic) { + if (_kinematic) { + if (prepareForSpringUpdate(deltaTimeStep)) { doKinematicUpdate(deltaTimeStep); - } else { - forceBodyNonStatic(); - ObjectActionSpring::updateActionWorker(deltaTimeStep); } + } else { + forceBodyNonStatic(); + ObjectActionSpring::updateActionWorker(deltaTimeStep); } } diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index 0ad9cff257..609fd57ff3 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -36,8 +36,8 @@ public: virtual bool shouldSuppressLocationEdits() override { return _active && !_ownerEntity.expired(); } bool getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); - std::shared_ptr getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity); + virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, + glm::vec3& linearVelocity, glm::vec3& angularVelocity) override; virtual void prepareForPhysicsSimulation() override; @@ -51,9 +51,6 @@ private: QString _hand { "right" }; QUuid _holderID; - glm::vec3 _linearVelocityTarget; - glm::vec3 _angularVelocityTarget; - bool _kinematic { false }; bool _kinematicSetVelocity { false }; bool _previousSet { false }; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 402245d0c3..41bcc0332a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -156,6 +156,15 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { simulateAvatarFades(deltaTime); } +void AvatarManager::postUpdate(float deltaTime) { + auto hashCopy = getHashCopy(); + AvatarHash::iterator avatarIterator = hashCopy.begin(); + for (avatarIterator = hashCopy.begin(); avatarIterator != hashCopy.end(); avatarIterator++) { + auto avatar = std::static_pointer_cast(avatarIterator.value()); + avatar->postUpdate(deltaTime); + } +} + void AvatarManager::simulateAvatarFades(float deltaTime) { QVector::iterator fadingIterator = _avatarFades.begin(); @@ -309,9 +318,11 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents // my avatar. (Other user machines will make a similar analysis and inject sound for their collisions.) if (collision.idA.isNull() || collision.idB.isNull()) { MyAvatar* myAvatar = getMyAvatar(); - const QString& collisionSoundURL = myAvatar->getCollisionSoundURL(); - if (!collisionSoundURL.isEmpty()) { - const float velocityChange = glm::length(collision.velocityChange); + auto collisionSound = myAvatar->getCollisionSound(); + if (collisionSound) { + const auto characterController = myAvatar->getCharacterController(); + const float avatarVelocityChange = (characterController ? glm::length(characterController->getVelocityChange()) : 0.0f); + const float velocityChange = glm::length(collision.velocityChange) + avatarVelocityChange; const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01f; const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION); @@ -327,7 +338,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents // but most avatars are roughly the same size, so let's not be so fancy yet. const float AVATAR_STRETCH_FACTOR = 1.0f; - AudioInjector::playSound(collisionSoundURL, energyFactorOfFull, AVATAR_STRETCH_FACTOR, myAvatar->getPosition()); + AudioInjector::playSound(collisionSound, energyFactorOfFull, AVATAR_STRETCH_FACTOR, myAvatar->getPosition()); myAvatar->collisionWithEntity(collision); return; } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 94a66782f2..1cd295d69f 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -44,6 +44,8 @@ public: void updateMyAvatar(float deltaTime); void updateOtherAvatars(float deltaTime); + void postUpdate(float deltaTime); + void clearOtherAvatars(); void clearAllAvatars(); diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index b7247334fc..3af8b8a423 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -46,8 +46,6 @@ Head::Head(Avatar* owningAvatar) : _mouth3(0.0f), _mouth4(0.0f), _mouthTime(0.0f), - _renderLookatVectors(false), - _renderLookatTarget(false), _saccade(0.0f, 0.0f, 0.0f), _saccadeTarget(0.0f, 0.0f, 0.0f), _leftEyeBlinkVelocity(0.0f), @@ -316,22 +314,6 @@ void Head::relaxLean(float deltaTime) { _deltaLeanForward *= relaxationFactor; } -void Head::render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustum) { -} - -void Head::renderLookAts(RenderArgs* renderArgs) { - renderLookAts(renderArgs, _leftEyePosition, _rightEyePosition); -} - -void Head::renderLookAts(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition) { - if (_renderLookatVectors) { - renderLookatVectors(renderArgs, leftEyePosition, rightEyePosition, getCorrectedLookAtPosition()); - } - if (_renderLookatTarget) { - renderLookatTarget(renderArgs, getCorrectedLookAtPosition()); - } -} - void Head::setScale (float scale) { if (_scale == scale) { return; @@ -442,31 +424,3 @@ void Head::addLeanDeltas(float sideways, float forward) { _deltaLeanSideways += sideways; _deltaLeanForward += forward; } - -void Head::renderLookatVectors(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition) { - auto& batch = *renderArgs->_batch; - auto transform = Transform{}; - batch.setModelTransform(transform); - // FIXME: THe line width of 2.0f is not supported anymore, we ll need a workaround - - glm::vec4 startColor(0.2f, 0.2f, 0.2f, 1.0f); - glm::vec4 endColor(1.0f, 1.0f, 1.0f, 0.0f); - - auto geometryCache = DependencyManager::get(); - geometryCache->bindSimpleProgram(batch); - geometryCache->renderLine(batch, leftEyePosition, lookatPosition, startColor, endColor, _leftEyeLookAtID); - geometryCache->renderLine(batch, rightEyePosition, lookatPosition, startColor, endColor, _rightEyeLookAtID); -} - -void Head::renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition) { - auto& batch = *renderArgs->_batch; - auto transform = Transform{}; - transform.setTranslation(lookatPosition); - - auto geometryCache = DependencyManager::get(); - const float LOOK_AT_TARGET_RADIUS = 0.075f; - transform.postScale(LOOK_AT_TARGET_RADIUS); - const glm::vec4 LOOK_AT_TARGET_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; - batch.setModelTransform(transform); - geometryCache->renderSolidSphereInstance(batch, LOOK_AT_TARGET_COLOR); -} diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index d0bd4fdb77..e4b8fefea5 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -28,29 +28,24 @@ class Avatar; class Head : public HeadData { public: explicit Head(Avatar* owningAvatar); - + void init(); void reset(); void simulate(float deltaTime, bool isMine, bool billboard = false); - void render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustum); void setScale(float scale); void setPosition(glm::vec3 position) { _position = position; } void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; } void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } - void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } - void setRenderLookatTarget(bool onOff) { _renderLookatTarget = onOff; } - void renderLookAts(RenderArgs* renderArgs); - void renderLookAts(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition); /// \return orientationBase+Delta glm::quat getFinalOrientationInLocalFrame() const; - + /// \return orientationBody * (orientationBase+Delta) glm::quat getFinalOrientationInWorldFrame() const; /// \return orientationBody * orientationBasePitch glm::quat getCameraOrientation () const; - + void setCorrectedLookAtPosition(glm::vec3 correctedLookAtPosition); glm::vec3 getCorrectedLookAtPosition(); void clearCorrectedLookAtPosition() { _isLookingAtMe = false; } @@ -66,9 +61,9 @@ public: glm::vec3 getFrontDirection() const { return getOrientation() * IDENTITY_FRONT; } float getFinalLeanSideways() const { return _leanSideways + _deltaLeanSideways; } float getFinalLeanForward() const { return _leanForward + _deltaLeanForward; } - + glm::quat getEyeRotation(const glm::vec3& eyePosition) const; - + const glm::vec3& getRightEyePosition() const { return _rightEyePosition; } const glm::vec3& getLeftEyePosition() const { return _leftEyePosition; } glm::vec3 getRightEarPosition() const { return _rightEyePosition + (getRightDirection() * EYE_EAR_GAP) + (getFrontDirection() * -EYE_EAR_GAP); } @@ -85,10 +80,10 @@ public: void setDeltaYaw(float yaw) { _deltaYaw = yaw; } float getDeltaYaw() const { return _deltaYaw; } - + void setDeltaRoll(float roll) { _deltaRoll = roll; } float getDeltaRoll() const { return _deltaRoll; } - + virtual void setFinalYaw(float finalYaw); virtual void setFinalPitch(float finalPitch); virtual void setFinalRoll(float finalRoll); @@ -100,7 +95,7 @@ public: void addLeanDeltas(float sideways, float forward); float getTimeWithoutTalking() const { return _timeWithoutTalking; } - + private: glm::vec3 calculateAverageEyePosition() const { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * 0.5f; } @@ -114,7 +109,7 @@ private: glm::vec3 _leftEyePosition; glm::vec3 _rightEyePosition; glm::vec3 _eyePosition; - + float _scale; float _lastLoudness; float _longTermAverageLoudness; @@ -125,8 +120,7 @@ private: float _mouth3; float _mouth4; float _mouthTime; - bool _renderLookatVectors; - bool _renderLookatTarget; + glm::vec3 _saccade; glm::vec3 _saccadeTarget; float _leftEyeBlinkVelocity; @@ -146,15 +140,13 @@ private: bool _isLookingAtMe; quint64 _lookingAtMeStarted; quint64 _wasLastLookingAtMe; - + glm::vec3 _correctedLookAtPosition; int _leftEyeLookAtID; int _rightEyeLookAtID; - + // private methods - void renderLookatVectors(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition); - void renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition); void calculateMouthShapes(); void applyEyelidOffset(glm::quat headOrientation); }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bad60643ec..a3cf1f9f4f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -32,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -97,7 +97,6 @@ MyAvatar::MyAvatar(RigPointer rig) : _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), - _collisionSoundURL(""), _characterController(this), _lookAtTargetAvatar(), _shouldRender(true), @@ -546,7 +545,9 @@ void MyAvatar::updateFromTrackers(float deltaTime) { head->setDeltaYaw(estimatedRotation.y); head->setDeltaRoll(estimatedRotation.z); } else { - float magnifyFieldOfView = qApp->getViewFrustum()->getFieldOfView() / _realWorldFieldOfView.get(); + ViewFrustum viewFrustum; + qApp->copyViewFrustum(viewFrustum); + float magnifyFieldOfView = viewFrustum.getFieldOfView() / _realWorldFieldOfView.get(); head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView); head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView); head->setDeltaRoll(estimatedRotation.z); @@ -929,15 +930,17 @@ void MyAvatar::updateLookAtTargetAvatar() { // (We will be adding that offset to the camera position, after making some other adjustments.) glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition(); + ViewFrustum viewFrustum; + qApp->copyViewFrustum(viewFrustum); + // scale gazeOffset by IPD, if wearing an HMD. if (qApp->isHMDMode()) { glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left); glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right); glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); - auto humanSystem = qApp->getViewFrustum(); - glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal); - glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal); + glm::vec3 humanLeftEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * leftEyeHeadLocal); + glm::vec3 humanRightEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * rightEyeHeadLocal); auto hmdInterface = DependencyManager::get(); float ipdScale = hmdInterface->getIPDScale(); @@ -951,7 +954,7 @@ void MyAvatar::updateLookAtTargetAvatar() { } // And now we can finally add that offset to the camera. - glm::vec3 corrected = qApp->getViewFrustum()->getPosition() + gazeOffset; + glm::vec3 corrected = viewFrustum.getPosition() + gazeOffset; avatar->getHead()->setCorrectedLookAtPosition(corrected); @@ -1232,12 +1235,20 @@ void MyAvatar::clearScriptableSettings() { } void MyAvatar::setCollisionSoundURL(const QString& url) { - _collisionSoundURL = url; - if (!url.isEmpty() && (url != _collisionSoundURL)) { - emit newCollisionSoundURL(QUrl(url)); + if (url != _collisionSoundURL) { + _collisionSoundURL = url; + + emit newCollisionSoundURL(QUrl(_collisionSoundURL)); } } +SharedSoundPointer MyAvatar::getCollisionSound() { + if (!_collisionSound) { + _collisionSound = DependencyManager::get()->getSound(_collisionSoundURL); + } + return _collisionSound; +} + void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, const glm::quat& rotation, float scale, bool isSoft, @@ -1259,40 +1270,6 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); } -void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel) { - - if (!_skeletonModel->isRenderable()) { - return; // wait until all models are loaded - } - - fixupModelsInScene(); - - // Render head so long as the camera isn't inside it - if (shouldRenderHead(renderArgs)) { - getHead()->render(renderArgs, 1.0f, renderFrustum); - } - - // This is drawing the lookat vectors from our avatar to wherever we're looking. - if (qApp->isHMDMode()) { - glm::vec3 cameraPosition = qApp->getCamera()->getPosition(); - - glm::mat4 headPose = qApp->getActiveDisplayPlugin()->getHeadPose(); - glm::mat4 leftEyePose = qApp->getActiveDisplayPlugin()->getEyeToHeadTransform(Eye::Left); - leftEyePose = leftEyePose * headPose; - glm::vec3 leftEyePosition = extractTranslation(leftEyePose); - glm::mat4 rightEyePose = qApp->getActiveDisplayPlugin()->getEyeToHeadTransform(Eye::Right); - rightEyePose = rightEyePose * headPose; - glm::vec3 rightEyePosition = extractTranslation(rightEyePose); - glm::vec3 headPosition = extractTranslation(headPose); - - getHead()->renderLookAts(renderArgs, - cameraPosition + getOrientation() * (leftEyePosition - headPosition), - cameraPosition + getOrientation() * (rightEyePosition - headPosition)); - } else { - getHead()->renderLookAts(renderArgs); - } -} - void MyAvatar::setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visible) { if (model->isActive() && model->isRenderable()) { model->setVisibleInScene(visible, scene); @@ -1349,11 +1326,11 @@ void MyAvatar::destroyAnimGraph() { _rig->destroyAnimGraph(); } -void MyAvatar::preRender(RenderArgs* renderArgs) { +void MyAvatar::postUpdate(float deltaTime) { + + Avatar::postUpdate(deltaTime); render::ScenePointer scene = qApp->getMain3DScene(); - const bool shouldDrawHead = shouldRenderHead(renderArgs); - if (_skeletonModel->initWhenReady(scene)) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); @@ -1403,7 +1380,13 @@ void MyAvatar::preRender(RenderArgs* renderArgs) { DebugDraw::getInstance().updateMyAvatarPos(getPosition()); DebugDraw::getInstance().updateMyAvatarRot(getOrientation()); +} + +void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { + + // toggle using the cauterizedBones depending on where the camera is and the rendering pass type. + const bool shouldDrawHead = shouldRenderHead(renderArgs); if (shouldDrawHead != _prevShouldDrawHead) { _skeletonModel->setCauterizeBones(!shouldDrawHead); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index fee1a9add3..66e6af340e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -16,6 +16,7 @@ #include #include +#include #include @@ -95,7 +96,8 @@ public: Q_INVOKABLE void reset(bool andRecenter = false); void update(float deltaTime); - void preRender(RenderArgs* renderArgs); + virtual void postUpdate(float deltaTime) override; + void preDisplaySide(RenderArgs* renderArgs); const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } @@ -222,6 +224,9 @@ public: const QString& getCollisionSoundURL() { return _collisionSoundURL; } void setCollisionSoundURL(const QString& url); + SharedSoundPointer getCollisionSound(); + void setCollisionSound(SharedSoundPointer sound) { _collisionSound = sound; } + void clearScriptableSettings(); float getBoomLength() const { return _boomLength; } @@ -306,7 +311,6 @@ private: void simulate(float deltaTime); void updateFromTrackers(float deltaTime); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPositio) override; - virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f) override; virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override; void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); } bool getShouldRenderLocally() const { return _shouldRender; } @@ -362,6 +366,8 @@ private: quint32 _motionBehaviors; QString _collisionSoundURL; + SharedSoundPointer _collisionSound; + MyCharacterController _characterController; AvatarWeakPointer _lookAtTargetAvatar; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 0fe1a59bb2..5deeb545a1 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "Application.h" #include "Avatar.h" @@ -92,7 +93,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Head* head = _owningAvatar->getHead(); - // make sure lookAt is not too close to face (avoid crosseyes) glm::vec3 lookAt = _owningAvatar->isMyAvatar() ? head->getLookAtPosition() : head->getCorrectedLookAtPosition(); glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition(); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 4b6d50c274..1726d47045 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -203,7 +203,7 @@ int main(int argc, const char* argv[]) { Application::shutdownPlugins(); qCDebug(interfaceapp, "Normal exit."); -#ifndef DEBUG +#if !defined(DEBUG) && !defined(Q_OS_LINUX) // HACK: exit immediately (don't handle shutdown callbacks) for Release build _exit(exitCode); #endif diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index da55a8829f..7eb80b5946 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -11,8 +11,11 @@ #include "MenuScriptingInterface.h" -#include "Menu.h" +#include +#include + #include +#include "Menu.h" MenuScriptingInterface* MenuScriptingInterface::getInstance() { static MenuScriptingInterface sharedInstance; @@ -36,6 +39,9 @@ void MenuScriptingInterface::removeMenu(const QString& menu) { } bool MenuScriptingInterface::menuExists(const QString& menu) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->menuExists(menu); + } bool result; QMetaObject::invokeMethod(Menu::getInstance(), "menuExists", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), @@ -76,11 +82,14 @@ void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& }; bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->menuItemExists(menu, menuitem); + } bool result; QMetaObject::invokeMethod(Menu::getInstance(), "menuItemExists", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, menu), - Q_ARG(const QString&, menuitem)); + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, menu), + Q_ARG(const QString&, menuitem)); return result; } @@ -101,6 +110,9 @@ void MenuScriptingInterface::removeActionGroup(const QString& groupName) { } bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->isOptionChecked(menuOption); + } bool result; QMetaObject::invokeMethod(Menu::getInstance(), "isOptionChecked", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), @@ -109,7 +121,7 @@ bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { } void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool isChecked) { - QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection, + QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Q_ARG(const QString&, menuOption), Q_ARG(bool, isChecked)); } diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 4001e615a1..437e173807 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -31,10 +31,12 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) : _releaseNotes = ""; for (int i = latestVersion; i > currentVersion; i--) { - QString releaseNotes = applicationUpdater.data()->getBuildData()[i]["releaseNotes"]; - releaseNotes.remove("
"); - releaseNotes.remove(QRegExp("^\n+")); - _releaseNotes += "\n" + QString().sprintf("%d", i) + "\n" + releaseNotes + "\n"; + if (applicationUpdater.data()->getBuildData().contains(i)) { + QString releaseNotes = applicationUpdater.data()->getBuildData()[i]["releaseNotes"]; + releaseNotes.remove("
"); + releaseNotes.remove(QRegExp("^\n+")); + _releaseNotes += "\n" + QString().sprintf("%d", i) + "\n" + releaseNotes + "\n"; + } } } diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 39106ad61e..edc27e35f2 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -65,7 +65,7 @@ void Grid3DOverlay::render(RenderArgs* args) { // Get the camera position rounded to the nearest major grid line // This grid is for UI and should lie on worldlines auto cameraPosition = - (float)_majorGridEvery * glm::round(args->_viewFrustum->getPosition() / (float)_majorGridEvery); + (float)_majorGridEvery * glm::round(args->getViewFrustum().getPosition() / (float)_majorGridEvery); position += glm::vec3(cameraPosition.x, 0.0f, cameraPosition.z); } diff --git a/interface/src/ui/overlays/LocalModelsOverlay.cpp b/interface/src/ui/overlays/LocalModelsOverlay.cpp index 38e11562da..ba82ba780a 100644 --- a/interface/src/ui/overlays/LocalModelsOverlay.cpp +++ b/interface/src/ui/overlays/LocalModelsOverlay.cpp @@ -37,10 +37,10 @@ void LocalModelsOverlay::render(RenderArgs* args) { auto batch = args ->_batch; Transform transform = Transform(); - transform.setTranslation(args->_viewFrustum->getPosition() + getPosition()); + transform.setTranslation(args->getViewFrustum().getPosition() + getPosition()); batch->setViewTransform(transform); _entityTreeRenderer->render(args); - transform.setTranslation(args->_viewFrustum->getPosition()); + transform.setTranslation(args->getViewFrustum().getPosition()); batch->setViewTransform(transform); } } diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 9364045857..482c4211cb 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -36,7 +36,7 @@ AnimationPointer AnimationCache::getAnimation(const QUrl& url) { } QSharedPointer AnimationCache::createResource(const QUrl& url, const QSharedPointer& fallback, - bool delayLoad, const void* extra) { + const void* extra) { return QSharedPointer(new Animation(url), &Resource::deleter); } diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 59a4ad0498..9da649e66e 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -35,8 +35,8 @@ public: protected: - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra); private: explicit AnimationCache(QObject* parent = NULL); virtual ~AnimationCache() { } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a7115199a2..67dfbec24a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1057,20 +1057,30 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { } void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) { + + // TODO: does not properly handle avatar scale. + if (isIndexValid(index)) { glm::mat4 rigToWorld = createMatFromQuatAndPos(modelRotation, modelTranslation); glm::mat4 worldToRig = glm::inverse(rigToWorld); - glm::vec3 zAxis = glm::normalize(_internalPoseSet._absolutePoses[index].trans - transformPoint(worldToRig, lookAtSpot)); + glm::vec3 lookAtVector = glm::normalize(transformPoint(worldToRig, lookAtSpot) - _internalPoseSet._absolutePoses[index].trans); - glm::quat desiredQuat = rotationBetween(IDENTITY_FRONT, zAxis); - glm::quat headQuat; int headIndex = indexOfJoint("Head"); + glm::quat headQuat; if (headIndex >= 0) { headQuat = _internalPoseSet._absolutePoses[headIndex].rot; } + + glm::vec3 headUp = headQuat * Vectors::UNIT_Y; + glm::vec3 z, y, x; + generateBasisVectors(lookAtVector, headUp, z, y, x); + glm::mat3 m(glm::cross(y, z), y, z); + glm::quat desiredQuat = glm::normalize(glm::quat_cast(m)); + glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat); - // limit rotation + // limit swing rotation of the deltaQuat by a 30 degree cone. + // TODO: use swing twist decomposition constraint instead, for off axis rotation clamping. const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) { deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index a6515f5f65..878a4c627c 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -365,17 +365,9 @@ void AudioInjector::stopAndDeleteLater() { QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); } -AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position) { - if (soundUrl.isEmpty()) { - return NULL; - } - auto soundCache = DependencyManager::get(); - if (soundCache.isNull()) { - return NULL; - } - SharedSoundPointer sound = soundCache->getSound(QUrl(soundUrl)); - if (sound.isNull() || !sound->isReady()) { - return NULL; +AudioInjector* AudioInjector::playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position) { + if (!sound || !sound->isReady()) { + return nullptr; } AudioInjectorOptions options; @@ -385,7 +377,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol QByteArray samples = sound->getByteArray(); if (stretchFactor == 1.0f) { - return playSoundAndDelete(samples, options, NULL); + return playSoundAndDelete(samples, options, nullptr); } const int standardRate = AudioConstants::SAMPLE_RATE; @@ -403,7 +395,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol nInputFrames); Q_UNUSED(nOutputFrames); - return playSoundAndDelete(resampled, options, NULL); + return playSoundAndDelete(resampled, options, nullptr); } AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 2dad2856b9..c90256429d 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -60,7 +60,7 @@ public: static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); - static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); + static AudioInjector* playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position); public slots: void restart(); diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 2752c6669f..96a2cee204 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -35,7 +35,7 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) { } QSharedPointer SoundCache::createResource(const QUrl& url, const QSharedPointer& fallback, - bool delayLoad, const void* extra) { + const void* extra) { qCDebug(audio) << "Requesting sound at" << url.toString(); return QSharedPointer(new Sound(url), &Resource::deleter); } diff --git a/libraries/audio/src/SoundCache.h b/libraries/audio/src/SoundCache.h index 7942e16010..59a25dd847 100644 --- a/libraries/audio/src/SoundCache.h +++ b/libraries/audio/src/SoundCache.h @@ -25,8 +25,8 @@ public: Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra); private: SoundCache(QObject* parent = NULL); }; diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 2b7d837aa5..c0d3ff40c0 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -58,7 +58,7 @@ controller::UserInputMapper::UserInputMapper() { namespace controller { - + UserInputMapper::~UserInputMapper() { } @@ -80,6 +80,7 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { recordDeviceOfType(device->getName()); qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID; + for (const auto& inputMapping : device->getAvailableInputs()) { const auto& input = inputMapping.first; // Ignore aliases @@ -102,6 +103,7 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { } _registeredDevices[deviceID] = device; + auto mapping = loadMappings(device->getDefaultMappingConfigs()); if (mapping) { _mappingsByDevice[deviceID] = mapping; @@ -111,15 +113,21 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { emit hardwareChanged(); } -// FIXME remove the associated device mappings void UserInputMapper::removeDevice(int deviceID) { + Locker locker(_lock); auto proxyEntry = _registeredDevices.find(deviceID); + if (_registeredDevices.end() == proxyEntry) { qCWarning(controllers) << "Attempted to remove unknown device " << deviceID; return; } - auto proxy = proxyEntry->second; + + auto device = proxyEntry->second; + qCDebug(controllers) << "Unregistering input device <" << device->getName() << "> deviceID = " << deviceID; + + unloadMappings(device->getDefaultMappingConfigs()); + auto mappingsEntry = _mappingsByDevice.find(deviceID); if (_mappingsByDevice.end() != mappingsEntry) { disableMapping(mappingsEntry->second); @@ -244,7 +252,7 @@ void UserInputMapper::update(float deltaTime) { for (auto& channel : _actionStates) { channel = 0.0f; } - + for (auto& channel : _poseStates) { channel = Pose(); } @@ -705,11 +713,10 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile, bool enab return Mapping::Pointer(); } // Each mapping only needs to be loaded once - static QSet loaded; - if (loaded.contains(jsonFile)) { + if (_loadedRouteJsonFiles.contains(jsonFile)) { return Mapping::Pointer(); } - loaded.insert(jsonFile); + _loadedRouteJsonFiles.insert(jsonFile); QString json; { QFile file(jsonFile); @@ -741,6 +748,18 @@ MappingPointer UserInputMapper::loadMappings(const QStringList& jsonFiles) { return result; } +void UserInputMapper::unloadMappings(const QStringList& jsonFiles) { + for (const QString& jsonFile : jsonFiles) { + unloadMapping(jsonFile); + } +} + +void UserInputMapper::unloadMapping(const QString& jsonFile) { + auto entry = _loadedRouteJsonFiles.find(jsonFile); + if (entry != _loadedRouteJsonFiles.end()) { + _loadedRouteJsonFiles.erase(entry); + } +} static const QString JSON_NAME = QStringLiteral("name"); static const QString JSON_CHANNELS = QStringLiteral("channels"); diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 95cc629c73..9c79415b6e 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -111,9 +111,18 @@ namespace controller { void loadDefaultMapping(uint16 deviceID); void enableMapping(const QString& mappingName, bool enable = true); + + void unloadMappings(const QStringList& jsonFiles); + void unloadMapping(const QString& jsonFile); + float getValue(const Input& input) const; Pose getPose(const Input& input) const; + // perform an action when the UserInputMapper mutex is acquired. + using Locker = std::unique_lock; + template + void withLock(F&& f) { Locker locker(_lock); f(); } + signals: void actionEvent(int action, float state); void inputEvent(int input, float state); @@ -177,7 +186,7 @@ namespace controller { RouteList _deviceRoutes; RouteList _standardRoutes; - using Locker = std::unique_lock; + QSet _loadedRouteJsonFiles; mutable std::recursive_mutex _lock; }; diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp index 809308eeab..f07ef25976 100644 --- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp @@ -13,11 +13,12 @@ using namespace controller; float DeadZoneFilter::apply(float value) const { - float scale = 1.0f / (1.0f - _min); - if (std::abs(value) < _min) { + float scale = ((value < 0.0f) ? -1.0f : 1.0f) / (1.0f - _min); + float magnitude = std::abs(value); + if (magnitude < _min) { return 0.0f; } - return (value - _min) * scale; + return (magnitude - _min) * scale; } bool DeadZoneFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 4648fc8957..f9d527de8f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include @@ -256,7 +258,7 @@ glm::vec2 CompositorHelper::getReticlePosition() const { QMutexLocker locker(&_reticleLock); return _reticlePositionInHMD; } - return toGlm(QCursor::pos()); + return toGlm(_renderingWidget->mapFromGlobal(QCursor::pos())); } bool CompositorHelper::getReticleOverDesktop() const { @@ -322,17 +324,8 @@ void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFa sendFakeMouseEvent(); } } else { - // NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies, - // remove it after we're done - const float REASONABLE_CHANGE = 50.0f; - glm::vec2 oldPos = toGlm(QCursor::pos()); - auto distance = glm::distance(oldPos, position); - if (distance > REASONABLE_CHANGE) { - qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" << - distance << " oldPos:" << oldPos.x << "," << oldPos.y << " newPos:" << position.x << "," << position.y; - } - - QCursor::setPos(position.x, position.y); + const QPoint point(position.x, position.y); + QCursor::setPos(_renderingWidget->mapToGlobal(point)); } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 7d4eebb7a2..a0b6ae3939 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -237,12 +237,6 @@ bool OpenGLDisplayPlugin::activate() { _vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported(); - // Child classes may override this in order to do things like initialize - // libraries, etc - if (!internalActivate()) { - return false; - } - #if THREADED_PRESENT // Start the present thread if necessary @@ -258,8 +252,18 @@ bool OpenGLDisplayPlugin::activate() { // Start execution presentThread->start(); } + _presentThread = presentThread.data(); +#endif + + // Child classes may override this in order to do things like initialize + // libraries, etc + if (!internalActivate()) { + return false; + } - // This should not return until the new context has been customized +#if THREADED_PRESENT + + // This should not return until the new context has been customized // and the old context (if any) has been uncustomized presentThread->setNewDisplayPlugin(this); #else diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index defa57fc58..c87ff1bc93 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -103,6 +103,7 @@ protected: virtual void updateFrameData(); + QThread* _presentThread{ nullptr }; ProgramPtr _program; int32_t _mvpUniform { -1 }; int32_t _alphaUniform { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 79b50a7f88..372337ae4d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -63,7 +63,7 @@ bool HmdDisplayPlugin::internalActivate() { } -static const char * REPROJECTION_VS = R"VS(#version 450 core +static const char * REPROJECTION_VS = R"VS(#version 410 core in vec3 Position; in vec2 TexCoord; @@ -78,15 +78,15 @@ void main() { )VS"; -static const GLint REPROJECTION_MATRIX_LOCATION = 0; -static const GLint INVERSE_PROJECTION_MATRIX_LOCATION = 4; -static const GLint PROJECTION_MATRIX_LOCATION = 12; -static const char * REPROJECTION_FS = R"FS(#version 450 core +static GLint REPROJECTION_MATRIX_LOCATION = -1; +static GLint INVERSE_PROJECTION_MATRIX_LOCATION = -1; +static GLint PROJECTION_MATRIX_LOCATION = -1; +static const char * REPROJECTION_FS = R"FS(#version 410 core uniform sampler2D sampler; -layout (location = 0) uniform mat3 reprojection = mat3(1); -layout (location = 4) uniform mat4 inverseProjections[2]; -layout (location = 12) uniform mat4 projections[2]; +uniform mat3 reprojection = mat3(1); +uniform mat4 inverseProjections[2]; +uniform mat4 projections[2]; in vec2 vTexCoord; in vec3 vPosition; @@ -205,6 +205,11 @@ void HmdDisplayPlugin::customizeContext() { _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); + + using namespace oglplus; + REPROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "reprojection").Location(); + INVERSE_PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "inverseProjections").Location(); + PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "projections").Location(); } void HmdDisplayPlugin::uncustomizeContext() { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 013385a169..814faa8874 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -820,14 +820,14 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT return; } - QString collisionSoundURL; + SharedSoundPointer collisionSound; float mass = 1.0; // value doesn't get used, but set it so compiler is quiet AACube minAACube; bool success = false; _tree->withReadLock([&] { EntityItemPointer entity = entityTree->findEntityByEntityItemID(id); if (entity) { - collisionSoundURL = entity->getCollisionSoundURL(); + collisionSound = entity->getCollisionSound(); mass = entity->computeMass(); minAACube = entity->getMinimumAACube(success); } @@ -835,9 +835,10 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT if (!success) { return; } - if (collisionSoundURL.isEmpty()) { + if (!collisionSound) { return; } + const float COLLISION_PENETRATION_TO_VELOCITY = 50; // as a subsitute for RELATIVE entity->getVelocity() // The collision.penetration is a pretty good indicator of changed velocity AFTER the initial contact, // but that first contact depends on exactly where we hit in the physics step. @@ -859,11 +860,10 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT const float COLLISION_SOUND_COMPRESSION_RANGE = 1.0f; // This section could be removed when the value is 1, but let's see how it goes. const float volume = (energyFactorOfFull * COLLISION_SOUND_COMPRESSION_RANGE) + (1.0f - COLLISION_SOUND_COMPRESSION_RANGE); - // Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2) const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f; const float stretchFactor = log(1.0f + (minAACube.getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); - AudioInjector::playSound(collisionSoundURL, volume, stretchFactor, position); + AudioInjector::playSound(collisionSound, volume, stretchFactor, position); } void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 6c4e3994c6..1d5570b8b7 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -977,8 +977,8 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { properties.setVoxelDataDirty(); properties.setLastEdited(now); - EntitySimulation* simulation = tree ? tree->getSimulation() : nullptr; - PhysicalEntitySimulation* peSimulation = static_cast(simulation); + EntitySimulationPointer simulation = tree ? tree->getSimulation() : nullptr; + PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast(simulation); EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; if (packetSender) { packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 5cf31c15e0..6773a906fe 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -51,7 +51,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) { } if (getFaceCamera()) { //rotate about vertical to face the camera - glm::vec3 dPosition = args->_viewFrustum->getPosition() - getPosition(); + glm::vec3 dPosition = args->getViewFrustum().getPosition() - getPosition(); // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 26aecf6050..891e1dca3b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -174,9 +174,14 @@ void RenderableWebEntityItem::render(RenderArgs* args) { #endif if (!_webSurface) { + #if defined(Q_OS_LINUX) + // these don't seem to work on Linux + return; + #else if (!buildWebSurface(static_cast(args->_renderer))) { return; } + #endif } _lastRenderTime = usecTimestampNow(); diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index c634326a31..9a881cf94c 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -20,6 +20,8 @@ class EntityItem; class EntitySimulation; using EntityItemPointer = std::shared_ptr; using EntityItemWeakPointer = std::weak_ptr; +class EntitySimulation; +using EntitySimulationPointer = std::shared_ptr; enum EntityActionType { // keep these synchronized with actionTypeFromString and actionTypeToString @@ -39,7 +41,7 @@ public: bool isActive() { return _active; } - virtual void removeFromSimulation(EntitySimulation* simulation) const = 0; + virtual void removeFromSimulation(EntitySimulationPointer simulation) const = 0; virtual EntityItemWeakPointer getOwnerEntity() const = 0; virtual void setOwnerEntity(const EntityItemPointer ownerEntity) = 0; virtual bool updateArguments(QVariantMap arguments) = 0; @@ -57,8 +59,6 @@ public: virtual bool isMine() { return _isMine; } virtual void setIsMine(bool value) { _isMine = value; } - bool locallyAddedButNotYetReceived = false; - virtual bool shouldSuppressLocationEdits() { return false; } virtual void prepareForPhysicsSimulation() { } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bdedbbce77..187c4f51be 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -89,7 +89,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : EntityItem::~EntityItem() { // clear out any left-over actions EntityTreePointer entityTree = _element ? _element->getTree() : nullptr; - EntitySimulation* simulation = entityTree ? entityTree->getSimulation() : nullptr; + EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; if (simulation) { clearActions(simulation); } @@ -726,8 +726,13 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setActionData); - READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID); - READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); + { // parentID and parentJointIndex are also protected by simulation ownership + bool oldOverwrite = overwriteLocalData; + overwriteLocalData = overwriteLocalData && !weOwnSimulation; + READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID); + READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); + overwriteLocalData = oldOverwrite; + } READ_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); @@ -852,6 +857,23 @@ void EntityItem::setHref(QString value) { _href = value; } +void EntityItem::setCollisionSoundURL(const QString& value) { + if (_collisionSoundURL != value) { + _collisionSoundURL = value; + + if (auto myTree = getTree()) { + myTree->notifyNewCollisionSoundURL(_collisionSoundURL, getEntityItemID()); + } + } +} + +SharedSoundPointer EntityItem::getCollisionSound() { + if (!_collisionSound) { + _collisionSound = DependencyManager::get()->getSound(_collisionSoundURL); + } + return _collisionSound; +} + void EntityItem::simulate(const quint64& now) { if (_lastSimulated == 0) { _lastSimulated = now; @@ -1700,23 +1722,38 @@ void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& tim _simulationOwner.setPendingPriority(priority, timestamp); } -bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) { +QString EntityItem::actionsToDebugString() { + QString result; + QVector serializedActions; + QHash::const_iterator i = _objectActions.begin(); + while (i != _objectActions.end()) { + const QUuid id = i.key(); + EntityActionPointer action = _objectActions[id]; + EntityActionType actionType = action->getType(); + result += QString("") + actionType + ":" + action->getID().toString() + " "; + i++; + } + return result; +} + +bool EntityItem::addAction(EntitySimulationPointer simulation, EntityActionPointer action) { bool result; withWriteLock([&] { checkWaitingToRemove(simulation); result = addActionInternal(simulation, action); - if (!result) { - removeActionInternal(action->getID()); + if (result) { + action->setIsMine(true); + _actionDataDirty = true; } else { - action->locallyAddedButNotYetReceived = true; + removeActionInternal(action->getID()); } }); return result; } -bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPointer action) { +bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityActionPointer action) { assert(action); assert(simulation); auto actionOwnerEntity = action->getOwnerEntity().lock(); @@ -1740,7 +1777,7 @@ bool EntityItem::addActionInternal(EntitySimulation* simulation, EntityActionPoi return success; } -bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments) { +bool EntityItem::updateAction(EntitySimulationPointer simulation, const QUuid& actionID, const QVariantMap& arguments) { bool success = false; withWriteLock([&] { checkWaitingToRemove(simulation); @@ -1763,7 +1800,7 @@ bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionI return success; } -bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionID) { +bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& actionID) { bool success = false; withWriteLock([&] { checkWaitingToRemove(simulation); @@ -1772,7 +1809,7 @@ bool EntityItem::removeAction(EntitySimulation* simulation, const QUuid& actionI return success; } -bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* simulation) { +bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) { _previouslyDeletedActions.insert(actionID, usecTimestampNow()); if (_objectActions.contains(actionID)) { if (!simulation) { @@ -1783,6 +1820,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* s EntityActionPointer action = _objectActions[actionID]; action->setOwnerEntity(nullptr); + action->setIsMine(false); _objectActions.remove(actionID); if (simulation) { @@ -1798,7 +1836,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulation* s return false; } -bool EntityItem::clearActions(EntitySimulation* simulation) { +bool EntityItem::clearActions(EntitySimulationPointer simulation) { withWriteLock([&] { QHash::iterator i = _objectActions.begin(); while (i != _objectActions.end()) { @@ -1834,7 +1872,7 @@ void EntityItem::deserializeActionsInternal() { EntityTreePointer entityTree = getTree(); assert(entityTree); - EntitySimulation* simulation = entityTree ? entityTree->getSimulation() : nullptr; + EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; assert(simulation); QVector serializedActions; @@ -1863,7 +1901,6 @@ void EntityItem::deserializeActionsInternal() { if (!action->isMine()) { action->deserialize(serializedAction); } - action->locallyAddedButNotYetReceived = false; updated << actionID; } else { auto actionFactory = DependencyManager::get(); @@ -1871,7 +1908,6 @@ void EntityItem::deserializeActionsInternal() { EntityActionPointer action = actionFactory->factoryBA(entity, serializedAction); if (action) { entity->addActionInternal(simulation, action); - action->locallyAddedButNotYetReceived = false; updated << actionID; } else { static QString repeatedMessage = @@ -1889,8 +1925,12 @@ void EntityItem::deserializeActionsInternal() { QUuid id = i.key(); if (!updated.contains(id)) { EntityActionPointer action = i.value(); - // if we've just added this action, don't remove it due to lack of mention in an incoming packet. - if (! action->locallyAddedButNotYetReceived) { + + if (action->isMine()) { + // we just received an update that didn't include one of our actions. tell the server about it (again). + setActionDataNeedsTransmit(true); + } else { + // don't let someone else delete my action. _actionsToRemove << id; _previouslyDeletedActions.insert(id, now); } @@ -1912,7 +1952,7 @@ void EntityItem::deserializeActionsInternal() { return; } -void EntityItem::checkWaitingToRemove(EntitySimulation* simulation) { +void EntityItem::checkWaitingToRemove(EntitySimulationPointer simulation) { foreach(QUuid actionID, _actionsToRemove) { removeActionInternal(actionID, simulation); } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ecb9800e70..8868830c8a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "EntityItemID.h" @@ -250,7 +251,10 @@ public: void setScriptTimestamp(const quint64 value) { _scriptTimestamp = value; } const QString& getCollisionSoundURL() const { return _collisionSoundURL; } - void setCollisionSoundURL(const QString& value) { _collisionSoundURL = value; } + void setCollisionSoundURL(const QString& value); + + SharedSoundPointer getCollisionSound(); + void setCollisionSound(SharedSoundPointer sound) { _collisionSound = sound; } const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity @@ -378,10 +382,11 @@ public: void grabSimulationOwnership(); void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; } - bool addAction(EntitySimulation* simulation, EntityActionPointer action); - bool updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments); - bool removeAction(EntitySimulation* simulation, const QUuid& actionID); - bool clearActions(EntitySimulation* simulation); + QString actionsToDebugString(); + bool addAction(EntitySimulationPointer simulation, EntityActionPointer action); + bool updateAction(EntitySimulationPointer simulation, const QUuid& actionID, const QVariantMap& arguments); + bool removeAction(EntitySimulationPointer simulation, const QUuid& actionID); + bool clearActions(EntitySimulationPointer simulation); void setActionData(QByteArray actionData); const QByteArray getActionData() const; bool hasActions() const { return !_objectActions.empty(); } @@ -478,6 +483,7 @@ protected: quint64 _loadedScriptTimestamp{ ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP + 1 }; QString _collisionSoundURL; + SharedSoundPointer _collisionSound; glm::vec3 _registrationPoint; float _angularDamping; bool _visible; @@ -516,8 +522,8 @@ protected: void* _physicsInfo = nullptr; // set by EntitySimulation bool _simulated; // set by EntitySimulation - bool addActionInternal(EntitySimulation* simulation, EntityActionPointer action); - bool removeActionInternal(const QUuid& actionID, EntitySimulation* simulation = nullptr); + bool addActionInternal(EntitySimulationPointer simulation, EntityActionPointer action); + bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr); void deserializeActionsInternal(); void serializeActions(bool& success, QByteArray& result) const; QHash _objectActions; @@ -528,7 +534,7 @@ protected: // when an entity-server starts up, EntityItem::setActionData is called before the entity-tree is // ready. This means we can't find our EntityItemPointer or add the action to the simulation. These // are used to keep track of and work around this situation. - void checkWaitingToRemove(EntitySimulation* simulation = nullptr); + void checkWaitingToRemove(EntitySimulationPointer simulation = nullptr); mutable QSet _actionsToRemove; mutable bool _actionDataDirty = false; mutable bool _actionDataNeedsTransmit = false; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 92849d6e2f..738e8910fe 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1615,7 +1615,7 @@ void EntityItemProperties::setSimulationOwner(const QByteArray& data) { QList EntityItemProperties::listChangedProperties() { QList out; if (containsPositionChange()) { - out += "posistion"; + out += "position"; } if (dimensionsChanged()) { out += "dimensions"; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 093fa73ace..01f444491d 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -766,7 +766,7 @@ bool EntityScriptingInterface::appendPoint(QUuid entityID, const glm::vec3& poin bool EntityScriptingInterface::actionWorker(const QUuid& entityID, - std::function actor) { + std::function actor) { if (!_entityTree) { return false; } @@ -774,7 +774,7 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, EntityItemPointer entity; bool doTransmit = false; _entityTree->withWriteLock([&] { - EntitySimulation* simulation = _entityTree->getSimulation(); + EntitySimulationPointer simulation = _entityTree->getSimulation(); entity = _entityTree->findEntityByEntityItemID(entityID); if (!entity) { qDebug() << "actionWorker -- unknown entity" << entityID; @@ -815,7 +815,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, QUuid actionID = QUuid::createUuid(); auto actionFactory = DependencyManager::get(); bool success = false; - actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { // create this action even if the entity doesn't have physics info. it will often be the // case that a script adds an action immediately after an object is created, and the physicsInfo // is computed asynchronously. @@ -843,7 +843,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments) { - return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { bool success = entity->updateAction(simulation, actionID, arguments); if (success) { entity->grabSimulationOwnership(); @@ -854,7 +854,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) { bool success = false; - actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { success = entity->removeAction(simulation, actionID); if (success) { // reduce from grab to poke @@ -867,7 +867,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& QVector EntityScriptingInterface::getActionIDs(const QUuid& entityID) { QVector result; - actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { QList actionIDs = entity->getActionIDs(); result = QVector::fromList(actionIDs); return false; // don't send an edit packet @@ -877,7 +877,7 @@ QVector EntityScriptingInterface::getActionIDs(const QUuid& entityID) { QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID, const QUuid& actionID) { QVariantMap result; - actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { result = entity->getActionArguments(actionID); return false; // don't send an edit packet }); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index e5f913dbf8..5f80b7abb2 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -200,11 +200,11 @@ signals: void debitEnergySource(float value); private: - bool actionWorker(const QUuid& entityID, std::function actor); + bool actionWorker(const QUuid& entityID, std::function actor); bool setVoxels(QUuid entityID, std::function actor); bool setPoints(QUuid entityID, std::function actor); void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); - + EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID, EntityTypes::EntityType entityType = EntityTypes::Unknown); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 524f52e815..893ae83cc5 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -64,7 +64,7 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { assert(entity->isDead()); if (entity->isSimulated()) { QMutexLocker lock(&_mutex); - entity->clearActions(this); + entity->clearActions(getThisPointer()); removeEntityInternal(entity); _entitiesToDelete.insert(entity); } diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index cbedbbd868..f8f506ac70 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -22,8 +22,9 @@ #include "EntityItem.h" #include "EntityTree.h" -typedef QSet SetOfEntities; -typedef QVector VectorOfEntities; +using EntitySimulationPointer = std::shared_ptr; +using SetOfEntities = QSet; +using VectorOfEntities = QVector; // the EntitySimulation needs to know when these things change on an entity, // so it can sort EntityItem or relay its state to the PhysicsEngine. @@ -41,12 +42,16 @@ const int DIRTY_SIMULATION_FLAGS = Simulation::DIRTY_MATERIAL | Simulation::DIRTY_SIMULATOR_ID; -class EntitySimulation : public QObject { +class EntitySimulation : public QObject, public std::enable_shared_from_this { Q_OBJECT public: EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { } virtual ~EntitySimulation() { setEntityTree(NULL); } + inline EntitySimulationPointer getThisPointer() const { + return std::const_pointer_cast(shared_from_this()); + } + /// \param tree pointer to EntityTree which is stored internally void setEntityTree(EntityTreePointer tree); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b4f0c484d5..30c37d46b1 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -95,7 +95,6 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { } _isDirty = true; - maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); emit addingEntity(entity->getEntityItemID()); // find and hook up any entities with this entity as a (previously) missing parent @@ -213,6 +212,8 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI properties.setVelocityChanged(false); properties.setAngularVelocityChanged(false); properties.setAccelerationChanged(false); + properties.setParentIDChanged(false); + properties.setParentJointIndexChanged(false); if (wantTerseEditLogging()) { qCDebug(entities) << (senderNode ? senderNode->getUUID() : "null") << "physical edits suppressed"; @@ -223,7 +224,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI QString entityScriptBefore = entity->getScript(); quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); - QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); AACube newQueryAACube; @@ -295,7 +295,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI if (entityScriptBefore != entityScriptAfter || reload) { emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed } - maybeNotifyNewCollisionSoundURL(collisionSoundURLBefore, entity->getCollisionSoundURL()); } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). @@ -362,13 +361,11 @@ void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID, cons emit entityScriptChanging(entityItemID, reload); } -void EntityTree::maybeNotifyNewCollisionSoundURL(const QString& previousCollisionSoundURL, const QString& nextCollisionSoundURL) { - if (!nextCollisionSoundURL.isEmpty() && (nextCollisionSoundURL != previousCollisionSoundURL)) { - emit newCollisionSoundURL(QUrl(nextCollisionSoundURL)); - } +void EntityTree::notifyNewCollisionSoundURL(const QString& newURL, const EntityItemID& entityID) { + emit newCollisionSoundURL(QUrl(newURL), entityID); } -void EntityTree::setSimulation(EntitySimulation* simulation) { +void EntityTree::setSimulation(EntitySimulationPointer simulation) { this->withWriteLock([&] { if (simulation) { // assert that the simulation's backpointer has already been properly connected @@ -848,6 +845,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList _entityToElementMap; - EntitySimulation* _simulation; + EntitySimulationPointer _simulation; bool _wantEditLogging = false; bool _wantTerseEditLogging = false; - void maybeNotifyNewCollisionSoundURL(const QString& oldCollisionSoundURL, const QString& newCollisionSoundURL); // some performance tracking properties - only used in server trees diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 8270dc7e69..37a0f36d2f 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "EntitiesLogging.h" #include "EntityItemProperties.h" @@ -34,8 +35,6 @@ OctreeElementPointer EntityTreeElement::createNewElement(unsigned char* octalCod return newChild; } - - void EntityTreeElement::init(unsigned char* octalCode) { OctreeElement::init(octalCode); _octreeMemoryUsage += sizeof(EntityTreeElement); @@ -297,7 +296,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID()); } - if (includeThisEntity && params.viewFrustum) { + if (includeThisEntity || params.recurseEverything) { // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of // simulation changing what's visible. consider the case where the entity contains an angular velocity @@ -305,7 +304,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // frustum culling on rendering. bool success; AACube entityCube = entity->getQueryAACube(success); - if (!success || !params.viewFrustum->cubeIntersectsKeyhole(entityCube)) { + if (!success || !params.viewFrustum.cubeIntersectsKeyhole(entityCube)) { includeThisEntity = false; // out of view, don't include it } else { // Check the size of the entity, it's possible that a "too small to see" entity is included in a @@ -322,9 +321,10 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // AABox. If this happens, fall back to the queryAACube. entityBounds = AABox(entityCube); } - auto renderAccuracy = params.viewFrustum->calculateRenderAccuracy(entityBounds, - params.octreeElementSizeScale, - params.boundaryLevelAdjust); + auto renderAccuracy = calculateRenderAccuracy(params.viewFrustum.getPosition(), + entityBounds, + params.octreeElementSizeScale, + params.boundaryLevelAdjust); if (renderAccuracy <= 0.0f) { includeThisEntity = false; // too small, don't include it diff --git a/libraries/entities/src/EntityTreeHeadlessViewer.cpp b/libraries/entities/src/EntityTreeHeadlessViewer.cpp index 149f92adad..81c42cda1e 100644 --- a/libraries/entities/src/EntityTreeHeadlessViewer.cpp +++ b/libraries/entities/src/EntityTreeHeadlessViewer.cpp @@ -22,7 +22,7 @@ EntityTreeHeadlessViewer::~EntityTreeHeadlessViewer() { void EntityTreeHeadlessViewer::init() { OctreeHeadlessViewer::init(); if (!_simulation) { - SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation(); + SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; EntityTreePointer entityTree = std::static_pointer_cast(_tree); simpleSimulation->setEntityTree(entityTree); entityTree->setSimulation(simpleSimulation); diff --git a/libraries/entities/src/EntityTreeHeadlessViewer.h b/libraries/entities/src/EntityTreeHeadlessViewer.h index 40e428b655..0e0d4f9726 100644 --- a/libraries/entities/src/EntityTreeHeadlessViewer.h +++ b/libraries/entities/src/EntityTreeHeadlessViewer.h @@ -49,7 +49,7 @@ protected: return newTree; } - EntitySimulation* _simulation; + EntitySimulationPointer _simulation; }; #endif // hifi_EntityTreeHeadlessViewer_h diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 12ded8a30d..9c7c9a374e 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -14,6 +14,10 @@ #include "EntitySimulation.h" +class SimpleEntitySimulation; +using SimpleEntitySimulationPointer = std::shared_ptr; + + /// provides simple velocity + gravity extrapolation of EntityItem's class SimpleEntitySimulation : public EntitySimulation { diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 185fdaf7f4..8c9a1ba113 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -17,8 +17,15 @@ QOpenGLContext* QOpenGLContextWrapper::currentContext() { return QOpenGLContext::currentContext(); } + QOpenGLContextWrapper::QOpenGLContextWrapper() : - _context(new QOpenGLContext) +_context(new QOpenGLContext) +{ +} + + +QOpenGLContextWrapper::QOpenGLContextWrapper(QOpenGLContext* context) : + _context(context) { } @@ -50,3 +57,7 @@ bool isCurrentContext(QOpenGLContext* context) { return QOpenGLContext::currentContext() == context; } +void QOpenGLContextWrapper::moveToThread(QThread* thread) { + _context->moveToThread(thread); +} + diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index 09f1d67280..33cc0b25e9 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -15,16 +15,19 @@ class QOpenGLContext; class QSurface; class QSurfaceFormat; +class QThread; class QOpenGLContextWrapper { public: QOpenGLContextWrapper(); + QOpenGLContextWrapper(QOpenGLContext* context); void setFormat(const QSurfaceFormat& format); bool create(); void swapBuffers(QSurface* surface); bool makeCurrent(QSurface* surface); void doneCurrent(); void setShareContext(QOpenGLContext* otherContext); + void moveToThread(QThread* thread); static QOpenGLContext* currentContext(); diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 0bf398cdec..4c0240eaf7 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -20,8 +20,12 @@ const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; -void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); +void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + }); // For touch event, we need to check that the last event is not too long ago // Maybe it's a Qt issue, but the touch event sequence (begin, update, end) is not always called properly diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 24834967d6..8cd6d9b65e 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -71,7 +71,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { GeometryExtra extra{ mapping, _textureBaseUrl }; // Get the raw GeometryResource, not the wrapped NetworkGeometry - _geometryResource = modelCache->getResource(url, QUrl(), false, &extra).staticCast(); + _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); // Avoid caching nested resources - their references will be held by the parent _geometryResource->_isCacheable = false; @@ -236,7 +236,7 @@ ModelCache::ModelCache() { } QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, - bool delayLoad, const void* extra) { + const void* extra) { Resource* resource = nullptr; if (url.path().toLower().endsWith(".fst")) { resource = new GeometryMappingResource(url); @@ -252,7 +252,7 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar std::shared_ptr ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { GeometryExtra geometryExtra = { mapping, textureBaseUrl }; - GeometryResource::Pointer resource = getResource(url, QUrl(), true, &geometryExtra).staticCast(); + GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index bf47f293e8..f15e1106e2 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -44,8 +44,8 @@ public: protected: friend class GeometryMappingResource; - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra); private: ModelCache(); diff --git a/libraries/model-networking/src/model-networking/ShaderCache.cpp b/libraries/model-networking/src/model-networking/ShaderCache.cpp index 3e14a99f87..bf7ade07f7 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.cpp +++ b/libraries/model-networking/src/model-networking/ShaderCache.cpp @@ -7,11 +7,8 @@ // #include "ShaderCache.h" -NetworkShader::NetworkShader(const QUrl& url, bool delayLoad) - : Resource(url, delayLoad) -{ - -} +NetworkShader::NetworkShader(const QUrl& url) : + Resource(url) {} void NetworkShader::downloadFinished(const QByteArray& data) { _source = QString::fromUtf8(data); @@ -24,10 +21,11 @@ ShaderCache& ShaderCache::instance() { } NetworkShaderPointer ShaderCache::getShader(const QUrl& url) { - return ResourceCache::getResource(url, QUrl(), false, nullptr).staticCast(); + return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); } -QSharedPointer ShaderCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkShader(url, delayLoad), &Resource::deleter); +QSharedPointer ShaderCache::createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra) { + return QSharedPointer(new NetworkShader(url), &Resource::deleter); } diff --git a/libraries/model-networking/src/model-networking/ShaderCache.h b/libraries/model-networking/src/model-networking/ShaderCache.h index ffe8d437ce..4af12fdbfa 100644 --- a/libraries/model-networking/src/model-networking/ShaderCache.h +++ b/libraries/model-networking/src/model-networking/ShaderCache.h @@ -13,7 +13,7 @@ class NetworkShader : public Resource { public: - NetworkShader(const QUrl& url, bool delayLoad); + NetworkShader(const QUrl& url); virtual void downloadFinished(const QByteArray& data) override; QString _source; @@ -28,7 +28,8 @@ public: NetworkShaderPointer getShader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra) override; }; #endif diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 2aaddace88..000ca67989 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -167,7 +167,7 @@ ScriptableResource* TextureCache::prefetch(const QUrl& url, int type) { NetworkTexturePointer TextureCache::getTexture(const QUrl& url, Type type, const QByteArray& content) { TextureExtra extra = { type, content }; - return ResourceCache::getResource(url, QUrl(), content.isEmpty(), &extra).staticCast(); + return ResourceCache::getResource(url, QUrl(), &extra).staticCast(); } @@ -231,8 +231,8 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString())); } -QSharedPointer TextureCache::createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra) { +QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra) { const TextureExtra* textureExtra = static_cast(extra); auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE; auto content = textureExtra ? textureExtra->content : QByteArray(); @@ -241,7 +241,7 @@ QSharedPointer TextureCache::createResource(const QUrl& url, } NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& content) : - Resource(url, !content.isEmpty()), + Resource(url), _type(type) { _textureSource = std::make_shared(); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 8fd0b12369..f3c6a86b49 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -131,8 +131,8 @@ protected: // Overload ResourceCache::prefetch to allow specifying texture type for loads Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type); - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra); private: TextureCache(); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 03b33eae38..757e145c9a 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -629,3 +629,32 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { } } } + +void AddressManager::ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, + std::function localSandboxNotRunningDoThat) { + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); + sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(sandboxStatus); + + connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() { + auto statusData = reply->readAll(); + auto statusJson = QJsonDocument::fromJson(statusData); + if (!statusJson.isEmpty()) { + auto statusObject = statusJson.object(); + auto serversValue = statusObject.value("servers"); + if (!serversValue.isUndefined() && serversValue.isObject()) { + auto serversObject = serversValue.toObject(); + auto serversCount = serversObject.size(); + const int MINIMUM_EXPECTED_SERVER_COUNT = 5; + if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) { + localSandboxRunningDoThis(); + return; + } + } + } + localSandboxNotRunningDoThat(); + }); +} + diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index e0b54e4072..c0ba69018c 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -24,6 +24,8 @@ const QString HIFI_URL_SCHEME = "hifi"; const QString DEFAULT_HIFI_ADDRESS = "hifi://entry"; +const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; +const QString SANDBOX_STATUS_URL = "http://localhost:60332/status"; const QString INDEX_PATH = "/"; const QString GET_PLACE = "/api/v1/places/%1"; @@ -65,6 +67,11 @@ public: const QStack& getBackStack() const { return _backStack; } const QStack& getForwardStack() const { return _forwardStack; } + /// determines if the local sandbox is likely running. It does not account for custom setups, and is only + /// intended to detect the standard local sandbox install. + void ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, + std::function localSandboxNotRunningDoThat); + public slots: void handleLookupString(const QString& lookupString); @@ -74,6 +81,8 @@ public slots: void goBack(); void goForward(); + void goToLocalSandbox(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS, trigger); } + void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); } void goToUser(const QString& username); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index a744236099..36828b3992 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -138,17 +138,17 @@ void ScriptableResource::setInScript(bool isInScript) { } void ScriptableResource::loadingChanged() { - emit stateChanged(LOADING); + setState(LOADING); } void ScriptableResource::loadedChanged() { - emit stateChanged(LOADED); + setState(LOADED); } void ScriptableResource::finished(bool success) { disconnectHelper(); - emit stateChanged(success ? FINISHED : FAILED); + setState(success ? FINISHED : FAILED); } void ScriptableResource::disconnectHelper() { @@ -179,7 +179,7 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { result = new ScriptableResource(url); - auto resource = getResource(url, QUrl(), false, extra); + auto resource = getResource(url, QUrl(), extra); result->_resource = resource; result->setObjectName(url.toString()); @@ -316,25 +316,7 @@ void ResourceCache::setRequestLimit(int limit) { } } -void ResourceCache::getResourceAsynchronously(const QUrl& url) { - qCDebug(networking) << "ResourceCache::getResourceAsynchronously" << url.toString(); - QWriteLocker locker(&_resourcesToBeGottenLock); - _resourcesToBeGotten.enqueue(QUrl(url)); -} - -void ResourceCache::checkAsynchronousGets() { - assert(QThread::currentThread() == thread()); - QWriteLocker locker(&_resourcesToBeGottenLock); - if (!_resourcesToBeGotten.isEmpty()) { - QUrl url = _resourcesToBeGotten.dequeue(); - - locker.unlock(); - getResource(url); - } -} - -QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, - bool delayLoad, void* extra) { +QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra) { QSharedPointer resource; { QReadLocker locker(&_resourcesLock); @@ -346,17 +328,21 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& } if (QThread::currentThread() != thread()) { - assert(delayLoad); - getResourceAsynchronously(url); + qCDebug(networking) << "Fetching asynchronously:" << url; + QMetaObject::invokeMethod(this, "getResource", + Q_ARG(QUrl, url), Q_ARG(QUrl, fallback)); + // Cannot use extra parameter as it might be freed before the invocation return QSharedPointer(); } if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { - return getResource(fallback, QUrl(), delayLoad); + return getResource(fallback, QUrl()); } - resource = createResource(url, fallback.isValid() ? - getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); + resource = createResource( + url, + fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer(), + extra); resource->setSelf(resource); resource->setCache(this); connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); @@ -508,7 +494,7 @@ const int DEFAULT_REQUEST_LIMIT = 10; int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT; int ResourceCache::_requestsActive = 0; -Resource::Resource(const QUrl& url, bool delayLoad) : +Resource::Resource(const QUrl& url) : _url(url), _activeUrl(url), _request(nullptr) { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 0f2d191567..11b80ca349 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -114,6 +114,9 @@ signals: void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); void stateChanged(int state); +protected: + void setState(State state) { _state = state; emit stateChanged(_state); } + private slots: void loadingChanged(); void loadedChanged(); @@ -176,9 +179,6 @@ public: signals: void dirty(); -public slots: - void checkAsynchronousGets(); - protected slots: void updateTotalSize(const qint64& deltaSize); @@ -187,6 +187,14 @@ protected slots: // and delegate to it (see TextureCache::prefetch(const QUrl&, int). ScriptableResource* prefetch(const QUrl& url, void* extra); + /// Loads a resource from the specified URL and returns it. + /// If the caller is on a different thread than the ResourceCache, + /// returns an empty smart pointer and loads its asynchronously. + /// \param fallback a fallback URL to load if the desired one is unavailable + /// \param extra extra data to pass to the creator, if appropriate + QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), + void* extra = NULL); + private slots: void clearATPAssets(); @@ -197,16 +205,9 @@ protected: // the QScriptEngine will delete the pointer when it is garbage collected. Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } - /// Loads a resource from the specified URL. - /// \param fallback a fallback URL to load if the desired one is unavailable - /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested - /// \param extra extra data to pass to the creator, if appropriate - QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), - bool delayLoad = false, void* extra = NULL); - /// Creates a new resource. - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra) = 0; + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra) = 0; void addUnusedResource(const QSharedPointer& resource); void removeUnusedResource(const QSharedPointer& resource); @@ -257,7 +258,7 @@ class Resource : public QObject { public: - Resource(const QUrl& url, bool delayLoad = false); + Resource(const QUrl& url); ~Resource(); /// Returns the key last used to identify this resource in the unused map. diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 051e4f8791..39be760944 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -40,12 +41,12 @@ #include #include #include -#include +#include #include "OctreeConstants.h" #include "OctreeElementBag.h" #include "Octree.h" -#include "ViewFrustum.h" +#include "OctreeUtils.h" #include "OctreeLogging.h" @@ -898,7 +899,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element, } // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - if (params.viewFrustum && !element->isInView(*params.viewFrustum)) { + if (!params.recurseEverything && !element->isInView(params.viewFrustum)) { params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; return bytesWritten; } @@ -1014,15 +1015,12 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside - - // caller can pass NULL as viewFrustum if they want everything - if (params.viewFrustum) { - float distance = element->distanceToCamera(*params.viewFrustum); + if (!params.recurseEverything) { float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); // If we're too far away for our render level, then just return - if (distance >= boundaryDistance) { + if (element->distanceToCamera(params.viewFrustum) >= boundaryDistance) { if (params.stats) { params.stats->skippedDistance(element); } @@ -1034,7 +1032,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // if we are INSIDE, INTERSECT, or OUTSIDE if (parentLocationThisView != ViewFrustum::INSIDE) { assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE! - nodeLocationThisView = element->computeViewIntersection(*params.viewFrustum); + nodeLocationThisView = element->computeViewIntersection(params.viewFrustum); } // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! @@ -1052,8 +1050,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // because we don't send nodes from the previously know in view frustum. bool wasInView = false; - if (params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::intersection location = element->computeViewIntersection(*params.lastViewFrustum); + if (params.deltaView) { + ViewFrustum::intersection location = element->computeViewIntersection(params.lastViewFrustum); // If we're a leaf, then either intersect or inside is considered "formerly in view" if (element->isLeaf()) { @@ -1067,10 +1065,9 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it // as "was in view"... if (wasInView) { - float distance = element->distanceToCamera(*params.lastViewFrustum); float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); - if (distance >= boundaryDistance) { + if (element->distanceToCamera(params.lastViewFrustum) >= boundaryDistance) { // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... wasInView = false; } @@ -1078,9 +1075,9 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } // If we were previously in the view, then we normally will return out of here and stop recursing. But - // if we're in deltaViewFrustum mode, and this element has changed since it was last sent, then we do + // if we're in deltaView mode, and this element has changed since it was last sent, then we do // need to send it. - if (wasInView && !(params.deltaViewFrustum && element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) { + if (wasInView && !(params.deltaView && element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) { if (params.stats) { params.stats->skippedWasInView(element); } @@ -1090,7 +1087,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, // then we can also bail early and save bits - if (!params.forceSendScene && !params.deltaViewFrustum && + if (!params.forceSendScene && !params.deltaView && !element->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) { if (params.stats) { params.stats->skippedNoChange(element); @@ -1179,10 +1176,10 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, int originalIndex = indexOfChildren[i]; bool childIsInView = (childElement && - ( !params.viewFrustum || // no view frustum was given, everything is assumed in view - (nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are + (params.recurseEverything || + (nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are (nodeLocationThisView == ViewFrustum::INTERSECT && - childElement->isInView(*params.viewFrustum)) // the parent intersects and the child is in view + childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view )); if (!childIsInView) { @@ -1192,12 +1189,11 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } } else { // Before we consider this further, let's see if it's in our LOD scope... - float distance = distancesToChildren[i]; - float boundaryDistance = !params.viewFrustum ? 1 : - boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust, + float boundaryDistance = params.recurseEverything ? 1 : + boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust, params.octreeElementSizeScale); - if (!(distance < boundaryDistance)) { + if (!(distancesToChildren[i] < boundaryDistance)) { // don't need to check childElement here, because we can't get here with no childElement if (params.stats) { params.stats->skippedDistance(childElement); @@ -1215,10 +1211,9 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, bool childIsOccluded = false; // assume it's not occluded - bool shouldRender = !params.viewFrustum - ? true - : childElement->calculateShouldRender(params.viewFrustum, - params.octreeElementSizeScale, params.boundaryLevelAdjust); + bool shouldRender = params.recurseEverything || + childElement->calculateShouldRender(params.viewFrustum, + params.octreeElementSizeScale, params.boundaryLevelAdjust); // track some stats if (params.stats) { @@ -1236,8 +1231,8 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, if (shouldRender && !childIsOccluded) { bool childWasInView = false; - if (childElement && params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::intersection location = childElement->computeViewIntersection(*params.lastViewFrustum); + if (childElement && params.deltaView) { + ViewFrustum::intersection location = childElement->computeViewIntersection(params.lastViewFrustum); // If we're a leaf, then either intersect or inside is considered "formerly in view" if (childElement->isLeaf()) { @@ -1251,7 +1246,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // Or if we were previously in the view, but this element has changed since it was last sent, then we do // need to send it. if (!childWasInView || - (params.deltaViewFrustum && + (params.deltaView && childElement->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){ childrenDataBits += (1 << (7 - originalIndex)); @@ -1456,7 +1451,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep // recursing, by returning TRUE in recurseChildrenWithData(). - if (recurseChildrenWithData() || !params.viewFrustum || !oneAtBit(childrenDataBits, originalIndex)) { + if (params.recurseEverything || recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) { // Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this // will be true. But if the tree has already been encoded, we will skip this. @@ -1978,7 +1973,8 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElementPointer element) bool lastPacketWritten = false; while (OctreeElementPointer subTree = elementBag.extract()) { - EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, NO_EXISTS_BITS); + EncodeBitstreamParams params(INT_MAX, NO_EXISTS_BITS); + params.recurseEverything = true; withReadLock([&] { params.extraEncodeData = &extraEncodeData; bytesWritten = encodeTreeBitstream(subTree, &packetData, elementBag, params); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 81a721c54e..2f0ce3b807 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -20,9 +20,9 @@ #include #include +#include #include "JurisdictionMap.h" -#include "ViewFrustum.h" #include "OctreeElement.h" #include "OctreeElementBag.h" #include "OctreePacketData.h" @@ -61,22 +61,22 @@ const int LOW_RES_MOVING_ADJUST = 1; const quint64 IGNORE_LAST_SENT = 0; #define IGNORE_SCENE_STATS NULL -#define IGNORE_VIEW_FRUSTUM NULL #define IGNORE_COVERAGE_MAP NULL #define IGNORE_JURISDICTION_MAP NULL class EncodeBitstreamParams { public: + ViewFrustum viewFrustum; + ViewFrustum lastViewFrustum; + quint64 lastViewFrustumSent; int maxEncodeLevel; int maxLevelReached; - const ViewFrustum* viewFrustum; bool includeExistsBits; int chopLevels; - bool deltaViewFrustum; - const ViewFrustum* lastViewFrustum; + bool deltaView; + bool recurseEverything { false }; int boundaryLevelAdjust; float octreeElementSizeScale; - quint64 lastViewFrustumSent; bool forceSendScene; OctreeSceneStats* stats; JurisdictionMap* jurisdictionMap; @@ -99,11 +99,9 @@ public: EncodeBitstreamParams( int maxEncodeLevel = INT_MAX, - const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM, bool includeExistsBits = WANT_EXISTS_BITS, int chopLevels = 0, - bool deltaViewFrustum = false, - const ViewFrustum* lastViewFrustum = IGNORE_VIEW_FRUSTUM, + bool useDeltaView = false, int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, quint64 lastViewFrustumSent = IGNORE_LAST_SENT, @@ -111,22 +109,22 @@ public: OctreeSceneStats* stats = IGNORE_SCENE_STATS, JurisdictionMap* jurisdictionMap = IGNORE_JURISDICTION_MAP, OctreeElementExtraEncodeData* extraEncodeData = NULL) : + lastViewFrustumSent(lastViewFrustumSent), maxEncodeLevel(maxEncodeLevel), maxLevelReached(0), - viewFrustum(viewFrustum), includeExistsBits(includeExistsBits), chopLevels(chopLevels), - deltaViewFrustum(deltaViewFrustum), - lastViewFrustum(lastViewFrustum), + deltaView(useDeltaView), boundaryLevelAdjust(boundaryLevelAdjust), octreeElementSizeScale(octreeElementSizeScale), - lastViewFrustumSent(lastViewFrustumSent), forceSendScene(forceSendScene), stats(stats), jurisdictionMap(jurisdictionMap), extraEncodeData(extraEncodeData), stopReason(UNKNOWN) - {} + { + lastViewFrustum.invalidate(); + } void displayStopReason() { printf("StopReason: "); @@ -341,7 +339,7 @@ public: bool getIsClient() const { return !_isServer; } /// Is this a client based tree. Allows guards for certain operations void setIsClient(bool isClient) { _isServer = !isClient; } - + virtual void dumpTree() { } virtual void pruneTree() { } @@ -352,7 +350,6 @@ public: virtual quint64 getAverageCreateTime() const { return 0; } virtual quint64 getAverageLoggingTime() const { return 0; } - signals: void importSize(float x, float y, float z); void importProgress(int progress); diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index db0948b8b6..48f6e08303 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -22,10 +22,11 @@ #include "AACube.h" #include "OctalCode.h" +#include "Octree.h" #include "OctreeConstants.h" #include "OctreeElement.h" -#include "Octree.h" #include "OctreeLogging.h" +#include "OctreeUtils.h" #include "SharedUtil.h" AtomicUIntStat OctreeElement::_octreeMemoryUsage { 0 }; @@ -471,11 +472,11 @@ ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrust // Since, if we know the camera position and orientation, we can know which of the corners is the "furthest" // corner. We can use we can use this corner as our "voxel position" to do our distance calculations off of. // By doing this, we don't need to test each child voxel's position vs the LOD boundary -bool OctreeElement::calculateShouldRender(const ViewFrustum* viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const { +bool OctreeElement::calculateShouldRender(const ViewFrustum& viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const { bool shouldRender = false; if (hasContent()) { - float furthestDistance = furthestDistanceToCamera(*viewFrustum); + float furthestDistance = furthestDistanceToCamera(viewFrustum); float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize); bool inChildBoundary = (furthestDistance <= childBoundary); if (hasDetailedContent() && inChildBoundary) { diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 398f1827b7..61211a40d8 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -21,9 +21,9 @@ #include #include +#include #include "AACube.h" -#include "ViewFrustum.h" #include "OctreeConstants.h" using AtomicUIntStat = std::atomic; @@ -139,7 +139,7 @@ public: float distanceToCamera(const ViewFrustum& viewFrustum) const; float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const; - bool calculateShouldRender(const ViewFrustum* viewFrustum, + bool calculateShouldRender(const ViewFrustum& viewFrustum, float voxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; // points are assumed to be in Voxel Coordinates (not TREE_SCALE'd) diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 33c12b1fe5..4a0a76cd02 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -14,14 +14,12 @@ #include "OctreeLogging.h" #include "OctreeHeadlessViewer.h" -OctreeHeadlessViewer::OctreeHeadlessViewer() : OctreeRenderer() -{ +OctreeHeadlessViewer::OctreeHeadlessViewer() : OctreeRenderer() { _viewFrustum.setProjection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), DEFAULT_ASPECT_RATIO, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); } void OctreeHeadlessViewer::init() { OctreeRenderer::init(); - setViewFrustum(&_viewFrustum); } void OctreeHeadlessViewer::queryOctree() { diff --git a/libraries/octree/src/OctreeHeadlessViewer.h b/libraries/octree/src/OctreeHeadlessViewer.h index 780b68b848..b43dceeba6 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.h +++ b/libraries/octree/src/OctreeHeadlessViewer.h @@ -14,6 +14,7 @@ #include #include +#include #include "JurisdictionListener.h" #include "Octree.h" @@ -22,7 +23,6 @@ #include "OctreeRenderer.h" #include "OctreeSceneStats.h" #include "Octree.h" -#include "ViewFrustum.h" // Generic client side Octree renderer class. class OctreeHeadlessViewer : public OctreeRenderer { @@ -66,7 +66,6 @@ public slots: unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); } private: - ViewFrustum _viewFrustum; JurisdictionListener* _jurisdictionListener = nullptr; OctreeQuery _octreeQuery; diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index a69d88c693..60e4b5fb3a 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -120,4 +120,3 @@ glm::vec3 OctreeQuery::calculateCameraDirection() const { glm::vec3 direction = glm::vec3(_cameraOrientation * glm::vec4(IDENTITY_FRONT, 0.0f)); return direction; } - diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index bcf7c301c7..a81f946680 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -22,8 +22,7 @@ OctreeRenderer::OctreeRenderer() : _tree(NULL), - _managedTree(false), - _viewFrustum(NULL) + _managedTree(false) { } @@ -201,9 +200,9 @@ void OctreeRenderer::processDatagram(ReceivedMessage& message, SharedNodePointer bool OctreeRenderer::renderOperation(OctreeElementPointer element, void* extraData) { RenderArgs* args = static_cast(extraData); - if (element->isInView(*args->_viewFrustum)) { + if (element->isInView(args->getViewFrustum())) { if (element->hasContent()) { - if (element->calculateShouldRender(args->_viewFrustum, args->_sizeScale, args->_boundaryLevelAdjust)) { + if (element->calculateShouldRender(args->getViewFrustum(), args->_sizeScale, args->_boundaryLevelAdjust)) { args->_renderer->renderElement(element, args); } else { return false; // if we shouldn't render, then we also should stop recursing. diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index 198de7ceed..cd4ddc4801 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -20,10 +20,10 @@ #include #include #include +#include #include "Octree.h" #include "OctreePacketData.h" -#include "ViewFrustum.h" class OctreeRenderer; @@ -51,8 +51,8 @@ public: /// render the content of the octree virtual void render(RenderArgs* renderArgs); - ViewFrustum* getViewFrustum() const { return _viewFrustum; } - void setViewFrustum(ViewFrustum* viewFrustum) { _viewFrustum = viewFrustum; } + const ViewFrustum& getViewFrustum() const { return _viewFrustum; } + void setViewFrustum(const ViewFrustum& viewFrustum) { _viewFrustum = viewFrustum; } static bool renderOperation(OctreeElementPointer element, void* extraData); @@ -75,7 +75,7 @@ protected: OctreePointer _tree; bool _managedTree; - ViewFrustum* _viewFrustum; + ViewFrustum _viewFrustum; SimpleMovingAverage _elementsPerPacket; SimpleMovingAverage _entitiesPerPacket; diff --git a/libraries/octree/src/OctreeUtils.cpp b/libraries/octree/src/OctreeUtils.cpp new file mode 100644 index 0000000000..c55016d8e2 --- /dev/null +++ b/libraries/octree/src/OctreeUtils.cpp @@ -0,0 +1,71 @@ +// +// OctreeUtils.cpp +// libraries/octree/src +// +// Created by Andrew Meadows 2016.03.04 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OctreeUtils.h" + +#include + +#include + +#include + + +float calculateRenderAccuracy(const glm::vec3& position, + const AABox& bounds, + float octreeSizeScale, + int boundaryLevelAdjust) { + float largestDimension = bounds.getLargestDimension(); + + const float maxScale = (float)TREE_SCALE; + float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; + + static std::once_flag once; + static QMap shouldRenderTable; + std::call_once(once, [&] { + float SMALLEST_SCALE_IN_TABLE = 0.001f; // 1mm is plenty small + float scale = maxScale; + float factor = 1.0f; + + while (scale > SMALLEST_SCALE_IN_TABLE) { + scale /= 2.0f; + factor /= 2.0f; + shouldRenderTable[scale] = factor; + } + }); + + float closestScale = maxScale; + float visibleDistanceAtClosestScale = visibleDistanceAtMaxScale; + QMap::const_iterator lowerBound = shouldRenderTable.lowerBound(largestDimension); + if (lowerBound != shouldRenderTable.constEnd()) { + closestScale = lowerBound.key(); + visibleDistanceAtClosestScale = visibleDistanceAtMaxScale * lowerBound.value(); + } + + if (closestScale < largestDimension) { + visibleDistanceAtClosestScale *= 2.0f; + } + + // FIXME - for now, it's either visible or not visible. We want to adjust this to eventually return + // a floating point for objects that have small angular size to indicate that they may be rendered + // with lower preciscion + float distanceToCamera = glm::length(bounds.calcCenter() - position); + return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f; +} + +float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { + return voxelSizeScale / powf(2.0f, renderLevel); +} + +float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) { + const float maxScale = (float)TREE_SCALE; + float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; + return atan(maxScale / visibleDistanceAtMaxScale); +} diff --git a/libraries/octree/src/OctreeUtils.h b/libraries/octree/src/OctreeUtils.h new file mode 100644 index 0000000000..279eb51509 --- /dev/null +++ b/libraries/octree/src/OctreeUtils.h @@ -0,0 +1,30 @@ +// +// OctreeUtils.h +// libraries/octree/src +// +// Created by Andrew Meadows 2016.03.04 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OctreeUtils_h +#define hifi_OctreeUtils_h + +#include "OctreeConstants.h" + +class AABox; + +/// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple +/// level it returns 0.0f for things that are so small for the current settings that they could not be visible. +float calculateRenderAccuracy(const glm::vec3& position, + const AABox& bounds, + float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, + int boundaryLevelAdjust = 0); + +float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); + +float getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust); + +#endif // hifi_OctreeUtils_h diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index f685aee748..6ebe05ba86 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -430,6 +430,14 @@ glm::vec3 CharacterController::getLinearVelocity() const { return velocity; } +glm::vec3 CharacterController::getVelocityChange() const { + glm::vec3 velocity(0.0f); + if (_rigidBody) { + velocity = bulletToGLM(_rigidBody->getLinearVelocity()); + } + return velocity; +} + void CharacterController::preSimulation() { if (_enabled && _dynamicsWorld) { quint64 now = usecTimestampNow(); @@ -437,6 +445,7 @@ void CharacterController::preSimulation() { // slam body to where it is supposed to be _rigidBody->setWorldTransform(_characterBodyTransform); btVector3 velocity = _rigidBody->getLinearVelocity(); + _preSimulationVelocity = velocity; btVector3 actualVertVelocity = velocity.dot(_currentUp) * _currentUp; btVector3 actualHorizVelocity = velocity - actualVertVelocity; @@ -531,6 +540,9 @@ void CharacterController::preSimulation() { void CharacterController::postSimulation() { // postSimulation() exists for symmetry and just in case we need to do something here later + + btVector3 velocity = _rigidBody->getLinearVelocity(); + _velocityChange = velocity - _preSimulationVelocity; } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index d810e904a7..a54ab97a14 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -77,6 +77,7 @@ public: glm::vec3 getFollowVelocity() const; glm::vec3 getLinearVelocity() const; + glm::vec3 getVelocityChange() const; float getCapsuleRadius() const { return _radius; } float getCapsuleHalfHeight() const { return _halfHeight; } @@ -112,6 +113,8 @@ protected: btVector3 _currentUp; btVector3 _targetVelocity; btVector3 _parentVelocity; + btVector3 _preSimulationVelocity; + btVector3 _velocityChange; btTransform _followDesiredBodyTransform; btScalar _followTimeRemaining; btTransform _characterBodyTransform; diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 8d33c69d1c..ca64cabe5f 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -138,7 +138,7 @@ QVariantMap ObjectAction::getArguments() { void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) { } -void ObjectAction::removeFromSimulation(EntitySimulation* simulation) const { +void ObjectAction::removeFromSimulation(EntitySimulationPointer simulation) const { QUuid myID; withReadLock([&]{ myID = _id; diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index efc5808f14..43330269ac 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -29,7 +29,7 @@ public: ObjectAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity); virtual ~ObjectAction(); - virtual void removeFromSimulation(EntitySimulation* simulation) const override; + virtual void removeFromSimulation(EntitySimulationPointer simulation) const override; virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; } virtual void setOwnerEntity(const EntityItemPointer ownerEntity) override { _ownerEntity = ownerEntity; } diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index dcd77c1010..e67a681481 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -21,9 +21,11 @@ const uint16_t ObjectActionSpring::springVersion = 1; ObjectActionSpring::ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity) : ObjectAction(ACTION_TYPE_SPRING, id, ownerEntity), _positionalTarget(glm::vec3(0.0f)), + _desiredPositionalTarget(glm::vec3(0.0f)), _linearTimeScale(FLT_MAX), _positionalTargetSet(true), _rotationalTarget(glm::quat()), + _desiredRotationalTarget(glm::quat()), _angularTimeScale(FLT_MAX), _rotationalTargetSet(true) { #if WANT_DEBUG @@ -37,9 +39,81 @@ ObjectActionSpring::~ObjectActionSpring() { #endif } +bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, + glm::vec3& linearVelocity, glm::vec3& angularVelocity) { + rotation = _desiredRotationalTarget; + position = _desiredPositionalTarget; + linearVelocity = glm::vec3(); + angularVelocity = glm::vec3(); + return true; +} + +bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) { + auto ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return false; + } + + glm::quat rotation; + glm::vec3 position; + glm::vec3 linearVelocity; + glm::vec3 angularVelocity; + + bool valid = false; + int springCount = 0; + + QList springDerivedActions; + springDerivedActions.append(ownerEntity->getActionsOfType(ACTION_TYPE_SPRING)); + springDerivedActions.append(ownerEntity->getActionsOfType(ACTION_TYPE_HOLD)); + + foreach (EntityActionPointer action, springDerivedActions) { + std::shared_ptr springAction = std::static_pointer_cast(action); + glm::quat rotationForAction; + glm::vec3 positionForAction; + glm::vec3 linearVelocityForAction, angularVelocityForAction; + bool success = springAction->getTarget(deltaTimeStep, rotationForAction, + positionForAction, linearVelocityForAction, + angularVelocityForAction); + if (success) { + springCount ++; + if (springAction.get() == this) { + // only use the rotation for this action + valid = true; + rotation = rotationForAction; + } + + position += positionForAction; + linearVelocity += linearVelocityForAction; + angularVelocity += angularVelocityForAction; + } + } + + if (valid && springCount > 0) { + position /= springCount; + linearVelocity /= springCount; + angularVelocity /= springCount; + + withWriteLock([&]{ + _positionalTarget = position; + _rotationalTarget = rotation; + _linearVelocityTarget = linearVelocity; + _angularVelocityTarget = angularVelocity; + _positionalTargetSet = true; + _rotationalTargetSet = true; + _active = true; + }); + } + + return valid; +} + + void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { - // don't risk hanging the thread running the physics simulation - auto lockResult = withTryReadLock([&]{ + if (!prepareForSpringUpdate(deltaTimeStep)) { + return; + } + + withReadLock([&]{ auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { return; @@ -65,6 +139,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { float speed = glm::min(offsetLength / _linearTimeScale, SPRING_MAX_SPEED); targetVelocity = (-speed / offsetLength) * offset; if (speed > rigidBody->getLinearSleepingThreshold()) { + forceBodyNonStatic(); rigidBody->activate(); } } @@ -101,9 +176,6 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { rigidBody->setAngularVelocity(targetVelocity); } }); - if (!lockResult) { - qDebug() << "ObjectActionSpring::updateActionWorker lock failed"; - } } const float MIN_TIMESCALE = 0.1f; @@ -122,7 +194,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { bool ok = true; positionalTarget = EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false); if (!ok) { - positionalTarget = _positionalTarget; + positionalTarget = _desiredPositionalTarget; } ok = true; linearTimeScale = EntityActionInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false); @@ -133,7 +205,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { ok = true; rotationalTarget = EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false); if (!ok) { - rotationalTarget = _rotationalTarget; + rotationalTarget = _desiredRotationalTarget; } ok = true; @@ -144,9 +216,9 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { } if (somethingChanged || - positionalTarget != _positionalTarget || + positionalTarget != _desiredPositionalTarget || linearTimeScale != _linearTimeScale || - rotationalTarget != _rotationalTarget || + rotationalTarget != _desiredRotationalTarget || angularTimeScale != _angularTimeScale) { // something changed needUpdate = true; @@ -155,9 +227,9 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { if (needUpdate) { withWriteLock([&] { - _positionalTarget = positionalTarget; + _desiredPositionalTarget = positionalTarget; _linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale)); - _rotationalTarget = rotationalTarget; + _desiredRotationalTarget = rotationalTarget; _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); _active = true; @@ -177,9 +249,9 @@ QVariantMap ObjectActionSpring::getArguments() { QVariantMap arguments = ObjectAction::getArguments(); withReadLock([&] { arguments["linearTimeScale"] = _linearTimeScale; - arguments["targetPosition"] = glmToQMap(_positionalTarget); + arguments["targetPosition"] = glmToQMap(_desiredPositionalTarget); - arguments["targetRotation"] = glmToQMap(_rotationalTarget); + arguments["targetRotation"] = glmToQMap(_desiredRotationalTarget); arguments["angularTimeScale"] = _angularTimeScale; }); return arguments; @@ -194,10 +266,10 @@ QByteArray ObjectActionSpring::serialize() const { dataStream << ObjectActionSpring::springVersion; withReadLock([&] { - dataStream << _positionalTarget; + dataStream << _desiredPositionalTarget; dataStream << _linearTimeScale; dataStream << _positionalTargetSet; - dataStream << _rotationalTarget; + dataStream << _desiredRotationalTarget; dataStream << _angularTimeScale; dataStream << _rotationalTargetSet; dataStream << localTimeToServerTime(_expires); @@ -226,11 +298,11 @@ void ObjectActionSpring::deserialize(QByteArray serializedArguments) { } withWriteLock([&] { - dataStream >> _positionalTarget; + dataStream >> _desiredPositionalTarget; dataStream >> _linearTimeScale; dataStream >> _positionalTargetSet; - dataStream >> _rotationalTarget; + dataStream >> _desiredRotationalTarget; dataStream >> _angularTimeScale; dataStream >> _rotationalTargetSet; diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index 96bb900bf6..498bb6c1f5 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -27,16 +27,26 @@ public: virtual QByteArray serialize() const override; virtual void deserialize(QByteArray serializedArguments) override; + virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, + glm::vec3& linearVelocity, glm::vec3& angularVelocity); + protected: static const uint16_t springVersion; glm::vec3 _positionalTarget; + glm::vec3 _desiredPositionalTarget; float _linearTimeScale; bool _positionalTargetSet; glm::quat _rotationalTarget; + glm::quat _desiredRotationalTarget; float _angularTimeScale; bool _rotationalTargetSet; + + glm::vec3 _linearVelocityTarget; + glm::vec3 _angularVelocityTarget; + + virtual bool prepareForSpringUpdate(btScalar deltaTimeStep); }; #endif // hifi_ObjectActionSpring_h diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 70ca512646..3fbf8ffaf5 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -156,7 +156,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { assert(entity); assert(entity->isDead()); - entity->clearActions(this); + entity->clearActions(getThisPointer()); removeEntityInternal(entity); } // end EntitySimulation overrides diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 935ee57115..24b9ba95f0 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -23,7 +23,9 @@ #include "PhysicsEngine.h" #include "EntityMotionState.h" -typedef QSet SetOfEntityMotionStates; +class PhysicalEntitySimulation; +using PhysicalEntitySimulationPointer = std::shared_ptr; +using SetOfEntityMotionStates = QSet; class PhysicalEntitySimulation :public EntitySimulation { public: diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h index feaabf83e1..e1d1a212e2 100644 --- a/libraries/plugins/src/plugins/PluginContainer.h +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -64,7 +64,7 @@ public: virtual MainWindow* getPrimaryWindow() = 0; virtual QOpenGLContext* getPrimaryContext() = 0; virtual bool isForeground() = 0; - virtual const DisplayPlugin* getActiveDisplayPlugin() const = 0; + virtual const DisplayPluginPointer getActiveDisplayPlugin() const = 0; /// settings interface bool getBoolSetting(const QString& settingName, bool defaultValue); diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 4429f49346..936cb8a7ee 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -32,7 +32,11 @@ const LoaderList& getLoadedPlugins() { static std::once_flag once; static LoaderList loadedPlugins; std::call_once(once, [&] { +#ifdef Q_OS_MAC + QString pluginPath = QCoreApplication::applicationDirPath() + "/../PlugIns/"; +#else QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins/"; +#endif QDir pluginDir(pluginPath); pluginDir.setFilter(QDir::Files); if (pluginDir.exists()) { diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index 145ecfc7b9..66d5fb0d88 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -9,12 +9,9 @@ #include "impl/PointerClip.h" using namespace recording; -NetworkClipLoader::NetworkClipLoader(const QUrl& url, bool delayLoad) - : Resource(url, delayLoad), _clip(std::make_shared(url)) -{ - -} - +NetworkClipLoader::NetworkClipLoader(const QUrl& url) : + Resource(url), + _clip(std::make_shared(url)) {} void NetworkClip::init(const QByteArray& clipData) { _clipData = clipData; @@ -32,10 +29,10 @@ ClipCache& ClipCache::instance() { } NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { - return ResourceCache::getResource(url, QUrl(), false, nullptr).staticCast(); + return ResourceCache::getResource(url, QUrl(), nullptr).staticCast(); } -QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkClipLoader(url, delayLoad), &Resource::deleter); +QSharedPointer ClipCache::createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) { + return QSharedPointer(new NetworkClipLoader(url), &Resource::deleter); } diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h index c72d45648d..7df83efdbf 100644 --- a/libraries/recording/src/recording/ClipCache.h +++ b/libraries/recording/src/recording/ClipCache.h @@ -31,7 +31,7 @@ private: class NetworkClipLoader : public Resource { public: - NetworkClipLoader(const QUrl& url, bool delayLoad); + NetworkClipLoader(const QUrl& url); virtual void downloadFinished(const QByteArray& data) override; ClipPointer getClip() { return _clip; } bool completed() { return _failedToLoad || isLoaded(); } @@ -49,7 +49,7 @@ public: NetworkClipLoaderPointer getClipLoader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) override; + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, const void* extra) override; }; } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 7c7c263562..65fa693914 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -28,14 +28,14 @@ class PickRay; /// Interface provided by Application to other objects that need access to the current view state details class AbstractViewStateInterface { public: - /// gets the current view frustum for rendering the view state - virtual ViewFrustum* getCurrentViewFrustum() = 0; + /// copies the current view frustum for rendering the view state + virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const = 0; - /// gets the shadow view frustum for rendering the view state - virtual ViewFrustum* getShadowViewFrustum() = 0; + /// copies the shadow view frustum for rendering the view state + virtual void copyShadowViewFrustum(ViewFrustum& viewOut) const = 0; virtual QThread* getMainThread() = 0; - + virtual PickRay computePickRay(float x, float y) const = 0; virtual glm::vec3 getAvatarPosition() const = 0; @@ -46,7 +46,7 @@ public: virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; - virtual void pushPreRenderLambda(void* key, std::function func) = 0; + virtual void pushPostUpdateLambda(void* key, std::function func) = 0; // FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface static AbstractViewStateInterface* instance(); diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index aae66f7098..3b0e4c8d18 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -284,7 +284,7 @@ void AmbientOcclusionEffect::updateGaussianDistribution() { void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; @@ -309,7 +309,7 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext auto resolutionLevel = getResolutionLevel(); // Update the depth info with near and far (same for stereo) - setDepthInfo(args->_viewFrustum->getNearClip(), args->_viewFrustum->getFarClip()); + setDepthInfo(args->getViewFrustum().getNearClip(), args->getViewFrustum().getFarClip()); _frameTransformBuffer.edit().pixelInfo = args->_viewport; //_parametersBuffer.edit()._ditheringInfo.y += 0.25f; @@ -319,7 +319,7 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext if (!isStereo) { // Eval the mono projection mat4 monoProjMat; - args->_viewFrustum->evalProjectionMatrix(monoProjMat); + args->getViewFrustum().evalProjectionMatrix(monoProjMat); _frameTransformBuffer.edit().projection[0] = monoProjMat; _frameTransformBuffer.edit().stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); @@ -365,7 +365,7 @@ void AmbientOcclusionEffect::run(const render::SceneContextPointer& sceneContext // Pyramid pass batch.setFramebuffer(pyramidFBO); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(args->_viewFrustum->getFarClip(), 0.0f, 0.0f, 0.0f)); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(args->getViewFrustum().getFarClip(), 0.0f, 0.0f, 0.0f)); batch.setPipeline(pyramidPipeline); batch.setResourceTexture(AmbientOcclusionEffect_DepthMapSlot, depthBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index e2abc226e5..220b673591 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -307,6 +307,16 @@ static void addLink(const AnimPose& rootPose, const AnimPose& pose, const AnimPo } } +static void addLine(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, Vertex*& v) { + uint32_t colorInt = toRGBA(color); + v->pos = start; + v->rgba = colorInt; + v++; + v->pos = end; + v->rgba = colorInt; + v++; +} + void AnimDebugDraw::update() { render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); @@ -319,6 +329,7 @@ void AnimDebugDraw::update() { const size_t VERTICES_PER_BONE = (6 + (NUM_CIRCLE_SLICES * 2) * 3); const size_t VERTICES_PER_LINK = 8 * 2; + const size_t VERTICES_PER_RAY = 2; const float BONE_RADIUS = 0.01f; // 1 cm const float POSE_RADIUS = 0.1f; // 10 cm @@ -342,6 +353,7 @@ void AnimDebugDraw::update() { numVerts += (int)markerMap.size() * VERTICES_PER_BONE; auto myAvatarMarkerMap = DebugDraw::getInstance().getMyAvatarMarkerMap(); numVerts += (int)myAvatarMarkerMap.size() * VERTICES_PER_BONE; + numVerts += (int)DebugDraw::getInstance().getRays().size() * VERTICES_PER_RAY; // allocate verts! data._vertexBuffer->resize(sizeof(Vertex) * numVerts); @@ -390,6 +402,12 @@ void AnimDebugDraw::update() { addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, v); } + // draw rays from shared DebugDraw singleton + for (auto& iter : DebugDraw::getInstance().getRays()) { + addLine(std::get<0>(iter), std::get<1>(iter), std::get<2>(iter), v); + } + DebugDraw::getInstance().clearRays(); + assert(numVerts == (v - verts)); render::Item::Bound theBound; diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 1f00d468b0..fc09f8431d 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -94,7 +94,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { void Antialiasing::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; @@ -118,8 +118,8 @@ void Antialiasing::run(const render::SceneContextPointer& sceneContext, const re glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); batch.setModelTransform(Transform()); @@ -134,7 +134,7 @@ void Antialiasing::run(const render::SceneContextPointer& sceneContext, const re float left, right, bottom, top, nearVal, farVal; glm::vec4 nearClipPlane, farClipPlane; - args->_viewFrustum->computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); + args->getViewFrustum().computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); // float depthScale = (farVal - nearVal) / farVal; // float nearScale = -1.0f / nearVal; diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index c9ea223b3f..12d3774ffd 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -275,7 +275,7 @@ void DebugDeferredBuffer::configure(const Config& config) { void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { @@ -283,20 +283,20 @@ void DebugDeferredBuffer::run(const SceneContextPointer& sceneContext, const Ren const auto framebufferCache = DependencyManager::get(); const auto textureCache = DependencyManager::get(); const auto& lightStage = DependencyManager::get()->getLightStage(); - + glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); batch.setModelTransform(Transform()); // TODO REMOVE: Temporary until UI auto first = _customPipelines.begin()->first; - + batch.setPipeline(getPipeline(_mode, first)); - + batch.setResourceTexture(Albedo, framebufferCache->getDeferredColorTexture()); batch.setResourceTexture(Normal, framebufferCache->getDeferredNormalTexture()); batch.setResourceTexture(Specular, framebufferCache->getDeferredSpecularTexture()); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index ac24b09c40..1d9ce65581 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -218,15 +218,15 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo float tHeight = args->_viewport.w / (float)framebufferSize.height(); // The view frustum is the mono frustum base - auto viewFrustum = args->_viewFrustum; + auto viewFrustum = args->getViewFrustum(); // Eval the mono projection mat4 monoProjMat; - viewFrustum->evalProjectionMatrix(monoProjMat); + viewFrustum.evalProjectionMatrix(monoProjMat); // The mono view transform Transform monoViewTransform; - viewFrustum->evalViewTransform(monoViewTransform); + viewFrustum.evalViewTransform(monoViewTransform); // THe mono view matrix coming from the mono view transform glm::mat4 monoViewMat; @@ -296,8 +296,8 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo fetchTexcoordRects[0] = glm::vec4(sMin, tMin, sWidth, tHeight); } - auto eyePoint = viewFrustum->getPosition(); - float nearRadius = glm::distance(eyePoint, viewFrustum->getNearTopLeft()); + auto eyePoint = viewFrustum.getPosition(); + float nearRadius = glm::distance(eyePoint, viewFrustum.getNearTopLeft()); for (int side = 0; side < numPasses; side++) { diff --git a/libraries/render-utils/src/HitEffect.cpp b/libraries/render-utils/src/HitEffect.cpp index 8f1e9259d4..febfd1942e 100644 --- a/libraries/render-utils/src/HitEffect.cpp +++ b/libraries/render-utils/src/HitEffect.cpp @@ -62,19 +62,19 @@ const gpu::PipelinePointer& HitEffect::getHitEffectPipeline() { void HitEffect::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - + glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); batch.setModelTransform(Transform()); - + batch.setPipeline(getHitEffectPipeline()); glm::vec4 color(0.0f, 0.0f, 0.0f, 1.0f); diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 0f1f396471..fc6c3ff514 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -20,7 +20,7 @@ LightStage::Shadow::Shadow(model::LightPointer light) : _light{ light}, _frustum _schemaBuffer = std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema); } -void LightStage::Shadow::setKeylightFrustum(ViewFrustum* viewFrustum, float nearDepth, float farDepth) { +void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth, float farDepth) { assert(nearDepth < farDepth); // Orient the keylight frustum @@ -38,14 +38,13 @@ void LightStage::Shadow::setKeylightFrustum(ViewFrustum* viewFrustum, float near _frustum->setOrientation(orientation); // Position the keylight frustum - _frustum->setPosition(viewFrustum->getPosition() - (nearDepth + farDepth)*direction); + _frustum->setPosition(viewFrustum.getPosition() - (nearDepth + farDepth)*direction); const Transform view{ _frustum->getView()}; const Transform viewInverse{ view.getInverseMatrix() }; - viewFrustum->calculate(); - auto nearCorners = viewFrustum->getCorners(nearDepth); - auto farCorners = viewFrustum->getCorners(farDepth); + auto nearCorners = viewFrustum.getCorners(nearDepth); + auto farCorners = viewFrustum.getCorners(farDepth); vec3 min{ viewInverse.transform(nearCorners.bottomLeft) }; vec3 max{ min }; diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index f776d8d817..9ed9789965 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -28,7 +28,7 @@ public: Shadow(model::LightPointer light); - void setKeylightFrustum(ViewFrustum* viewFrustum, float nearDepth, float farDepth); + void setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth, float farDepth); const std::shared_ptr getFrustum() const { return _frustum; } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index eda856342c..2fe4427333 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -132,7 +132,7 @@ void Model::updateRenderItems() { // the application will ensure only the last lambda is actually invoked. void* key = (void*)this; std::weak_ptr weakSelf = shared_from_this(); - AbstractViewStateInterface::instance()->pushPreRenderLambda(key, [weakSelf]() { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() { // do nothing, if the model has already been destroyed. auto self = weakSelf.lock(); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 81da0e320a..444c52623e 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -163,7 +163,7 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend // Is it possible that we render without a viewFrustum ? - if (!(renderContext->args && renderContext->args->_viewFrustum)) { + if (!(renderContext->args && renderContext->args->hasViewFrustum())) { return; } @@ -174,7 +174,7 @@ void RenderDeferredTask::run(const SceneContextPointer& sceneContext, const Rend void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); auto config = std::static_pointer_cast(renderContext->jobConfig); @@ -187,8 +187,8 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -202,7 +202,7 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); auto config = std::static_pointer_cast(renderContext->jobConfig); @@ -215,8 +215,8 @@ void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const R glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -240,7 +240,7 @@ DrawOverlay3D::DrawOverlay3D(bool opaque) : void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const render::ItemBounds& inItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); auto config = std::static_pointer_cast(renderContext->jobConfig); @@ -268,8 +268,8 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -290,7 +290,7 @@ const gpu::PipelinePointer& DrawStencilDeferred::getOpaquePipeline() { void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); // from the touched pixel generate the stencil buffer RenderArgs* args = renderContext->args; @@ -316,7 +316,7 @@ void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const Ren void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; doInBatch(args->_context, [&](gpu::Batch& batch) { @@ -334,8 +334,8 @@ void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index d9e0c06a90..f695e2d04c 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -34,7 +34,7 @@ using namespace render; void RenderShadowMap::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ShapeBounds& inShapes) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); const auto& lightStage = DependencyManager::get()->getLightStage(); const auto globalLight = lightStage.lights[0]; @@ -146,16 +146,15 @@ void RenderShadowTask::run(const SceneContextPointer& sceneContext, const render } // Cache old render args - ViewFrustum* viewFrustum = args->_viewFrustum; RenderArgs::RenderMode mode = args->_renderMode; - auto nearClip = viewFrustum->getNearClip(); + auto nearClip = args->getViewFrustum().getNearClip(); float nearDepth = -args->_boomOffset.z; const int SHADOW_FAR_DEPTH = 20; - globalLight->shadow.setKeylightFrustum(viewFrustum, nearDepth, nearClip + SHADOW_FAR_DEPTH); + globalLight->shadow.setKeylightFrustum(args->getViewFrustum(), nearDepth, nearClip + SHADOW_FAR_DEPTH); // Set the keylight render args - args->_viewFrustum = globalLight->shadow.getFrustum().get(); + args->pushViewFrustum(*(globalLight->shadow.getFrustum())); args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; // TODO: Allow runtime manipulation of culling ShouldRenderFunctor @@ -165,6 +164,6 @@ void RenderShadowTask::run(const SceneContextPointer& sceneContext, const render } // Reset the render args - args->_viewFrustum = viewFrustum; + args->popViewFrustum(); args->_renderMode = mode; }; diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 56805e8f83..e27895352f 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -23,10 +24,10 @@ using namespace render; void render::cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details, const ItemBounds& inItems, ItemBounds& outItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; - ViewFrustum* frustum = args->_viewFrustum; + const ViewFrustum& frustum = args->getViewFrustum(); details._considered += (int)inItems.size(); @@ -42,7 +43,7 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc bool inView; { PerformanceTimer perfTimer("boxIntersectsFrustum"); - inView = frustum->boxIntersectsFrustum(item.bound); + inView = frustum.boxIntersectsFrustum(item.bound); } if (inView) { bool bigEnoughToRender; @@ -64,7 +65,7 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc void FetchNonspatialItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); auto& scene = sceneContext->_scene; outItems.clear(); @@ -85,7 +86,7 @@ void FetchSpatialTree::configure(const Config& config) { void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemSpatialTree::ItemSelection& outSelection) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = sceneContext->_scene; @@ -93,22 +94,18 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render outSelection.clear(); // Eventually use a frozen frustum - auto queryFrustum = *args->_viewFrustum; + auto queryFrustum = args->getViewFrustum(); if (_freezeFrustum) { if (_justFrozeFrustum) { _justFrozeFrustum = false; - _frozenFrutstum = *args->_viewFrustum; + _frozenFrutstum = args->getViewFrustum(); } queryFrustum = _frozenFrutstum; } // Octree selection! - - float angle = glm::degrees(queryFrustum.getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); - - + float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); scene->getSpatialTree().selectCellItems(outSelection, _filter, queryFrustum, angle); - } void CullSpatialSelection::configure(const Config& config) { @@ -120,7 +117,7 @@ void CullSpatialSelection::configure(const Config& config) { void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection, ItemBounds& outItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = sceneContext->_scene; @@ -128,13 +125,12 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re details._considered += (int)inSelection.numItems(); // Eventually use a frozen frustum - auto argFrustum = args->_viewFrustum; if (_freezeFrustum) { if (_justFrozeFrustum) { _justFrozeFrustum = false; - _frozenFrutstum = *args->_viewFrustum; + _frozenFrutstum = args->getViewFrustum(); } - args->_viewFrustum = &_frozenFrutstum; // replace the true view frustum by the frozen one + args->pushViewFrustum(_frozenFrutstum); // replace the true view frustum by the frozen one } // Culling Frustum / solidAngle test helper class @@ -151,8 +147,8 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re _renderDetails(renderDetails) { // FIXME: Keep this code here even though we don't use it yet - /*_eyePos = _args->_viewFrustum->getPosition(); - float a = glm::degrees(_args->_viewFrustum->getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); + /*_eyePos = _args->getViewFrustum().getPosition(); + float a = glm::degrees(Octree::getAccuracyAngle(_args->_sizeScale, _args->_boundaryLevelAdjust)); auto angle = std::min(glm::radians(45.0f), a); // no worse than 45 degrees angle = std::max(glm::radians(1.0f / 60.0f), a); // no better than 1 minute of degree auto tanAlpha = tan(angle); @@ -161,7 +157,7 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re } bool frustumTest(const AABox& bound) { - if (!_args->_viewFrustum->boxIntersectsFrustum(bound)) { + if (!_args->getViewFrustum().boxIntersectsFrustum(bound)) { _renderDetails._outOfView++; return false; } @@ -305,7 +301,7 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re // Restore frustum if using the frozen one: if (_freezeFrustum) { - args->_viewFrustum = argFrustum; + args->popViewFrustum(); } std::static_pointer_cast(renderContext->jobConfig)->numItems = (int)outItems.size(); diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 9b22649a8d..efcb4eea37 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -86,7 +87,7 @@ void DrawSceneOctree::configure(const Config& config) { void DrawSceneOctree::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = sceneContext->_scene; @@ -97,8 +98,8 @@ void DrawSceneOctree::run(const SceneContextPointer& sceneContext, gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(projMat); @@ -148,7 +149,7 @@ void DrawSceneOctree::run(const SceneContextPointer& sceneContext, } // Draw the LOD Reticle { - float angle = glm::degrees(args->_viewFrustum->getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); + float angle = glm::degrees(getAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust)); Transform crosshairModel; crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0)); crosshairModel.setScale(1000.0 * tan(glm::radians(angle))); // Scaling at the actual tan of the lod angle => Multiplied by TWO @@ -198,15 +199,15 @@ void DrawItemSelection::configure(const Config& config) { void DrawItemSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& inSelection) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = sceneContext->_scene; gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(projMat); diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index 584f41499a..c2724fce42 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -107,7 +107,7 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& scene = sceneContext->_scene; const int NUM_STATUS_VEC4_PER_ITEM = 2; @@ -183,8 +183,8 @@ void DrawStatus::run(const SceneContextPointer& sceneContext, gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { glm::mat4 projMat; Transform viewMat; - args->_viewFrustum->evalProjectionMatrix(projMat); - args->_viewFrustum->evalViewTransform(viewMat); + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(projMat); diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 6d43d169c3..e3cfc9fa63 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -120,7 +120,7 @@ void render::renderStateSortShapes(const SceneContextPointer& sceneContext, cons void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; // render lights diff --git a/libraries/render/src/render/SortTask.cpp b/libraries/render/src/render/SortTask.cpp index d2fda542b1..28860aa410 100644 --- a/libraries/render/src/render/SortTask.cpp +++ b/libraries/render/src/render/SortTask.cpp @@ -1,5 +1,5 @@ // -// CullTask.cpp +// SortTask.cpp // render/src/render // // Created by Sam Gateau on 2/2/16. @@ -42,7 +42,7 @@ struct BackToFrontSort { void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) { assert(renderContext->args); - assert(renderContext->args->_viewFrustum); + assert(renderContext->args->hasViewFrustum()); auto& scene = sceneContext->_scene; RenderArgs* args = renderContext->args; @@ -60,7 +60,7 @@ void render::depthSortItems(const SceneContextPointer& sceneContext, const Rende for (auto itemDetails : inItems) { auto item = scene->getItem(itemDetails.id); auto bound = itemDetails.bound; // item.getBound(); - float distance = args->_viewFrustum->distanceToCamera(bound.calcCenter()); + float distance = args->getViewFrustum().distanceToCamera(bound.calcCenter()); itemBoundSorts.emplace_back(ItemBoundSort(distance, distance, distance, itemDetails.id, bound)); } diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 8ff13aba70..48fda99b9d 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,3 +1,3 @@ set(TARGET_NAME script-engine) -setup_hifi_library(Gui Network Script WebSockets Widgets) +setup_hifi_library(Gui Network Script ScriptTools WebSockets Widgets) link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6c7739c784..f7ac7894ff 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -9,17 +9,26 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include + #include #include #include #include #include +#include + +#include +#include + #include #include -#include + #include #include -#include + +#include #include #include @@ -31,6 +40,7 @@ #include #include #include +#include #include #include @@ -166,6 +176,93 @@ void ScriptEngine::disconnectNonEssentialSignals() { } } +void ScriptEngine::runDebuggable() { + static QMenuBar* menuBar { nullptr }; + static QMenu* scriptDebugMenu { nullptr }; + static size_t scriptMenuCount { 0 }; + if (!scriptDebugMenu) { + for (auto window : qApp->topLevelWidgets()) { + auto mainWindow = qobject_cast(window); + if (mainWindow) { + menuBar = mainWindow->menuBar(); + break; + } + } + if (menuBar) { + scriptDebugMenu = menuBar->addMenu("Script Debug"); + } + } + + init(); + _isRunning = true; + _debuggable = true; + _debugger = new QScriptEngineDebugger(this); + _debugger->attachTo(this); + + QMenu* parentMenu = scriptDebugMenu; + QMenu* scriptMenu { nullptr }; + if (parentMenu) { + ++scriptMenuCount; + scriptMenu = parentMenu->addMenu(_fileNameString); + scriptMenu->addMenu(_debugger->createStandardMenu(qApp->activeWindow())); + } else { + qWarning() << "Unable to add script debug menu"; + } + + QScriptValue result = evaluate(_scriptContents, _fileNameString); + + _lastUpdate = usecTimestampNow(); + QTimer* timer = new QTimer(this); + connect(this, &ScriptEngine::finished, [this, timer, parentMenu, scriptMenu] { + if (scriptMenu) { + parentMenu->removeAction(scriptMenu->menuAction()); + --scriptMenuCount; + if (0 == scriptMenuCount) { + menuBar->removeAction(scriptDebugMenu->menuAction()); + scriptDebugMenu = nullptr; + } + } + disconnect(timer); + }); + + connect(timer, &QTimer::timeout, [this, timer] { + if (_isFinished) { + if (!_isRunning) { + return; + } + stopAllTimers(); // make sure all our timers are stopped if the script is ending + if (_wantSignals) { + emit scriptEnding(); + emit finished(_fileNameString, this); + } + _isRunning = false; + if (_wantSignals) { + emit runningStateChanged(); + emit doneRunning(); + } + timer->deleteLater(); + return; + } + + qint64 now = usecTimestampNow(); + // we check for 'now' in the past in case people set their clock back + if (_lastUpdate < now) { + float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND; + if (!_isFinished) { + if (_wantSignals) { + emit update(deltaTime); + } + } + } + _lastUpdate = now; + // Debug and clear exceptions + hadUncaughtExceptions(*this, _fileNameString); + }); + + timer->start(10); +} + + void ScriptEngine::runInThread() { Q_ASSERT_X(!_isThreaded, "ScriptEngine::runInThread()", "runInThread should not be called more than once"); @@ -257,6 +354,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { // FIXME - switch this to the new model of ScriptCache callbacks void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { _scriptContents = scriptContents; + static const QString DEBUG_FLAG("#debug"); + if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) { + _debuggable = true; + } if (_wantSignals) { emit scriptLoaded(url.toString()); } @@ -706,22 +807,45 @@ void ScriptEngine::run() { QScriptValue result = evaluate(_scriptContents, _fileNameString); - QElapsedTimer startTime; - startTime.start(); +#ifdef _WIN32 + // VS13 does not sleep_until unless it uses the system_clock, see: + // https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/ + using clock = std::chrono::system_clock; +#else + using clock = std::chrono::high_resolution_clock; +#endif + clock::time_point startTime = clock::now(); int thisFrame = 0; auto nodeList = DependencyManager::get(); auto entityScriptingInterface = DependencyManager::get(); - qint64 lastUpdate = usecTimestampNow(); + _lastUpdate = usecTimestampNow(); + // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptEngine while (!_isFinished) { - int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec - if (usecToSleep > 0) { - usleep(usecToSleep); - } + // Throttle to SCRIPT_FPS + const std::chrono::microseconds FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1); + clock::time_point sleepTime(startTime + thisFrame++ * FRAME_DURATION); + std::this_thread::sleep_until(sleepTime); +#ifdef SCRIPT_DELAY_DEBUG + { + auto now = clock::now(); + uint64_t seconds = std::chrono::duration_cast(now - startTime).count(); + if (seconds > 0) { // avoid division by zero and time travel + uint64_t fps = thisFrame / seconds; + // Overreporting artificially reduces the reported rate + if (thisFrame % SCRIPT_FPS == 0) { + qCDebug(scriptengine) << + "Frame:" << thisFrame << + "Slept (us):" << std::chrono::duration_cast(now - sleepTime).count() << + "FPS:" << fps; + } + } + } +#endif if (_isFinished) { break; } @@ -745,15 +869,15 @@ void ScriptEngine::run() { qint64 now = usecTimestampNow(); // we check for 'now' in the past in case people set their clock back - if (lastUpdate < now) { - float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; + if (_lastUpdate < now) { + float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; if (!_isFinished) { if (_wantSignals) { emit update(deltaTime); } } } - lastUpdate = now; + _lastUpdate = now; // Debug and clear exceptions hadUncaughtExceptions(*this, _fileNameString); @@ -969,7 +1093,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac } QList urls; bool knowsSensitivity = false; - Qt::CaseSensitivity sensitivity; + Qt::CaseSensitivity sensitivity { Qt::CaseSensitive }; auto getSensitivity = [&]() { if (!knowsSensitivity) { QString path = currentSandboxURL.path(); @@ -986,6 +1110,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment; for (QString file : includeFiles) { QUrl thisURL; + bool isStandardLibrary = false; if (file.startsWith("/~/")) { thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file))); QUrl defaultScriptsLoc = defaultScriptsLocation(); @@ -993,21 +1118,17 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac qDebug() << "ScriptEngine::include -- skipping" << file << "-- outside of standard libraries"; continue; } + isStandardLibrary = true; } else { thisURL = resolvePath(file); } if (!_includedURLs.contains(thisURL)) { - if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && - ( - (currentSandboxURL.scheme() != "file") || - ( - !thisURL.toString(strippingFlags).startsWith(defaultScriptsLocation().toString(), getSensitivity()) && - !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()) - ) - ) - ) { - qCWarning(scriptengine) << "Script.include() ignoring file path" << thisURL << "outside of original entity script" << currentSandboxURL; + if (!isStandardLibrary && !currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && + (currentSandboxURL.scheme() != "file" || + !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()))) { + qCWarning(scriptengine) << "Script.include() ignoring file path" + << thisURL << "outside of original entity script" << currentSandboxURL; } else { // We could also check here for CORS, but we don't yet. // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 175a3f1f1c..d37e3eb177 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -18,9 +18,10 @@ #include #include #include -#include #include +#include + #include #include #include @@ -39,9 +40,11 @@ #include "ScriptUUID.h" #include "Vec3.h" -const QString NO_SCRIPT(""); +class QScriptEngineDebugger; -const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1000) + 0.5f); +static const QString NO_SCRIPT(""); + +static const int SCRIPT_FPS = 60; class CallbackData { public: @@ -75,6 +78,8 @@ public: /// services before calling this. void runInThread(); + void runDebuggable(); + /// run the script in the callers thread, exit when stop() is called. void run(); @@ -140,6 +145,8 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget + bool isDebuggable() const { return _debuggable; } + void disconnectNonEssentialSignals(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -187,6 +194,9 @@ protected: bool _wantSignals { true }; QHash _entityScripts; bool _isThreaded { false }; + QScriptEngineDebugger* _debugger { nullptr }; + bool _debuggable { false }; + qint64 _lastUpdate; void init(); QString getFilename() const; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index c6070e0598..330a94cf0b 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -9,7 +9,8 @@ #include "ScriptEngines.h" #include -#include + +#include #include #include @@ -270,12 +271,12 @@ void ScriptEngines::loadOneScript(const QString& scriptFilename) { void ScriptEngines::loadScripts() { // check first run... - if (_firstRun.get()) { + Setting::Handle firstRun { Settings::firstRun, true }; + if (firstRun.get()) { qCDebug(scriptengine) << "This is a first run..."; // clear the scripts, and set out script to our default scripts clearScripts(); loadDefaultScripts(); - _firstRun.set(false); return; } @@ -490,7 +491,12 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) { for (auto initializer : _scriptInitializers) { initializer(scriptEngine); } - scriptEngine->runInThread(); + + if (scriptEngine->isDebuggable() || (qApp->queryKeyboardModifiers() & Qt::ShiftModifier)) { + scriptEngine->runDebuggable(); + } else { + scriptEngine->runInThread(); + } } diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 0963b21600..6522aa9bb3 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -87,8 +87,6 @@ protected: void onScriptEngineError(const QString& scriptFilename); void launchScriptEngine(ScriptEngine* engine); - - Setting::Handle _firstRun { "firstRun", true }; QReadWriteLock _scriptEnginesHashLock; QHash _scriptEnginesHash; QSet _allKnownScriptEngines; diff --git a/libraries/octree/src/OctreeProjectedPolygon.cpp b/libraries/shared/src/CubeProjectedPolygon.cpp similarity index 93% rename from libraries/octree/src/OctreeProjectedPolygon.cpp rename to libraries/shared/src/CubeProjectedPolygon.cpp index b8e2d4d999..04d6e8bb4e 100644 --- a/libraries/octree/src/OctreeProjectedPolygon.cpp +++ b/libraries/shared/src/CubeProjectedPolygon.cpp @@ -1,6 +1,6 @@ // -// OctreeProjectedPolygon.cpp -// libraries/octree/src +// CubeProjectedPolygon.cpp +// libraries/shared/src // // Created by Brad Hefta-Gaub on 06/11/13. // Copyright 2013 High Fidelity, Inc. @@ -15,50 +15,50 @@ #include "GeometryUtil.h" #include "SharedUtil.h" -#include "OctreeLogging.h" -#include "OctreeProjectedPolygon.h" +#include "SharedLogging.h" +#include "CubeProjectedPolygon.h" -glm::vec2 BoundingBox::getVertex(int vertexNumber) const { +glm::vec2 BoundingRectangle::getVertex(int vertexNumber) const { switch (vertexNumber) { - case BoundingBox::BOTTOM_LEFT: + case BoundingRectangle::BOTTOM_LEFT: return corner; - case BoundingBox::TOP_LEFT: + case BoundingRectangle::TOP_LEFT: return glm::vec2(corner.x, corner.y + size.y); - case BoundingBox::BOTTOM_RIGHT: + case BoundingRectangle::BOTTOM_RIGHT: return glm::vec2(corner.x + size.x, corner.y); - case BoundingBox::TOP_RIGHT: + case BoundingRectangle::TOP_RIGHT: return corner + size; } assert(false); // not allowed return glm::vec2(0,0); } -BoundingBox BoundingBox::topHalf() const { +BoundingRectangle BoundingRectangle::topHalf() const { float halfY = size.y/2.0f; - BoundingBox result(glm::vec2(corner.x,corner.y + halfY), glm::vec2(size.x, halfY)); + BoundingRectangle result(glm::vec2(corner.x,corner.y + halfY), glm::vec2(size.x, halfY)); return result; } -BoundingBox BoundingBox::bottomHalf() const { +BoundingRectangle BoundingRectangle::bottomHalf() const { float halfY = size.y/2.0f; - BoundingBox result(corner, glm::vec2(size.x, halfY)); + BoundingRectangle result(corner, glm::vec2(size.x, halfY)); return result; } -BoundingBox BoundingBox::leftHalf() const { +BoundingRectangle BoundingRectangle::leftHalf() const { float halfX = size.x/2.0f; - BoundingBox result(corner, glm::vec2(halfX, size.y)); + BoundingRectangle result(corner, glm::vec2(halfX, size.y)); return result; } -BoundingBox BoundingBox::rightHalf() const { +BoundingRectangle BoundingRectangle::rightHalf() const { float halfX = size.x/2.0f; - BoundingBox result(glm::vec2(corner.x + halfX , corner.y), glm::vec2(halfX, size.y)); + BoundingRectangle result(glm::vec2(corner.x + halfX , corner.y), glm::vec2(halfX, size.y)); return result; } -bool BoundingBox::contains(const BoundingBox& box) const { +bool BoundingRectangle::contains(const BoundingRectangle& box) const { return ( _set && (box.corner.x >= corner.x) && (box.corner.y >= corner.y) && @@ -67,7 +67,7 @@ bool BoundingBox::contains(const BoundingBox& box) const { ); } -bool BoundingBox::contains(const glm::vec2& point) const { +bool BoundingRectangle::contains(const glm::vec2& point) const { return ( _set && (point.x > corner.x) && (point.y > corner.y) && @@ -76,7 +76,7 @@ bool BoundingBox::contains(const glm::vec2& point) const { ); } -void BoundingBox::explandToInclude(const BoundingBox& box) { +void BoundingRectangle::explandToInclude(const BoundingRectangle& box) { if (!_set) { corner = box.corner; size = box.size; @@ -94,20 +94,20 @@ void BoundingBox::explandToInclude(const BoundingBox& box) { } -void BoundingBox::printDebugDetails(const char* label) const { - qCDebug(octree, "%s _set=%s\n corner=%f,%f size=%f,%f\n bounds=[(%f,%f) to (%f,%f)]", - (label ? label : "BoundingBox"), +void BoundingRectangle::printDebugDetails(const char* label) const { + qCDebug(shared, "%s _set=%s\n corner=%f,%f size=%f,%f\n bounds=[(%f,%f) to (%f,%f)]", + (label ? label : "BoundingRectangle"), debug::valueOf(_set), (double)corner.x, (double)corner.y, (double)size.x, (double)size.y, (double)corner.x, (double)corner.y, (double)(corner.x+size.x), (double)(corner.y+size.y)); } -long OctreeProjectedPolygon::pointInside_calls = 0; -long OctreeProjectedPolygon::occludes_calls = 0; -long OctreeProjectedPolygon::intersects_calls = 0; +long CubeProjectedPolygon::pointInside_calls = 0; +long CubeProjectedPolygon::occludes_calls = 0; +long CubeProjectedPolygon::intersects_calls = 0; -OctreeProjectedPolygon::OctreeProjectedPolygon(const BoundingBox& box) : +CubeProjectedPolygon::CubeProjectedPolygon(const BoundingRectangle& box) : _vertexCount(4), _maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX), _distance(0) @@ -118,7 +118,7 @@ OctreeProjectedPolygon::OctreeProjectedPolygon(const BoundingBox& box) : } -void OctreeProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { +void CubeProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { _vertices[vertex] = point; // keep track of our bounding box @@ -138,9 +138,9 @@ void OctreeProjectedPolygon::setVertex(int vertex, const glm::vec2& point) { } // can be optimized with new pointInside() -bool OctreeProjectedPolygon::occludes(const OctreeProjectedPolygon& occludee, bool checkAllInView) const { +bool CubeProjectedPolygon::occludes(const CubeProjectedPolygon& occludee, bool checkAllInView) const { - OctreeProjectedPolygon::occludes_calls++; + CubeProjectedPolygon::occludes_calls++; // if we are completely out of view, then we definitely don't occlude! // if the occludee is completely out of view, then we also don't occlude it @@ -197,12 +197,12 @@ bool OctreeProjectedPolygon::occludes(const OctreeProjectedPolygon& occludee, bo return false; // if we got this far, then we're not occluded } -bool OctreeProjectedPolygon::occludes(const BoundingBox& boxOccludee) const { - OctreeProjectedPolygon testee(boxOccludee); +bool CubeProjectedPolygon::occludes(const BoundingRectangle& boxOccludee) const { + CubeProjectedPolygon testee(boxOccludee); return occludes(testee); } -bool OctreeProjectedPolygon::matches(const OctreeProjectedPolygon& testee) const { +bool CubeProjectedPolygon::matches(const CubeProjectedPolygon& testee) const { if (testee.getVertexCount() != getVertexCount()) { return false; } @@ -231,14 +231,14 @@ bool OctreeProjectedPolygon::matches(const OctreeProjectedPolygon& testee) const return true; // all of our vertices match, therefore we're the same } -bool OctreeProjectedPolygon::matches(const BoundingBox& box) const { - OctreeProjectedPolygon testee(box); +bool CubeProjectedPolygon::matches(const BoundingRectangle& box) const { + CubeProjectedPolygon testee(box); return matches(testee); } -bool OctreeProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVertex) const { +bool CubeProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVertex) const { - OctreeProjectedPolygon::pointInside_calls++; + CubeProjectedPolygon::pointInside_calls++; // first check the bounding boxes, the point must be fully within the boounding box of this polygon if ((point.x > getMaxX()) || @@ -264,23 +264,23 @@ bool OctreeProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVe return true; } -void OctreeProjectedPolygon::printDebugDetails() const { - qCDebug(octree, "OctreeProjectedPolygon..." +void CubeProjectedPolygon::printDebugDetails() const { + qCDebug(shared, "CubeProjectedPolygon..." " minX=%f maxX=%f minY=%f maxY=%f", (double)getMinX(), (double)getMaxX(), (double)getMinY(), (double)getMaxY()); - qCDebug(octree, " vertex count=%d distance=%f", getVertexCount(), (double)getDistance()); + qCDebug(shared, " vertex count=%d distance=%f", getVertexCount(), (double)getDistance()); for (int i = 0; i < getVertexCount(); i++) { glm::vec2 point = getVertex(i); - qCDebug(octree, " vertex[%d] = %f, %f ", i, (double)point.x, (double)point.y); + qCDebug(shared, " vertex[%d] = %f, %f ", i, (double)point.x, (double)point.y); } } -bool OctreeProjectedPolygon::intersects(const BoundingBox& box) const { - OctreeProjectedPolygon testee(box); +bool CubeProjectedPolygon::intersects(const BoundingRectangle& box) const { + CubeProjectedPolygon testee(box); return intersects(testee); } -bool OctreeProjectedPolygon::intersects(const OctreeProjectedPolygon& testee) const { - OctreeProjectedPolygon::intersects_calls++; +bool CubeProjectedPolygon::intersects(const CubeProjectedPolygon& testee) const { + CubeProjectedPolygon::intersects_calls++; return intersectsOnAxes(testee) && testee.intersectsOnAxes(*this); } @@ -294,7 +294,7 @@ bool OctreeProjectedPolygon::intersects(const OctreeProjectedPolygon& testee) co // Note: this only works on convex polygons // // -bool OctreeProjectedPolygon::intersectsOnAxes(const OctreeProjectedPolygon& testee) const { +bool CubeProjectedPolygon::intersectsOnAxes(const CubeProjectedPolygon& testee) const { // consider each edge of this polygon as a potential separating axis for (int i = 0; i < getVertexCount(); i++) { @@ -324,7 +324,7 @@ bool OctreeProjectedPolygon::intersectsOnAxes(const OctreeProjectedPolygon& test return true; } -bool OctreeProjectedPolygon::canMerge(const OctreeProjectedPolygon& that) const { +bool CubeProjectedPolygon::canMerge(const CubeProjectedPolygon& that) const { // RIGHT/NEAR // LEFT/NEAR @@ -642,7 +642,7 @@ bool OctreeProjectedPolygon::canMerge(const OctreeProjectedPolygon& that) const } -void OctreeProjectedPolygon::merge(const OctreeProjectedPolygon& that) { +void CubeProjectedPolygon::merge(const CubeProjectedPolygon& that) { // RIGHT/NEAR // LEFT/NEAR diff --git a/libraries/octree/src/OctreeProjectedPolygon.h b/libraries/shared/src/CubeProjectedPolygon.h similarity index 64% rename from libraries/octree/src/OctreeProjectedPolygon.h rename to libraries/shared/src/CubeProjectedPolygon.h index a8507e8fb2..6eb35e100b 100644 --- a/libraries/octree/src/OctreeProjectedPolygon.h +++ b/libraries/shared/src/CubeProjectedPolygon.h @@ -1,55 +1,55 @@ // -// OctreeProjectedPolygon.h -// libraries/octree/src +// CubeProjectedPolygon.h +// libraries/shared/src // // Created by Brad Hefta-Gaub on 06/11/13. // Copyright 2013 High Fidelity, Inc. // -// The projected shadow (on the 2D view plane) for a voxel +// The projected shadow (on the 2D view plane) for a cube // // 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_OctreeProjectedPolygon_h -#define hifi_OctreeProjectedPolygon_h +#ifndef hifi_CubeProjectedPolygon_h +#define hifi_CubeProjectedPolygon_h #include // there's a max of 6 vertices of a project polygon, and a max of twice that when clipped to the screen -const int MAX_PROJECTED_POLYGON_VERTEX_COUNT = 6; -const int MAX_CLIPPED_PROJECTED_POLYGON_VERTEX_COUNT = MAX_PROJECTED_POLYGON_VERTEX_COUNT * 2; +const int MAX_PROJECTED_POLYGON_VERTEX_COUNT = 6; +const int MAX_CLIPPED_PROJECTED_POLYGON_VERTEX_COUNT = MAX_PROJECTED_POLYGON_VERTEX_COUNT * 2; typedef glm::vec2 ProjectedVertices[MAX_CLIPPED_PROJECTED_POLYGON_VERTEX_COUNT]; -class BoundingBox { +class BoundingRectangle { public: enum { BOTTOM_LEFT, BOTTOM_RIGHT, TOP_RIGHT, TOP_LEFT, VERTEX_COUNT }; - BoundingBox(const glm::vec2 corner, const glm::vec2 size) : corner(corner), size(size), _set(true) {} - BoundingBox() : _set(false) {} + BoundingRectangle(const glm::vec2 corner, const glm::vec2 size) : corner(corner), size(size), _set(true) {} + BoundingRectangle() : _set(false) {} glm::vec2 corner; glm::vec2 size; - bool contains(const BoundingBox& box) const; + bool contains(const BoundingRectangle& box) const; bool contains(const glm::vec2& point) const; bool pointInside(const glm::vec2& point) const { return contains(point); } - void explandToInclude(const BoundingBox& box); + void explandToInclude(const BoundingRectangle& box); float area() const { return size.x * size.y; } int getVertexCount() const { return VERTEX_COUNT; } glm::vec2 getVertex(int vertexNumber) const; - BoundingBox topHalf() const; - BoundingBox bottomHalf() const; - BoundingBox leftHalf() const; - BoundingBox rightHalf() const; + BoundingRectangle topHalf() const; + BoundingRectangle bottomHalf() const; + BoundingRectangle leftHalf() const; + BoundingRectangle rightHalf() const; float getMaxX() const { return corner.x + size.x; } float getMaxY() const { return corner.y + size.y; } float getMinX() const { return corner.x; } float getMinY() const { return corner.y; } - + void printDebugDetails(const char* label=NULL) const; private: bool _set; @@ -63,18 +63,18 @@ const int PROJECTION_NEAR = 16; const int PROJECTION_FAR = 32; const int PROJECTION_CLIPPED = 64; -class OctreeProjectedPolygon { +class CubeProjectedPolygon { public: - OctreeProjectedPolygon(const BoundingBox& box); + CubeProjectedPolygon(const BoundingRectangle& box); - OctreeProjectedPolygon(int vertexCount = 0) : - _vertexCount(vertexCount), + CubeProjectedPolygon(int vertexCount = 0) : + _vertexCount(vertexCount), _maxX(-FLT_MAX), _maxY(-FLT_MAX), _minX(FLT_MAX), _minY(FLT_MAX), _distance(0) { } - - ~OctreeProjectedPolygon() { } + + ~CubeProjectedPolygon() { } const ProjectedVertices& getVertices() const { return _vertices; } const glm::vec2& getVertex(int i) const { return _vertices[i]; } void setVertex(int vertex, const glm::vec2& point); @@ -92,28 +92,28 @@ public: bool pointInside(const glm::vec2& point, bool* matchesVertex = NULL) const; - bool occludes(const OctreeProjectedPolygon& occludee, bool checkAllInView = false) const; - bool occludes(const BoundingBox& occludee) const; - bool intersects(const OctreeProjectedPolygon& testee) const; - bool intersects(const BoundingBox& box) const; - bool matches(const OctreeProjectedPolygon& testee) const; - bool matches(const BoundingBox& testee) const; - bool intersectsOnAxes(const OctreeProjectedPolygon& testee) const; + bool occludes(const CubeProjectedPolygon& occludee, bool checkAllInView = false) const; + bool occludes(const BoundingRectangle& occludee) const; + bool intersects(const CubeProjectedPolygon& testee) const; + bool intersects(const BoundingRectangle& box) const; + bool matches(const CubeProjectedPolygon& testee) const; + bool matches(const BoundingRectangle& testee) const; + bool intersectsOnAxes(const CubeProjectedPolygon& testee) const; + + bool canMerge(const CubeProjectedPolygon& that) const; + void merge(const CubeProjectedPolygon& that); // replaces vertices of this with new merged version - bool canMerge(const OctreeProjectedPolygon& that) const; - void merge(const OctreeProjectedPolygon& that); // replaces vertices of this with new merged version - float getMaxX() const { return _maxX; } float getMaxY() const { return _maxY; } float getMinX() const { return _minX; } float getMinY() const { return _minY; } - - BoundingBox getBoundingBox() const { - return BoundingBox(glm::vec2(_minX,_minY), glm::vec2(_maxX - _minX, _maxY - _minY)); + + BoundingRectangle getBoundingBox() const { + return BoundingRectangle(glm::vec2(_minX,_minY), glm::vec2(_maxX - _minX, _maxY - _minY)); } void printDebugDetails() const; - + static long pointInside_calls; static long occludes_calls; static long intersects_calls; @@ -132,4 +132,4 @@ private: }; -#endif // hifi_OctreeProjectedPolygon_h +#endif // hifi_CubeProjectedPolygon_h diff --git a/libraries/shared/src/DebugDraw.cpp b/libraries/shared/src/DebugDraw.cpp index 557889cab0..04759e6187 100644 --- a/libraries/shared/src/DebugDraw.cpp +++ b/libraries/shared/src/DebugDraw.cpp @@ -23,6 +23,11 @@ DebugDraw::~DebugDraw() { } +// world space line, drawn only once +void DebugDraw::drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color) { + _rays.push_back(Ray(start, end, color)); +} + void DebugDraw::addMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { _markers[key] = MarkerInfo(rotation, position, color); } diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index 2f62f17abf..f77e281e06 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -23,16 +24,21 @@ public: DebugDraw(); ~DebugDraw(); - // world space maker + // world space line, drawn only once + void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color); + + // world space maker, marker drawn every frame until it is removed. void addMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); void removeMarker(const std::string& key); - // myAvatar relative marker + // myAvatar relative marker, maker is drawn every frame until it is removed. void addMyAvatarMarker(const std::string& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); void removeMyAvatarMarker(const std::string& key); using MarkerInfo = std::tuple; using MarkerMap = std::unordered_map; + using Ray = std::tuple; + using Rays = std::vector; // // accessors used by renderer @@ -44,12 +50,15 @@ public: const glm::vec3& getMyAvatarPos() const { return _myAvatarPos; } void updateMyAvatarRot(const glm::quat& rot) { _myAvatarRot = rot; } const glm::quat& getMyAvatarRot() const { return _myAvatarRot; } + const Rays getRays() const { return _rays; } + void clearRays() { _rays.clear(); } protected: MarkerMap _markers; MarkerMap _myAvatarMarkers; glm::quat _myAvatarRot; glm::vec3 _myAvatarPos; + Rays _rays; }; #endif // hifi_DebugDraw_h diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 53abb3827d..bd8bffefd2 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -431,13 +431,27 @@ glm::vec3 transformVectorFull(const glm::mat4& m, const glm::vec3& v) { void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& secondaryAxis, glm::vec3& uAxisOut, glm::vec3& vAxisOut, glm::vec3& wAxisOut) { + // primaryAxis & secondaryAxis must not be zero. +#ifndef NDEBUG + const float MIN_LENGTH_SQUARED = 1.0e-6f; +#endif + assert(fabsf(glm::length2(primaryAxis) > MIN_LENGTH_SQUARED)); + assert(fabsf(glm::length2(secondaryAxis) > MIN_LENGTH_SQUARED)); + uAxisOut = glm::normalize(primaryAxis); - wAxisOut = glm::cross(uAxisOut, secondaryAxis); - if (glm::length(wAxisOut) > 0.0f) { - wAxisOut = glm::normalize(wAxisOut); - } else { - wAxisOut = glm::normalize(glm::cross(uAxisOut, glm::vec3(0, 1, 0))); + glm::vec3 normSecondary = glm::normalize(secondaryAxis); + + // if secondaryAxis is parallel with the primaryAxis, pick another axis. + const float EPSILON = 1.0e-4f; + if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { + // pick a better secondaryAxis. + normSecondary = glm::vec3(1.0f, 0.0f, 0.0f); + if (fabsf(fabsf(glm::dot(uAxisOut, secondaryAxis)) - 1.0f) > EPSILON) { + normSecondary = glm::vec3(0.0f, 1.0f, 0.0f); + } } + + wAxisOut = glm::normalize(glm::cross(uAxisOut, secondaryAxis)); vAxisOut = glm::cross(wAxisOut, uAxisOut); } diff --git a/libraries/shared/src/PIDController.cpp b/libraries/shared/src/PIDController.cpp index 0d1d267dea..790c26ac25 100644 --- a/libraries/shared/src/PIDController.cpp +++ b/libraries/shared/src/PIDController.cpp @@ -35,7 +35,7 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue); } Q_ASSERT(!glm::isnan(computedValue)); - + // update state for next time _lastError = error; _lastAccumulation = accumulatedError; diff --git a/libraries/octree/src/Plane.cpp b/libraries/shared/src/Plane.cpp similarity index 93% rename from libraries/octree/src/Plane.cpp rename to libraries/shared/src/Plane.cpp index 8e782dd087..bd7937471c 100644 --- a/libraries/octree/src/Plane.cpp +++ b/libraries/shared/src/Plane.cpp @@ -1,6 +1,6 @@ // // Plane.cpp -// libraries/octree/src/ +// libraries/shared/src/ // // Created by Brad Hefta-Gaub on 04/11/13. // Copyright 2013 High Fidelity, Inc. @@ -13,12 +13,12 @@ // #include "Plane.h" -#include "OctreeLogging.h" - - -#include #include +#include + +#include "SharedLogging.h" + void Plane::set3Points(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3) { glm::vec3 linev1v2, linev1v3; @@ -65,7 +65,7 @@ float Plane::distance(const glm::vec3 &point) const { } void Plane::print() const { - qCDebug(octree, "Plane - point (x=%f y=%f z=%f) normal (x=%f y=%f z=%f) d=%f", + qCDebug(shared, "Plane - point (x=%f y=%f z=%f) normal (x=%f y=%f z=%f) d=%f", (double)_point.x, (double)_point.y, (double)_point.z, (double)_normal.x, (double)_normal.y, (double)_normal.z, (double)_dCoefficient); } diff --git a/libraries/octree/src/Plane.h b/libraries/shared/src/Plane.h similarity index 83% rename from libraries/octree/src/Plane.h rename to libraries/shared/src/Plane.h index db809ad542..c903ad9db7 100644 --- a/libraries/octree/src/Plane.h +++ b/libraries/shared/src/Plane.h @@ -1,6 +1,6 @@ // // Plane.h -// libraries/octree/src/ +// libraries/shared/src/ // // Created by Brad Hefta-Gaub on 04/11/13. // Copyright 2013 High Fidelity, Inc. @@ -20,7 +20,7 @@ class Plane { public: Plane(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3) { set3Points(v1,v2,v3); } - Plane() : _normal(0,0,0), _point(0,0,0), _dCoefficient(0) {}; + Plane() : _normal(0.0f), _point(0.0f), _dCoefficient(0.0f) {}; ~Plane() {} ; // methods for defining the plane @@ -28,12 +28,13 @@ public: void setNormalAndPoint(const glm::vec3 &normal, const glm::vec3 &point); void setCoefficients(float a, float b, float c, float d); - // getters + // getters const glm::vec3& getNormal() const { return _normal; }; const glm::vec3& getPoint() const { return _point; }; float getDCoefficient() const { return _dCoefficient; }; // utilities + void invalidate() { _normal = glm::vec3(0.0f), _dCoefficient = 1.0e6f; } // distance() never less than 10^6 float distance(const glm::vec3 &point) const; void print() const; @@ -44,4 +45,4 @@ private: }; -#endif // hifi_Plane_h \ No newline at end of file +#endif // hifi_Plane_h diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index 341553b743..e8f0002fed 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -14,12 +14,15 @@ #include #include -#include "GLMHelpers.h" +#include + +#include +#include + class AABox; class OctreeRenderer; -class ViewFrustum; namespace gpu { class Batch; @@ -39,21 +42,21 @@ public: SHADOW, OTHER }; - + struct Item { int _considered = 0; int _outOfView = 0; int _tooSmall = 0; int _rendered = 0; }; - + int _materialSwitches = 0; int _trianglesRendered = 0; - + Item _item; Item _shadow; Item _other; - + Item& edit(Type type) { switch (type) { case SHADOW: @@ -77,7 +80,6 @@ public: RenderArgs(std::shared_ptr context = nullptr, OctreeRenderer* renderer = nullptr, - ViewFrustum* viewFrustum = nullptr, float sizeScale = 1.0f, int boundaryLevelAdjust = 0, RenderMode renderMode = DEFAULT_RENDER_MODE, @@ -86,7 +88,6 @@ public: gpu::Batch* batch = nullptr) : _context(context), _renderer(renderer), - _viewFrustum(viewFrustum), _sizeScale(sizeScale), _boundaryLevelAdjust(boundaryLevelAdjust), _renderMode(renderMode), @@ -95,11 +96,22 @@ public: _batch(batch) { } + bool hasViewFrustum() const { return _viewFrustums.size() > 0; } + void setViewFrustum(const ViewFrustum& viewFrustum) { + while (_viewFrustums.size() > 0) { + _viewFrustums.pop(); + } + _viewFrustums.push(viewFrustum); + } + const ViewFrustum& getViewFrustum() const { assert(_viewFrustums.size() > 0); return _viewFrustums.top(); } + void pushViewFrustum(const ViewFrustum& viewFrustum) { _viewFrustums.push(viewFrustum); } + void popViewFrustum() { _viewFrustums.pop(); } + std::shared_ptr _context = nullptr; std::shared_ptr _blitFramebuffer = nullptr; std::shared_ptr _pipeline = nullptr; OctreeRenderer* _renderer = nullptr; - ViewFrustum* _viewFrustum = nullptr; + std::stack _viewFrustums; glm::ivec4 _viewport{ 0.0f, 0.0f, 1.0f, 1.0f }; glm::vec3 _boomOffset{ 0.0f, 0.0f, 1.0f }; float _sizeScale = 1.0f; @@ -108,7 +120,7 @@ public: RenderSide _renderSide = MONO; DebugFlags _debugFlags = RENDER_DEBUG_NONE; gpu::Batch* _batch = nullptr; - + std::shared_ptr _whiteTexture; RenderDetails _details; diff --git a/libraries/shared/src/SettingHandle.cpp b/libraries/shared/src/SettingHandle.cpp index 951d004318..d2e84d8b75 100644 --- a/libraries/shared/src/SettingHandle.cpp +++ b/libraries/shared/src/SettingHandle.cpp @@ -13,6 +13,7 @@ #include +const QString Settings::firstRun { "firstRun" }; void Settings::getFloatValueIfValid(const QString& name, float& floatValue) { const QVariant badDefaultValue = NAN; diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index c803efaa71..bef2daf84c 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -26,6 +26,8 @@ // TODO: remove class Settings : public QSettings { public: + static const QString firstRun; + void getFloatValueIfValid(const QString& name, float& floatValue); void getBoolValue(const QString& name, bool& boolValue); diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp similarity index 84% rename from libraries/octree/src/ViewFrustum.cpp rename to libraries/shared/src/ViewFrustum.cpp index 228e4c7d35..4a7d4eff95 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -1,6 +1,6 @@ // // ViewFrustum.cpp -// libraries/octree/src +// libraries/shared/src // // Created by Brad Hefta-Gaub on 04/11/13. // Copyright 2013 High Fidelity, Inc. @@ -17,13 +17,13 @@ #include #include -#include #include "GeometryUtil.h" #include "GLMHelpers.h" +#include "NumericalConstants.h" +#include "SharedLogging.h" +//#include "OctreeConstants.h" #include "ViewFrustum.h" -#include "OctreeLogging.h" -#include "OctreeConstants.h" using namespace std; @@ -41,24 +41,24 @@ void ViewFrustum::setPosition(const glm::vec3& position) { } // Order cooresponds to the order defined in the BoxVertex enum. -static const glm::vec4 NDC_VALUES[8] = { - glm::vec4(-1, -1, -1, 1), - glm::vec4(1, -1, -1, 1), - glm::vec4(1, 1, -1, 1), - glm::vec4(-1, 1, -1, 1), - glm::vec4(-1, -1, 1, 1), - glm::vec4(1, -1, 1, 1), - glm::vec4(1, 1, 1, 1), - glm::vec4(-1, 1, 1, 1), +static const glm::vec4 NDC_VALUES[NUM_FRUSTUM_CORNERS] = { + glm::vec4(-1.0f, -1.0f, -1.0f, 1.0f), + glm::vec4(1.0f, -1.0f, -1.0f, 1.0f), + glm::vec4(1.0f, 1.0f, -1.0f, 1.0f), + glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f), + glm::vec4(-1.0f, -1.0f, 1.0f, 1.0f), + glm::vec4(1.0f, -1.0f, 1.0f, 1.0f), + glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), + glm::vec4(-1.0f, 1.0f, 1.0f, 1.0f), }; void ViewFrustum::setProjection(const glm::mat4& projection) { _projection = projection; - _inverseProjection = glm::inverse(projection); + glm::mat4 inverseProjection = glm::inverse(projection); // compute our dimensions the usual way - for (int i = 0; i < 8; ++i) { - _corners[i] = _inverseProjection * NDC_VALUES[i]; + for (int i = 0; i < NUM_FRUSTUM_CORNERS; ++i) { + _corners[i] = inverseProjection * NDC_VALUES[i]; _corners[i] /= _corners[i].w; } _nearClip = -_corners[BOTTOM_LEFT_NEAR].z; @@ -66,12 +66,12 @@ void ViewFrustum::setProjection(const glm::mat4& projection) { _aspectRatio = (_corners[TOP_RIGHT_NEAR].x - _corners[BOTTOM_LEFT_NEAR].x) / (_corners[TOP_RIGHT_NEAR].y - _corners[BOTTOM_LEFT_NEAR].y); - glm::vec4 top = _inverseProjection * vec4(0, 1, -1, 1); + glm::vec4 top = inverseProjection * vec4(0.0f, 1.0f, -1.0f, 1.0f); top /= top.w; - _fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0, 0, -1), glm::normalize(vec3(top)))))); + _fieldOfView = abs(glm::degrees(2.0f * abs(glm::angle(vec3(0.0f, 0.0f, -1.0f), glm::normalize(vec3(top)))))); } -// ViewFrustum::calculateViewFrustum() +// ViewFrustum::calculate() // // Description: this will calculate the view frustum bounds for a given position and direction // @@ -84,7 +84,7 @@ void ViewFrustum::calculate() { // then transform them to world space glm::mat4 worldMatrix = glm::translate(_position) * glm::mat4(glm::mat3(_right, _up, -_direction)); glm::vec4 v; - for (int i = 0; i < 8; ++i) { + for (int i = 0; i < NUM_FRUSTUM_CORNERS; ++i) { v = worldMatrix * _corners[i]; v /= v.w; _cornersWorld[i] = glm::vec3(v); @@ -133,7 +133,7 @@ const char* ViewFrustum::debugPlaneName (int plane) const { ViewFrustum::intersection ViewFrustum::calculateCubeFrustumIntersection(const AACube& cube) const { // only check against frustum ViewFrustum::intersection result = INSIDE; - for(int i=0; i < 6; i++) { + for(int i = 0; i < NUM_FRUSTUM_PLANES; i++) { const glm::vec3& normal = _planes[i].getNormal(); // check distance to farthest cube point if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) { @@ -178,7 +178,7 @@ ViewFrustum::intersection ViewFrustum::calculateCubeKeyholeIntersection(const AA bool ViewFrustum::pointIntersectsFrustum(const glm::vec3& point) const { // only check against frustum - for(int i = 0; i < 6; ++i) { + for(int i = 0; i < NUM_FRUSTUM_PLANES; ++i) { float distance = _planes[i].distance(point); if (distance < 0.0f) { return false; @@ -189,7 +189,7 @@ bool ViewFrustum::pointIntersectsFrustum(const glm::vec3& point) const { bool ViewFrustum::sphereIntersectsFrustum(const glm::vec3& center, float radius) const { // only check against frustum - for(int i=0; i < 6; i++) { + for(int i = 0; i < NUM_FRUSTUM_PLANES; i++) { float distance = _planes[i].distance(center); if (distance < -radius) { // This is outside the regular frustum, so just return the value from checking the keyhole @@ -201,7 +201,7 @@ bool ViewFrustum::sphereIntersectsFrustum(const glm::vec3& center, float radius) bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const { // only check against frustum - for(int i=0; i < 6; i++) { + for(int i = 0; i < NUM_FRUSTUM_PLANES; i++) { const glm::vec3& normal = _planes[i].getNormal(); // check distance to farthest box point if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) { @@ -217,7 +217,7 @@ bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) return true; } // check negative touches against frustum planes - for(int i=0; i < 6; i++) { + for(int i = 0; i < NUM_FRUSTUM_PLANES; i++) { if ( _planes[i].distance(center) < -radius) { return false; } @@ -231,7 +231,7 @@ bool ViewFrustum::cubeIntersectsKeyhole(const AACube& cube) const { return true; } // check negative touches against frustum planes - for(int i=0; i < 6; i++) { + for(int i = 0; i < NUM_FRUSTUM_PLANES; i++) { const glm::vec3& normal = _planes[i].getNormal(); if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) { return false; @@ -246,7 +246,7 @@ bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const { return true; } // check negative touches against frustum planes - for(int i=0; i < 6; i++) { + for(int i = 0; i < NUM_FRUSTUM_PLANES; i++) { const glm::vec3& normal = _planes[i].getNormal(); if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) { return false; @@ -281,36 +281,36 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const { testMatches(compareTo._focalLength, _focalLength); if (!result && debug) { - qCDebug(octree, "ViewFrustum::matches()... result=%s", debug::valueOf(result)); - qCDebug(octree, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f", + qCDebug(shared, "ViewFrustum::matches()... result=%s", debug::valueOf(result)); + qCDebug(shared, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f", (testMatches(compareTo._position,_position) ? "MATCHES " : "NO MATCH"), (double)compareTo._position.x, (double)compareTo._position.y, (double)compareTo._position.z, (double)_position.x, (double)_position.y, (double)_position.z); - qCDebug(octree, "%s -- compareTo._direction=%f,%f,%f _direction=%f,%f,%f", + qCDebug(shared, "%s -- compareTo._direction=%f,%f,%f _direction=%f,%f,%f", (testMatches(compareTo._direction, _direction) ? "MATCHES " : "NO MATCH"), (double)compareTo._direction.x, (double)compareTo._direction.y, (double)compareTo._direction.z, (double)_direction.x, (double)_direction.y, (double)_direction.z ); - qCDebug(octree, "%s -- compareTo._up=%f,%f,%f _up=%f,%f,%f", + qCDebug(shared, "%s -- compareTo._up=%f,%f,%f _up=%f,%f,%f", (testMatches(compareTo._up, _up) ? "MATCHES " : "NO MATCH"), (double)compareTo._up.x, (double)compareTo._up.y, (double)compareTo._up.z, (double)_up.x, (double)_up.y, (double)_up.z ); - qCDebug(octree, "%s -- compareTo._right=%f,%f,%f _right=%f,%f,%f", + qCDebug(shared, "%s -- compareTo._right=%f,%f,%f _right=%f,%f,%f", (testMatches(compareTo._right, _right) ? "MATCHES " : "NO MATCH"), (double)compareTo._right.x, (double)compareTo._right.y, (double)compareTo._right.z, (double)_right.x, (double)_right.y, (double)_right.z ); - qCDebug(octree, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f", + qCDebug(shared, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f", (testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"), (double)compareTo._fieldOfView, (double)_fieldOfView); - qCDebug(octree, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f", + qCDebug(shared, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f", (testMatches(compareTo._aspectRatio, _aspectRatio) ? "MATCHES " : "NO MATCH"), (double)compareTo._aspectRatio, (double)_aspectRatio); - qCDebug(octree, "%s -- compareTo._nearClip=%f _nearClip=%f", + qCDebug(shared, "%s -- compareTo._nearClip=%f _nearClip=%f", (testMatches(compareTo._nearClip, _nearClip) ? "MATCHES " : "NO MATCH"), (double)compareTo._nearClip, (double)_nearClip); - qCDebug(octree, "%s -- compareTo._farClip=%f _farClip=%f", + qCDebug(shared, "%s -- compareTo._farClip=%f _farClip=%f", (testMatches(compareTo._farClip, _farClip) ? "MATCHES " : "NO MATCH"), (double)compareTo._farClip, (double)_farClip); - qCDebug(octree, "%s -- compareTo._focalLength=%f _focalLength=%f", + qCDebug(shared, "%s -- compareTo._focalLength=%f _focalLength=%f", (testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"), (double)compareTo._focalLength, (double)_focalLength); } @@ -342,34 +342,34 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const if (!result && debug) { - qCDebug(octree, "ViewFrustum::isVerySimilar()... result=%s\n", debug::valueOf(result)); - qCDebug(octree, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f", + qCDebug(shared, "ViewFrustum::isVerySimilar()... result=%s\n", debug::valueOf(result)); + qCDebug(shared, "%s -- compareTo._position=%f,%f,%f _position=%f,%f,%f", (testMatches(compareTo._position,_position, POSITION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), (double)compareTo._position.x, (double)compareTo._position.y, (double)compareTo._position.z, (double)_position.x, (double)_position.y, (double)_position.z ); - qCDebug(octree, "%s -- positionDistance=%f", + qCDebug(shared, "%s -- positionDistance=%f", (testMatches(0,positionDistance, POSITION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), (double)positionDistance); - qCDebug(octree, "%s -- angleOrientation=%f", + qCDebug(shared, "%s -- angleOrientation=%f", (testMatches(0, angleOrientation, ORIENTATION_SIMILAR_ENOUGH) ? "IS SIMILAR ENOUGH " : "IS NOT SIMILAR ENOUGH"), (double)angleOrientation); - qCDebug(octree, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f", + qCDebug(shared, "%s -- compareTo._fieldOfView=%f _fieldOfView=%f", (testMatches(compareTo._fieldOfView, _fieldOfView) ? "MATCHES " : "NO MATCH"), (double)compareTo._fieldOfView, (double)_fieldOfView); - qCDebug(octree, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f", + qCDebug(shared, "%s -- compareTo._aspectRatio=%f _aspectRatio=%f", (testMatches(compareTo._aspectRatio, _aspectRatio) ? "MATCHES " : "NO MATCH"), (double)compareTo._aspectRatio, (double)_aspectRatio); - qCDebug(octree, "%s -- compareTo._nearClip=%f _nearClip=%f", + qCDebug(shared, "%s -- compareTo._nearClip=%f _nearClip=%f", (testMatches(compareTo._nearClip, _nearClip) ? "MATCHES " : "NO MATCH"), (double)compareTo._nearClip, (double)_nearClip); - qCDebug(octree, "%s -- compareTo._farClip=%f _farClip=%f", + qCDebug(shared, "%s -- compareTo._farClip=%f _farClip=%f", (testMatches(compareTo._farClip, _farClip) ? "MATCHES " : "NO MATCH"), (double)compareTo._farClip, (double)_farClip); - qCDebug(octree, "%s -- compareTo._focalLength=%f _focalLength=%f", + qCDebug(shared, "%s -- compareTo._focalLength=%f _focalLength=%f", (testMatches(compareTo._focalLength, _focalLength) ? "MATCHES " : "NO MATCH"), (double)compareTo._focalLength, (double)_focalLength); } @@ -394,7 +394,7 @@ void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom // find the minimum and maximum z values, which will be our near and far clip distances nearValue = FLT_MAX; farValue = -FLT_MAX; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < NUM_FRUSTUM_CORNERS; i++) { nearValue = min(nearValue, -_corners[i].z); farValue = max(farValue, -_corners[i].z); } @@ -427,17 +427,17 @@ void ViewFrustum::computeOffAxisFrustum(float& left, float& right, float& bottom } void ViewFrustum::printDebugDetails() const { - qCDebug(octree, "ViewFrustum::printDebugDetails()..."); - qCDebug(octree, "_position=%f,%f,%f", (double)_position.x, (double)_position.y, (double)_position.z ); - qCDebug(octree, "_direction=%f,%f,%f", (double)_direction.x, (double)_direction.y, (double)_direction.z ); - qCDebug(octree, "_up=%f,%f,%f", (double)_up.x, (double)_up.y, (double)_up.z ); - qCDebug(octree, "_right=%f,%f,%f", (double)_right.x, (double)_right.y, (double)_right.z ); - qCDebug(octree, "_fieldOfView=%f", (double)_fieldOfView); - qCDebug(octree, "_aspectRatio=%f", (double)_aspectRatio); - qCDebug(octree, "_centerSphereRadius=%f", (double)_centerSphereRadius); - qCDebug(octree, "_nearClip=%f", (double)_nearClip); - qCDebug(octree, "_farClip=%f", (double)_farClip); - qCDebug(octree, "_focalLength=%f", (double)_focalLength); + qCDebug(shared, "ViewFrustum::printDebugDetails()..."); + qCDebug(shared, "_position=%f,%f,%f", (double)_position.x, (double)_position.y, (double)_position.z ); + qCDebug(shared, "_direction=%f,%f,%f", (double)_direction.x, (double)_direction.y, (double)_direction.z ); + qCDebug(shared, "_up=%f,%f,%f", (double)_up.x, (double)_up.y, (double)_up.z ); + qCDebug(shared, "_right=%f,%f,%f", (double)_right.x, (double)_right.y, (double)_right.z ); + qCDebug(shared, "_fieldOfView=%f", (double)_fieldOfView); + qCDebug(shared, "_aspectRatio=%f", (double)_aspectRatio); + qCDebug(shared, "_centerSphereRadius=%f", (double)_centerSphereRadius); + qCDebug(shared, "_nearClip=%f", (double)_nearClip); + qCDebug(shared, "_farClip=%f", (double)_farClip); + qCDebug(shared, "_focalLength=%f", (double)_focalLength); } glm::vec2 ViewFrustum::projectPoint(glm::vec3 point, bool& pointInView) const { @@ -536,7 +536,7 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_PROJECTED_POLYGON_VERT {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left }; -OctreeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { +CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { const glm::vec3& bottomNearRight = box.getCorner(); glm::vec3 topFarLeft = box.calcTopFarLeft(); @@ -549,7 +549,7 @@ OctreeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices - OctreeProjectedPolygon projectedPolygon(vertexCount); + CubeProjectedPolygon projectedPolygon(vertexCount); bool pointInView = true; bool allPointsInView = false; // assume the best, but wait till we know we have a vertex @@ -630,7 +630,7 @@ void ViewFrustum::getFurthestPointFromCamera(const AACube& box, glm::vec3& furth } } -const ViewFrustum::Corners ViewFrustum::getCorners(const float& depth) { +const ViewFrustum::Corners ViewFrustum::getCorners(const float& depth) const { glm::vec3 normal = glm::normalize(_direction); auto getCorner = [&](enum::BoxVertex nearCorner, enum::BoxVertex farCorner) { @@ -662,51 +662,10 @@ void ViewFrustum::evalViewTransform(Transform& view) const { view.setRotation(getOrientation()); } -float ViewFrustum::calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale, int boundaryLevelAdjust) const { - float distanceToCamera = glm::length(bounds.calcCenter() - getPosition()); - float largestDimension = bounds.getLargestDimension(); - - const float maxScale = (float)TREE_SCALE; - float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; - - static std::once_flag once; - static QMap shouldRenderTable; - std::call_once(once, [&] { - float SMALLEST_SCALE_IN_TABLE = 0.001f; // 1mm is plenty small - float scale = maxScale; - float factor = 1.0f; - - while (scale > SMALLEST_SCALE_IN_TABLE) { - scale /= 2.0f; - factor /= 2.0f; - shouldRenderTable[scale] = factor; - } - }); - - float closestScale = maxScale; - float visibleDistanceAtClosestScale = visibleDistanceAtMaxScale; - QMap::const_iterator lowerBound = shouldRenderTable.lowerBound(largestDimension); - if (lowerBound != shouldRenderTable.constEnd()) { - closestScale = lowerBound.key(); - visibleDistanceAtClosestScale = visibleDistanceAtMaxScale * lowerBound.value(); +void ViewFrustum::invalidate() { + // these setting should make nearly all intersection tests fail + for (int i = 0; i < NUM_FRUSTUM_PLANES; ++i) { + _planes[i].invalidate(); } - - if (closestScale < largestDimension) { - visibleDistanceAtClosestScale *= 2.0f; - } - - // FIXME - for now, it's either visible or not visible. We want to adjust this to eventually return - // a floating point for objects that have small angular size to indicate that they may be rendered - // with lower preciscion - return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f; -} - -float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { - return voxelSizeScale / powf(2, renderLevel); -} - -float ViewFrustum::getAccuracyAngle(float octreeSizeScale, int boundaryLevelAdjust) const { - const float maxScale = (float)TREE_SCALE; - float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / OCTREE_TO_MESH_RATIO; - return atan(maxScale / visibleDistanceAtMaxScale); + _centerSphereRadius = -1.0e6f; // -10^6 should be negative enough } diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h similarity index 79% rename from libraries/octree/src/ViewFrustum.h rename to libraries/shared/src/ViewFrustum.h index 412d0f82f2..48d45ab455 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -1,6 +1,6 @@ // // ViewFrustum.h -// libraries/octree/src +// libraries/shared/src // // Created by Brad Hefta-Gaub on 04/11/13. // Copyright 2013 High Fidelity, Inc. @@ -17,21 +17,21 @@ #include #include -#include -#include - -#include "Transform.h" #include "AABox.h" #include "AACube.h" +#include "CubeProjectedPolygon.h" #include "Plane.h" -#include "OctreeConstants.h" -#include "OctreeProjectedPolygon.h" +#include "RegisteredMetaTypes.h" +#include "Transform.h" + +const int NUM_FRUSTUM_CORNERS = 8; +const int NUM_FRUSTUM_PLANES = 6; const float DEFAULT_CENTER_SPHERE_RADIUS = 3.0f; const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f; const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f; const float DEFAULT_NEAR_CLIP = 0.08f; -const float DEFAULT_FAR_CLIP = (float)HALF_TREE_SCALE; +const float DEFAULT_FAR_CLIP = 16384.0f; // the "ViewFrustum" has a "keyhole" shape: a regular frustum for stuff that is "visible" with // a central sphere for stuff that is nearby (for physics simulation). @@ -74,7 +74,7 @@ public: glm::vec3 bottomRight; // Get the corners depth units from frustum position, along frustum orientation }; - const Corners getCorners(const float& depth); + const Corners getCorners(const float& depth) const; // getters for corners const glm::vec3& getFarTopLeft() const { return _cornersWorld[TOP_LEFT_FAR]; } @@ -123,7 +123,7 @@ public: void printDebugDetails() const; glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const; - OctreeProjectedPolygon getProjectedPolygon(const AACube& box) const; + CubeProjectedPolygon getProjectedPolygon(const AACube& box) const; void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const; float distanceToCamera(const glm::vec3& point) const; @@ -131,44 +131,37 @@ public: void evalProjectionMatrix(glm::mat4& proj) const; void evalViewTransform(Transform& view) const; - /// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple - /// level it returns 0.0f for things that are so small for the current settings that they could not be visible. - float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, - int boundaryLevelAdjust = 0) const; - - float getAccuracyAngle(float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; - enum PlaneIndex { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE, NUM_PLANES }; const ::Plane* getPlanes() const { return _planes; } -private: - // camera location/orientation attributes - glm::vec3 _position; // the position in world-frame - glm::quat _orientation; - glm::mat4 _view; - // Lens attributes + void invalidate(); // causes all reasonable intersection tests to fail +private: + glm::mat4 _view; glm::mat4 _projection; - // calculated for orientation + ::Plane _planes[NUM_FRUSTUM_PLANES]; // plane normals point inside frustum + + glm::vec3 _position; // position in world-frame + glm::quat _orientation; // orientation in world-frame + + // calculated from orientation glm::vec3 _direction = IDENTITY_FRONT; glm::vec3 _up = IDENTITY_UP; glm::vec3 _right = IDENTITY_RIGHT; + // calculated from projection + glm::vec4 _corners[NUM_FRUSTUM_CORNERS]; + glm::vec3 _cornersWorld[NUM_FRUSTUM_CORNERS]; float _centerSphereRadius = DEFAULT_CENTER_SPHERE_RADIUS; + float _width { 1.0f }; + float _height { 1.0f }; + float _aspectRatio { 1.0f }; + float _focalLength { 0.25f }; + float _fieldOfView { DEFAULT_FIELD_OF_VIEW_DEGREES }; - // Calculated values - glm::mat4 _inverseProjection; - float _width = 1.0f; - float _height = 1.0f; - float _aspectRatio = 1.0f; - float _nearClip = DEFAULT_NEAR_CLIP; - float _farClip = DEFAULT_FAR_CLIP; - float _focalLength = 0.25f; - float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES; - glm::vec4 _corners[8]; - glm::vec3 _cornersWorld[8]; - ::Plane _planes[6]; // plane normals point inside frustum + float _nearClip { DEFAULT_NEAR_CLIP }; + float _farClip { DEFAULT_FAR_CLIP }; const char* debugPlaneName (int plane) const; @@ -177,6 +170,4 @@ private: }; using ViewFrustumPointer = std::shared_ptr; -float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); - #endif // hifi_ViewFrustum_h diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index f2b8c04827..6e2f744173 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -509,7 +509,12 @@ void NeuronPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrat std::lock_guard guard(_jointsMutex); joints = _joints; } - _inputDevice->update(deltaTime, inputCalibrationData, joints, _prevJoints); + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, joints, _prevJoints); + }); + _prevJoints = joints; } diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index eb55d84664..fdb7bb17fe 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -136,7 +136,12 @@ void SixenseManager::setSixenseFilter(bool filter) { void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { BAIL_IF_NOT_LOADED - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + }); + if (_inputDevice->_requestReset) { _container->requestReset(); _inputDevice->_requestReset = false; diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index 9e97b3791c..a4e00013f1 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -12,13 +12,17 @@ if (APPLE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() - link_hifi_libraries(shared gl gpu plugins display-plugins input-plugins) + link_hifi_libraries(shared gl gpu plugins ui display-plugins input-plugins) include_hifi_library_headers(octree) add_dependency_external_projects(LibOVR) find_package(LibOVR REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES}) + find_library(COCOA_LIBRARY Cocoa) + find_library(IOKIT_LIBRARY IOKit) + target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES} ${COCOA_LIBRARY} ${IOKIT_LIBRARY}) + + endif() diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 34e36d6a6b..6ceddec11b 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -16,7 +17,12 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -39,7 +45,7 @@ void OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo = FrameInfo(); _currentRenderFrameInfo.predictedDisplayTime = _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _trackingState = ovrHmd_GetTrackingState(_hmd, _currentRenderFrameInfo.predictedDisplayTime); - _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose); + _currentRenderFrameInfo.rawRenderPose = _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose); Lock lock(_mutex); _frameInfos[frameIndex] = _currentRenderFrameInfo; } @@ -72,14 +78,34 @@ bool OculusLegacyDisplayPlugin::isSupported() const { } bool OculusLegacyDisplayPlugin::internalActivate() { - Parent::internalActivate(); - + if (!Parent::internalActivate()) { + return false; + } + if (!(ovr_Initialize(nullptr))) { Q_ASSERT(false); qFatal("Failed to Initialize SDK"); return false; } + + _hmdWindow = new GLWindow(); + _hmdWindow->create(); + _hmdWindow->createContext(_container->getPrimaryContext()); + auto hmdScreen = qApp->screens()[_hmdScreen]; + auto hmdGeometry = hmdScreen->geometry(); + _hmdWindow->setGeometry(hmdGeometry); + _hmdWindow->showFullScreen(); + + _hmdWindow->makeCurrent(); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + _hmdWindow->swapBuffers(); + + _container->makeRenderingContextCurrent(); + + QOpenGLContextWrapper(_hmdWindow->context()).moveToThread(_presentThread); + _hswDismissed = false; _hmd = ovrHmd_Create(0); if (!_hmd) { @@ -96,7 +122,8 @@ bool OculusLegacyDisplayPlugin::internalActivate() { ovrMatrix4f ovrPerspectiveProjection = ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); - _eyeOffsets[eye] = glm::translate(mat4(), toGlm(erd.HmdToEyeViewOffset)); + _ovrEyeOffsets[eye] = erd.HmdToEyeViewOffset; + _eyeOffsets[eye] = glm::translate(mat4(), -1.0f * toGlm(_ovrEyeOffsets[eye])); eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); }); @@ -121,6 +148,11 @@ void OculusLegacyDisplayPlugin::internalDeactivate() { ovrHmd_Destroy(_hmd); _hmd = nullptr; ovr_Shutdown(); + _hmdWindow->showNormal(); + _hmdWindow->destroy(); + _hmdWindow->deleteLater(); + _hmdWindow = nullptr; + _container->makeRenderingContextCurrent(); } // DLL based display plugins MUST initialize GLEW inside the DLL code. @@ -131,20 +163,21 @@ void OculusLegacyDisplayPlugin::customizeContext() { glewInit(); glGetError(); }); + _hmdWindow->requestActivate(); + QThread::msleep(1000); Parent::customizeContext(); -#if 0 ovrGLConfig config; memset(&config, 0, sizeof(ovrRenderAPIConfig)); auto& header = config.Config.Header; header.API = ovrRenderAPI_OpenGL; header.BackBufferSize = _hmd->Resolution; header.Multisample = 1; - int distortionCaps = ovrDistortionCap_TimeWarp; + int distortionCaps = ovrDistortionCap_TimeWarp | ovrDistortionCap_Vignette; memset(_eyeTextures, 0, sizeof(ovrTexture) * 2); ovr_for_each_eye([&](ovrEyeType eye) { auto& header = _eyeTextures[eye].Header; header.API = ovrRenderAPI_OpenGL; - header.TextureSize = { (int)_desiredFramebufferSize.x, (int)_desiredFramebufferSize.y }; + header.TextureSize = { (int)_renderTargetSize.x, (int)_renderTargetSize.y }; header.RenderViewport.Size = header.TextureSize; header.RenderViewport.Size.w /= 2; if (eye == ovrEye_Right) { @@ -152,29 +185,57 @@ void OculusLegacyDisplayPlugin::customizeContext() { } }); + if (_hmdWindow->makeCurrent()) { #ifndef NDEBUG - ovrBool result = -#endif - ovrHmd_ConfigureRendering(_hmd, &config.Config, distortionCaps, _eyeFovs, _eyeRenderDescs); - assert(result); + ovrBool result = #endif + ovrHmd_ConfigureRendering(_hmd, &config.Config, distortionCaps, _eyeFovs, _eyeRenderDescs); + assert(result); + _hmdWindow->doneCurrent(); + } + } -#if 0 void OculusLegacyDisplayPlugin::uncustomizeContext() { - HmdDisplayPlugin::uncustomizeContext(); + _hmdWindow->doneCurrent(); + QOpenGLContextWrapper(_hmdWindow->context()).moveToThread(qApp->thread()); + Parent::uncustomizeContext(); } -void OculusLegacyDisplayPlugin::internalPresent() { - ovrHmd_BeginFrame(_hmd, 0); - ovr_for_each_eye([&](ovrEyeType eye) { - reinterpret_cast(_eyeTextures[eye]).OGL.TexId = _currentSceneTexture; - }); - ovrHmd_EndFrame(_hmd, _eyePoses, _eyeTextures); -} +void OculusLegacyDisplayPlugin::hmdPresent() { + if (!_hswDismissed) { + ovrHSWDisplayState hswState; + ovrHmd_GetHSWDisplayState(_hmd, &hswState); + if (hswState.Displayed) { + ovrHmd_DismissHSWDisplay(_hmd); + } -#endif + } + auto r = glm::quat_cast(_currentPresentFrameInfo.presentPose); + ovrQuatf ovrRotation = { r.x, r.y, r.z, r.w }; + ovrPosef eyePoses[2]; + memset(eyePoses, 0, sizeof(ovrPosef) * 2); + eyePoses[0].Orientation = eyePoses[1].Orientation = ovrRotation; + + GLint texture = oglplus::GetName(_compositeFramebuffer->color); + auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + if (_hmdWindow->makeCurrent()) { + glClearColor(0, 0.4, 0.8, 1); + glClear(GL_COLOR_BUFFER_BIT); + ovrHmd_BeginFrame(_hmd, _currentPresentFrameIndex); + glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(sync); + ovr_for_each_eye([&](ovrEyeType eye) { + reinterpret_cast(_eyeTextures[eye]).OGL.TexId = texture; + }); + ovrHmd_EndFrame(_hmd, eyePoses, _eyeTextures); + _hmdWindow->doneCurrent(); + } + static auto widget = _container->getPrimaryWidget(); + widget->makeCurrent(); +} int OculusLegacyDisplayPlugin::getHmdScreen() const { return _hmdScreen; @@ -184,4 +245,3 @@ float OculusLegacyDisplayPlugin::getTargetFrameRate() const { return TARGET_RATE_OculusLegacy; } - diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index bebf3fa2c7..5900ad4c58 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -14,42 +14,43 @@ #include const float TARGET_RATE_OculusLegacy = 75.0f; +class GLWindow; class OculusLegacyDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: OculusLegacyDisplayPlugin(); - virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } + bool isSupported() const override; + const QString& getName() const override { return NAME; } - virtual int getHmdScreen() const override; + int getHmdScreen() const override; // Stereo specific methods - virtual void resetSensors() override; - virtual void beginFrameRender(uint32_t frameIndex) override; + void resetSensors() override; + void beginFrameRender(uint32_t frameIndex) override; - virtual float getTargetFrameRate() const override; + float getTargetFrameRate() const override; protected: - virtual bool internalActivate() override; - virtual void internalDeactivate() override; + bool internalActivate() override; + void internalDeactivate() override; - virtual void customizeContext() override; - void hmdPresent() override {} + void customizeContext() override; + void uncustomizeContext() override; + void hmdPresent() override; bool isHmdMounted() const override { return true; } -#if 0 - virtual void uncustomizeContext() override; - virtual void internalPresent() override; -#endif private: static const QString NAME; + GLWindow* _hmdWindow{ nullptr }; ovrHmd _hmd; mutable ovrTrackingState _trackingState; ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrVector3f _ovrEyeOffsets[2]; + ovrFovPort _eyeFovs[2]; - //ovrTexture _eyeTextures[2]; // FIXME - not currently in use + ovrTexture _eyeTextures[2]; // FIXME - not currently in use mutable int _hmdScreen { -1 }; bool _hswDismissed { false }; }; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index c1ed5aa880..fab383a955 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,6 +11,8 @@ #include "ViveControllerManager.h" +#include + #include #include #include @@ -48,9 +50,11 @@ static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; static const QString RENDER_CONTROLLERS = "Render Hand Controllers"; const QString ViveControllerManager::NAME = "OpenVR"; +static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); +static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); bool ViveControllerManager::isSupported() const { - return !isOculusPresent() && vr::VR_IsHmdPresent(); + return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); } bool ViveControllerManager::activate() { @@ -211,9 +215,13 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); auto userInputMapper = DependencyManager::get(); + // because update mutates the internal state we need to lock + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + }); + if (_inputDevice->_trackedControllers == 0 && _registeredWithInputMapper) { userInputMapper->removeDevice(_inputDevice->_deviceID); _registeredWithInputMapper = false; @@ -270,7 +278,8 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) { auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i); bool pressed = 0 != (controllerState.ulButtonPressed & mask); - handleButtonEvent(deltaTime, i, pressed, isLeftHand); + bool touched = 0 != (controllerState.ulButtonTouched & mask); + handleButtonEvent(deltaTime, i, pressed, touched, isLeftHand); } // process each axis @@ -314,20 +323,26 @@ enum ViveButtonChannel { // These functions do translation from the Steam IDs to the standard controller IDs -void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool isLeftHand) { - if (!pressed) { - return; - } +void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand) { using namespace controller; - if (button == vr::k_EButton_ApplicationMenu) { - _buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU); - } else if (button == vr::k_EButton_Grip) { - _buttonPressedMap.insert(isLeftHand ? LB : RB); - } else if (button == vr::k_EButton_SteamVR_Trigger) { - _buttonPressedMap.insert(isLeftHand ? LT : RT); - } else if (button == vr::k_EButton_SteamVR_Touchpad) { - _buttonPressedMap.insert(isLeftHand ? LS : RS); + + if (pressed) { + if (button == vr::k_EButton_ApplicationMenu) { + _buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU); + } else if (button == vr::k_EButton_Grip) { + _buttonPressedMap.insert(isLeftHand ? LEFT_GRIP : RIGHT_GRIP); + } else if (button == vr::k_EButton_SteamVR_Trigger) { + _buttonPressedMap.insert(isLeftHand ? LT : RT); + } else if (button == vr::k_EButton_SteamVR_Touchpad) { + _buttonPressedMap.insert(isLeftHand ? LS : RS); + } + } + + if (touched) { + if (button == vr::k_EButton_SteamVR_Touchpad) { + _buttonPressedMap.insert(isLeftHand ? LS_TOUCH : RS_TOUCH); + } } } @@ -424,18 +439,28 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(LY, "LY"), makePair(RX, "RX"), makePair(RY, "RY"), - // trigger analogs + + // capacitive touch on the touch pad + makePair(LS_TOUCH, "LSTouch"), + makePair(RS_TOUCH, "RSTouch"), + + // touch pad press + makePair(LS, "LS"), + makePair(RS, "RS"), + + // triggers makePair(LT, "LT"), makePair(RT, "RT"), - makePair(LB, "LB"), - makePair(RB, "RB"), + // low profile side grip button. + makePair(LEFT_GRIP, "LeftGrip"), + makePair(RIGHT_GRIP, "RightGrip"), - makePair(LS, "LS"), - makePair(RS, "RS"), + // 3d location of controller makePair(LEFT_HAND, "LeftHand"), makePair(RIGHT_HAND, "RightHand"), + // app button above trackpad. Input::NamedPair(Input(_deviceID, LEFT_APP_MENU, ChannelType::BUTTON), "LeftApplicationMenu"), Input::NamedPair(Input(_deviceID, RIGHT_APP_MENU, ChannelType::BUTTON), "RightApplicationMenu"), }; diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index d3645304c5..d55d4e726c 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -59,7 +59,7 @@ private: virtual void focusOutEvent() override; void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); - void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool isLeftHand); + void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 9b11fa73cc..2a050d183e 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -17,7 +17,7 @@ Script.load("system/edit.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); Script.load("system/controllers/handControllerGrab.js"); +Script.load("system/controllers/handControllerPointer.js"); Script.load("system/controllers/squeezeHands.js"); Script.load("system/controllers/grab.js"); Script.load("system/dialTone.js"); -Script.load("system/depthReticle.js"); \ No newline at end of file diff --git a/scripts/developer/tests/scriptableResource/lib.js b/scripts/developer/tests/scriptableResource/lib.js index 5241d0968e..255b3bd036 100644 --- a/scripts/developer/tests/scriptableResource/lib.js +++ b/scripts/developer/tests/scriptableResource/lib.js @@ -26,8 +26,8 @@ function getFrame(callback) { } function makeFrame(state) { - if (state == Resource.State.FAILED) { throw "Failed to load frame"; } - if (state != Resource.State.FINISHED) { return; } + if (state === Resource.State.FAILED) { throw "Failed to load frame"; } + if (state !== Resource.State.FINISHED) { return; } var pictureFrameProperties = { name: 'scriptableResourceTest Picture Frame', @@ -50,7 +50,6 @@ function getFrame(callback) { position.x += - 5 * Math.sin(rads); position.z += - 5 * Math.cos(rads); - print(JSON.stringify(position)); return position; } } @@ -67,10 +66,10 @@ function prefetch(callback) { var filepath = MOVIE_URL + padded + '.jpg'; var texture = TextureCache.prefetch(filepath); frames.push(texture); - if (!texture.state == Resource.State.FINISHED) { + if (texture.state !== Resource.State.FINISHED) { numLoading++; texture.stateChanged.connect(function(state) { - if (state == Resource.State.FAILED || state == Resource.State.FINISHED) { + if (state === Resource.State.FAILED || state === Resource.State.FINISHED) { --numLoading; if (!numLoading) { callback(frames); } } diff --git a/scripts/developer/tests/scriptableResource/loadPerfTest.js b/scripts/developer/tests/scriptableResource/loadPerfTest.js new file mode 100644 index 0000000000..17feef264d --- /dev/null +++ b/scripts/developer/tests/scriptableResource/loadPerfTest.js @@ -0,0 +1,34 @@ +// +// loadPerfTest.js +// scripts/developer/tests/scriptableResource +// +// Created by Zach Pomerantz on 4/27/16. +// Copyright 2016 High Fidelity, Inc. +// +// Preloads 158 textures 50 times for performance profiling. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var TIMES = 50; + +Script.include([ + '../../../developer/utilities/cache/cacheStats.js', + 'lib.js', +], function() { + var fetch = function() { + prefetch(function(frames) { + while (frames.length) { frames.pop(); } + Script.requestGarbageCollection(); + + if (--TIMES > 0) { + // Pause a bit to avoid a deadlock + var DEADLOCK_AVOIDANCE_TIMEOUT = 100; + Script.setTimeout(fetch, DEADLOCK_AVOIDANCE_TIMEOUT); + } + }); + }; + + fetch(); +}); diff --git a/scripts/developer/tests/scriptableResource/movieTest.js b/scripts/developer/tests/scriptableResource/movieTest.js index 61b2bf7942..557c53af6a 100644 --- a/scripts/developer/tests/scriptableResource/movieTest.js +++ b/scripts/developer/tests/scriptableResource/movieTest.js @@ -1,5 +1,5 @@ // -// testMovie.js +// movieTest.js // scripts/developer/tests/scriptableResource // // Created by Zach Pomerantz on 4/27/16. diff --git a/scripts/developer/tests/scriptableResource/prefetchTest.js b/scripts/developer/tests/scriptableResource/prefetchTest.js index cda805967e..07acb462cb 100644 --- a/scripts/developer/tests/scriptableResource/prefetchTest.js +++ b/scripts/developer/tests/scriptableResource/prefetchTest.js @@ -1,5 +1,5 @@ // -// testPrefetch.js +// prefetchTest.js // scripts/developer/tests/scriptableResource // // Created by Zach Pomerantz on 4/27/16. diff --git a/scripts/developer/tests/viveTouchpadTest.js b/scripts/developer/tests/viveTouchpadTest.js new file mode 100644 index 0000000000..913da5888d --- /dev/null +++ b/scripts/developer/tests/viveTouchpadTest.js @@ -0,0 +1,79 @@ +// +// viveTouchpadTest.js +// +// Anthony J. Thibault +// Copyright 2016 High Fidelity, Inc. +// +// An example of reading touch and move events from the vive controller touch pad. +// +// It will spawn a gray cube in front of you, then as you use the right touch pad, +// the cube should turn green and respond to the motion of your thumb on the pad. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var GRAY = {red: 57, green: 57, blue: 57}; +var GREEN = {red: 0, green: 255, blue: 0}; +var ZERO = {x: 0, y: 0, z: 0}; +var Y_AXIS = {x: 0, y: 1, x: 0}; +var ROT_Y_90 = Quat.angleAxis(Y_AXIS, 90.0); + +var boxEntity; +var boxPosition; +var boxZAxis, boxYAxis; +var prevThumbDown = false; + +function init() { + boxPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))); + var front = Quat.getFront(Camera.getOrientation()); + boxZAxis = Vec3.normalize(Vec3.cross(front, Y_AXIS)); + boxYAxis = Vec3.normalize(Vec3.cross(boxZAxis, front)); + + boxEntity = Entities.addEntity({ + type: "Box", + position: boxPosition, + dimentions: {x: 0.25, y: 0.25, z: 0.25}, + color: GRAY, + gravity: ZERO, + visible: true, + locked: false, + lifetime: 60000 + }); +} + +function shutdown() { + Entities.deleteEntity(boxEntity); +} +Script.scriptEnding.connect(shutdown); + +function viveIsConnected() { + return Controller.Hardware.Vive; +} + +function update(dt) { + if (viveIsConnected()) { + var thumbDown = Controller.getValue(Controller.Hardware.Vive.RSTouch); + if (thumbDown) { + var x = Controller.getValue(Controller.Hardware.Vive.RX); + var y = Controller.getValue(Controller.Hardware.Vive.RY); + var xOffset = Vec3.multiply(boxZAxis, x); + var yOffset = Vec3.multiply(boxYAxis, y); + var offset = Vec3.sum(xOffset, yOffset); + Entities.editEntity(boxEntity, {position: Vec3.sum(boxPosition, offset)}); + } + if (thumbDown && !prevThumbDown) { + Entities.editEntity(boxEntity, {color: GREEN}); + } + if (!thumbDown && prevThumbDown) { + Entities.editEntity(boxEntity, {color: GRAY}); + } + prevThumbDown = thumbDown; + } +} + +Script.update.connect(update); + +init(); + + + diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index fcc7a6d291..a7e08451bc 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -29,6 +29,10 @@ var IDENTITY_QUAT = { var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with handControllerGrab.js var GRAB_USER_DATA_KEY = "grabKey"; // shared with handControllerGrab.js +var MSECS_PER_SEC = 1000.0; +var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; +var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; + var DEFAULT_GRABBABLE_DATA = { grabbable: true, invertSolidWhileHeld: false @@ -335,6 +339,9 @@ Grabber.prototype.pressEvent = function(event) { mouse.startDrag(event); + var now = Date.now(); + this.lastHeartBeat = 0; + var clickedEntity = pickResults.entityID; var entityProperties = Entities.getEntityProperties(clickedEntity) this.startPosition = entityProperties.position; @@ -376,7 +383,16 @@ Grabber.prototype.pressEvent = function(event) { if(!entityIsGrabbedByOther(this.entityID)){ this.moveEvent(event); } - + + var args = "mouse"; + Entities.callEntityMethod(this.entityID, "startDistanceGrab", args); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: this.entityID + })); + + // TODO: play sounds again when we aren't leaking AudioInjector threads //Audio.playSound(grabSound, { position: entityProperties.position, volume: VOLUME }); } @@ -394,17 +410,41 @@ Grabber.prototype.releaseEvent = function(event) { beacon.disable(); + var args = "mouse"; + Entities.callEntityMethod(this.entityID, "releaseGrab", args); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.entityID, + joint: "mouse" + })); + + // TODO: play sounds again when we aren't leaking AudioInjector threads //Audio.playSound(releaseSound, { position: entityProperties.position, volume: VOLUME }); } } + +Grabber.prototype.heartBeat = function(entityID) { + var now = Date.now(); + if (now - this.lastHeartBeat > HEART_BEAT_INTERVAL) { + var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + data["heartBeat"] = now; + setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); + this.lastHeartBeat = now; + } +}; + + Grabber.prototype.moveEvent = function(event) { if (!this.isGrabbing) { return; } mouse.updateDrag(event); + this.heartBeat(this.entityID); + // see if something added/restored gravity var entityProperties = Entities.getEntityProperties(this.entityID); if (Vec3.length(entityProperties.gravity) != 0) { diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 5924d566b4..0fdaed58f2 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ -Script.include("../libraries/utils.js"); +Script.include("/~/system/libraries/utils.js"); // @@ -28,7 +28,7 @@ var WANT_DEBUG_SEARCH_NAME = null; var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab +var TRIGGER_GRAB_VALUE = 0.75; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; @@ -102,7 +102,6 @@ var ZERO_VEC = { }; var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; -var MSEC_PER_SEC = 1000.0; // these control how long an abandoned pointer line or action will hang around var LIFETIME = 10; @@ -740,10 +739,6 @@ function MyController(hand) { }; this.propsArePhysical = function(props) { - if (!props.dynamic && props.parentID != MyAvatar.sessionUUID) { - // if we have parented something, don't do this check on dynamic. - return false; - } var isPhysical = (props.shapeType && props.shapeType != 'none'); return isPhysical; } @@ -837,7 +832,7 @@ function MyController(hand) { this.search = function() { this.grabbedEntity = null; this.isInitialGrab = false; - this.doubleParentGrab = false; + this.shouldResetParentOnRelease = false; this.checkForStrayChildren(); @@ -914,7 +909,7 @@ function MyController(hand) { candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); var forbiddenNames = ["Grab Debug Entity", "grab pointer"]; - var forbiddenTypes = ['Unknown', 'Light', 'ParticleEffect', 'PolyLine', 'Zone']; + var forbiddenTypes = ['Unknown', 'Light', 'PolyLine', 'Zone']; var minDistance = PICK_MAX_DISTANCE; var i, props, distance, grabbableData; @@ -1019,6 +1014,10 @@ function MyController(hand) { if (this.state == STATE_SEARCHING) { this.setState(STATE_NEAR_GRABBING); } else { // (this.state == STATE_HOLD_SEARCHING) + // if there was already an action, we'll need to set the parent back to null once we release + this.shouldResetParentOnRelease = true; + this.previousParentID = props.parentID; + this.previousParentJointIndex = props.parentJointIndex; this.setState(STATE_HOLD); } return; @@ -1064,7 +1063,7 @@ function MyController(hand) { // it's not physical and it's already held via parenting. go ahead and grab it, but // save off the current parent and joint. this wont always be right if there are more than // two grabs and the order of release isn't opposite of the order of grabs. - this.doubleParentGrab = true; + this.shouldResetParentOnRelease = true; this.previousParentID = props.parentID; this.previousParentJointIndex = props.parentJointIndex; if (this.state == STATE_SEARCHING) { @@ -1149,7 +1148,7 @@ function MyController(hand) { if (this.actionID === NULL_UUID) { this.actionID = null; } - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); if (this.actionID !== null) { this.setState(STATE_CONTINUE_DISTANCE_HOLDING); @@ -1184,7 +1183,7 @@ function MyController(hand) { var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds this.currentObjectTime = now; // the action was set up when this.distanceHolding was called. update the targets. @@ -1302,7 +1301,7 @@ function MyController(hand) { ttl: ACTION_TTL }); if (success) { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); } else { print("continueDistanceHolding -- updateAction failed"); } @@ -1327,7 +1326,7 @@ function MyController(hand) { return false; } var now = Date.now(); - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); return true; }; @@ -1436,6 +1435,10 @@ function MyController(hand) { if (!this.setupHoldAction()) { return; } + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: this.grabbedEntity + })); } else { // grab entity via parenting this.actionID = null; @@ -1563,7 +1566,7 @@ function MyController(hand) { var now = Date.now(); var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters - var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds + var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds if (deltaTime > 0.0) { var worldDeltaPosition = Vec3.subtract(props.position, this.currentObjectPosition); @@ -1590,7 +1593,7 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueNearGrab"); } - if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { + if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl var success = Entities.updateAction(this.grabbedEntity, this.actionID, { hand: this.hand === RIGHT_HAND ? "right" : "left", @@ -1603,7 +1606,7 @@ function MyController(hand) { ignoreIK: this.ignoreIK }); if (success) { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); } else { print("continueNearGrabbing -- updateAction failed"); Entities.deleteAction(this.grabbedEntity, this.actionID); @@ -1838,12 +1841,12 @@ function MyController(hand) { // things that are held by parenting and dropped with no velocity will end up as "static" in bullet. If // it looks like the dropped thing should fall, give it a little velocity. - var props = Entities.getEntityProperties(entityID, ["parentID", "velocity"]) + var props = Entities.getEntityProperties(entityID, ["parentID", "velocity", "dynamic", "shapeType"]) var parentID = props.parentID; var forceVelocity = false; var doSetVelocity = false; - if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID) { + if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && this.propsArePhysical(props)) { // TODO: EntityScriptingInterface::convertLocationToScriptSemantics should be setting up // props.velocity to be a world-frame velocity and localVelocity to be vs parent. Until that // is done, we use a measured velocity here so that things held via a bumper-grab / parenting-grab @@ -1881,7 +1884,7 @@ function MyController(hand) { } data = null; - } else if (this.doubleParentGrab) { + } else if (this.shouldResetParentOnRelease) { // we parent-grabbed this from another parent grab. try to put it back where we found it. var deactiveProps = { parentID: this.previousParentID, @@ -1892,7 +1895,8 @@ function MyController(hand) { Entities.editEntity(entityID, deactiveProps); } else if (noVelocity) { Entities.editEntity(entityID, {velocity: {x: 0.0, y: 0.0, z: 0.0}, - angularVelocity: {x: 0.0, y: 0.0, z: 0.0}}); + angularVelocity: {x: 0.0, y: 0.0, z: 0.0}, + dynamic: data["dynamic"]}); } } else { data = null; diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js new file mode 100644 index 0000000000..6e9fe17077 --- /dev/null +++ b/scripts/system/controllers/handControllerPointer.js @@ -0,0 +1,456 @@ +"use strict"; +/*jslint vars: true, plusplus: true*/ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print */ + +// +// handControllerPointer.js +// examples/controllers +// +// Created by Howard Stearns on 2016/04/22 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Control the "mouse" using hand controller. (HMD and desktop.) +// For now: +// Hydra thumb button 3 is left-mouse, button 4 is right-mouse. +// A click in the center of the vive thumb pad is left mouse. Vive menu button is context menu (right mouse). +// First-person only. +// Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed. +// (For now, the thumb buttons on both controllers are always on.) +// When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD. +// Otherwise, the active hand controller shows a red ball where a click will act. +// +// Bugs: +// On Windows, the upper left corner of Interface must be in the upper left corner of the screen, and the title bar must be 50px high. (System bug.) +// While hardware mouse move switches to mouse move, hardware mouse click (without amove) does not. + + +// UTILITIES ------------- +// + +// Utility to make it easier to setup and disconnect cleanly. +function setupHandler(event, handler) { + event.connect(handler); + Script.scriptEnding.connect(function () { + event.disconnect(handler); + }); +} +// If some capability is not available until expiration milliseconds after the last update. +function TimeLock(expiration) { + var last = 0; + this.update = function (optionalNow) { + last = optionalNow || Date.now(); + }; + this.expired = function (optionalNow) { + return ((optionalNow || Date.now()) - last) > expiration; + }; +} +var handControllerLockOut = new TimeLock(2000); + +// Calls onFunction() or offFunction() when swtich(on), but only if it is to a new value. +function LatchedToggle(onFunction, offFunction, state) { + this.getState = function () { + return state; + }; + this.setState = function (on) { + if (state === on) { + return; + } + state = on; + if (on) { + onFunction(); + } else { + offFunction(); + } + }; +} + +// VERTICAL FIELD OF VIEW --------- +// +// Cache the verticalFieldOfView setting and update it every so often. +var verticalFieldOfView, DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees +function updateFieldOfView() { + verticalFieldOfView = Settings.getValue('fieldOfView') || DEFAULT_VERTICAL_FIELD_OF_VIEW; +} + +// SHIMS ---------- +// +var weMovedReticle = false; +function ignoreMouseActivity() { + // If we're paused, or if change in cursor position is from this script, not the hardware mouse. + if (!Reticle.allowMouseCapture) { + return true; + } + // Only we know if we moved it, which is why this script has to replace depthReticle.js + if (!weMovedReticle) { + return false; + } + weMovedReticle = false; + return true; +} +var setReticlePosition = function (point2d) { + weMovedReticle = true; + Reticle.setPosition(point2d); +}; + +// Generalizations of utilities that work with system and overlay elements. +function findRayIntersection(pickRay) { + // Check 3D overlays and entities. Argument is an object with origin and direction. + var result = Overlays.findRayIntersection(pickRay); + if (!result.intersects) { + result = Entities.findRayIntersection(pickRay, true); + } + return result; +} +function isPointingAtOverlay(optionalHudPosition2d) { + return Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(optionalHudPosition2d || Reticle.position); +} + +// Generalized HUD utilities, with or without HMD: +// These two "vars" are for documentation. Do not change their values! +var SPHERICAL_HUD_DISTANCE = 1; // meters. +var PLANAR_PERPENDICULAR_HUD_DISTANCE = SPHERICAL_HUD_DISTANCE; +function calculateRayUICollisionPoint(position, direction) { + // Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection. + if (HMD.active) { + return HMD.calculateRayUICollisionPoint(position, direction); + } + // interect HUD plane, 1m in front of camera, using formula: + // scale = hudNormal dot (hudPoint - position) / hudNormal dot direction + // intersection = postion + scale*direction + var hudNormal = Quat.getFront(Camera.getOrientation()); + var hudPoint = Vec3.sum(Camera.getPosition(), hudNormal); // must also scale if PLANAR_PERPENDICULAR_HUD_DISTANCE!=1 + var denominator = Vec3.dot(hudNormal, direction); + if (denominator === 0) { + return null; + } // parallel to plane + var numerator = Vec3.dot(hudNormal, Vec3.subtract(hudPoint, position)); + var scale = numerator / denominator; + return Vec3.sum(position, Vec3.multiply(scale, direction)); +} +var DEGREES_TO_HALF_RADIANS = Math.PI / 360; +function overlayFromWorldPoint(point) { + // Answer the 2d pixel-space location in the HUD that covers the given 3D point. + // REQUIRES: that the 3d point be on the hud surface! + // Note that this is based on the Camera, and doesn't know anything about any + // ray that may or may not have been used to compute the point. E.g., the + // overlay point is NOT the intersection of some non-camera ray with the HUD. + if (HMD.active) { + return HMD.overlayFromWorldPoint(point); + } + var cameraToPoint = Vec3.subtract(point, Camera.getPosition()); + var cameraX = Vec3.dot(cameraToPoint, Quat.getRight(Camera.getOrientation())); + var cameraY = Vec3.dot(cameraToPoint, Quat.getUp(Camera.getOrientation())); + var size = Controller.getViewportDimensions(); + var hudHeight = 2 * Math.tan(verticalFieldOfView * DEGREES_TO_HALF_RADIANS); // must adjust if PLANAR_PERPENDICULAR_HUD_DISTANCE!=1 + var hudWidth = hudHeight * size.x / size.y; + var horizontalFraction = (cameraX / hudWidth + 0.5); + var verticalFraction = 1 - (cameraY / hudHeight + 0.5); + var horizontalPixels = size.x * horizontalFraction; + var verticalPixels = size.y * verticalFraction; + return { x: horizontalPixels, y: verticalPixels }; +} + +// MOUSE ACTIVITY -------- +// +var isSeeking = false; +var averageMouseVelocity = 0, lastIntegration = 0, lastMouse; +var WEIGHTING = 1 / 20; // simple moving average over last 20 samples +var ONE_MINUS_WEIGHTING = 1 - WEIGHTING; +var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 20; +function isShakingMouse() { // True if the person is waving the mouse around trying to find it. + var now = Date.now(), mouse = Reticle.position, isShaking = false; + if (lastIntegration && (lastIntegration !== now)) { + var velocity = Vec3.length(Vec3.subtract(mouse, lastMouse)) / (now - lastIntegration); + averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * velocity); + if (averageMouseVelocity > AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO) { + isShaking = true; + } + } + lastIntegration = now; + lastMouse = mouse; + return isShaking; +} +var NON_LINEAR_DIVISOR = 2; +var MINIMUM_SEEK_DISTANCE = 0.01; +function updateSeeking() { + if (!Reticle.visible || isShakingMouse()) { + isSeeking = true; + } // e.g., if we're about to turn it on with first movement. + if (!isSeeking) { + return; + } + averageMouseVelocity = lastIntegration = 0; + var lookAt2D = HMD.getHUDLookAtPosition2D(); + if (!lookAt2D) { + print('Cannot seek without lookAt position'); + return; + } // E.g., if parallel to location in HUD + var copy = Reticle.position; + function updateDimension(axis) { + var distanceBetween = lookAt2D[axis] - Reticle.position[axis]; + var move = distanceBetween / NON_LINEAR_DIVISOR; + if (Math.abs(move) < MINIMUM_SEEK_DISTANCE) { + return false; + } + copy[axis] += move; + return true; + } + var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit. + if (okX && okY) { + isSeeking = false; + } else { + Reticle.setPosition(copy); // Not setReticlePosition + } +} + +var mouseCursorActivity = new TimeLock(5000); +var APPARENT_MAXIMUM_DEPTH = 100.0; // this is a depth at which things all seem sufficiently distant +function updateMouseActivity(isClick) { + if (ignoreMouseActivity()) { + return; + } + var now = Date.now(); + mouseCursorActivity.update(now); + if (isClick) { + return; + } // Bug: mouse clicks should keep going. Just not hand controller clicks + handControllerLockOut.update(now); + Reticle.visible = true; +} +function expireMouseCursor(now) { + if (!isPointingAtOverlay() && mouseCursorActivity.expired(now)) { + Reticle.visible = false; + } +} +function onMouseMove() { + // Display cursor at correct depth (as in depthReticle.js), and updateMouseActivity. + if (ignoreMouseActivity()) { + return; + } + + if (HMD.active) { // set depth + updateSeeking(); + if (isPointingAtOverlay()) { + Reticle.setDepth(SPHERICAL_HUD_DISTANCE); // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + } else { + var result = findRayIntersection(Camera.computePickRay(Reticle.position.x, Reticle.position.y)); + var depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; + Reticle.setDepth(depth); + } + } + updateMouseActivity(); // After the above, just in case the depth movement is awkward when becoming visible. +} +function onMouseClick() { + updateMouseActivity(true); +} +setupHandler(Controller.mouseMoveEvent, onMouseMove); +setupHandler(Controller.mousePressEvent, onMouseClick); +setupHandler(Controller.mouseDoublePressEvent, onMouseClick); + +// CONTROLLER MAPPING --------- +// + +var activeHand = Controller.Standard.RightHand; +function toggleHand() { + if (activeHand === Controller.Standard.RightHand) { + activeHand = Controller.Standard.LeftHand; + } else { + activeHand = Controller.Standard.RightHand; + } +} + +// Create clickMappings as needed, on demand. +var clickMappings = {}, clickMapping, clickMapToggle; +var hardware; // undefined +function checkHardware() { + var newHardware = Controller.Hardware.Hydra ? 'Hydra' : (Controller.Hardware.Vive ? 'Vive' : null); // not undefined + if (hardware === newHardware) { + return; + } + print('Setting mapping for new controller hardware:', newHardware); + if (clickMapToggle) { + clickMapToggle.setState(false); + } + hardware = newHardware; + if (clickMappings[hardware]) { + clickMapping = clickMappings[hardware]; + } else { + clickMapping = Controller.newMapping(Script.resolvePath('') + '-click-' + hardware); + Script.scriptEnding.connect(clickMapping.disable); + function mapToAction(button, action) { + clickMapping.from(Controller.Hardware[hardware][button]).peek().to(Controller.Actions[action]); + } + function makeHandToggle(button, hand, optionalWhen) { + var whenThunk = optionalWhen || function () { + return true; + }; + function maybeToggle() { + if (activeHand !== Controller.Standard[hand]) { + toggleHand(); + } + + } + clickMapping.from(Controller.Hardware[hardware][button]).peek().when(whenThunk).to(maybeToggle); + } + function makeViveWhen(click, x, y) { + var viveClick = Controller.Hardware.Vive[click], + viveX = Controller.Standard[x], // Standard after filtering by mapping + viveY = Controller.Standard[y]; + return function () { + var clickValue = Controller.getValue(viveClick); + var xValue = Controller.getValue(viveX); + var yValue = Controller.getValue(viveY); + return clickValue && !xValue && !yValue; + }; + } + switch (hardware) { + case 'Hydra': + makeHandToggle('R3', 'RightHand'); + makeHandToggle('L3', 'LeftHand'); + + mapToAction('R3', 'ReticleClick'); + mapToAction('L3', 'ReticleClick'); + mapToAction('R4', 'ContextMenu'); + mapToAction('L4', 'ContextMenu'); + break; + case 'Vive': + // When touchpad click is NOT treated as movement, treat as left click + makeHandToggle('RS', 'RightHand', makeViveWhen('RS', 'RX', 'RY')); + makeHandToggle('LS', 'LeftHand', makeViveWhen('LS', 'LX', 'LY')); + clickMapping.from(Controller.Hardware.Vive.RS).when(makeViveWhen('RS', 'RX', 'RY')).to(Controller.Actions.ReticleClick); + clickMapping.from(Controller.Hardware.Vive.LS).when(makeViveWhen('LS', 'LX', 'LY')).to(Controller.Actions.ReticleClick); + mapToAction('RightApplicationMenu', 'ContextMenu'); + mapToAction('LeftApplicationMenu', 'ContextMenu'); + break; + } + clickMappings[hardware] = clickMapping; + } + clickMapToggle = new LatchedToggle(clickMapping.enable, clickMapping.disable); + clickMapToggle.setState(true); +} +checkHardware(); + +// VISUAL AID ----------- +// Same properties as handControllerGrab search sphere +var BALL_SIZE = 0.011; +var BALL_ALPHA = 0.5; +var fakeProjectionBall = Overlays.addOverlay("sphere", { + size: 5 * BALL_SIZE, + color: {red: 255, green: 10, blue: 10}, + ignoreRayIntersection: true, + alpha: BALL_ALPHA, + visible: false, + solid: true, + drawInFront: true // Even when burried inside of something, show it. +}); +var overlays = [fakeProjectionBall]; // If we want to try showing multiple balls and lasers. +Script.scriptEnding.connect(function () { + overlays.forEach(Overlays.deleteOverlay); +}); +var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. +function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD + if (!optionalEnableClicks) { + expireMouseCursor(); + } + if (!visualizationIsShowing) { + return; + } + visualizationIsShowing = false; + overlays.forEach(function (overlay) { + Overlays.editOverlay(overlay, {visible: false}); + }); +} +var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. +function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { + // Show an indication of where the cursor will appear when crossing a HUD element, + // and where in-world clicking will occur. + // + // There are a number of ways we could do this, but for now, it's a blue sphere that rolls along + // the HUD surface, and a red sphere that rolls along the 3d objects that will receive the click. + // We'll leave it to other scripts (like handControllerGrab) to show a search beam when desired. + + function intersection3d(position, direction) { + // Answer in-world intersection (entity or 3d overlay), or way-out point + var pickRay = {origin: position, direction: direction}; + var result = findRayIntersection(pickRay); + return result.intersects ? result.intersection : Vec3.sum(position, Vec3.multiply(MAX_RAY_SCALE, direction)); + } + + visualizationIsShowing = true; + // We'd rather in-world interactions be done at the termination of the hand beam + // -- intersection3d(controllerPosition, controllerDirection). Maybe have handControllerGrab + // direclty manipulate both entity and 3d overlay objects. + // For now, though, we present a false projection of the cursor onto whatever is below it. This is + // different from the hand beam termination because the false projection is from the camera, while + // the hand beam termination is from the hand. + var eye = Camera.getPosition(); + var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye)); + Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection}); + Reticle.visible = false; + + return visualizationIsShowing; // In case we change caller to act conditionally. +} + +// MAIN OPERATIONS ----------- +// +function update() { + var now = Date.now(); + if (!handControllerLockOut.expired(now)) { + return turnOffVisualization(); + } // Let them use mouse it in peace. + if (!Menu.isOptionChecked("First Person")) { + return turnOffVisualization(); + } // What to do? menus can be behind hand! + var controllerPose = Controller.getPoseValue(activeHand); + // Vive is effectively invalid when not in HMD + if (!controllerPose.valid || ((hardware === 'Vive') && !HMD.active)) { + return turnOffVisualization(); + } // Controller is cradled. + var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), + MyAvatar.position); + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); + + var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); + if (!hudPoint3d) { + print('Controller is parallel to HUD'); + return turnOffVisualization(); + } + var hudPoint2d = overlayFromWorldPoint(hudPoint3d); + + // We don't know yet if we'll want to make the cursor visble, but we need to move it to see if + // it's pointing at a QML tool (aka system overlay). + setReticlePosition(hudPoint2d); + // If there's a HUD element at the (newly moved) reticle, just make it visible and bail. + if (isPointingAtOverlay(hudPoint2d)) { + if (HMD.active) { // Doesn't hurt anything without the guard, but consider it documentation. + Reticle.depth = SPHERICAL_HUD_DISTANCE; // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + } + Reticle.visible = true; + return turnOffVisualization(true); + } + // We are not pointing at a HUD element (but it could be a 3d overlay). + updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); +} + +var UPDATE_INTERVAL = 20; // milliseconds. Script.update is too frequent. +var updater = Script.setInterval(update, UPDATE_INTERVAL); +Script.scriptEnding.connect(function () { + Script.clearInterval(updater); +}); + +// Check periodically for changes to setup. +var SETTINGS_CHANGE_RECHECK_INTERVAL = 10 * 1000; // milliseconds +function checkSettings() { + updateFieldOfView(); + checkHardware(); +} +checkSettings(); +var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL); +Script.scriptEnding.connect(function () { + Script.clearInterval(settingsChecker); +}); diff --git a/scripts/system/depthReticle.js b/scripts/system/depthReticle.js deleted file mode 100644 index 10d604f707..0000000000 --- a/scripts/system/depthReticle.js +++ /dev/null @@ -1,185 +0,0 @@ -// depthReticle.js -// examples -// -// Created by Brad Hefta-Gaub on 2/23/16. -// Copyright 2016 High Fidelity, Inc. -// -// When used in HMD, this script will make the reticle depth track to any clickable item in view. -// This script also handles auto-hiding the reticle after inactivity, as well as having the reticle -// seek the look at position upon waking up. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var APPARENT_2D_OVERLAY_DEPTH = 1.0; -var APPARENT_MAXIMUM_DEPTH = 100.0; // this is a depth at which things all seem sufficiently distant -var lastDepthCheckTime = Date.now(); -var desiredDepth = APPARENT_2D_OVERLAY_DEPTH; -var TIME_BETWEEN_DEPTH_CHECKS = 100; -var MINIMUM_DEPTH_ADJUST = 0.01; -var NON_LINEAR_DIVISOR = 2; -var MINIMUM_SEEK_DISTANCE = 0.01; - -var lastMouseMoveOrClick = Date.now(); -var lastMouseX = Reticle.position.x; -var lastMouseY = Reticle.position.y; -var HIDE_STATIC_MOUSE_AFTER = 3000; // 3 seconds -var shouldSeekToLookAt = false; -var fastMouseMoves = 0; -var averageMouseVelocity = 0; -var WEIGHTING = 1/20; // simple moving average over last 20 samples -var ONE_MINUS_WEIGHTING = 1 - WEIGHTING; -var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50; - -function showReticleOnMouseClick() { - Reticle.visible = true; - lastMouseMoveOrClick = Date.now(); // move or click -} - -Controller.mousePressEvent.connect(showReticleOnMouseClick); -Controller.mouseDoublePressEvent.connect(showReticleOnMouseClick); - -Controller.mouseMoveEvent.connect(function(mouseEvent) { - var now = Date.now(); - - // if the reticle is hidden, and we're not in away mode... - if (!Reticle.visible && Reticle.allowMouseCapture) { - Reticle.visible = true; - if (HMD.active) { - shouldSeekToLookAt = true; - } - } else { - // even if the reticle is visible, if we're in HMD mode, and the person is moving their mouse quickly (shaking it) - // then they are probably looking for it, and we should move into seekToLookAt mode - if (HMD.active && !shouldSeekToLookAt && Reticle.allowMouseCapture) { - var dx = Reticle.position.x - lastMouseX; - var dy = Reticle.position.y - lastMouseY; - var dt = Math.max(1, (now - lastMouseMoveOrClick)); // mSecs since last mouse move - var mouseMoveDistance = Math.sqrt((dx*dx) + (dy*dy)); - var mouseVelocity = mouseMoveDistance / dt; - averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * mouseVelocity); - if (averageMouseVelocity > AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO) { - shouldSeekToLookAt = true; - } - } - } - lastMouseMoveOrClick = now; - lastMouseX = mouseEvent.x; - lastMouseY = mouseEvent.y; -}); - -function seekToLookAt() { - // if we're currently seeking the lookAt move the mouse toward the lookat - if (shouldSeekToLookAt) { - averageMouseVelocity = 0; // reset this, these never count for movement... - var lookAt2D = HMD.getHUDLookAtPosition2D(); - var currentReticlePosition = Reticle.position; - var distanceBetweenX = lookAt2D.x - Reticle.position.x; - var distanceBetweenY = lookAt2D.y - Reticle.position.y; - var moveX = distanceBetweenX / NON_LINEAR_DIVISOR; - var moveY = distanceBetweenY / NON_LINEAR_DIVISOR; - var newPosition = { x: Reticle.position.x + moveX, y: Reticle.position.y + moveY }; - var closeEnoughX = false; - var closeEnoughY = false; - if (moveX < MINIMUM_SEEK_DISTANCE) { - newPosition.x = lookAt2D.x; - closeEnoughX = true; - } - if (moveY < MINIMUM_SEEK_DISTANCE) { - newPosition.y = lookAt2D.y; - closeEnoughY = true; - } - Reticle.position = newPosition; - if (closeEnoughX && closeEnoughY) { - shouldSeekToLookAt = false; - } - } -} - -function autoHideReticle() { - var now = Date.now(); - - // sometimes we don't actually get mouse move messages (for example, if the focus has been set - // to an overlay or web page 'overlay') in but the mouse can still be moving, and we don't want - // to autohide in these cases, so we will take this opportunity to also check if the reticle - // position has changed. - if (lastMouseX != Reticle.position.x || lastMouseY != Reticle.position.y) { - lastMouseMoveOrClick = now; - lastMouseX = Reticle.position.x; - lastMouseY = Reticle.position.y; - } - - // if we haven't moved in a long period of time, and we're not pointing at some - // system overlay (like a window), then hide the reticle - if (Reticle.visible && !Reticle.pointingAtSystemOverlay) { - var timeSinceLastMouseMove = now - lastMouseMoveOrClick; - if (timeSinceLastMouseMove > HIDE_STATIC_MOUSE_AFTER) { - Reticle.visible = false; - } - } -} - -function checkReticleDepth() { - var now = Date.now(); - var timeSinceLastDepthCheck = now - lastDepthCheckTime; - if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS && Reticle.visible) { - var newDesiredDepth = desiredDepth; - lastDepthCheckTime = now; - var reticlePosition = Reticle.position; - - // first check the 2D Overlays - if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(reticlePosition)) { - newDesiredDepth = APPARENT_2D_OVERLAY_DEPTH; - } else { - var pickRay = Camera.computePickRay(reticlePosition.x, reticlePosition.y); - - // Then check the 3D overlays - var result = Overlays.findRayIntersection(pickRay); - - if (!result.intersects) { - // finally check the entities - result = Entities.findRayIntersection(pickRay, true); - } - - // If either the overlays or entities intersect, then set the reticle depth to - // the distance of intersection - if (result.intersects) { - newDesiredDepth = result.distance; - } else { - // if nothing intersects... set the depth to some sufficiently large depth - newDesiredDepth = APPARENT_MAXIMUM_DEPTH; - } - } - - // If the desired depth has changed, reset our fade start time - if (desiredDepth != newDesiredDepth) { - desiredDepth = newDesiredDepth; - } - } - -} - -function moveToDesiredDepth() { - // move the reticle toward the desired depth - if (desiredDepth != Reticle.depth) { - - // cut distance between desiredDepth and current depth in half until we're close enough - var distanceToAdjustThisCycle = (desiredDepth - Reticle.depth) / NON_LINEAR_DIVISOR; - if (Math.abs(distanceToAdjustThisCycle) < MINIMUM_DEPTH_ADJUST) { - newDepth = desiredDepth; - } else { - newDepth = Reticle.depth + distanceToAdjustThisCycle; - } - Reticle.setDepth(newDepth); - } -} - -Script.update.connect(function(deltaTime) { - autoHideReticle(); // auto hide reticle for desktop or HMD mode - if (HMD.active) { - seekToLookAt(); // handle moving the reticle toward the look at - checkReticleDepth(); // make sure reticle is at correct depth - moveToDesiredDepth(); // move the fade the reticle to the desired depth - } -}); diff --git a/scripts/tutorials/NBody/gravity.js b/scripts/tutorials/NBody/gravity.js new file mode 100644 index 0000000000..b6bdb9d20b --- /dev/null +++ b/scripts/tutorials/NBody/gravity.js @@ -0,0 +1,181 @@ +// gravity.js +// +// Created by Philip Rosedale on March 29, 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// This entity script causes the object to move with gravitational force and be attracted to other spheres nearby. +// The force is scaled by GRAVITY_STRENGTH, and only entities of type "Sphere" within GRAVITY_RANGE will affect it. +// The person who has most recently grabbed this object will simulate it. +// + +function Timer() { + var time; + var count = 0; + var totalTime = 0; + this.reset = function() { + count = 0; + totalTime = 0; + } + this.start = function() { + time = new Date().getTime(); + } + this.record = function() { + var elapsed = new Date().getTime() - time; + totalTime += elapsed; + count++; + return elapsed; + } + this.count = function() { + return count; + } + this.average = function() { + return (count == 0) ? 0 : totalTime / count; + } + this.elapsed = function() { + return new Date().getTime() - time; + } +} + +(function () { + var entityID, + wantDebug = true, + CHECK_INTERVAL = 10.00, + SEARCH_INTERVAL = 1000, + GRAVITY_RANGE = 20.0, + GRAVITY_STRENGTH = 1.0, + MIN_VELOCITY = 0.01, + timeoutID = null, + timeSinceLastSearch = 0, + timer = new Timer(), + simulate = false, + spheres = []; + + var printDebug = function(message) { + if (wantDebug) { + print(message); + } + } + + var greatestDimension = function(dimensions) { + return Math.max(Math.max(dimensions.x, dimensions.y), dimensions.z); + } + + var mass2 = function(dimensions) { + return dimensions.x * dimensions.y * dimensions.z; + } + + var findSpheres = function(position) { + var entities = Entities.findEntities(position, GRAVITY_RANGE); + spheres = []; + for (var i = 0; i < entities.length; i++) { + if (entityID == spheres[i]) { + // this entity doesn't experience its own gravity. + continue; + } + var props = Entities.getEntityProperties(entities[i]); + if (props && (props.shapeType == "sphere" || props.type == "Sphere")) { + spheres.push(entities[i]); + } + } + // print("FOUND " + spheres.length + " SPHERES"); + } + + var applyGravity = function() { + if (!simulate) { + return; + } + + var properties = Entities.getEntityProperties(entityID); + if (!properties || !properties.position) { + return; + } + + // update the list of nearby spheres + var deltaTime = timer.elapsed() / 1000.0; + if (deltaTime == 0.0) { + return; + } + timeSinceLastSearch += CHECK_INTERVAL; + if (timeSinceLastSearch >= SEARCH_INTERVAL) { + findSpheres(properties.position); + timeSinceLastSearch = 0; + } + + var deltaVelocity = { x: 0, y: 0, z: 0 }; + var otherCount = 0; + var mass = mass2(properties.dimensions); + + for (var i = 0; i < spheres.length; i++) { + otherProperties = Entities.getEntityProperties(spheres[i]); + if (!otherProperties || !otherProperties.position) { + continue; // sphere was deleted + } + otherCount++; + var radius = Vec3.distance(properties.position, otherProperties.position); + var otherMass = mass2(otherProperties.dimensions); + var r = (greatestDimension(properties.dimensions) + greatestDimension(otherProperties.dimensions)) / 2; + if (radius > r) { + var n0 = Vec3.normalize(Vec3.subtract(otherProperties.position, properties.position)); + var n1 = Vec3.multiply(deltaTime * GRAVITY_STRENGTH * otherMass / (radius * radius), n0); + deltaVelocity = Vec3.sum(deltaVelocity, n1); + } + } + Entities.editEntity(entityID, { velocity: Vec3.sum(properties.velocity, deltaVelocity) }); + if (Vec3.length(properties.velocity) < MIN_VELOCITY) { + print("Gravity simulation stopped due to velocity"); + simulate = false; + } else { + timer.start(); + timeoutID = Script.setTimeout(applyGravity, CHECK_INTERVAL); + } + } + this.applyGravity = applyGravity; + + var releaseGrab = function() { + printDebug("Gravity simulation started."); + var properties = Entities.getEntityProperties(entityID); + findSpheres(properties.position); + timer.start(); + timeoutID = Script.setTimeout(applyGravity, CHECK_INTERVAL); + simulate = true; + } + this.releaseGrab = releaseGrab; + + var preload = function (givenEntityID) { + printDebug("load gravity..."); + entityID = givenEntityID; + }; + this.preload = preload; + + var unload = function () { + printDebug("Unload gravity..."); + if (timeoutID !== undefined) { + Script.clearTimeout(timeoutID); + } + if (simulate) { + Entities.editEntity(entityID, { velocity: { x: 0, y: 0, z: 0 } }); + } + }; + this.unload = unload; + + var handleMessages = function(channel, message, sender) { + if (channel === 'Hifi-Object-Manipulation') { + try { + var parsedMessage = JSON.parse(message); + if (parsedMessage.action === 'grab' && parsedMessage.grabbedEntity == entityID) { + print("Gravity simulation stopped due to grab"); + simulate = false; + } + } catch (e) { + print('error parsing Hifi-Object-Manipulation message: ' + message); + } + } + } + this.handleMessages = handleMessages; + + Messages.messageReceived.connect(this.handleMessages); + Messages.subscribe('Hifi-Object-Manipulation'); +}); diff --git a/scripts/tutorials/NBody/gravity.svg b/scripts/tutorials/NBody/gravity.svg new file mode 100644 index 0000000000..9bac2d65e1 --- /dev/null +++ b/scripts/tutorials/NBody/gravity.svg @@ -0,0 +1,72 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/tutorials/NBody/makePlanets.js b/scripts/tutorials/NBody/makePlanets.js new file mode 100644 index 0000000000..58a3c7cc2d --- /dev/null +++ b/scripts/tutorials/NBody/makePlanets.js @@ -0,0 +1,166 @@ +// makePlanets.js +// +// Created by Philip Rosedale on March 29, 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Make an earth and moon, where you can grab and throw moon into orbit. Entity +// script attached to moon gives it gravitation behavior and will also make it attracted to +// other spheres placed nearby. +// + +var SCALE = 3.0; +var EARTH_SIZE = 3.959 / SCALE; +var MOON_SIZE = 1.079 / SCALE; + +var BUTTON_SIZE = 32; +var PADDING = 3; + +var earth = null; +var moon = null; + +var SCRIPT_URL = Script.resolvePath("gravity.js"); + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +Script.include(["/~/system/libraries/toolBars.js"]); +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.makePlanets.js"); + +var makePlanetsIconURL = Script.resolvePath("gravity.svg"); +var button = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: makePlanetsIconURL, + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); + +var deleteButton = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/delete.png", + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); + +function inFrontOfMe(distance) { + return Vec3.sum(Camera.getPosition(), Vec3.multiply(distance, Quat.getFront(Camera.getOrientation()))); +} + +function onButtonClick() { + earth = Entities.addEntity({ + type: "Model", + name: "Earth", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/seth/production/NBody/earth.fbx", + position: inFrontOfMe(2 * EARTH_SIZE), + dimensions: { x: EARTH_SIZE, y: EARTH_SIZE, z: EARTH_SIZE }, + shapeType: "sphere", + lifetime: 86400, // 1 day + angularDamping: 0, + angularVelocity: { x: 0, y: 0.1, z: 0 }, + }); + moon = Entities.addEntity({ + type: "Model", + name: "Moon", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/seth/production/NBody/moon.fbx", + position: inFrontOfMe(EARTH_SIZE - MOON_SIZE), + dimensions: { x: MOON_SIZE, y: MOON_SIZE, z: MOON_SIZE }, + dynamic: true, + damping: 0, // 0.01, + angularDamping: 0, // 0.01, + script: SCRIPT_URL, + shapeType: "sphere" + }); + Entities.addEntity({ + "accelerationSpread": { + "x": 0, + "y": 0, + "z": 0 + }, + "alpha": 1, + "alphaFinish": 0, + "alphaStart": 1, + "azimuthFinish": 0, + "azimuthStart": 0, + "color": { + "blue": 255, + "green": 255, + "red": 255 + }, + "colorFinish": { + "blue": 255, + "green": 255, + "red": 255 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255 + }, + "dimensions": { + "x": 0.10890001058578491, + "y": 0.10890001058578491, + "z": 0.10890001058578491 + }, + "emitAcceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitOrientation": { + "w": 0.99999994039535522, + "x": 0, + "y": 0, + "z": 0 + }, + "emitRate": 300, + "emitSpeed": 0, + "emitterShouldTrail": 1, + "maxParticles": 10000, + "name": "moon trail", + "parentID": moon, + "particleRadius": 0.005, + "radiusFinish": 0.005, + "radiusSpread": 0.005, + "radiusStart": 0.005, + "speedSpread": 0, + "lifespan": 20, + "textures": "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }); +} + +function onDeleteButton() { + Entities.deleteEntity(earth); + Entities.deleteEntity(moon); +} + +function mousePressEvent(event) { + var clickedText = false; + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == button) { + onButtonClick(); + } else if (clickedOverlay == deleteButton) { + onDeleteButton(); + } +} + +function scriptEnding() { + toolBar.cleanup(); +} + +Controller.mousePressEvent.connect(mousePressEvent); +Script.scriptEnding.connect(scriptEnding); diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 81acf2594d..e978dd9a38 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -91,7 +91,7 @@ public: virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } virtual ui::Menu* getPrimaryMenu() { return nullptr; } virtual bool isForeground() override { return true; } - virtual const DisplayPlugin* getActiveDisplayPlugin() const override { return nullptr; } + virtual const DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } }; class MyControllerScriptingInterface : public controller::ScriptingInterface { diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt index b12771b368..a6ce4cf956 100644 --- a/tests/entities/CMakeLists.txt +++ b/tests/entities/CMakeLists.txt @@ -7,6 +7,6 @@ setup_hifi_project(Network Script) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation) +link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation audio) package_libraries_for_deployment()