444 lines · 17.4 KB · C++
RawDownload
////  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, &center, 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;}