QScreen* hmdScreen = hmdTools->getHMDScreen(); QWindow* appWindow = getWindow()->windowHandle(); QScreen* appScreen = appWindow->screen(); // if our app's screen is the hmd screen, we don't want to place the // running scripts widget on it. So we need to pick a better screen. // we will use the screen for the HMDTools since it's a guaranteed // better screen. if (appScreen == hmdScreen) { QScreen* betterScreen = hmdTools->windowHandle()->screen(); applicationGeometry = betterScreen->geometry(); } } return applicationGeometry;} /////////////////////////////////////////////////////////////////////////////////////// loadViewFrustum()//// Description: this will load the view frustum bounds for EITHER the head// 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()); // Set the viewFrustum up with the correct position and orientation of the camera viewFrustum.setPosition(camera.getPosition()); viewFrustum.setOrientation(camera.getOrientation()); // Ask the ViewFrustum class to calculate our corners viewFrustum.calculate();} glm::vec3 Application::getSunDirection() const { // Sun direction is in fact just the location of the sun relative to the origin auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); return skyStage->getSunLight()->getDirection();} // FIXME, preprocessor guard this check to occur only in DEBUG buildsstatic QThread * activeRenderingThread = nullptr; PickRay Application::computePickRay(float x, float y) const { vec2 pickPoint { x, y }; PickRay result; if (isHMDMode()) { getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction); } else { pickPoint /= getCanvasSize(); QMutexLocker viewLocker(&_viewMutex); _viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction); } return result;} MyAvatar* Application::getMyAvatar() const { return DependencyManager::get<AvatarManager>()->getMyAvatar();} glm::vec3 Application::getAvatarPosition() const { return getMyAvatar()->getPosition();} void Application::copyViewFrustum(ViewFrustum& viewOut) const { QMutexLocker viewLocker(&_viewMutex); viewOut = _viewFrustum;} void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const { QMutexLocker viewLocker(&_viewMutex); viewOut = _displayViewFrustum;} void Application::copyShadowViewFrustum(ViewFrustum& viewOut) const { QMutexLocker viewLocker(&_viewMutex); viewOut = _shadowViewFrustum;} // WorldBox Render Data & rendering functions class WorldBoxRenderData {public: typedef render::Payload<WorldBoxRenderData> Payload; typedef Payload::DataPointer Pointer; int _val = 0; static render::ItemID _item; // unique WorldBoxRenderData}; render::ItemID WorldBoxRenderData::_item { render::Item::INVALID_ITEM_ID }; namespace render { template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { PerformanceTimer perfTimer("worldBox"); auto& batch = *args->_batch; DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch); renderWorldBox(batch); } }} // Background Render Data & rendering functionsclass BackgroundRenderData {public: typedef render::Payload<BackgroundRenderData> Payload; typedef Payload::DataPointer Pointer; Stars _stars; static render::ItemID _item; // unique WorldBoxRenderData}; render::ItemID BackgroundRenderData::_item = 0; namespace render { template <> const ItemKey payloadGetKey(const BackgroundRenderData::Pointer& stuff) { return ItemKey::Builder::background(); } template <> const Item::Bound payloadGetBound(const BackgroundRenderData::Pointer& stuff) { return Item::Bound(); } template <> void payloadRender(const BackgroundRenderData::Pointer& background, RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; // Background rendering decision auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); auto backgroundMode = skyStage->getBackgroundMode(); switch (backgroundMode) { case model::SunSkyStage::SKY_BOX: { auto skybox = skyStage->getSkybox(); if (skybox) { PerformanceTimer perfTimer("skybox"); skybox->render(batch, args->getViewFrustum()); break; } } // Fall through: if no skybox is available, render the SKY_DOME case model::SunSkyStage::SKY_DOME: { if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { PerformanceTimer perfTimer("stars"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::payloadRender<BackgroundRenderData>() ... My god, it's full of stars..."); // should be the first rendering pass - w/o depth buffer / lighting static const float alpha = 1.0f; background->_stars.render(args, alpha); } } break; case model::SunSkyStage::NO_BACKGROUND: default: // this line intentionally left blank break; } }} void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly) { // 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->preDisplaySide(renderArgs); activeRenderingThread = QThread::currentThread(); PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("display"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); // load the view frustum { QMutexLocker viewLocker(&_viewMutex); loadViewFrustum(theCamera, _displayViewFrustum); } // TODO fix shadows and make them use the GPU library // The pending changes collecting the changes here render::PendingChanges pendingChanges; // FIXME: Move this out of here!, Background / skybox should be driven by the enityt content just like the other entities // Background rendering decision if (!render::Item::isValidID(BackgroundRenderData::_item)) { auto backgroundRenderData = make_shared<BackgroundRenderData>(); auto backgroundRenderPayload = make_shared<BackgroundRenderData::Payload>(backgroundRenderData); BackgroundRenderData::_item = _main3DScene->allocateID(); pendingChanges.resetItem(BackgroundRenderData::_item, backgroundRenderPayload); } // Assuming nothing get's rendered through that if (!selfAvatarOnly) { if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { // render models... PerformanceTimer perfTimer("entities"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... entities..."); RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE; if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { renderDebugFlags = static_cast<RenderArgs::DebugFlags>(renderDebugFlags | static_cast<int>(RenderArgs::RENDER_DEBUG_HULLS)); } renderArgs->_debugFlags = renderDebugFlags; //ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, pendingChanges); } } // FIXME: Move this out of here!, WorldBox should be driven by the entity content just like the other entities // Make sure the WorldBox is in the scene if (!render::Item::isValidID(WorldBoxRenderData::_item)) { auto worldBoxRenderData = make_shared<WorldBoxRenderData>(); auto worldBoxRenderPayload = make_shared<WorldBoxRenderData::Payload>(worldBoxRenderData); WorldBoxRenderData::_item = _main3DScene->allocateID(); pendingChanges.resetItem(WorldBoxRenderData::_item, worldBoxRenderPayload); } else { pendingChanges.updateItem<WorldBoxRenderData>(WorldBoxRenderData::_item, [](WorldBoxRenderData& payload) { payload._val++; }); } // Setup the current Zone Entity lighting { auto stage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(stage->getSunLight()); } { PerformanceTimer perfTimer("SceneProcessPendingChanges"); _main3DScene->enqueuePendingChanges(pendingChanges); _main3DScene->processPendingChangesQueue(); } // For now every frame pass the renderContext { PerformanceTimer perfTimer("EngineRun"); { QMutexLocker viewLocker(&_viewMutex); renderArgs->setViewFrustum(_displayViewFrustum); } _renderEngine->getRenderContext()->args = renderArgs; // Before the deferred pass, let's try to use the render engine _renderEngine->run(); } activeRenderingThread = nullptr;} void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed) { auto originalViewport = renderArgs->_viewport; // Grab current viewport to reset it at the end float aspect = (float)region.width() / region.height(); float fov = MIRROR_FIELD_OF_VIEW; auto myAvatar = getMyAvatar(); // bool eyeRelativeCamera = false; if (!isZoomed) { _mirrorCamera.setPosition(myAvatar->getChestPosition() + myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * myAvatar->getScale()); } else { // HEAD zoom level // FIXME note that the positioning of the camera relative to the avatar can suffer limited // precision as the user's position moves further away from the origin. Thus at // /1e7,1e7,1e7 (well outside the buildable volume) the mirror camera veers and sways // wildly as you rotate your avatar because the floating point values are becoming // larger, squeezing out the available digits of precision you have available at the // human scale for camera positioning. // Previously there was a hack to correct this using the mechanism of repositioning // the avatar at the origin of the world for the purposes of rendering the mirror, // but it resulted in failing to render the avatar's head model in the mirror view // when in first person mode. Presumably this was because of some missed culling logic // that was not accounted for in the hack. // This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further // investigated in order to adapt the technique while fixing the head rendering issue, // but the complexity of the hack suggests that a better approach _mirrorCamera.setPosition(myAvatar->getDefaultEyePosition() + myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * myAvatar->getScale()); } _mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); _mirrorCamera.setOrientation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); // set the bounds of rear mirror view // the region is in device independent coordinates; must convert to device float ratio = (float)QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale(); int width = region.width() * ratio; int height = region.height() * ratio; gpu::Vec4i viewport = gpu::Vec4i(0, 0, width, height); renderArgs->_viewport = viewport; // render rear mirror view displaySide(renderArgs, _mirrorCamera, true); renderArgs->_viewport = originalViewport;} void Application::resetSensors(bool andReload) { DependencyManager::get<Faceshift>()->reset(); DependencyManager::get<DdeFaceTracker>()->reset(); DependencyManager::get<EyeTracker>()->reset(); getActiveDisplayPlugin()->resetSensors(); _overlayConductor.centerUI(); getMyAvatar()->reset(andReload); QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);} void Application::updateWindowTitle() const { QString buildVersion = " (build " + applicationVersion() + ")"; auto nodeList = DependencyManager::get<NodeList>(); QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; QString username = DependencyManager::get<AccountManager>()->getAccountInfo().getUsername(); QString currentPlaceName = DependencyManager::get<AddressManager>()->getHost(); if (currentPlaceName.isEmpty()) { currentPlaceName = nodeList->getDomainHandler().getHostname(); } QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + currentPlaceName + connectionStatus + buildVersion; #ifndef WIN32 // crashes with vs2013/win32 qCDebug(interfaceapp, "Application title set to: %s", title.toStdString().c_str());#endif _window->setWindowTitle(title);} void Application::clearDomainOctreeDetails() { // if we're about to quit, we really don't need to do any of these things... if (_aboutToQuit) { return; } qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { _entityServerJurisdictions.clear(); }); _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); }); // reset the model renderer getEntities()->clear(); auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); _recentlyClearedDomain = true;} void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); // disable physics until we have enough information about our new location to not cause craziness. resetPhysicsReadyInformation();} void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false;} void Application::nodeAdded(SharedNodePointer node) const { if (node->getType() == NodeType::AvatarMixer) { // new avatar mixer, send off our identity packet right away getMyAvatar()->sendIdentityPacket(); }} void Application::nodeActivated(SharedNodePointer node) { if (node->getType() == NodeType::AssetServer) { // asset server just connected - check if we have the asset browser showing auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer"); if (assetDialog) { auto nodeList = DependencyManager::get<NodeList>(); if (nodeList->getThisNodeCanWriteAssets()) { // call reload on the shown asset browser dialog to get the mappings (if permissions allow) QMetaObject::invokeMethod(assetDialog, "reload"); } else { // we switched to an Asset Server that we can't modify, hide the Asset Browser assetDialog->setVisible(false); } } } // If we get a new EntityServer activated, do a "forceRedraw" query. This will send a degenerate // query so that the server will think our next non-degenerate query is "different enough" to send // us a full scene if (_recentlyClearedDomain && node->getType() == NodeType::EntityServer) { _recentlyClearedDomain = false; if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions, true); } } } void Application::nodeKilled(SharedNodePointer node) { // These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work: // OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted. // This may have to do with GenericThread::threadRoutine() blocking the QThread event loop _octreeProcessor.nodeKilled(node); _entityEditSender.nodeKilled(node); if (node->getType() == NodeType::AudioMixer) { QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "audioMixerKilled"); } else if (node->getType() == NodeType::EntityServer) { QUuid nodeUUID = node->getUUID(); // see if this is the first we've heard of this node... _entityServerJurisdictions.withReadLock([&] { if (_entityServerJurisdictions.find(nodeUUID) == _entityServerJurisdictions.end()) { return; } auto rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode.get(), rootDetails); qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]", (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); }); // If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server _entityServerJurisdictions.withWriteLock([&] { _entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID)); }); // also clean up scene stats for that server _octreeServerSceneStats.withWriteLock([&] { if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { _octreeServerSceneStats.erase(nodeUUID); } }); } else if (node->getType() == NodeType::AvatarMixer) { // our avatar mixer has gone away - clear the hash of avatars DependencyManager::get<AvatarManager>()->clearOtherAvatars(); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer"); if (assetDialog) { // call reload on the shown asset browser dialog QMetaObject::invokeMethod(assetDialog, "clear"); } }} void Application::trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket) { // Attempt to identify the sender from its address. if (sendingNode) { const QUuid& nodeUUID = sendingNode->getUUID(); // now that we know the node ID, let's add these stats to the stats for that node... _octreeServerSceneStats.withWriteLock([&] { if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID]; stats.trackIncomingOctreePacket(message, wasStatsPacket, sendingNode->getClockSkewUsec()); } });Lines 4001–4500 of 5476. Previous lines Load more
QScreen* hmdScreen = hmdTools->getHMDScreen(); QWindow* appWindow = getWindow()->windowHandle(); QScreen* appScreen = appWindow->screen(); // if our app's screen is the hmd screen, we don't want to place the // running scripts widget on it. So we need to pick a better screen. // we will use the screen for the HMDTools since it's a guaranteed // better screen. if (appScreen == hmdScreen) { QScreen* betterScreen = hmdTools->windowHandle()->screen(); applicationGeometry = betterScreen->geometry(); } } return applicationGeometry; } ///////////////////////////////////////////////////////////////////////////////////// // loadViewFrustum() // // Description: this will load the view frustum bounds for EITHER the head // 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()); // Set the viewFrustum up with the correct position and orientation of the camera viewFrustum.setPosition(camera.getPosition()); viewFrustum.setOrientation(camera.getOrientation()); // Ask the ViewFrustum class to calculate our corners viewFrustum.calculate(); } glm::vec3 Application::getSunDirection() const { // Sun direction is in fact just the location of the sun relative to the origin auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); return skyStage->getSunLight()->getDirection(); } // FIXME, preprocessor guard this check to occur only in DEBUG builds static QThread * activeRenderingThread = nullptr; PickRay Application::computePickRay(float x, float y) const { vec2 pickPoint { x, y }; PickRay result; if (isHMDMode()) { getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction); } else { pickPoint /= getCanvasSize(); QMutexLocker viewLocker(&_viewMutex); _viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction); } return result; } MyAvatar* Application::getMyAvatar() const { return DependencyManager::get<AvatarManager>()->getMyAvatar(); } glm::vec3 Application::getAvatarPosition() const { return getMyAvatar()->getPosition(); } void Application::copyViewFrustum(ViewFrustum& viewOut) const { QMutexLocker viewLocker(&_viewMutex); viewOut = _viewFrustum; } void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const { QMutexLocker viewLocker(&_viewMutex); viewOut = _displayViewFrustum; } void Application::copyShadowViewFrustum(ViewFrustum& viewOut) const { QMutexLocker viewLocker(&_viewMutex); viewOut = _shadowViewFrustum; } // WorldBox Render Data & rendering functions class WorldBoxRenderData { public: typedef render::Payload<WorldBoxRenderData> Payload; typedef Payload::DataPointer Pointer; int _val = 0; static render::ItemID _item; // unique WorldBoxRenderData }; render::ItemID WorldBoxRenderData::_item { render::Item::INVALID_ITEM_ID }; namespace render { template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { PerformanceTimer perfTimer("worldBox"); auto& batch = *args->_batch; DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch); renderWorldBox(batch); } } } // Background Render Data & rendering functions class BackgroundRenderData { public: typedef render::Payload<BackgroundRenderData> Payload; typedef Payload::DataPointer Pointer; Stars _stars; static render::ItemID _item; // unique WorldBoxRenderData }; render::ItemID BackgroundRenderData::_item = 0; namespace render { template <> const ItemKey payloadGetKey(const BackgroundRenderData::Pointer& stuff) { return ItemKey::Builder::background(); } template <> const Item::Bound payloadGetBound(const BackgroundRenderData::Pointer& stuff) { return Item::Bound(); } template <> void payloadRender(const BackgroundRenderData::Pointer& background, RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; // Background rendering decision auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); auto backgroundMode = skyStage->getBackgroundMode(); switch (backgroundMode) { case model::SunSkyStage::SKY_BOX: { auto skybox = skyStage->getSkybox(); if (skybox) { PerformanceTimer perfTimer("skybox"); skybox->render(batch, args->getViewFrustum()); break; } } // Fall through: if no skybox is available, render the SKY_DOME case model::SunSkyStage::SKY_DOME: { if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { PerformanceTimer perfTimer("stars"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::payloadRender<BackgroundRenderData>() ... My god, it's full of stars..."); // should be the first rendering pass - w/o depth buffer / lighting static const float alpha = 1.0f; background->_stars.render(args, alpha); } } break; case model::SunSkyStage::NO_BACKGROUND: default: // this line intentionally left blank break; } } } void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool selfAvatarOnly) { // 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->preDisplaySide(renderArgs); activeRenderingThread = QThread::currentThread(); PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("display"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); // load the view frustum { QMutexLocker viewLocker(&_viewMutex); loadViewFrustum(theCamera, _displayViewFrustum); } // TODO fix shadows and make them use the GPU library // The pending changes collecting the changes here render::PendingChanges pendingChanges; // FIXME: Move this out of here!, Background / skybox should be driven by the enityt content just like the other entities // Background rendering decision if (!render::Item::isValidID(BackgroundRenderData::_item)) { auto backgroundRenderData = make_shared<BackgroundRenderData>(); auto backgroundRenderPayload = make_shared<BackgroundRenderData::Payload>(backgroundRenderData); BackgroundRenderData::_item = _main3DScene->allocateID(); pendingChanges.resetItem(BackgroundRenderData::_item, backgroundRenderPayload); } // Assuming nothing get's rendered through that if (!selfAvatarOnly) { if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { // render models... PerformanceTimer perfTimer("entities"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... entities..."); RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE; if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { renderDebugFlags = static_cast<RenderArgs::DebugFlags>(renderDebugFlags | static_cast<int>(RenderArgs::RENDER_DEBUG_HULLS)); } renderArgs->_debugFlags = renderDebugFlags; //ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, pendingChanges); } } // FIXME: Move this out of here!, WorldBox should be driven by the entity content just like the other entities // Make sure the WorldBox is in the scene if (!render::Item::isValidID(WorldBoxRenderData::_item)) { auto worldBoxRenderData = make_shared<WorldBoxRenderData>(); auto worldBoxRenderPayload = make_shared<WorldBoxRenderData::Payload>(worldBoxRenderData); WorldBoxRenderData::_item = _main3DScene->allocateID(); pendingChanges.resetItem(WorldBoxRenderData::_item, worldBoxRenderPayload); } else { pendingChanges.updateItem<WorldBoxRenderData>(WorldBoxRenderData::_item, [](WorldBoxRenderData& payload) { payload._val++; }); } // Setup the current Zone Entity lighting { auto stage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(stage->getSunLight()); } { PerformanceTimer perfTimer("SceneProcessPendingChanges"); _main3DScene->enqueuePendingChanges(pendingChanges); _main3DScene->processPendingChangesQueue(); } // For now every frame pass the renderContext { PerformanceTimer perfTimer("EngineRun"); { QMutexLocker viewLocker(&_viewMutex); renderArgs->setViewFrustum(_displayViewFrustum); } _renderEngine->getRenderContext()->args = renderArgs; // Before the deferred pass, let's try to use the render engine _renderEngine->run(); } activeRenderingThread = nullptr; } void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed) { auto originalViewport = renderArgs->_viewport; // Grab current viewport to reset it at the end float aspect = (float)region.width() / region.height(); float fov = MIRROR_FIELD_OF_VIEW; auto myAvatar = getMyAvatar(); // bool eyeRelativeCamera = false; if (!isZoomed) { _mirrorCamera.setPosition(myAvatar->getChestPosition() + myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * myAvatar->getScale()); } else { // HEAD zoom level // FIXME note that the positioning of the camera relative to the avatar can suffer limited // precision as the user's position moves further away from the origin. Thus at // /1e7,1e7,1e7 (well outside the buildable volume) the mirror camera veers and sways // wildly as you rotate your avatar because the floating point values are becoming // larger, squeezing out the available digits of precision you have available at the // human scale for camera positioning. // Previously there was a hack to correct this using the mechanism of repositioning // the avatar at the origin of the world for the purposes of rendering the mirror, // but it resulted in failing to render the avatar's head model in the mirror view // when in first person mode. Presumably this was because of some missed culling logic // that was not accounted for in the hack. // This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further // investigated in order to adapt the technique while fixing the head rendering issue, // but the complexity of the hack suggests that a better approach _mirrorCamera.setPosition(myAvatar->getDefaultEyePosition() + myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * myAvatar->getScale()); } _mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); _mirrorCamera.setOrientation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); // set the bounds of rear mirror view // the region is in device independent coordinates; must convert to device float ratio = (float)QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale(); int width = region.width() * ratio; int height = region.height() * ratio; gpu::Vec4i viewport = gpu::Vec4i(0, 0, width, height); renderArgs->_viewport = viewport; // render rear mirror view displaySide(renderArgs, _mirrorCamera, true); renderArgs->_viewport = originalViewport; } void Application::resetSensors(bool andReload) { DependencyManager::get<Faceshift>()->reset(); DependencyManager::get<DdeFaceTracker>()->reset(); DependencyManager::get<EyeTracker>()->reset(); getActiveDisplayPlugin()->resetSensors(); _overlayConductor.centerUI(); getMyAvatar()->reset(andReload); QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection); } void Application::updateWindowTitle() const { QString buildVersion = " (build " + applicationVersion() + ")"; auto nodeList = DependencyManager::get<NodeList>(); QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; QString username = DependencyManager::get<AccountManager>()->getAccountInfo().getUsername(); QString currentPlaceName = DependencyManager::get<AddressManager>()->getHost(); if (currentPlaceName.isEmpty()) { currentPlaceName = nodeList->getDomainHandler().getHostname(); } QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + currentPlaceName + connectionStatus + buildVersion; #ifndef WIN32 // crashes with vs2013/win32 qCDebug(interfaceapp, "Application title set to: %s", title.toStdString().c_str()); #endif _window->setWindowTitle(title); } void Application::clearDomainOctreeDetails() { // if we're about to quit, we really don't need to do any of these things... if (_aboutToQuit) { return; } qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { _entityServerJurisdictions.clear(); }); _octreeServerSceneStats.withWriteLock([&] { _octreeServerSceneStats.clear(); }); // reset the model renderer getEntities()->clear(); auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage(); skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); _recentlyClearedDomain = true; } void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); // disable physics until we have enough information about our new location to not cause craziness. resetPhysicsReadyInformation(); } void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; } void Application::nodeAdded(SharedNodePointer node) const { if (node->getType() == NodeType::AvatarMixer) { // new avatar mixer, send off our identity packet right away getMyAvatar()->sendIdentityPacket(); } } void Application::nodeActivated(SharedNodePointer node) { if (node->getType() == NodeType::AssetServer) { // asset server just connected - check if we have the asset browser showing auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer"); if (assetDialog) { auto nodeList = DependencyManager::get<NodeList>(); if (nodeList->getThisNodeCanWriteAssets()) { // call reload on the shown asset browser dialog to get the mappings (if permissions allow) QMetaObject::invokeMethod(assetDialog, "reload"); } else { // we switched to an Asset Server that we can't modify, hide the Asset Browser assetDialog->setVisible(false); } } } // If we get a new EntityServer activated, do a "forceRedraw" query. This will send a degenerate // query so that the server will think our next non-degenerate query is "different enough" to send // us a full scene if (_recentlyClearedDomain && node->getType() == NodeType::EntityServer) { _recentlyClearedDomain = false; if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions, true); } } } void Application::nodeKilled(SharedNodePointer node) { // These are here because connecting NodeList::nodeKilled to OctreePacketProcessor::nodeKilled doesn't work: // OctreePacketProcessor::nodeKilled is not being called when NodeList::nodeKilled is emitted. // This may have to do with GenericThread::threadRoutine() blocking the QThread event loop _octreeProcessor.nodeKilled(node); _entityEditSender.nodeKilled(node); if (node->getType() == NodeType::AudioMixer) { QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "audioMixerKilled"); } else if (node->getType() == NodeType::EntityServer) { QUuid nodeUUID = node->getUUID(); // see if this is the first we've heard of this node... _entityServerJurisdictions.withReadLock([&] { if (_entityServerJurisdictions.find(nodeUUID) == _entityServerJurisdictions.end()) { return; } auto rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode.get(), rootDetails); qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]", (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); }); // If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server _entityServerJurisdictions.withWriteLock([&] { _entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID)); }); // also clean up scene stats for that server _octreeServerSceneStats.withWriteLock([&] { if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { _octreeServerSceneStats.erase(nodeUUID); } }); } else if (node->getType() == NodeType::AvatarMixer) { // our avatar mixer has gone away - clear the hash of avatars DependencyManager::get<AvatarManager>()->clearOtherAvatars(); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto assetDialog = offscreenUi->getRootItem()->findChild<QQuickItem*>("AssetServer"); if (assetDialog) { // call reload on the shown asset browser dialog QMetaObject::invokeMethod(assetDialog, "clear"); } } } void Application::trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket) { // Attempt to identify the sender from its address. if (sendingNode) { const QUuid& nodeUUID = sendingNode->getUUID(); // now that we know the node ID, let's add these stats to the stats for that node... _octreeServerSceneStats.withWriteLock([&] { if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID]; stats.trackIncomingOctreePacket(message, wasStatsPacket, sendingNode->getClockSkewUsec()); } });