Brad Hefta-GaubCR feedback7f164a710 years agoHistory
1125 lines · 45.8 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 "EntityScriptingInterface.h" #include "EntityItemID.h"#include <VariantMapToScriptValue.h>#include <SpatialParentFinder.h> #include "EntitiesLogging.h"#include "EntityActionFactoryInterface.h"#include "EntityActionInterface.h"#include "EntitySimulation.h"#include "EntityTree.h"#include "LightEntityItem.h"#include "ModelEntityItem.h"#include "SimulationOwner.h"#include "ZoneEntityItem.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);} void EntityScriptingInterface::queueEntityMessage(PacketType packetType,                                                  EntityItemID entityID, const EntityItemProperties& properties) {    getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);} bool EntityScriptingInterface::canAdjustLocks() {    auto nodeList = DependencyManager::get<NodeList>();    return nodeList->isAllowedEditor();} bool EntityScriptingInterface::canRez() {    auto nodeList = DependencyManager::get<NodeList>();    return nodeList->getThisNodeCanRez();} 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());     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);    // TODO -- handle velocity and angularVelocity     scriptSideProperties.setPosition(worldPosition);    scriptSideProperties.setRotation(worldRotation);     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);    }     return entitySideProperties;}  QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {    EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties);    propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());     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);                    }                }                 if (_bidOnSimulationOwnership) {                    // This Node is creating a new object.  If it's in motion, set this Node as the simulator.                    auto nodeList = DependencyManager::get<NodeList>();                    const QUuid myNodeID = nodeList->getSessionUUID();                     // and make note of it now, so we can act on it right away.                    propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);                    entity->setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);                }                 entity->setLastBroadcast(usecTimestampNow());            } 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;} QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position) {    EntityItemProperties properties;    properties.setType(EntityTypes::Model);    properties.setName(name);    properties.setModelURL(modelUrl);    properties.setPosition(position);    return addEntity(properties);} EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {    EntityPropertyFlags noSpecificProperties;    return getEntityProperties(identity, noSpecificProperties);} EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) {    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 sitting points and naturalDimensions in the future,                //       for now we've included the old sitting points model behavior for entity types that are models                //        we've also added this hack for setting natural dimensions of models                if (entity->getType() == EntityTypes::Model) {                    const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity);                    if (geometry) {                        results.setSittingPoints(geometry->sittingPoints);                        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) {    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([&] {        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.            EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);            if (!entity) {                return;            }            //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);         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);            }        }    });     if (!updatedEntity) {        return QUuid();    }     _entityTree->withReadLock([&] {        EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);        if (entity) {            // 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->pokeSimulationOwnership();                }            }            if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) {                properties.setQueryAACube(entity->getQueryAACube());            }            entity->setLastBroadcast(usecTimestampNow());             // 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());                    }                }            });        }    });    queueEntityMessage(PacketType::EntityEdit, entityID, properties);    return id;} void EntityScriptingInterface::deleteEntity(QUuid id) {    EntityItemID entityID(id);    bool shouldDelete = true;     // If we have a local entity tree set, then also update it.    if (_entityTree) {        _entityTree->withWriteLock([&] {            EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);            if (entity) {                 auto dimensions = entity->getDimensions();                float volume = dimensions.x * dimensions.y * dimensions.z;                auto density = entity->getDensity();                auto velocity = entity->getVelocity().length();                float cost = calculateCost(density * volume, velocity, 0);                cost *= costMultiplier;                 if (cost > _currentAvatarEnergy) {                    shouldDelete = false;                    return;                } else {                    //debit the avatar energy and continue                    emit debitEnergySource(cost);                }                 if (entity->getLocked()) {                    shouldDelete = false;                } else {                    _entityTree->deleteEntity(entityID);                }            }        });    }     // if at this point, we know the id, and we should still delete the entity, send the update to the entity server    if (shouldDelete) {        getEntityPacketSender()->queueEraseEntityMessage(entityID);    }} void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, const QStringList& params) {    if (_entitiesScriptEngine) {        EntityItemID entityID{ id };        _entitiesScriptEngine->callEntityScriptMethod(entityID, method, params);    }} QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const {    EntityItemID result;    if (_entityTree) {        EntityItemPointer closestEntity;        _entityTree->withReadLock([&] {            closestEntity = _entityTree->findClosestEntity(center, radius);        });        if (closestEntity) {            result = closestEntity->getEntityItemID();        }    }    return result;}  void EntityScriptingInterface::dumpTree() const {    if (_entityTree) {        _entityTree->withReadLock([&] {            _entityTree->dumpTree();        });    }} QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const {    QVector<QUuid> result;    if (_entityTree) {        QVector<EntityItemPointer> entities;        _entityTree->withReadLock([&] {            _entityTree->findEntities(center, radius, entities);        });         foreach (EntityItemPointer entity, entities) {            result << entity->getEntityItemID();        }    }    return result;} QVector<QUuid> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const {    QVector<QUuid> result;    if (_entityTree) {        QVector<EntityItemPointer> entities;        _entityTree->withReadLock([&] {            AABox box(corner, dimensions);            _entityTree->findEntities(box, entities);        });         foreach (EntityItemPointer entity, entities) {            result << entity->getEntityItemID();        }    }    return result;} RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking,                 const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) {     QVector<EntityItemID> entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude);    QVector<EntityItemID> entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard);    return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entitiesToInclude, entitiesToDiscard);} // FIXME - we should remove this API and encourage all users to use findRayIntersection() instead. We've changed//         findRayIntersection() to be blocking because it never makes sense for a script to get back a non-answerRayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking,                 const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) {     qWarning() << "Entities.findRayIntersectionBlocking() is obsolete, use Entities.findRayIntersection() instead.";    const QVector<EntityItemID>& entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude);    const QVector<EntityItemID> entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard);    return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entitiesToInclude, entitiesToDiscard);} RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray,                                                                                    Octree::lockType lockType,                                                                                    bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard) { 

Lines 1–500 of 1125.