// // 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 #include "EntityScriptingInterface.h" #include "EntityTree.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" EntityScriptingInterface::EntityScriptingInterface() : _nextCreatorTokenID(0), _entityTree(NULL) { auto nodeList = DependencyManager::get(); 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(); return nodeList->getThisNodeCanAdjustLocks(); } bool EntityScriptingInterface::canRez() { auto nodeList = DependencyManager::get(); return nodeList->getThisNodeCanRez(); } void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { if (_entityTree) { disconnect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); disconnect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); disconnect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); disconnect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } _entityTree = modelTree; if (_entityTree) { connect(_entityTree, &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); connect(_entityTree, &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity); connect(_entityTree, &EntityTree::changingEntityID, this, &EntityScriptingInterface::changingEntityID); connect(_entityTree, &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities); } } void setSimId(EntityItemProperties& propertiesWithSimID, EntityItem* entity) { auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); // if this entity has non-zero physics/simulation related values, claim simulation ownership if (propertiesWithSimID.velocityChanged() || propertiesWithSimID.rotationChanged() || propertiesWithSimID.containsPositionChange()) { propertiesWithSimID.setSimulatorID(myNodeID); entity->setSimulatorID(myNodeID); qDebug() << "script claiming ownership"; } else if (entity->getSimulatorID() == myNodeID) { propertiesWithSimID.setSimulatorID(QUuid()); // give up simulation ownership entity->setSimulatorID(QUuid()); qDebug() << "script releasing ownership"; } } EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { // The application will keep track of creatorTokenID uint32_t creatorTokenID = EntityItemID::getNextCreatorTokenID(); EntityItemProperties propertiesWithSimID = properties; EntityItemID id(NEW_ENTITY, creatorTokenID, false ); // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->lockForWrite(); EntityItem* entity = _entityTree->addEntity(id, propertiesWithSimID); // This Node is creating a new object. If it's in motion, set this Node as the simulator. setSimId(propertiesWithSimID, entity); _entityTree->unlock(); } // queue the packet queueEntityMessage(PacketTypeEntityAddOrEdit, id, propertiesWithSimID); return id; } EntityItemID EntityScriptingInterface::identifyEntity(EntityItemID entityID) { EntityItemID actualID = entityID; if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); if (actualID == UNKNOWN_ENTITY_ID) { return entityID; // bailing early } // found it! entityID.id = actualID.id; entityID.isKnownID = true; } return entityID; } EntityItemProperties EntityScriptingInterface::getEntityProperties(EntityItemID entityID) { EntityItemProperties results; EntityItemID identity = identifyEntity(entityID); if (_entityTree) { _entityTree->lockForRead(); EntityItem* entity = const_cast(_entityTree->findEntityByEntityItemID(identity)); if (entity) { results = entity->getProperties(); // 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); } } } else { results.setIsUnknownID(); } _entityTree->unlock(); } return results; } EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const EntityItemProperties& properties) { EntityItemID actualID = entityID; // if the entity is unknown, attempt to look it up if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); if (actualID.id != UNKNOWN_ENTITY_ID) { entityID.id = actualID.id; entityID.isKnownID = true; } } EntityItemProperties propertiesWithSimID = properties; // If we have a local entity tree set, then also update it. We can do this even if we don't know // the actual id, because we can edit out local entities just with creatorTokenID if (_entityTree) { _entityTree->lockForWrite(); _entityTree->updateEntity(entityID, propertiesWithSimID, canAdjustLocks()); _entityTree->unlock(); } // if at this point, we know the id, send the update to the entity server if (entityID.isKnownID) { // make sure the properties has a type, so that the encode can know which properties to include if (propertiesWithSimID.getType() == EntityTypes::Unknown) { EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { propertiesWithSimID.setType(entity->getType()); setSimId(propertiesWithSimID, entity); } } queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, propertiesWithSimID); } return entityID; } void EntityScriptingInterface::deleteEntity(EntityItemID entityID) { EntityItemID actualID = entityID; // if the entity is unknown, attempt to look it up if (!entityID.isKnownID) { actualID = EntityItemID::getIDfromCreatorTokenID(entityID.creatorTokenID); if (actualID.id != UNKNOWN_ENTITY_ID) { entityID.id = actualID.id; entityID.isKnownID = true; } } bool shouldDelete = true; // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->lockForWrite(); EntityItem* entity = const_cast(_entityTree->findEntityByEntityItemID(actualID)); if (entity) { if (entity->getLocked()) { shouldDelete = false; } else { _entityTree->deleteEntity(entityID); } } _entityTree->unlock(); } // if at this point, we know the id, and we should still delete the entity, send the update to the entity server if (shouldDelete && entityID.isKnownID) { getEntityPacketSender()->queueEraseEntityMessage(entityID); } } EntityItemID EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const { EntityItemID result(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false); if (_entityTree) { _entityTree->lockForRead(); const EntityItem* closestEntity = _entityTree->findClosestEntity(center, radius); _entityTree->unlock(); if (closestEntity) { result = closestEntity->getEntityItemID(); } } return result; } void EntityScriptingInterface::dumpTree() const { if (_entityTree) { _entityTree->lockForRead(); _entityTree->dumpTree(); _entityTree->unlock(); } } QVector EntityScriptingInterface::findEntities(const glm::vec3& center, float radius) const { QVector result; if (_entityTree) { _entityTree->lockForRead(); QVector entities; _entityTree->findEntities(center, radius, entities); _entityTree->unlock(); foreach (const EntityItem* entity, entities) { result << entity->getEntityItemID(); } } return result; } QVector EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const { QVector result; if (_entityTree) { _entityTree->lockForRead(); AABox box(corner, dimensions); QVector entities; _entityTree->findEntities(box, entities); _entityTree->unlock(); foreach (const EntityItem* entity, entities) { result << entity->getEntityItemID(); } } return result; } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking) { return findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking) { return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); } RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, bool precisionPicking) { RayToEntityIntersectionResult result; if (_entityTree) { OctreeElement* element; EntityItem* intersectedEntity = NULL; result.intersects = _entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, (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::setSendPhysicsUpdates(bool value) { EntityItem::setSendPhysicsUpdates(value); } bool EntityScriptingInterface::getSendPhysicsUpdates() const { return EntityItem::getSendPhysicsUpdates(); } 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); 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"); if (entityIDValue.isValid()) { EntityItemIDfromScriptValue(entityIDValue, value.entityID); } QScriptValue entityPropertiesValue = object.property("properties"); if (entityPropertiesValue.isValid()) { EntityItemPropertiesFromScriptValue(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()) { vec3FromScriptValue(intersection, value.intersection); } }