1769 lines · 71.9 KB · C++
RawDownload
////  EntityScriptingInterface.cpp//  libraries/entities/src////  Created by Brad Hefta-Gaub on 12/6/13.//  Copyright 2013 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include <glm/gtc/matrix_transform.hpp>#include <glm/gtx/transform.hpp> #include "EntityScriptingInterface.h" #include <QFutureWatcher>#include <QtConcurrent/QtConcurrentRun> #include "EntityItemID.h"#include <VariantMapToScriptValue.h>#include <SharedUtil.h>#include <SpatialParentFinder.h>#include <model-networking/MeshProxy.h> #include "EntitiesLogging.h"#include "EntityDynamicFactoryInterface.h"#include "EntityDynamicInterface.h"#include "EntitySimulation.h"#include "EntityTree.h"#include "LightEntityItem.h"#include "ModelEntityItem.h"#include "QVariantGLM.h"#include "SimulationOwner.h"#include "ZoneEntityItem.h"#include "WebEntityItem.h"#include <EntityScriptClient.h>#include <Profile.h>  EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) :    _entityTree(NULL),    _bidOnSimulationOwnership(bidOnSimulationOwnership){    auto nodeList = DependencyManager::get<NodeList>();    connect(nodeList.data(), &NodeList::isAllowedEditorChanged, this, &EntityScriptingInterface::canAdjustLocksChanged);    connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged);    connect(nodeList.data(), &NodeList::canRezTmpChanged, this, &EntityScriptingInterface::canRezTmpChanged);    connect(nodeList.data(), &NodeList::canWriteAssetsChanged, this, &EntityScriptingInterface::canWriteAssetsChanged);} void EntityScriptingInterface::queueEntityMessage(PacketType packetType,                                                  EntityItemID entityID, const EntityItemProperties& properties) {    getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties);} void EntityScriptingInterface::resetActivityTracking() {    _activityTracking.addedEntityCount = 0;    _activityTracking.deletedEntityCount = 0;    _activityTracking.editedEntityCount = 0;} bool EntityScriptingInterface::canAdjustLocks() {    auto nodeList = DependencyManager::get<NodeList>();    return nodeList->isAllowedEditor();} bool EntityScriptingInterface::canRez() {    auto nodeList = DependencyManager::get<NodeList>();    return nodeList->getThisNodeCanRez();} bool EntityScriptingInterface::canRezTmp() {    auto nodeList = DependencyManager::get<NodeList>();    return nodeList->getThisNodeCanRezTmp();} bool EntityScriptingInterface::canWriteAssets() {    auto nodeList = DependencyManager::get<NodeList>();    return nodeList->getThisNodeCanWriteAssets();} void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {    if (_entityTree) {        disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);        disconnect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);        disconnect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);    }     _entityTree = elementTree;     if (_entityTree) {        connect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);        connect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);        connect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);    }} EntityItemProperties convertLocationToScriptSemantics(const EntityItemProperties& entitySideProperties) {    // In EntityTree code, properties.position and properties.rotation are relative to the parent.  In javascript,    // they are in world-space.  The local versions are put into localPosition and localRotation and position and    // rotation are converted from local to world space.    EntityItemProperties scriptSideProperties = entitySideProperties;    scriptSideProperties.setLocalPosition(entitySideProperties.getPosition());    scriptSideProperties.setLocalRotation(entitySideProperties.getRotation());    scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity());    scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity());     bool success;    glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(),                                                              entitySideProperties.getParentID(),                                                              entitySideProperties.getParentJointIndex(),                                                              success);    glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(),                                                              entitySideProperties.getParentID(),                                                              entitySideProperties.getParentJointIndex(),                                                              success);    glm::vec3 worldVelocity = SpatiallyNestable::localToWorldVelocity(entitySideProperties.getVelocity(),                                                                      entitySideProperties.getParentID(),                                                                      entitySideProperties.getParentJointIndex(),                                                                      success);    glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(entitySideProperties.getAngularVelocity(),                                                                                    entitySideProperties.getParentID(),                                                                                    entitySideProperties.getParentJointIndex(),                                                                                    success);     scriptSideProperties.setPosition(worldPosition);    scriptSideProperties.setRotation(worldRotation);    scriptSideProperties.setVelocity(worldVelocity);    scriptSideProperties.setAngularVelocity(worldAngularVelocity);     return scriptSideProperties;}  EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperties& scriptSideProperties) {    // convert position and rotation properties from world-space to local, unless localPosition and localRotation    // are set.  If they are set, they overwrite position and rotation.    EntityItemProperties entitySideProperties = scriptSideProperties;    bool success;     // TODO -- handle velocity and angularVelocity     if (scriptSideProperties.localPositionChanged()) {        entitySideProperties.setPosition(scriptSideProperties.getLocalPosition());    } else if (scriptSideProperties.positionChanged()) {        glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(),                                                                  entitySideProperties.getParentID(),                                                                  entitySideProperties.getParentJointIndex(),                                                                  success);        entitySideProperties.setPosition(localPosition);    }     if (scriptSideProperties.localRotationChanged()) {        entitySideProperties.setRotation(scriptSideProperties.getLocalRotation());    } else if (scriptSideProperties.rotationChanged()) {        glm::quat localRotation = SpatiallyNestable::worldToLocal(entitySideProperties.getRotation(),                                                                  entitySideProperties.getParentID(),                                                                  entitySideProperties.getParentJointIndex(),                                                                  success);        entitySideProperties.setRotation(localRotation);    }     if (scriptSideProperties.localVelocityChanged()) {        entitySideProperties.setVelocity(scriptSideProperties.getLocalVelocity());    } else if (scriptSideProperties.velocityChanged()) {        glm::vec3 localVelocity = SpatiallyNestable::worldToLocalVelocity(entitySideProperties.getVelocity(),                                                                          entitySideProperties.getParentID(),                                                                          entitySideProperties.getParentJointIndex(),                                                                          success);        entitySideProperties.setVelocity(localVelocity);    }     if (scriptSideProperties.localAngularVelocityChanged()) {        entitySideProperties.setAngularVelocity(scriptSideProperties.getLocalAngularVelocity());    } else if (scriptSideProperties.angularVelocityChanged()) {        glm::vec3 localAngularVelocity =            SpatiallyNestable::worldToLocalAngularVelocity(entitySideProperties.getAngularVelocity(),                                                           entitySideProperties.getParentID(),                                                           entitySideProperties.getParentJointIndex(),                                                           success);        entitySideProperties.setAngularVelocity(localAngularVelocity);    }     return entitySideProperties;}  QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) {    PROFILE_RANGE(script_entities, __FUNCTION__);     _activityTracking.addedEntityCount++;     EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties);    propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());     if (clientOnly) {        auto nodeList = DependencyManager::get<NodeList>();        const QUuid myNodeID = nodeList->getSessionUUID();        propertiesWithSimID.setClientOnly(clientOnly);        propertiesWithSimID.setOwningAvatarID(myNodeID);    }     auto dimensions = propertiesWithSimID.getDimensions();    float volume = dimensions.x * dimensions.y * dimensions.z;    auto density = propertiesWithSimID.getDensity();    auto newVelocity = propertiesWithSimID.getVelocity().length();    float cost = calculateCost(density * volume, 0, newVelocity);    cost *= costMultiplier;     if (cost > _currentAvatarEnergy) {        return QUuid();    }     EntityItemID id = EntityItemID(QUuid::createUuid());     // If we have a local entity tree set, then also update it.    bool success = true;    if (_entityTree) {        _entityTree->withWriteLock([&] {            EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);            if (entity) {                if (propertiesWithSimID.parentRelatedPropertyChanged()) {                    // due to parenting, the server may not know where something is in world-space, so include the bounding cube.                    bool success;                    AACube queryAACube = entity->getQueryAACube(success);                    if (success) {                        propertiesWithSimID.setQueryAACube(queryAACube);                    }                }                 entity->setLastBroadcast(usecTimestampNow());                // since we're creating this object we will immediately volunteer to own its simulation                entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);                propertiesWithSimID.setLastEdited(entity->getLastEdited());            } else {                qCDebug(entities) << "script failed to add new Entity to local Octree";                success = false;            }        });    }     // queue the packet    if (success) {        emit debitEnergySource(cost);        queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID);         return id;    } else {        return QUuid();    }} QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& shapeType,                                               bool dynamic, const glm::vec3& position, const glm::vec3& gravity) {    _activityTracking.addedEntityCount++;     EntityItemProperties properties;    properties.setType(EntityTypes::Model);    properties.setName(name);    properties.setModelURL(modelUrl);    properties.setShapeTypeFromString(shapeType);    properties.setDynamic(dynamic);    properties.setPosition(position);    properties.setGravity(gravity);    return addEntity(properties);} EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {    EntityPropertyFlags noSpecificProperties;    return getEntityProperties(identity, noSpecificProperties);} EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) {    PROFILE_RANGE(script_entities, __FUNCTION__);     EntityItemProperties results;    if (_entityTree) {        _entityTree->withReadLock([&] {            EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity));            if (entity) {                if (desiredProperties.getHasProperty(PROP_POSITION) ||                    desiredProperties.getHasProperty(PROP_ROTATION) ||                    desiredProperties.getHasProperty(PROP_LOCAL_POSITION) ||                    desiredProperties.getHasProperty(PROP_LOCAL_ROTATION)) {                    // if we are explicitly getting position or rotation, we need parent information to make sense of them.                    desiredProperties.setHasProperty(PROP_PARENT_ID);                    desiredProperties.setHasProperty(PROP_PARENT_JOINT_INDEX);                }                 if (desiredProperties.isEmpty()) {                    // these are left out of EntityItem::getEntityProperties so that localPosition and localRotation                    // don't end up in json saves, etc.  We still want them here, though.                    EncodeBitstreamParams params; // unknown                    desiredProperties = entity->getEntityProperties(params);                    desiredProperties.setHasProperty(PROP_LOCAL_POSITION);                    desiredProperties.setHasProperty(PROP_LOCAL_ROTATION);                }                 results = entity->getProperties(desiredProperties);                 // TODO: improve naturalDimensions in the future,                //       for now we've added this hack for setting natural dimensions of models                if (entity->getType() == EntityTypes::Model) {                    const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity);                    if (geometry) {                        Extents meshExtents = geometry->getUnscaledMeshExtents();                        results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum);                        results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum);                    }                }             }        });    }     return convertLocationToScriptSemantics(results);} QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) {    PROFILE_RANGE(script_entities, __FUNCTION__);     _activityTracking.editedEntityCount++;     EntityItemProperties properties = scriptSideProperties;     auto dimensions = properties.getDimensions();    float volume = dimensions.x * dimensions.y * dimensions.z;    auto density = properties.getDensity();    auto newVelocity = properties.getVelocity().length();    float oldVelocity = { 0.0f };     EntityItemID entityID(id);    if (!_entityTree) {        queueEntityMessage(PacketType::EntityEdit, entityID, properties);         //if there is no local entity entity tree, no existing velocity, use 0.        float cost = calculateCost(density * volume, oldVelocity, newVelocity);        cost *= costMultiplier;         if (cost > _currentAvatarEnergy) {            return QUuid();        } else {            //debit the avatar energy and continue            emit debitEnergySource(cost);        }         return id;    }    // If we have a local entity tree set, then also update it.     bool updatedEntity = false;    _entityTree->withWriteLock([&] {        EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);        if (!entity) {            return;        }         auto nodeList = DependencyManager::get<NodeList>();        if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) {            // don't edit other avatar's avatarEntities            return;        }         if (scriptSideProperties.parentRelatedPropertyChanged()) {            // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them.            // If any of these changed, pull any missing properties from the entity.             //existing entity, retrieve old velocity for check down below            oldVelocity = entity->getVelocity().length();             if (!scriptSideProperties.parentIDChanged()) {                properties.setParentID(entity->getParentID());            }            if (!scriptSideProperties.parentJointIndexChanged()) {                properties.setParentJointIndex(entity->getParentJointIndex());            }            if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) {                properties.setPosition(entity->getPosition());            }            if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) {                properties.setRotation(entity->getOrientation());            }        }        properties = convertLocationFromScriptSemantics(properties);        properties.setClientOnly(entity->getClientOnly());        properties.setOwningAvatarID(entity->getOwningAvatarID());         float cost = calculateCost(density * volume, oldVelocity, newVelocity);        cost *= costMultiplier;         if (cost > _currentAvatarEnergy) {            updatedEntity = false;        } else {            //debit the avatar energy and continue            updatedEntity = _entityTree->updateEntity(entityID, properties);            if (updatedEntity) {                emit debitEnergySource(cost);            }        }    });     // FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially    // breaks avatar energy and entities that are parented.    //    // To handle cases where a script needs to edit an entity with a _known_ entity id but doesn't exist    // in the local entity tree, we need to allow those edits to go through to the server.    // if (!updatedEntity) {    //     return QUuid();    // }     bool entityFound { false };    _entityTree->withReadLock([&] {        EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);        if (entity) {            entityFound = true;            // make sure the properties has a type, so that the encode can know which properties to include            properties.setType(entity->getType());            bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges();            bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges;            if (_bidOnSimulationOwnership && hasPhysicsChanges) {                auto nodeList = DependencyManager::get<NodeList>();                const QUuid myNodeID = nodeList->getSessionUUID();                 if (entity->getSimulatorID() == myNodeID) {                    // we think we already own the simulation, so make sure to send ALL TerseUpdate properties                    if (hasTerseUpdateChanges) {                        entity->getAllTerseUpdateProperties(properties);                    }                    // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object                    // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update                    // and instead let the physics simulation decide when to send a terse update.  This would remove                    // the "slide-no-rotate" glitch (and typical double-update) that we see during the "poke rolling                    // balls" test.  However, even if we solve this problem we still need to provide a "slerp the visible                    // proxy toward the true physical position" feature to hide the final glitches in the remote watcher's                    // simulation.                     if (entity->getSimulationPriority() < SCRIPT_POKE_SIMULATION_PRIORITY) {                        // we re-assert our simulation ownership at a higher priority                        properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);                    }                } else {                    // we make a bid for simulation ownership                    properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);                    entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY);                    entity->rememberHasSimulationOwnershipBid();                }            }            if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) {                properties.setQueryAACube(entity->getQueryAACube());            }            entity->setLastBroadcast(usecTimestampNow());            properties.setLastEdited(entity->getLastEdited());             // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server            // if they've changed.            entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {                if (descendant->getNestableType() == NestableType::Entity) {                    if (descendant->computePuffedQueryAACube()) {                        EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);                        EntityItemProperties newQueryCubeProperties;                        newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());                        newQueryCubeProperties.setLastEdited(properties.getLastEdited());                        queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties);                        entityDescendant->setLastBroadcast(usecTimestampNow());                    }                }            });        }    });    if (!entityFound) {        // we've made an edit to an entity we don't know about, or to a non-entity.  If it's a known non-entity,        // print a warning and don't send an edit packet to the entity-server.        QSharedPointer<SpatialParentFinder> parentFinder = DependencyManager::get<SpatialParentFinder>();        if (parentFinder) {            bool success;            auto nestableWP = parentFinder->find(id, success, static_cast<SpatialParentTree*>(_entityTree.get()));            if (success) {                auto nestable = nestableWP.lock();                if (nestable) {                    NestableType nestableType = nestable->getNestableType();                    if (nestableType == NestableType::Overlay || nestableType == NestableType::Avatar) {                        qCWarning(entities) << "attempted edit on non-entity: " << id << nestable->getName();                        return QUuid(); // null UUID to indicate failure                    }                }            }        }    }    // we queue edit packets even if we don't know about the entity.  This is to allow AC agents    // to edit entities they know only by ID.    queueEntityMessage(PacketType::EntityEdit, entityID, properties);    return id;} void EntityScriptingInterface::deleteEntity(QUuid id) {    PROFILE_RANGE(script_entities, __FUNCTION__);     _activityTracking.deletedEntityCount++; 

Lines 1–500 of 1769.