//// Application_Entities.cpp// interface/src//// Split from Application.cpp by HifiExperiments on 3/30/24// Created by Andrzej Kapolka on 5/10/13.// Copyright 2013 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.// Copyright 2022-2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #include "Application.h" #include <AnimationCache.h>#include <input-plugins/KeyboardMouseDevice.h>#include <MainWindow.h>#include <recording/ClipCache.h>#include <RenderableEntityItem.h>#include <SoundCache.h>#include <QSaveFile> #include "InterfaceLogging.h"#include "LODManager.h" static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f; static const float INITIAL_QUERY_RADIUS = 10.0f; // priority radius for entities before physics enabled void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) { if (maxOctreePPS != _maxOctreePPS) { _maxOctreePPS = maxOctreePPS; _maxOctreePacketsPerSecond.set(_maxOctreePPS); }} QVector<EntityItemID> Application::pasteEntities(const QString& entityHostType, float x, float y, float z) { return _entityClipboard->sendEntities(_entityEditSender.get(), getEntities()->getTree(), entityHostType, x, y, z);} bool Application::exportEntities(const QString& filename, const QVector<QUuid>& entityIDs, const glm::vec3* givenOffset, const QVariantMap& options) { QVector<QString> propertiesToPrune = { "queryAACube", "age", "created", "lastEdited", "lastEditedBy", "faceCamera", "isFacingAvatar", "clientOnly", "avatarEntity", "localEntity", }; bool globalPositions = options.value("globalPositions", false).toBool(); auto paths = options.value("paths", QVariantMap()).toMap(); QHash<EntityItemID, EntityItemPointer> entities; auto nodeList = DependencyManager::get<NodeList>(); const QUuid myAvatarID = nodeList->getSessionUUID(); auto entityTree = getEntities()->getTree(); auto exportTree = std::make_shared<EntityTree>(); exportTree->setMyAvatar(getMyAvatar()); exportTree->createRootElement(); glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE); bool success = true; entityTree->withReadLock([entityIDs, entityTree, givenOffset, myAvatarID, &root, &entities, &success, &exportTree, globalPositions] { for (auto entityID : entityIDs) { // Gather entities and properties. auto entityItem = entityTree->findEntityByEntityItemID(entityID); if (!entityItem) { qCWarning(interfaceapp) << "Skipping export of" << entityID << "that is not in scene."; continue; } if (!givenOffset && !globalPositions) { EntityItemID parentID = entityItem->getParentID(); bool parentIsAvatar = (parentID == AVATAR_SELF_ID || parentID == myAvatarID); if (!parentIsAvatar && (parentID.isInvalidID() || !entityIDs.contains(parentID) || !entityTree->findEntityByEntityItemID(parentID))) { // If parent wasn't selected, we want absolute position, which isn't in properties. auto position = entityItem->getWorldPosition(); 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 (globalPositions) { root = {0.0f, 0.0f, 0.0f}; } else if (givenOffset) { root = *givenOffset; } for (EntityItemPointer& entityDatum : entities) { auto properties = entityDatum->getProperties(); EntityItemID parentID = properties.getParentID(); bool parentIsAvatar = (parentID == AVATAR_SELF_ID || parentID == myAvatarID); if (parentIsAvatar) { properties.setParentID(AVATAR_SELF_ID); } else { 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) { QJsonDocument jsonDocument; bool success = exportTree->toJSONDocument(&jsonDocument); if (!success) { return false; } auto object = jsonDocument.object(); if (propertiesToPrune.size() != 0) { auto entityList = object["Entities"].toArray(); for (int i = 0; i < entityList.size(); i++) { auto entity = entityList[i].toObject(); for (auto property : propertiesToPrune) { entity.remove(property); } entityList[i] = entity; } // these have to be re-assigned rather than modified by reference // because it looks like Qt only gives us copies object["Entities"] = entityList; } if (!paths.isEmpty()) { QJsonObject pathsObject; foreach (const QString& key, paths.keys()) { auto object = paths.value(key).toMap(); auto position = object.value("position").toMap(); auto orientation = object.value("orientation").toMap(); pathsObject[key] = QString("/%1,%2,%3/%4,%5,%6,%7") .arg(position["x"].toDouble()) .arg(position["y"].toDouble()) .arg(position["z"].toDouble()) .arg(orientation["x"].toDouble()) .arg(orientation["y"].toDouble()) .arg(orientation["z"].toDouble()) .arg(orientation["w"].toDouble()); } object["Paths"] = pathsObject; } // weirdly QJsonDocument::object returns a copy and not a reference jsonDocument.setObject(object); // Octree::writeToJSONFile QSaveFile persistFile(filename); if (persistFile.open(QIODevice::WriteOnly)) { if (persistFile.write(jsonDocument.toJson()) != -1) { success = persistFile.commit(); if (!success) { qCritical() << "Failed to commit to JSON save file:" << persistFile.errorString(); } } else { qCritical("Failed to write to JSON file."); } } else { qCritical("Failed to open JSON file for writing."); } // restore the main window's active state#ifdef USE_GL _window->activateWindow(); //VKTODO#endif } return success;} bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale, const QVariantMap& options) { glm::vec3 center(x, y, z); glm::vec3 minCorner = center - vec3(scale); float cubeSize = scale * 2; AACube boundingCube(minCorner, cubeSize); QVector<QUuid> entities; auto entityTree = getEntities()->getTree(); entityTree->withReadLock([&] { entityTree->evalEntitiesInCube( boundingCube, PickFilter(PickFilter::getBitMask(PickFilter::DOMAIN_ENTITIES)), entities ); }); return exportEntities(filename, entities, ¢er, options);} bool Application::importEntities(const QString& urlOrFilename, const bool isObservable, const qint64 callerId) { bool success = false; _entityClipboard->withWriteLock([&] { _entityClipboard->eraseAllOctreeElements(); // FIXME: readFromURL() can take over the main event loop which may cause problems, especially if downloading the JSON // from the Web. success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId, true); if (success) { _entityClipboard->reaverageOctreeElements(); } }); return success;} void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions) { if (qApp->getLoginDialogPoppedUp()) { return; } auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); if (_keyboardFocusHighlightID == UNKNOWN_ENTITY_ID || !entityScriptingInterface->isAddedEntity(_keyboardFocusHighlightID)) { EntityItemProperties properties; properties.setType(EntityTypes::Box); properties.setAlpha(1.0f); properties.setColor({ 0xFF, 0xEF, 0x00 }); properties.setPrimitiveMode(PrimitiveMode::LINES); properties.getPulse().setMin(0.5); properties.getPulse().setMax(1.0f); properties.getPulse().setColorMode(PulseMode::IN_PHASE); properties.setIgnorePickIntersection(true); _keyboardFocusHighlightID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL); } // Position focus EntityItemProperties properties; properties.setPosition(position); properties.setRotation(rotation); properties.setDimensions(dimensions); properties.setVisible(true); entityScriptingInterface->editEntity(_keyboardFocusHighlightID, properties);} void Application::setKeyboardFocusEntity(const QUuid& id) { if (_keyboardFocusedEntity.get() != id) { if (qApp->getLoginDialogPoppedUp() && !_loginDialogID.isNull()) { if (id == _loginDialogID) { emit loginDialogFocusEnabled(); } else if (!_keyboardFocusWaitingOnRenderable) { // that's the only entity we want in focus; return; } } _keyboardFocusedEntity.set(id); auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); if (id != UNKNOWN_ENTITY_ID) { EntityPropertyFlags desiredProperties; desiredProperties += PROP_VISIBLE; desiredProperties += PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT; auto properties = entityScriptingInterface->getEntityProperties(id); if (properties.getVisible()) { auto entities = getEntities(); auto entityId = _keyboardFocusedEntity.get(); auto entityItemRenderable = entities->renderableForEntityId(entityId); if (!entityItemRenderable) { _keyboardFocusWaitingOnRenderable = true; } else if (entityItemRenderable->wantsKeyboardFocus()) { entities->setProxyWindow(entityId, _window->windowHandle()); if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); } _lastAcceptedKeyPress = usecTimestampNow(); if (properties.getShowKeyboardFocusHighlight()) { if (auto entity = entities->getEntity(entityId)) { setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); return; } } } } } EntityItemProperties properties; properties.setVisible(false); entityScriptingInterface->editEntity(_keyboardFocusHighlightID, properties); }} void Application::clearDomainOctreeDetails(bool clearAll) { // if we're about to quit, we really don't need to do the rest of these things... if (_aboutToQuit) { return; } qCDebug(interfaceapp) << "Clearing domain octree details..."; _waitForServerlessToBeSet = true; resetPhysicsReadyInformation(); setIsInterstitialMode(true); auto octreeServerSceneStats = getOcteeSceneStats(); octreeServerSceneStats->withWriteLock([&] { octreeServerSceneStats->clear(); }); // reset the model renderer clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities(); DependencyManager::get<AnimationCache>()->clearUnusedResources(); DependencyManager::get<SoundCache>()->clearUnusedResources(); DependencyManager::get<MaterialCache>()->clearUnusedResources(); DependencyManager::get<ModelCache>()->clearUnusedResources(); ShaderCache::instance().clearUnusedResources(); DependencyManager::get<TextureCache>()->clearUnusedResources(); DependencyManager::get<recording::ClipCache>()->clearUnusedResources();} void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; clearDomainOctreeDetails(false);} void Application::queryOctree(NodeType_t serverType, PacketType packetType) { if (!_settingsLoaded) { return; // bail early if settings are not loaded } const bool isModifiedQuery = !_physicsEnabled; if (isModifiedQuery) { if (!_octreeProcessor->safeLandingIsActive()) { // don't send the octreeQuery until SafeLanding knows it has started return; } // Create modified view that is a simple sphere. bool interstitialModeEnabled = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled(); ConicalViewFrustum sphericalView; AABox box = getMyAvatar()->getGlobalBoundingBox(); float radius = glm::max(INITIAL_QUERY_RADIUS, 0.5f * glm::length(box.getDimensions())); sphericalView.setPositionAndSimpleRadius(box.calcCenter(), radius); if (interstitialModeEnabled) { ConicalViewFrustum farView; farView.set(_viewFrustum); _octreeQuery.setConicalViews({ sphericalView, farView }); } else { _octreeQuery.setConicalViews({ sphericalView }); } _octreeQuery.setOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE); static constexpr float MIN_LOD_ADJUST = -20.0f; _octreeQuery.setBoundaryLevelAdjust(MIN_LOD_ADJUST); } else { _octreeQuery.setConicalViews(_conicalViews); auto lodManager = DependencyManager::get<LODManager>(); _octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale()); _octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust()); } _octreeQuery.setReportInitialCompletion(isModifiedQuery); auto nodeList = DependencyManager::get<NodeList>(); auto node = nodeList->soloNodeOfType(serverType); if (node && node->getActiveSocket()) { _octreeQuery.setMaxQueryPacketsPerSecond(getMaxOctreePacketsPerSecond()); auto queryPacket = NLPacket::create(packetType); // encode the query data auto packetData = reinterpret_cast<unsigned char*>(queryPacket->getPayload()); int packetSize = _octreeQuery.getBroadcastData(packetData); queryPacket->setPayloadSize(packetSize); // make sure we still have an active socket nodeList->sendUnreliablePacket(*queryPacket, *node); }} 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; auto octreeServerSceneStats = getOcteeSceneStats(); 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(); }); _isMissingSequenceNumbers = (missingSequenceNumbers.size() != 0); // 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;}
//
// Application_Entities.cpp
// interface/src
//
// Split from Application.cpp by HifiExperiments on 3/30/24
// Created by Andrzej Kapolka on 5/10/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// Copyright 2022-2023 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// SPDX-License-Identifier: Apache-2.0
//
#include "Application.h"
#include <AnimationCache.h>
#include <input-plugins/KeyboardMouseDevice.h>
#include <MainWindow.h>
#include <recording/ClipCache.h>
#include <RenderableEntityItem.h>
#include <SoundCache.h>
#include <QSaveFile>
#include "InterfaceLogging.h"
#include "LODManager.h"
static const float FOCUS_HIGHLIGHT_EXPANSION_FACTOR = 1.05f;
static const float INITIAL_QUERY_RADIUS = 10.0f; // priority radius for entities before physics enabled
void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) {
if (maxOctreePPS != _maxOctreePPS) {
_maxOctreePPS = maxOctreePPS;
_maxOctreePacketsPerSecond.set(_maxOctreePPS);
}
}
QVector<EntityItemID> Application::pasteEntities(const QString& entityHostType, float x, float y, float z) {
return _entityClipboard->sendEntities(_entityEditSender.get(), getEntities()->getTree(), entityHostType, x, y, z);
}
bool Application::exportEntities(const QString& filename,
const QVector<QUuid>& entityIDs,
const glm::vec3* givenOffset,
const QVariantMap& options) {
QVector<QString> propertiesToPrune = {
"queryAACube", "age", "created", "lastEdited", "lastEditedBy", "faceCamera",
"isFacingAvatar", "clientOnly", "avatarEntity", "localEntity",
};
bool globalPositions = options.value("globalPositions", false).toBool();
auto paths = options.value("paths", QVariantMap()).toMap();
QHash<EntityItemID, EntityItemPointer> entities;
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myAvatarID = nodeList->getSessionUUID();
auto entityTree = getEntities()->getTree();
auto exportTree = std::make_shared<EntityTree>();
exportTree->setMyAvatar(getMyAvatar());
exportTree->createRootElement();
glm::vec3 root(TREE_SCALE, TREE_SCALE, TREE_SCALE);
bool success = true;
entityTree->withReadLock([entityIDs, entityTree, givenOffset, myAvatarID, &root, &entities, &success, &exportTree, globalPositions] {
for (auto entityID : entityIDs) { // Gather entities and properties.
auto entityItem = entityTree->findEntityByEntityItemID(entityID);
if (!entityItem) {
qCWarning(interfaceapp) << "Skipping export of" << entityID << "that is not in scene.";
continue;
}
if (!givenOffset && !globalPositions) {
EntityItemID parentID = entityItem->getParentID();
bool parentIsAvatar = (parentID == AVATAR_SELF_ID || parentID == myAvatarID);
if (!parentIsAvatar && (parentID.isInvalidID() ||
!entityIDs.contains(parentID) ||
!entityTree->findEntityByEntityItemID(parentID))) {
// If parent wasn't selected, we want absolute position, which isn't in properties.
auto position = entityItem->getWorldPosition();
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 (globalPositions) {
root = {0.0f, 0.0f, 0.0f};
} else if (givenOffset) {
root = *givenOffset;
}
for (EntityItemPointer& entityDatum : entities) {
auto properties = entityDatum->getProperties();
EntityItemID parentID = properties.getParentID();
bool parentIsAvatar = (parentID == AVATAR_SELF_ID || parentID == myAvatarID);
if (parentIsAvatar) {
properties.setParentID(AVATAR_SELF_ID);
} else {
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) {
QJsonDocument jsonDocument;
bool success = exportTree->toJSONDocument(&jsonDocument);
if (!success) { return false; }
auto object = jsonDocument.object();
if (propertiesToPrune.size() != 0) {
auto entityList = object["Entities"].toArray();
for (int i = 0; i < entityList.size(); i++) {
auto entity = entityList[i].toObject();
for (auto property : propertiesToPrune) {
entity.remove(property);
}
entityList[i] = entity;
}
// these have to be re-assigned rather than modified by reference
// because it looks like Qt only gives us copies
object["Entities"] = entityList;
}
if (!paths.isEmpty()) {
QJsonObject pathsObject;
foreach (const QString& key, paths.keys()) {
auto object = paths.value(key).toMap();
auto position = object.value("position").toMap();
auto orientation = object.value("orientation").toMap();
pathsObject[key] = QString("/%1,%2,%3/%4,%5,%6,%7")
.arg(position["x"].toDouble())
.arg(position["y"].toDouble())
.arg(position["z"].toDouble())
.arg(orientation["x"].toDouble())
.arg(orientation["y"].toDouble())
.arg(orientation["z"].toDouble())
.arg(orientation["w"].toDouble());
}
object["Paths"] = pathsObject;
}
// weirdly QJsonDocument::object returns a copy and not a reference
jsonDocument.setObject(object);
// Octree::writeToJSONFile
QSaveFile persistFile(filename);
if (persistFile.open(QIODevice::WriteOnly)) {
if (persistFile.write(jsonDocument.toJson()) != -1) {
success = persistFile.commit();
if (!success) {
qCritical() << "Failed to commit to JSON save file:" << persistFile.errorString();
}
} else {
qCritical("Failed to write to JSON file.");
}
} else {
qCritical("Failed to open JSON file for writing.");
}
// restore the main window's active state
#ifdef USE_GL
_window->activateWindow(); //VKTODO
#endif
}
return success;
}
bool Application::exportEntities(const QString& filename, float x, float y, float z, float scale, const QVariantMap& options) {
glm::vec3 center(x, y, z);
glm::vec3 minCorner = center - vec3(scale);
float cubeSize = scale * 2;
AACube boundingCube(minCorner, cubeSize);
QVector<QUuid> entities;
auto entityTree = getEntities()->getTree();
entityTree->withReadLock([&] {
entityTree->evalEntitiesInCube(
boundingCube,
PickFilter(PickFilter::getBitMask(PickFilter::DOMAIN_ENTITIES)),
entities
);
});
return exportEntities(filename, entities, ¢er, options);
}
bool Application::importEntities(const QString& urlOrFilename, const bool isObservable, const qint64 callerId) {
bool success = false;
_entityClipboard->withWriteLock([&] {
_entityClipboard->eraseAllOctreeElements();
// FIXME: readFromURL() can take over the main event loop which may cause problems, especially if downloading the JSON
// from the Web.
success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId, true);
if (success) {
_entityClipboard->reaverageOctreeElements();
}
});
return success;
}
void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions) {
if (qApp->getLoginDialogPoppedUp()) {
return;
}
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (_keyboardFocusHighlightID == UNKNOWN_ENTITY_ID || !entityScriptingInterface->isAddedEntity(_keyboardFocusHighlightID)) {
EntityItemProperties properties;
properties.setType(EntityTypes::Box);
properties.setAlpha(1.0f);
properties.setColor({ 0xFF, 0xEF, 0x00 });
properties.setPrimitiveMode(PrimitiveMode::LINES);
properties.getPulse().setMin(0.5);
properties.getPulse().setMax(1.0f);
properties.getPulse().setColorMode(PulseMode::IN_PHASE);
properties.setIgnorePickIntersection(true);
_keyboardFocusHighlightID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
}
// Position focus
EntityItemProperties properties;
properties.setPosition(position);
properties.setRotation(rotation);
properties.setDimensions(dimensions);
properties.setVisible(true);
entityScriptingInterface->editEntity(_keyboardFocusHighlightID, properties);
}
void Application::setKeyboardFocusEntity(const QUuid& id) {
if (_keyboardFocusedEntity.get() != id) {
if (qApp->getLoginDialogPoppedUp() && !_loginDialogID.isNull()) {
if (id == _loginDialogID) {
emit loginDialogFocusEnabled();
} else if (!_keyboardFocusWaitingOnRenderable) {
// that's the only entity we want in focus;
return;
}
}
_keyboardFocusedEntity.set(id);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (id != UNKNOWN_ENTITY_ID) {
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_VISIBLE;
desiredProperties += PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT;
auto properties = entityScriptingInterface->getEntityProperties(id);
if (properties.getVisible()) {
auto entities = getEntities();
auto entityId = _keyboardFocusedEntity.get();
auto entityItemRenderable = entities->renderableForEntityId(entityId);
if (!entityItemRenderable) {
_keyboardFocusWaitingOnRenderable = true;
} else if (entityItemRenderable->wantsKeyboardFocus()) {
entities->setProxyWindow(entityId, _window->windowHandle());
if (_keyboardMouseDevice->isActive()) {
_keyboardMouseDevice->pluginFocusOutEvent();
}
_lastAcceptedKeyPress = usecTimestampNow();
if (properties.getShowKeyboardFocusHighlight()) {
if (auto entity = entities->getEntity(entityId)) {
setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(),
entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR);
return;
}
}
}
}
}
EntityItemProperties properties;
properties.setVisible(false);
entityScriptingInterface->editEntity(_keyboardFocusHighlightID, properties);
}
}
void Application::clearDomainOctreeDetails(bool clearAll) {
// if we're about to quit, we really don't need to do the rest of these things...
if (_aboutToQuit) {
return;
}
qCDebug(interfaceapp) << "Clearing domain octree details...";
_waitForServerlessToBeSet = true;
resetPhysicsReadyInformation();
setIsInterstitialMode(true);
auto octreeServerSceneStats = getOcteeSceneStats();
octreeServerSceneStats->withWriteLock([&] {
octreeServerSceneStats->clear();
});
// reset the model renderer
clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities();
DependencyManager::get<AnimationCache>()->clearUnusedResources();
DependencyManager::get<SoundCache>()->clearUnusedResources();
DependencyManager::get<MaterialCache>()->clearUnusedResources();
DependencyManager::get<ModelCache>()->clearUnusedResources();
ShaderCache::instance().clearUnusedResources();
DependencyManager::get<TextureCache>()->clearUnusedResources();
DependencyManager::get<recording::ClipCache>()->clearUnusedResources();
}
void Application::resettingDomain() {
_notifiedPacketVersionMismatchThisDomain = false;
clearDomainOctreeDetails(false);
}
void Application::queryOctree(NodeType_t serverType, PacketType packetType) {
if (!_settingsLoaded) {
return; // bail early if settings are not loaded
}
const bool isModifiedQuery = !_physicsEnabled;
if (isModifiedQuery) {
if (!_octreeProcessor->safeLandingIsActive()) {
// don't send the octreeQuery until SafeLanding knows it has started
return;
}
// Create modified view that is a simple sphere.
bool interstitialModeEnabled = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
ConicalViewFrustum sphericalView;
AABox box = getMyAvatar()->getGlobalBoundingBox();
float radius = glm::max(INITIAL_QUERY_RADIUS, 0.5f * glm::length(box.getDimensions()));
sphericalView.setPositionAndSimpleRadius(box.calcCenter(), radius);
if (interstitialModeEnabled) {
ConicalViewFrustum farView;
farView.set(_viewFrustum);
_octreeQuery.setConicalViews({ sphericalView, farView });
} else {
_octreeQuery.setConicalViews({ sphericalView });
}
_octreeQuery.setOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE);
static constexpr float MIN_LOD_ADJUST = -20.0f;
_octreeQuery.setBoundaryLevelAdjust(MIN_LOD_ADJUST);
} else {
_octreeQuery.setConicalViews(_conicalViews);
auto lodManager = DependencyManager::get<LODManager>();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
}
_octreeQuery.setReportInitialCompletion(isModifiedQuery);
auto nodeList = DependencyManager::get<NodeList>();
auto node = nodeList->soloNodeOfType(serverType);
if (node && node->getActiveSocket()) {
_octreeQuery.setMaxQueryPacketsPerSecond(getMaxOctreePacketsPerSecond());
auto queryPacket = NLPacket::create(packetType);
// encode the query data
auto packetData = reinterpret_cast<unsigned char*>(queryPacket->getPayload());
int packetSize = _octreeQuery.getBroadcastData(packetData);
queryPacket->setPayloadSize(packetSize);
// make sure we still have an active socket
nodeList->sendUnreliablePacket(*queryPacket, *node);
}
}
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;
auto octreeServerSceneStats = getOcteeSceneStats();
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();
});
_isMissingSequenceNumbers = (missingSequenceNumbers.size() != 0);
// 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;
}