//// 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 "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() : _entityTree(NULL){ auto nodeList = DependencyManager::get<NodeList>(); connect(nodeList.data(), &NodeList::canAdjustLocksChanged, 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->getThisNodeCanAdjustLocks();} 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()); glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex()); glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex()); 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; if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex()); 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()); entitySideProperties.setRotation(localRotation); } return entitySideProperties;} QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); 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) { // 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(); propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); // and make note of it now, so we can act on it right away. entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->setLastBroadcast(usecTimestampNow()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; } }); } // queue the packet if (success) { queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); } return id;} 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); } 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; EntityItemID entityID(id); // If we have a local entity tree set, then also update it. if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } bool updatedEntity = false; _entityTree->withWriteLock([&] { if (scriptSideProperties.parentDependentPropertyChanged() || scriptSideProperties.parentIDChanged() || scriptSideProperties.parentJointIndexChanged()) { // 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; } 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); updatedEntity = _entityTree->updateEntity(entityID, properties); }); 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 (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 a 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_EDIT_SIMULATION_PRIORITY) { // we re-assert our simulation ownership at a higher priority properties.setSimulationOwner(myNodeID, glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY)); } } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->flagForOwnership(); } } entity->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) { 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) { QVector<EntityItemID> entities = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking, entities);} RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) { const QVector<EntityItemID>& entities = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entities);} RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude) { RayToEntityIntersectionResult result; if (_entityTree) { OctreeElementPointer element; EntityItemPointer intersectedEntity = NULL; result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, result.surfaceNormal, entityIdsToInclude, (void**)&intersectedEntity, lockType, &result.accurate, precisionPicking); if (result.intersects && intersectedEntity) { result.entityID = intersectedEntity->getEntityItemID(); result.properties = intersectedEntity->getProperties(); result.intersection = ray.origin + (ray.direction * result.distance); } } return result;} void EntityScriptingInterface::setLightsArePickable(bool value) { LightEntityItem::setLightsArePickable(value);} bool EntityScriptingInterface::getLightsArePickable() const { return LightEntityItem::getLightsArePickable();} void EntityScriptingInterface::setZonesArePickable(bool value) { ZoneEntityItem::setZonesArePickable(value);} bool EntityScriptingInterface::getZonesArePickable() const { return ZoneEntityItem::getZonesArePickable();} void EntityScriptingInterface::setDrawZoneBoundaries(bool value) { ZoneEntityItem::setDrawZoneBoundaries(value);} bool EntityScriptingInterface::getDrawZoneBoundaries() const { return ZoneEntityItem::getDrawZoneBoundaries();} RayToEntityIntersectionResult::RayToEntityIntersectionResult() : intersects(false), accurate(true), // assume it's accurate entityID(), properties(), distance(0), face(), entity(NULL){} QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("accurate", value.accurate); QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID); obj.setProperty("entityID", entityItemValue); QScriptValue propertiesValue = EntityItemPropertiesToScriptValue(engine, value.properties); obj.setProperty("properties", propertiesValue); obj.setProperty("distance", value.distance); QString faceName = ""; // handle BoxFace switch (value.face) { case MIN_X_FACE: faceName = "MIN_X_FACE"; break; case MAX_X_FACE: faceName = "MAX_X_FACE"; break; case MIN_Y_FACE: faceName = "MIN_Y_FACE"; break; case MAX_Y_FACE: faceName = "MAX_Y_FACE"; break; case MIN_Z_FACE: faceName = "MIN_Z_FACE"; break; case MAX_Z_FACE: faceName = "MAX_Z_FACE"; break; case UNKNOWN_FACE: faceName = "UNKNOWN_FACE"; break; } obj.setProperty("face", faceName); QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); return obj;} void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); value.accurate = object.property("accurate").toVariant().toBool(); QScriptValue entityIDValue = object.property("entityID"); // EntityItemIDfromScriptValue(entityIDValue, value.entityID); quuidFromScriptValue(entityIDValue, value.entityID); QScriptValue entityPropertiesValue = object.property("properties"); if (entityPropertiesValue.isValid()) { EntityItemPropertiesFromScriptValueHonorReadOnly(entityPropertiesValue, value.properties); } value.distance = object.property("distance").toVariant().toFloat(); QString faceName = object.property("face").toVariant().toString(); if (faceName == "MIN_X_FACE") { value.face = MIN_X_FACE; } else if (faceName == "MAX_X_FACE") { value.face = MAX_X_FACE; } else if (faceName == "MIN_Y_FACE") { value.face = MIN_Y_FACE; } else if (faceName == "MAX_Y_FACE") { value.face = MAX_Y_FACE; } else if (faceName == "MIN_Z_FACE") { value.face = MIN_Z_FACE; } else { value.face = MAX_Z_FACE; }; QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) {Lines 1–500 of 870. Load more
// // 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 "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() : _entityTree(NULL) { auto nodeList = DependencyManager::get<NodeList>(); connect(nodeList.data(), &NodeList::canAdjustLocksChanged, 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->getThisNodeCanAdjustLocks(); } 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()); glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex()); glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex()); 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; if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), entitySideProperties.getParentID(), entitySideProperties.getParentJointIndex()); 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()); entitySideProperties.setRotation(localRotation); } return entitySideProperties; } QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); 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) { // 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(); propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); // and make note of it now, so we can act on it right away. entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->setLastBroadcast(usecTimestampNow()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; } }); } // queue the packet if (success) { queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID); } return id; } 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); } 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; EntityItemID entityID(id); // If we have a local entity tree set, then also update it. if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } bool updatedEntity = false; _entityTree->withWriteLock([&] { if (scriptSideProperties.parentDependentPropertyChanged() || scriptSideProperties.parentIDChanged() || scriptSideProperties.parentJointIndexChanged()) { // 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; } 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); updatedEntity = _entityTree->updateEntity(entityID, properties); }); 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 (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 a 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_EDIT_SIMULATION_PRIORITY) { // we re-assert our simulation ownership at a higher priority properties.setSimulationOwner(myNodeID, glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY)); } } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); entity->flagForOwnership(); } } entity->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) { 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) { QVector<EntityItemID> entities = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking, entities); } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude) { const QVector<EntityItemID>& entities = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entities); } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude) { RayToEntityIntersectionResult result; if (_entityTree) { OctreeElementPointer element; EntityItemPointer intersectedEntity = NULL; result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, result.surfaceNormal, entityIdsToInclude, (void**)&intersectedEntity, lockType, &result.accurate, precisionPicking); if (result.intersects && intersectedEntity) { result.entityID = intersectedEntity->getEntityItemID(); result.properties = intersectedEntity->getProperties(); result.intersection = ray.origin + (ray.direction * result.distance); } } return result; } void EntityScriptingInterface::setLightsArePickable(bool value) { LightEntityItem::setLightsArePickable(value); } bool EntityScriptingInterface::getLightsArePickable() const { return LightEntityItem::getLightsArePickable(); } void EntityScriptingInterface::setZonesArePickable(bool value) { ZoneEntityItem::setZonesArePickable(value); } bool EntityScriptingInterface::getZonesArePickable() const { return ZoneEntityItem::getZonesArePickable(); } void EntityScriptingInterface::setDrawZoneBoundaries(bool value) { ZoneEntityItem::setDrawZoneBoundaries(value); } bool EntityScriptingInterface::getDrawZoneBoundaries() const { return ZoneEntityItem::getDrawZoneBoundaries(); } RayToEntityIntersectionResult::RayToEntityIntersectionResult() : intersects(false), accurate(true), // assume it's accurate entityID(), properties(), distance(0), face(), entity(NULL) { } QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("accurate", value.accurate); QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID); obj.setProperty("entityID", entityItemValue); QScriptValue propertiesValue = EntityItemPropertiesToScriptValue(engine, value.properties); obj.setProperty("properties", propertiesValue); obj.setProperty("distance", value.distance); QString faceName = ""; // handle BoxFace switch (value.face) { case MIN_X_FACE: faceName = "MIN_X_FACE"; break; case MAX_X_FACE: faceName = "MAX_X_FACE"; break; case MIN_Y_FACE: faceName = "MIN_Y_FACE"; break; case MAX_Y_FACE: faceName = "MAX_Y_FACE"; break; case MIN_Z_FACE: faceName = "MIN_Z_FACE"; break; case MAX_Z_FACE: faceName = "MAX_Z_FACE"; break; case UNKNOWN_FACE: faceName = "UNKNOWN_FACE"; break; } obj.setProperty("face", faceName); QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); return obj; } void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) { value.intersects = object.property("intersects").toVariant().toBool(); value.accurate = object.property("accurate").toVariant().toBool(); QScriptValue entityIDValue = object.property("entityID"); // EntityItemIDfromScriptValue(entityIDValue, value.entityID); quuidFromScriptValue(entityIDValue, value.entityID); QScriptValue entityPropertiesValue = object.property("properties"); if (entityPropertiesValue.isValid()) { EntityItemPropertiesFromScriptValueHonorReadOnly(entityPropertiesValue, value.properties); } value.distance = object.property("distance").toVariant().toFloat(); QString faceName = object.property("face").toVariant().toString(); if (faceName == "MIN_X_FACE") { value.face = MIN_X_FACE; } else if (faceName == "MAX_X_FACE") { value.face = MAX_X_FACE; } else if (faceName == "MIN_Y_FACE") { value.face = MIN_Y_FACE; } else if (faceName == "MAX_Y_FACE") { value.face = MAX_Y_FACE; } else if (faceName == "MIN_Z_FACE") { value.face = MIN_Z_FACE; } else { value.face = MAX_Z_FACE; }; QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) {