} if (!givenOffset) { EntityItemID parentID = entityItem->getParentID(); if (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID)) { auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties. root.x = glm::min(root.x, position.x); root.y = glm::min(root.y, position.y); root.z = glm::min(root.z, position.z); } } entities[entityID] = entityItem; } if (entities.size() == 0) { success = false; return; } if (givenOffset) { root = *givenOffset; } for (EntityItemPointer& entityDatum : entities) { auto properties = entityDatum->getProperties(); EntityItemID parentID = properties.getParentID(); if (parentID.isInvalidID()) { properties.setPosition(properties.getPosition() - root); } else if (!entities.contains(parentID)) { entityDatum->globalizeProperties(properties, "Parent %3 of %2 %1 is not selected for export.", -root); } // else valid parent -- don't offset exportTree->addEntity(entityDatum->getEntityItemID(), properties); } }); if (success) { success = exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); // restore the main window's active state _window->activateWindow(); } return success;} bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) { glm::vec3 offset(x, y, z); QVector<EntityItemPointer> entities; QVector<EntityItemID> ids; auto entityTree = getEntities()->getTree(); entityTree->withReadLock([&] { entityTree->findEntities(AACube(offset, scale), entities); foreach(EntityItemPointer entity, entities) { ids << entity->getEntityItemID(); } }); return exportEntities(filename, ids, &offset);} void Application::loadSettings() { sessionRunTime.set(0); // Just clean living. We're about to saveSettings, which will update value. DependencyManager::get<AudioClient>()->loadSettings(); DependencyManager::get<LODManager>()->loadSettings(); // DONT CHECK IN //DependencyManager::get<LODManager>()->setAutomaticLODAdjust(false); Menu::getInstance()->loadSettings(); // If there is a preferred plugin, we probably messed it up with the menu settings, so fix it. auto pluginManager = PluginManager::getInstance(); auto plugins = pluginManager->getPreferredDisplayPlugins(); for (auto plugin : plugins) { auto menu = Menu::getInstance(); if (auto action = menu->getActionForOption(plugin->getName())) { action->setChecked(true); action->trigger(); // Find and activated highest priority plugin, bail for the rest break; } } auto inputs = pluginManager->getInputPlugins(); for (auto plugin : inputs) { if (!plugin->isActive()) { plugin->activate(); } } getMyAvatar()->loadData(); _settingsLoaded = true;} void Application::saveSettings() const { sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SECOND); DependencyManager::get<AudioClient>()->saveSettings(); DependencyManager::get<LODManager>()->saveSettings(); Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings();} bool Application::importEntities(const QString& urlOrFilename) { bool success = false; _entityClipboard->withWriteLock([&] { _entityClipboard->eraseAllOctreeElements(); success = _entityClipboard->readFromURL(urlOrFilename); if (success) { _entityClipboard->reaverageOctreeElements(); } }); return success;} QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) { return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), x, y, z);} void Application::initDisplay() {} void Application::init() { // Make sure Login state is up to date DependencyManager::get<DialogsManager>()->toggleLoginDialog(); DependencyManager::get<DeferredLightingEffect>()->init(); DependencyManager::get<AvatarManager>()->init(); _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); _mirrorCamera.setMode(CAMERA_MODE_MIRROR); _timerStart.start(); _lastTimeUpdated.start(); // when --url in command line, teleport to location const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); QString addressLookupString; if (urlIndex != -1) { addressLookupString = arguments().value(urlIndex + 1); } Setting::Handle<bool> 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<AddressManager>()->ifLocalSandboxRunningElse([](){ qDebug() << "Home sandbox appears to be running, going to Home."; DependencyManager::get<AddressManager>()->goToLocalSandbox(); }, [](){ qDebug() << "Home sandbox does not appear to be running, going to Entry."; DependencyManager::get<AddressManager>()->goToEntry(); }); } else { qDebug() << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); } qCDebug(interfaceapp) << "Loaded settings"; Leapmotion::init(); // fire off an immediate domain-server check in now that settings are loaded DependencyManager::get<NodeList>()->sendDomainServerCheckIn(); getEntities()->init(); { QMutexLocker viewLocker(&_viewMutex); getEntities()->setViewFrustum(_viewFrustum); } ObjectMotionState::setShapeManager(&_shapeManager); _physicsEngine->init(); EntityTreePointer tree = getEntities()->getTree(); _entitySimulation->init(tree, _physicsEngine, &_entityEditSender); tree->setSimulation(_entitySimulation); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity, getEntities(), &EntityTreeRenderer::entityCollisionWithEntity); // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing // of events related clicking, hovering over, and entering entities getEntities()->connectSignalsToSlots(entityScriptingInterface.data()); _entityClipboardRenderer.init(); { 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, this, [this](QUrl newURL, EntityItemID id) { EntityTreePointer tree = getEntities()->getTree(); if (auto entity = tree->findEntityByEntityItemID(id)) { auto sound = DependencyManager::get<SoundCache>()->getSound(newURL); entity->setCollisionSound(sound); } }, Qt::QueuedConnection); connect(getMyAvatar(), &MyAvatar::newCollisionSoundURL, this, [this](QUrl newURL) { if (auto avatar = getMyAvatar()) { auto sound = DependencyManager::get<SoundCache>()->getSound(newURL); avatar->setCollisionSound(sound); } }, Qt::QueuedConnection);} void Application::updateLOD() const { PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode if (!isThrottleRendering()) { DependencyManager::get<LODManager>()->autoAdjustLOD(_frameCounter.rate()); } else { DependencyManager::get<LODManager>()->resetLODAdjust(); }} void Application::pushPostUpdateLambda(void* key, std::function<void()> func) { std::unique_lock<std::mutex> guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func;} // Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone.// (Maybe this code should be moved there?)// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().// Note that it is called BEFORE we update position or joints based on sensors, etc.void Application::updateMyAvatarLookAtPosition() { PerformanceTimer perfTimer("lookAt"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); auto myAvatar = getMyAvatar(); myAvatar->updateLookAtTargetAvatar(); FaceTracker* faceTracker = getActiveFaceTracker(); auto eyeTracker = DependencyManager::get<EyeTracker>(); bool isLookingAtSomeone = false; bool isHMD = qApp->isHMDMode(); glm::vec3 lookAtSpot; if (eyeTracker->isTracking() && (isHMD || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. glm::vec3 lookAtPosition = eyeTracker->getLookAtPosition(); if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { lookAtPosition.x = -lookAtPosition.x; } if (isHMD) { glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); glm::quat hmdRotation = glm::quat_cast(headPose); lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * lookAtPosition); } } else { AvatarSharedPointer lookingAt = myAvatar->getLookAtTargetAvatar().lock(); if (lookingAt && myAvatar != lookingAt.get()) { // If I am looking at someone else, look directly at one of their eyes isLookingAtSomeone = true; auto lookingAtHead = static_pointer_cast<Avatar>(lookingAt)->getHead(); const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT; glm::vec3 fromLookingAtToMe = glm::normalize(myAvatar->getHead()->getEyePosition() - lookingAtHead->getEyePosition()); float faceAngle = glm::angle(lookingAtFaceOrientation, fromLookingAtToMe); if (faceAngle < MAXIMUM_FACE_ANGLE) { // Randomly look back and forth between look targets eyeContactTarget target = Menu::getInstance()->isOptionChecked(MenuOption::FixGaze) ? LEFT_EYE : myAvatar->getEyeContactTarget(); switch (target) { case LEFT_EYE: lookAtSpot = lookingAtHead->getLeftEyePosition(); break; case RIGHT_EYE: lookAtSpot = lookingAtHead->getRightEyePosition(); break; case MOUTH: lookAtSpot = lookingAtHead->getMouthPosition(); break; } } else { // Just look at their head (mid point between eyes) lookAtSpot = lookingAtHead->getEyePosition(); } } else { // I am not looking at anyone else, so just look forward if (isHMD) { glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); lookAtSpot = transformPoint(worldHMDMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } } // Deflect the eyes a bit to match the detected gaze from the face tracker if active. if (faceTracker && !faceTracker->isMuted()) { float eyePitch = faceTracker->getEstimatedEyePitch(); float eyeYaw = faceTracker->getEstimatedEyeYaw(); const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f; glm::vec3 origin = myAvatar->getHead()->getEyePosition(); float deflection = faceTracker->getEyeDeflection(); if (isLookingAtSomeone) { deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; } lookAtSpot = origin + _myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3( eyePitch * deflection, eyeYaw * deflection, 0.0f))) * glm::inverse(_myCamera.getOrientation()) * (lookAtSpot - origin); } } myAvatar->getHead()->setLookAtPosition(lookAtSpot);} void Application::updateThreads(float deltaTime) { PerformanceTimer perfTimer("updateThreads"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateThreads()"); // parse voxel packets if (!_enableProcessOctreeThread) { _octreeProcessor.threadRoutine(); _entityEditSender.threadRoutine(); }} void Application::toggleOverlays() { auto menu = Menu::getInstance(); menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays));} void Application::setOverlaysVisible(bool visible) { auto menu = Menu::getInstance(); menu->setIsOptionChecked(MenuOption::Overlays, true);} void Application::cycleCamera() { auto menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::FullscreenMirror)) { menu->setIsOptionChecked(MenuOption::FullscreenMirror, false); menu->setIsOptionChecked(MenuOption::FirstPerson, true); } else if (menu->isOptionChecked(MenuOption::FirstPerson)) { menu->setIsOptionChecked(MenuOption::FirstPerson, false); menu->setIsOptionChecked(MenuOption::ThirdPerson, true); } else if (menu->isOptionChecked(MenuOption::ThirdPerson)) { menu->setIsOptionChecked(MenuOption::ThirdPerson, false); menu->setIsOptionChecked(MenuOption::FullscreenMirror, true); } else if (menu->isOptionChecked(MenuOption::IndependentMode) || menu->isOptionChecked(MenuOption::CameraEntityMode)) { // do nothing if in independent or camera entity modes return; } cameraMenuChanged(); // handle the menu change} void Application::cameraMenuChanged() { if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { _myCamera.setMode(CAMERA_MODE_MIRROR); } } else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); } } else if (Menu::getInstance()->isOptionChecked(MenuOption::ThirdPerson)) { if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) { getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT); } } } else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) { if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { _myCamera.setMode(CAMERA_MODE_INDEPENDENT); } } else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) { if (_myCamera.getMode() != CAMERA_MODE_ENTITY) { _myCamera.setMode(CAMERA_MODE_ENTITY); } }} void Application::resetPhysicsReadyInformation() { // we've changed domains or cleared out caches or something. we no longer know enough about the // collision information of nearby entities to make running bullet be safe. _fullSceneReceivedCounter = 0; _fullSceneCounterAtLastPhysicsCheck = 0; _nearbyEntitiesCountAtLastPhysicsCheck = 0; _nearbyEntitiesStabilityCount = 0; _physicsEnabled = false;} 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 queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); DependencyManager::get<AssetClient>()->clearCache(); DependencyManager::get<AnimationCache>()->refreshAll(); DependencyManager::get<ModelCache>()->refreshAll(); DependencyManager::get<SoundCache>()->refreshAll(); DependencyManager::get<TextureCache>()->refreshAll(); DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models getMyAvatar()->resetFullAvatarURL();} void Application::rotationModeChanged() const { if (!Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { getMyAvatar()->setHeadPitch(0); }} void Application::updateDialogs(float deltaTime) const { PerformanceTimer perfTimer("updateDialogs"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateDialogs()"); auto dialogsManager = DependencyManager::get<DialogsManager>(); // Update audio stats dialog, if any AudioStatsDialog* audioStatsDialog = dialogsManager->getAudioStatsDialog(); if(audioStatsDialog) { audioStatsDialog->update(); } // Update bandwidth dialog, if any BandwidthDialog* bandwidthDialog = dialogsManager->getBandwidthDialog(); if (bandwidthDialog) { bandwidthDialog->update(); } QPointer<OctreeStatsDialog> octreeStatsDialog = dialogsManager->getOctreeStatsDialog(); if (octreeStatsDialog) { octreeStatsDialog->update(); }} void Application::update(float deltaTime) { PROFILE_RANGE_EX(__FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); updateLOD(); if (!_physicsEnabled) { // we haven't yet enabled physics. we wait until we think we have all the collision information // for nearby entities before starting bullet up. quint64 now = usecTimestampNow(); bool timeout = false; const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND; if (_lastPhysicsCheckTime > 0 && now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT) { timeout = true; } if (timeout || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) { // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway _lastPhysicsCheckTime = now; _fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter; // process octree stats packets are sent in between full sends of a scene (this isn't currently true). // We keep physics disabled until we've received a full scene and everything near the avatar in that // scene is ready to compute its collision shape. if (nearbyEntitiesAreReadyForPhysics()) { _physicsEnabled = true; getMyAvatar()->updateMotionBehaviorFromMenu(); } else { auto characterController = getMyAvatar()->getCharacterController(); if (characterController) { // if we have a character controller, disable it here so the avatar doesn't get stuck due to // a non-loading collision hull. characterController->setEnabled(false); } } } } {Lines 3001–3500 of 5476. Previous lines Load more
} if (!givenOffset) { EntityItemID parentID = entityItem->getParentID(); if (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID)) { auto position = entityItem->getPosition(); // If parent wasn't selected, we want absolute position, which isn't in properties. root.x = glm::min(root.x, position.x); root.y = glm::min(root.y, position.y); root.z = glm::min(root.z, position.z); } } entities[entityID] = entityItem; } if (entities.size() == 0) { success = false; return; } if (givenOffset) { root = *givenOffset; } for (EntityItemPointer& entityDatum : entities) { auto properties = entityDatum->getProperties(); EntityItemID parentID = properties.getParentID(); if (parentID.isInvalidID()) { properties.setPosition(properties.getPosition() - root); } else if (!entities.contains(parentID)) { entityDatum->globalizeProperties(properties, "Parent %3 of %2 %1 is not selected for export.", -root); } // else valid parent -- don't offset exportTree->addEntity(entityDatum->getEntityItemID(), properties); } }); if (success) { success = exportTree->writeToJSONFile(filename.toLocal8Bit().constData()); // restore the main window's active state _window->activateWindow(); } return success; } bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale) { glm::vec3 offset(x, y, z); QVector<EntityItemPointer> entities; QVector<EntityItemID> ids; auto entityTree = getEntities()->getTree(); entityTree->withReadLock([&] { entityTree->findEntities(AACube(offset, scale), entities); foreach(EntityItemPointer entity, entities) { ids << entity->getEntityItemID(); } }); return exportEntities(filename, ids, &offset); } void Application::loadSettings() { sessionRunTime.set(0); // Just clean living. We're about to saveSettings, which will update value. DependencyManager::get<AudioClient>()->loadSettings(); DependencyManager::get<LODManager>()->loadSettings(); // DONT CHECK IN //DependencyManager::get<LODManager>()->setAutomaticLODAdjust(false); Menu::getInstance()->loadSettings(); // If there is a preferred plugin, we probably messed it up with the menu settings, so fix it. auto pluginManager = PluginManager::getInstance(); auto plugins = pluginManager->getPreferredDisplayPlugins(); for (auto plugin : plugins) { auto menu = Menu::getInstance(); if (auto action = menu->getActionForOption(plugin->getName())) { action->setChecked(true); action->trigger(); // Find and activated highest priority plugin, bail for the rest break; } } auto inputs = pluginManager->getInputPlugins(); for (auto plugin : inputs) { if (!plugin->isActive()) { plugin->activate(); } } getMyAvatar()->loadData(); _settingsLoaded = true; } void Application::saveSettings() const { sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SECOND); DependencyManager::get<AudioClient>()->saveSettings(); DependencyManager::get<LODManager>()->saveSettings(); Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); } bool Application::importEntities(const QString& urlOrFilename) { bool success = false; _entityClipboard->withWriteLock([&] { _entityClipboard->eraseAllOctreeElements(); success = _entityClipboard->readFromURL(urlOrFilename); if (success) { _entityClipboard->reaverageOctreeElements(); } }); return success; } QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) { return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), x, y, z); } void Application::initDisplay() { } void Application::init() { // Make sure Login state is up to date DependencyManager::get<DialogsManager>()->toggleLoginDialog(); DependencyManager::get<DeferredLightingEffect>()->init(); DependencyManager::get<AvatarManager>()->init(); _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); _mirrorCamera.setMode(CAMERA_MODE_MIRROR); _timerStart.start(); _lastTimeUpdated.start(); // when --url in command line, teleport to location const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY); QString addressLookupString; if (urlIndex != -1) { addressLookupString = arguments().value(urlIndex + 1); } Setting::Handle<bool> 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<AddressManager>()->ifLocalSandboxRunningElse([](){ qDebug() << "Home sandbox appears to be running, going to Home."; DependencyManager::get<AddressManager>()->goToLocalSandbox(); }, [](){ qDebug() << "Home sandbox does not appear to be running, going to Entry."; DependencyManager::get<AddressManager>()->goToEntry(); }); } else { qDebug() << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); DependencyManager::get<AddressManager>()->loadSettings(addressLookupString); } qCDebug(interfaceapp) << "Loaded settings"; Leapmotion::init(); // fire off an immediate domain-server check in now that settings are loaded DependencyManager::get<NodeList>()->sendDomainServerCheckIn(); getEntities()->init(); { QMutexLocker viewLocker(&_viewMutex); getEntities()->setViewFrustum(_viewFrustum); } ObjectMotionState::setShapeManager(&_shapeManager); _physicsEngine->init(); EntityTreePointer tree = getEntities()->getTree(); _entitySimulation->init(tree, _physicsEngine, &_entityEditSender); tree->setSimulation(_entitySimulation); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity, getEntities(), &EntityTreeRenderer::entityCollisionWithEntity); // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing // of events related clicking, hovering over, and entering entities getEntities()->connectSignalsToSlots(entityScriptingInterface.data()); _entityClipboardRenderer.init(); { 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, this, [this](QUrl newURL, EntityItemID id) { EntityTreePointer tree = getEntities()->getTree(); if (auto entity = tree->findEntityByEntityItemID(id)) { auto sound = DependencyManager::get<SoundCache>()->getSound(newURL); entity->setCollisionSound(sound); } }, Qt::QueuedConnection); connect(getMyAvatar(), &MyAvatar::newCollisionSoundURL, this, [this](QUrl newURL) { if (auto avatar = getMyAvatar()) { auto sound = DependencyManager::get<SoundCache>()->getSound(newURL); avatar->setCollisionSound(sound); } }, Qt::QueuedConnection); } void Application::updateLOD() const { PerformanceTimer perfTimer("LOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode if (!isThrottleRendering()) { DependencyManager::get<LODManager>()->autoAdjustLOD(_frameCounter.rate()); } else { DependencyManager::get<LODManager>()->resetLODAdjust(); } } void Application::pushPostUpdateLambda(void* key, std::function<void()> func) { std::unique_lock<std::mutex> guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func; } // Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone. // (Maybe this code should be moved there?) // The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition(). // Note that it is called BEFORE we update position or joints based on sensors, etc. void Application::updateMyAvatarLookAtPosition() { PerformanceTimer perfTimer("lookAt"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); auto myAvatar = getMyAvatar(); myAvatar->updateLookAtTargetAvatar(); FaceTracker* faceTracker = getActiveFaceTracker(); auto eyeTracker = DependencyManager::get<EyeTracker>(); bool isLookingAtSomeone = false; bool isHMD = qApp->isHMDMode(); glm::vec3 lookAtSpot; if (eyeTracker->isTracking() && (isHMD || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. glm::vec3 lookAtPosition = eyeTracker->getLookAtPosition(); if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { lookAtPosition.x = -lookAtPosition.x; } if (isHMD) { glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); glm::quat hmdRotation = glm::quat_cast(headPose); lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * lookAtPosition); } } else { AvatarSharedPointer lookingAt = myAvatar->getLookAtTargetAvatar().lock(); if (lookingAt && myAvatar != lookingAt.get()) { // If I am looking at someone else, look directly at one of their eyes isLookingAtSomeone = true; auto lookingAtHead = static_pointer_cast<Avatar>(lookingAt)->getHead(); const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT; glm::vec3 fromLookingAtToMe = glm::normalize(myAvatar->getHead()->getEyePosition() - lookingAtHead->getEyePosition()); float faceAngle = glm::angle(lookingAtFaceOrientation, fromLookingAtToMe); if (faceAngle < MAXIMUM_FACE_ANGLE) { // Randomly look back and forth between look targets eyeContactTarget target = Menu::getInstance()->isOptionChecked(MenuOption::FixGaze) ? LEFT_EYE : myAvatar->getEyeContactTarget(); switch (target) { case LEFT_EYE: lookAtSpot = lookingAtHead->getLeftEyePosition(); break; case RIGHT_EYE: lookAtSpot = lookingAtHead->getRightEyePosition(); break; case MOUTH: lookAtSpot = lookingAtHead->getMouthPosition(); break; } } else { // Just look at their head (mid point between eyes) lookAtSpot = lookingAtHead->getEyePosition(); } } else { // I am not looking at anyone else, so just look forward if (isHMD) { glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); lookAtSpot = transformPoint(worldHMDMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } else { lookAtSpot = myAvatar->getHead()->getEyePosition() + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } } // Deflect the eyes a bit to match the detected gaze from the face tracker if active. if (faceTracker && !faceTracker->isMuted()) { float eyePitch = faceTracker->getEstimatedEyePitch(); float eyeYaw = faceTracker->getEstimatedEyeYaw(); const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f; glm::vec3 origin = myAvatar->getHead()->getEyePosition(); float deflection = faceTracker->getEyeDeflection(); if (isLookingAtSomeone) { deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; } lookAtSpot = origin + _myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3( eyePitch * deflection, eyeYaw * deflection, 0.0f))) * glm::inverse(_myCamera.getOrientation()) * (lookAtSpot - origin); } } myAvatar->getHead()->setLookAtPosition(lookAtSpot); } void Application::updateThreads(float deltaTime) { PerformanceTimer perfTimer("updateThreads"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateThreads()"); // parse voxel packets if (!_enableProcessOctreeThread) { _octreeProcessor.threadRoutine(); _entityEditSender.threadRoutine(); } } void Application::toggleOverlays() { auto menu = Menu::getInstance(); menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays)); } void Application::setOverlaysVisible(bool visible) { auto menu = Menu::getInstance(); menu->setIsOptionChecked(MenuOption::Overlays, true); } void Application::cycleCamera() { auto menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::FullscreenMirror)) { menu->setIsOptionChecked(MenuOption::FullscreenMirror, false); menu->setIsOptionChecked(MenuOption::FirstPerson, true); } else if (menu->isOptionChecked(MenuOption::FirstPerson)) { menu->setIsOptionChecked(MenuOption::FirstPerson, false); menu->setIsOptionChecked(MenuOption::ThirdPerson, true); } else if (menu->isOptionChecked(MenuOption::ThirdPerson)) { menu->setIsOptionChecked(MenuOption::ThirdPerson, false); menu->setIsOptionChecked(MenuOption::FullscreenMirror, true); } else if (menu->isOptionChecked(MenuOption::IndependentMode) || menu->isOptionChecked(MenuOption::CameraEntityMode)) { // do nothing if in independent or camera entity modes return; } cameraMenuChanged(); // handle the menu change } void Application::cameraMenuChanged() { if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { _myCamera.setMode(CAMERA_MODE_MIRROR); } } else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); } } else if (Menu::getInstance()->isOptionChecked(MenuOption::ThirdPerson)) { if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) { getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT); } } } else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) { if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { _myCamera.setMode(CAMERA_MODE_INDEPENDENT); } } else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) { if (_myCamera.getMode() != CAMERA_MODE_ENTITY) { _myCamera.setMode(CAMERA_MODE_ENTITY); } } } void Application::resetPhysicsReadyInformation() { // we've changed domains or cleared out caches or something. we no longer know enough about the // collision information of nearby entities to make running bullet be safe. _fullSceneReceivedCounter = 0; _fullSceneCounterAtLastPhysicsCheck = 0; _nearbyEntitiesCountAtLastPhysicsCheck = 0; _nearbyEntitiesStabilityCount = 0; _physicsEnabled = false; } 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 queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); DependencyManager::get<AssetClient>()->clearCache(); DependencyManager::get<AnimationCache>()->refreshAll(); DependencyManager::get<ModelCache>()->refreshAll(); DependencyManager::get<SoundCache>()->refreshAll(); DependencyManager::get<TextureCache>()->refreshAll(); DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models getMyAvatar()->resetFullAvatarURL(); } void Application::rotationModeChanged() const { if (!Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { getMyAvatar()->setHeadPitch(0); } } void Application::updateDialogs(float deltaTime) const { PerformanceTimer perfTimer("updateDialogs"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateDialogs()"); auto dialogsManager = DependencyManager::get<DialogsManager>(); // Update audio stats dialog, if any AudioStatsDialog* audioStatsDialog = dialogsManager->getAudioStatsDialog(); if(audioStatsDialog) { audioStatsDialog->update(); } // Update bandwidth dialog, if any BandwidthDialog* bandwidthDialog = dialogsManager->getBandwidthDialog(); if (bandwidthDialog) { bandwidthDialog->update(); } QPointer<OctreeStatsDialog> octreeStatsDialog = dialogsManager->getOctreeStatsDialog(); if (octreeStatsDialog) { octreeStatsDialog->update(); } } void Application::update(float deltaTime) { PROFILE_RANGE_EX(__FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); updateLOD(); if (!_physicsEnabled) { // we haven't yet enabled physics. we wait until we think we have all the collision information // for nearby entities before starting bullet up. quint64 now = usecTimestampNow(); bool timeout = false; const int PHYSICS_CHECK_TIMEOUT = 2 * USECS_PER_SECOND; if (_lastPhysicsCheckTime > 0 && now - _lastPhysicsCheckTime > PHYSICS_CHECK_TIMEOUT) { timeout = true; } if (timeout || _fullSceneReceivedCounter > _fullSceneCounterAtLastPhysicsCheck) { // we've received a new full-scene octree stats packet, or it's been long enough to try again anyway _lastPhysicsCheckTime = now; _fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter; // process octree stats packets are sent in between full sends of a scene (this isn't currently true). // We keep physics disabled until we've received a full scene and everything near the avatar in that // scene is ready to compute its collision shape. if (nearbyEntitiesAreReadyForPhysics()) { _physicsEnabled = true; getMyAvatar()->updateMotionBehaviorFromMenu(); } else { auto characterController = getMyAvatar()->getCharacterController(); if (characterController) { // if we have a character controller, disable it here so the avatar doesn't get stuck due to // a non-loading collision hull. characterController->setEnabled(false); } } } } {