PerformanceTimer perfTimer("devices"); DeviceTracker::updateAll(); FaceTracker* tracker = getSelectedFaceTracker(); if (tracker && Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking) != tracker->isMuted()) { tracker->toggleMute(); } tracker = getActiveFaceTracker(); if (tracker && !tracker->isMuted()) { tracker->update(deltaTime); // Auto-mute microphone after losing face tracking? if (tracker->isTracking()) { _lastFaceTrackerUpdate = usecTimestampNow(); } else { const quint64 MUTE_MICROPHONE_AFTER_USECS = 5000000; //5 secs Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !menu->isOptionChecked(MenuOption::MuteAudio)) { if (_lastFaceTrackerUpdate > 0 && ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) { menu->triggerOption(MenuOption::MuteAudio); _lastFaceTrackerUpdate = 0; } } else { _lastFaceTrackerUpdate = 0; } } } else { _lastFaceTrackerUpdate = 0; } } auto myAvatar = getMyAvatar(); auto userInputMapper = DependencyManager::get<UserInputMapper>(); controller::InputCalibrationData calibrationData = { myAvatar->getSensorToWorldMatrix(), createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()), myAvatar->getHMDSensorMatrix() }; InputPluginPointer keyboardMousePlugin; for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->getName() == KeyboardMouseDevice::NAME) { keyboardMousePlugin = inputPlugin; } else if (inputPlugin->isActive()) { inputPlugin->pluginUpdate(deltaTime, calibrationData); } } userInputMapper->update(deltaTime); if (keyboardMousePlugin && keyboardMousePlugin->isActive()) { keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData); } _controllerScriptingInterface->updateInputControllers(); // Transfer the user inputs to the driveKeys // FIXME can we drop drive keys and just have the avatar read the action states directly? myAvatar->clearDriveKeys(); if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { if (!_controllerScriptingInterface->areActionsCaptured()) { myAvatar->setDriveKeys(TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); myAvatar->setDriveKeys(TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); myAvatar->setDriveKeys(TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X)); if (deltaTime > FLT_EPSILON) { myAvatar->setDriveKeys(PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH)); myAvatar->setDriveKeys(YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW)); myAvatar->setDriveKeys(STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW)); } } myAvatar->setDriveKeys(ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z)); } controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND); controller::Pose rightHandPose = userInputMapper->getPoseState(controller::Action::RIGHT_HAND); auto myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()); auto worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix()); auto avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix; myAvatar->setHandControllerPosesInSensorFrame(leftHandPose.transform(avatarToSensorMatrix), rightHandPose.transform(avatarToSensorMatrix)); updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... updateDialogs(deltaTime); // update various stats dialogs if present QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>(); if (_physicsEnabled) { PROFILE_RANGE_EX("Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("physics"); { PROFILE_RANGE_EX("UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("updateStates)"); static VectorOfMotionStates motionStates; _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); _entitySimulation->deleteObjectsRemovedFromPhysics(); getEntities()->getTree()->withReadLock([&] { _entitySimulation->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); }); getEntities()->getTree()->withReadLock([&] { _entitySimulation->getObjectsToChange(motionStates); VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates); _entitySimulation->setObjectsToChange(stillNeedChange); }); _entitySimulation->applyActionChanges(); avatarManager->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); avatarManager->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); avatarManager->getObjectsToChange(motionStates); _physicsEngine->changeObjects(motionStates); myAvatar->prepareForPhysicsSimulation(); _physicsEngine->forEachAction([&](EntityActionPointer action) { action->prepareForPhysicsSimulation(); }); } { PROFILE_RANGE_EX("StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("stepSimulation"); getEntities()->getTree()->withWriteLock([&] { _physicsEngine->stepSimulation(); }); } { PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("harvestChanges"); if (_physicsEngine->hasOutgoingChanges()) { getEntities()->getTree()->withWriteLock([&] { PerformanceTimer perfTimer("handleOutgoingChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges(); _entitySimulation->handleOutgoingChanges(outgoingChanges); avatarManager->handleOutgoingChanges(outgoingChanges); }); auto collisionEvents = _physicsEngine->getCollisionEvents(); avatarManager->handleCollisionEvents(collisionEvents); _physicsEngine->dumpStatsIfNecessary(); if (!_aboutToQuit) { PerformanceTimer perfTimer("entities"); // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) _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). getEntities()->update(); // update the models... } myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } } } // AvatarManager update { PerformanceTimer perfTimer("AvatarManger"); _avatarSimCounter.increment(); { PROFILE_RANGE_EX("OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount()); avatarManager->updateOtherAvatars(deltaTime); } qApp->updateMyAvatarLookAtPosition(); { PROFILE_RANGE_EX("MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount()); avatarManager->updateMyAvatar(deltaTime); } } { PROFILE_RANGE_EX("Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); _overlays.update(deltaTime); } // Update _viewFrustum with latest camera and view frustum data... // NOTE: we get this from the view frustum, to make it simpler, since the // loadViewFrumstum() method will get the correct details from the camera // We could optimize this to not actually load the viewFrustum, since we don't // actually need to calculate the view frustum planes to send these details // to the server. { QMutexLocker viewLocker(&_viewMutex); loadViewFrustum(_myCamera, _viewFrustum); } quint64 now = usecTimestampNow(); // 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; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it if (queryIsDue || viewIsDifferentEnough) { _lastQueriedTime = now; if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); } _lastQueriedViewFrustum = _viewFrustum; } } // sent nack packets containing missing sequence numbers of received packets from nodes { quint64 sinceLastNack = now - _lastNackTime; const quint64 TOO_LONG_SINCE_LAST_NACK = 1 * USECS_PER_SECOND; if (sinceLastNack > TOO_LONG_SINCE_LAST_NACK) { _lastNackTime = now; sendNackPackets(); } } // send packet containing downstream audio stats to the AudioMixer { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { _lastSendDownstreamAudioStats = now; QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); } } avatarManager->postUpdate(deltaTime); { PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0); std::unique_lock<std::mutex> guard(_postUpdateLambdasLock); for (auto& iter : _postUpdateLambdas) { iter.second(); } _postUpdateLambdas.clear(); } AnimDebugDraw::getInstance().update();} int Application::sendNackPackets() { // iterates through all nodes in NodeList auto nodeList = DependencyManager::get<NodeList>(); int packetsSent = 0; nodeList->eachNode([&](const SharedNodePointer& node){ if (node->getActiveSocket() && node->getType() == NodeType::EntityServer) { auto nackPacketList = NLPacketList::create(PacketType::OctreeDataNack); QUuid nodeUUID = node->getUUID(); // if there are octree packets from this node that are waiting to be processed, // don't send a NACK since the missing packets may be among those waiting packets. if (_octreeProcessor.hasPacketsToProcessFrom(nodeUUID)) { return; } QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers; _octreeServerSceneStats.withReadLock([&] { // retrieve octree scene stats of this node if (_octreeServerSceneStats.find(nodeUUID) == _octreeServerSceneStats.end()) { return; } // get sequence number stats of node, prune its missing set, and make a copy of the missing set SequenceNumberStats& sequenceNumberStats = _octreeServerSceneStats[nodeUUID].getIncomingOctreeSequenceNumberStats(); sequenceNumberStats.pruneMissingSet(); missingSequenceNumbers = sequenceNumberStats.getMissingSet(); }); // construct nack packet(s) for this node foreach(const OCTREE_PACKET_SEQUENCE& missingNumber, missingSequenceNumbers) { nackPacketList->writePrimitive(missingNumber); } if (nackPacketList->getNumPackets()) { packetsSent += (int)nackPacketList->getNumPackets(); // send the packet list nodeList->sendPacketList(std::move(nackPacketList), *node); } } }); return packetsSent;} void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend) { if (!_settingsLoaded) { return; // bail early if settings are not loaded } //qCDebug(interfaceapp) << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView(); bool wantExtraDebugging = getLogger()->extraDebugging(); 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()); auto lodManager = DependencyManager::get<LODManager>(); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); // Iterate all of the nodes, and get a count of how many octree servers we have... int totalServers = 0; int inViewServers = 0; int unknownJurisdictionServers = 0; auto nodeList = DependencyManager::get<NodeList>(); nodeList->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { totalServers++; // get the server bounds for this server QUuid nodeUUID = node->getUUID(); // if we haven't heard from this voxel server, go ahead and send it a query, so we // can get the jurisdiction... if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { unknownJurisdictionServers++; } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), rootDetails.s * TREE_SCALE); if (viewFrustum.cubeIntersectsKeyhole(serverBounds)) { inViewServers++; } } } } }); if (wantExtraDebugging) { qCDebug(interfaceapp, "Servers: total %d, in view %d, unknown jurisdiction %d", totalServers, inViewServers, unknownJurisdictionServers); } int perServerPPS = 0; const int SMALL_BUDGET = 10; int perUnknownServer = SMALL_BUDGET; int totalPPS = getMaxOctreePacketsPerSecond(); // determine PPS based on number of servers if (inViewServers >= 1) { // set our preferred PPS to be exactly evenly divided among all of the voxel servers... and allocate 1 PPS // for each unknown jurisdiction server perServerPPS = (totalPPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer); } else { if (unknownJurisdictionServers > 0) { perUnknownServer = (totalPPS / unknownJurisdictionServers); } } if (wantExtraDebugging) { qCDebug(interfaceapp, "perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer); } auto queryPacket = NLPacket::create(packetType); nodeList->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { // get the server bounds for this server QUuid nodeUUID = node->getUUID(); bool inView = false; bool unknownView = false; // if we haven't heard from this voxel server, go ahead and send it a query, so we // can get the jurisdiction... if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { unknownView = true; // assume it's in view if (wantExtraDebugging) { qCDebug(interfaceapp) << "no known jurisdiction for node " << *node << ", assume it's visible."; } } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), rootDetails.s * TREE_SCALE); inView = viewFrustum.cubeIntersectsKeyhole(serverBounds); } else if (wantExtraDebugging) { qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; } } if (inView) { _octreeQuery.setMaxQueryPacketsPerSecond(perServerPPS); } else if (unknownView) { if (wantExtraDebugging) { qCDebug(interfaceapp) << "no known jurisdiction for node " << *node << ", give it budget of " << perUnknownServer << " to send us jurisdiction."; } // set the query's position/orientation to be degenerate in a manner that will get the scene quickly // If there's only one server, then don't do this, and just let the normal voxel query pass through // as expected... this way, we will actually get a valid scene if there is one to be seen if (totalServers > 1) { _octreeQuery.setCameraPosition(glm::vec3(-0.1,-0.1,-0.1)); const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); _octreeQuery.setCameraNearClip(0.1f); _octreeQuery.setCameraFarClip(0.1f); if (wantExtraDebugging) { qCDebug(interfaceapp) << "Using 'minimal' camera position for node" << *node; } } else { if (wantExtraDebugging) { qCDebug(interfaceapp) << "Using regular camera position for node" << *node; } } _octreeQuery.setMaxQueryPacketsPerSecond(perUnknownServer); } else { _octreeQuery.setMaxQueryPacketsPerSecond(0); } // if asked to forceResend, then set the query's position/orientation to be degenerate in a manner // that will cause our next query to be guarenteed to be different and the server will resend to us if (forceResend) { _octreeQuery.setCameraPosition(glm::vec3(-0.1, -0.1, -0.1)); const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); _octreeQuery.setCameraNearClip(0.1f); _octreeQuery.setCameraFarClip(0.1f); } // encode the query data int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload())); queryPacket->setPayloadSize(packetSize); // make sure we still have an active socket nodeList->sendUnreliablePacket(*queryPacket, *node); } });} bool Application::isHMDMode() const { return getActiveDisplayPlugin()->isHmd();} float Application::getTargetFrameRate() const { return getActiveDisplayPlugin()->getTargetFrameRate(); } QRect Application::getDesirableApplicationGeometry() const { QRect applicationGeometry = getWindow()->geometry(); // If our parent window is on the HMD, then don't use its geometry, instead use // the "main screen" geometry. HMDToolsDialog* hmdTools = DependencyManager::get<DialogsManager>()->getHMDToolsDialog(); if (hmdTools && hmdTools->hasHMDScreen()) {Lines 3501–4000 of 5476. Previous lines Load more
PerformanceTimer perfTimer("devices"); DeviceTracker::updateAll(); FaceTracker* tracker = getSelectedFaceTracker(); if (tracker && Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking) != tracker->isMuted()) { tracker->toggleMute(); } tracker = getActiveFaceTracker(); if (tracker && !tracker->isMuted()) { tracker->update(deltaTime); // Auto-mute microphone after losing face tracking? if (tracker->isTracking()) { _lastFaceTrackerUpdate = usecTimestampNow(); } else { const quint64 MUTE_MICROPHONE_AFTER_USECS = 5000000; //5 secs Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !menu->isOptionChecked(MenuOption::MuteAudio)) { if (_lastFaceTrackerUpdate > 0 && ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) { menu->triggerOption(MenuOption::MuteAudio); _lastFaceTrackerUpdate = 0; } } else { _lastFaceTrackerUpdate = 0; } } } else { _lastFaceTrackerUpdate = 0; } } auto myAvatar = getMyAvatar(); auto userInputMapper = DependencyManager::get<UserInputMapper>(); controller::InputCalibrationData calibrationData = { myAvatar->getSensorToWorldMatrix(), createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()), myAvatar->getHMDSensorMatrix() }; InputPluginPointer keyboardMousePlugin; for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->getName() == KeyboardMouseDevice::NAME) { keyboardMousePlugin = inputPlugin; } else if (inputPlugin->isActive()) { inputPlugin->pluginUpdate(deltaTime, calibrationData); } } userInputMapper->update(deltaTime); if (keyboardMousePlugin && keyboardMousePlugin->isActive()) { keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData); } _controllerScriptingInterface->updateInputControllers(); // Transfer the user inputs to the driveKeys // FIXME can we drop drive keys and just have the avatar read the action states directly? myAvatar->clearDriveKeys(); if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { if (!_controllerScriptingInterface->areActionsCaptured()) { myAvatar->setDriveKeys(TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); myAvatar->setDriveKeys(TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); myAvatar->setDriveKeys(TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X)); if (deltaTime > FLT_EPSILON) { myAvatar->setDriveKeys(PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH)); myAvatar->setDriveKeys(YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW)); myAvatar->setDriveKeys(STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW)); } } myAvatar->setDriveKeys(ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z)); } controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND); controller::Pose rightHandPose = userInputMapper->getPoseState(controller::Action::RIGHT_HAND); auto myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()); auto worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix()); auto avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix; myAvatar->setHandControllerPosesInSensorFrame(leftHandPose.transform(avatarToSensorMatrix), rightHandPose.transform(avatarToSensorMatrix)); updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... updateDialogs(deltaTime); // update various stats dialogs if present QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>(); if (_physicsEnabled) { PROFILE_RANGE_EX("Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("physics"); { PROFILE_RANGE_EX("UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("updateStates)"); static VectorOfMotionStates motionStates; _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); _entitySimulation->deleteObjectsRemovedFromPhysics(); getEntities()->getTree()->withReadLock([&] { _entitySimulation->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); }); getEntities()->getTree()->withReadLock([&] { _entitySimulation->getObjectsToChange(motionStates); VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates); _entitySimulation->setObjectsToChange(stillNeedChange); }); _entitySimulation->applyActionChanges(); avatarManager->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); avatarManager->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); avatarManager->getObjectsToChange(motionStates); _physicsEngine->changeObjects(motionStates); myAvatar->prepareForPhysicsSimulation(); _physicsEngine->forEachAction([&](EntityActionPointer action) { action->prepareForPhysicsSimulation(); }); } { PROFILE_RANGE_EX("StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("stepSimulation"); getEntities()->getTree()->withWriteLock([&] { _physicsEngine->stepSimulation(); }); } { PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("harvestChanges"); if (_physicsEngine->hasOutgoingChanges()) { getEntities()->getTree()->withWriteLock([&] { PerformanceTimer perfTimer("handleOutgoingChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges(); _entitySimulation->handleOutgoingChanges(outgoingChanges); avatarManager->handleOutgoingChanges(outgoingChanges); }); auto collisionEvents = _physicsEngine->getCollisionEvents(); avatarManager->handleCollisionEvents(collisionEvents); _physicsEngine->dumpStatsIfNecessary(); if (!_aboutToQuit) { PerformanceTimer perfTimer("entities"); // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) _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). getEntities()->update(); // update the models... } myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } } } // AvatarManager update { PerformanceTimer perfTimer("AvatarManger"); _avatarSimCounter.increment(); { PROFILE_RANGE_EX("OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount()); avatarManager->updateOtherAvatars(deltaTime); } qApp->updateMyAvatarLookAtPosition(); { PROFILE_RANGE_EX("MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount()); avatarManager->updateMyAvatar(deltaTime); } } { PROFILE_RANGE_EX("Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); _overlays.update(deltaTime); } // Update _viewFrustum with latest camera and view frustum data... // NOTE: we get this from the view frustum, to make it simpler, since the // loadViewFrumstum() method will get the correct details from the camera // We could optimize this to not actually load the viewFrustum, since we don't // actually need to calculate the view frustum planes to send these details // to the server. { QMutexLocker viewLocker(&_viewMutex); loadViewFrustum(_myCamera, _viewFrustum); } quint64 now = usecTimestampNow(); // 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; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it if (queryIsDue || viewIsDifferentEnough) { _lastQueriedTime = now; if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) { queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); } _lastQueriedViewFrustum = _viewFrustum; } } // sent nack packets containing missing sequence numbers of received packets from nodes { quint64 sinceLastNack = now - _lastNackTime; const quint64 TOO_LONG_SINCE_LAST_NACK = 1 * USECS_PER_SECOND; if (sinceLastNack > TOO_LONG_SINCE_LAST_NACK) { _lastNackTime = now; sendNackPackets(); } } // send packet containing downstream audio stats to the AudioMixer { quint64 sinceLastNack = now - _lastSendDownstreamAudioStats; if (sinceLastNack > TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS) { _lastSendDownstreamAudioStats = now; QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); } } avatarManager->postUpdate(deltaTime); { PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0); std::unique_lock<std::mutex> guard(_postUpdateLambdasLock); for (auto& iter : _postUpdateLambdas) { iter.second(); } _postUpdateLambdas.clear(); } AnimDebugDraw::getInstance().update(); } int Application::sendNackPackets() { // iterates through all nodes in NodeList auto nodeList = DependencyManager::get<NodeList>(); int packetsSent = 0; nodeList->eachNode([&](const SharedNodePointer& node){ if (node->getActiveSocket() && node->getType() == NodeType::EntityServer) { auto nackPacketList = NLPacketList::create(PacketType::OctreeDataNack); QUuid nodeUUID = node->getUUID(); // if there are octree packets from this node that are waiting to be processed, // don't send a NACK since the missing packets may be among those waiting packets. if (_octreeProcessor.hasPacketsToProcessFrom(nodeUUID)) { return; } QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers; _octreeServerSceneStats.withReadLock([&] { // retrieve octree scene stats of this node if (_octreeServerSceneStats.find(nodeUUID) == _octreeServerSceneStats.end()) { return; } // get sequence number stats of node, prune its missing set, and make a copy of the missing set SequenceNumberStats& sequenceNumberStats = _octreeServerSceneStats[nodeUUID].getIncomingOctreeSequenceNumberStats(); sequenceNumberStats.pruneMissingSet(); missingSequenceNumbers = sequenceNumberStats.getMissingSet(); }); // construct nack packet(s) for this node foreach(const OCTREE_PACKET_SEQUENCE& missingNumber, missingSequenceNumbers) { nackPacketList->writePrimitive(missingNumber); } if (nackPacketList->getNumPackets()) { packetsSent += (int)nackPacketList->getNumPackets(); // send the packet list nodeList->sendPacketList(std::move(nackPacketList), *node); } } }); return packetsSent; } void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions, bool forceResend) { if (!_settingsLoaded) { return; // bail early if settings are not loaded } //qCDebug(interfaceapp) << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView(); bool wantExtraDebugging = getLogger()->extraDebugging(); 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()); auto lodManager = DependencyManager::get<LODManager>(); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); // Iterate all of the nodes, and get a count of how many octree servers we have... int totalServers = 0; int inViewServers = 0; int unknownJurisdictionServers = 0; auto nodeList = DependencyManager::get<NodeList>(); nodeList->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { totalServers++; // get the server bounds for this server QUuid nodeUUID = node->getUUID(); // if we haven't heard from this voxel server, go ahead and send it a query, so we // can get the jurisdiction... if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { unknownJurisdictionServers++; } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), rootDetails.s * TREE_SCALE); if (viewFrustum.cubeIntersectsKeyhole(serverBounds)) { inViewServers++; } } } } }); if (wantExtraDebugging) { qCDebug(interfaceapp, "Servers: total %d, in view %d, unknown jurisdiction %d", totalServers, inViewServers, unknownJurisdictionServers); } int perServerPPS = 0; const int SMALL_BUDGET = 10; int perUnknownServer = SMALL_BUDGET; int totalPPS = getMaxOctreePacketsPerSecond(); // determine PPS based on number of servers if (inViewServers >= 1) { // set our preferred PPS to be exactly evenly divided among all of the voxel servers... and allocate 1 PPS // for each unknown jurisdiction server perServerPPS = (totalPPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer); } else { if (unknownJurisdictionServers > 0) { perUnknownServer = (totalPPS / unknownJurisdictionServers); } } if (wantExtraDebugging) { qCDebug(interfaceapp, "perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer); } auto queryPacket = NLPacket::create(packetType); nodeList->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { // get the server bounds for this server QUuid nodeUUID = node->getUUID(); bool inView = false; bool unknownView = false; // if we haven't heard from this voxel server, go ahead and send it a query, so we // can get the jurisdiction... if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { unknownView = true; // assume it's in view if (wantExtraDebugging) { qCDebug(interfaceapp) << "no known jurisdiction for node " << *node << ", assume it's visible."; } } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), rootDetails.s * TREE_SCALE); inView = viewFrustum.cubeIntersectsKeyhole(serverBounds); } else if (wantExtraDebugging) { qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; } } if (inView) { _octreeQuery.setMaxQueryPacketsPerSecond(perServerPPS); } else if (unknownView) { if (wantExtraDebugging) { qCDebug(interfaceapp) << "no known jurisdiction for node " << *node << ", give it budget of " << perUnknownServer << " to send us jurisdiction."; } // set the query's position/orientation to be degenerate in a manner that will get the scene quickly // If there's only one server, then don't do this, and just let the normal voxel query pass through // as expected... this way, we will actually get a valid scene if there is one to be seen if (totalServers > 1) { _octreeQuery.setCameraPosition(glm::vec3(-0.1,-0.1,-0.1)); const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); _octreeQuery.setCameraNearClip(0.1f); _octreeQuery.setCameraFarClip(0.1f); if (wantExtraDebugging) { qCDebug(interfaceapp) << "Using 'minimal' camera position for node" << *node; } } else { if (wantExtraDebugging) { qCDebug(interfaceapp) << "Using regular camera position for node" << *node; } } _octreeQuery.setMaxQueryPacketsPerSecond(perUnknownServer); } else { _octreeQuery.setMaxQueryPacketsPerSecond(0); } // if asked to forceResend, then set the query's position/orientation to be degenerate in a manner // that will cause our next query to be guarenteed to be different and the server will resend to us if (forceResend) { _octreeQuery.setCameraPosition(glm::vec3(-0.1, -0.1, -0.1)); const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0); _octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE); _octreeQuery.setCameraNearClip(0.1f); _octreeQuery.setCameraFarClip(0.1f); } // encode the query data int packetSize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload())); queryPacket->setPayloadSize(packetSize); // make sure we still have an active socket nodeList->sendUnreliablePacket(*queryPacket, *node); } }); } bool Application::isHMDMode() const { return getActiveDisplayPlugin()->isHmd(); } float Application::getTargetFrameRate() const { return getActiveDisplayPlugin()->getTargetFrameRate(); } QRect Application::getDesirableApplicationGeometry() const { QRect applicationGeometry = getWindow()->geometry(); // If our parent window is on the HMD, then don't use its geometry, instead use // the "main screen" geometry. HMDToolsDialog* hmdTools = DependencyManager::get<DialogsManager>()->getHMDToolsDialog(); if (hmdTools && hmdTools->hasHMDScreen()) {