2781 lines · 151.2 KB · C
RawDownload
////  EntityScriptingInterface.h//  libraries/entities/src////  Created by Brad Hefta-Gaub on 12/6/13.//  Copyright 2013 High Fidelity, Inc.//  Copyright 2020 Vircadia contributors.//  Copyright 2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0//// TODO: How will we handle collision callbacks with Entities// #ifndef hifi_EntityScriptingInterface_h#define hifi_EntityScriptingInterface_h #include <memory> #include <QtCore/QObject>#include <QtCore/QStringList>#include <QtCore/QSharedPointer>#include <QtQml/QJSValue>#include <QtQml/QJSValueList>#include <QtCore/QSharedPointer> #include <DependencyManager.h>#include <Octree.h>#include <OctreeScriptingInterface.h>#include <RegisteredMetaTypes.h>#include "PointerEvent.h"#include <PickFilter.h>#include <ScriptManager.h>#include <ScriptValue.h>#include <CanvasCommand.h> #include "PolyVoxEntityItem.h"#include "LineEntityItem.h"#include "PolyLineEntityItem.h"#include "EntityTree.h" #include "EntityEditPacketSender.h"#include "EntitiesScriptEngineProvider.h"#include "EntityItemProperties.h" class EntityTree;class MeshProxy;class ScriptContext;class ScriptEngine; extern const QString GRABBABLE_USER_DATA;extern const QString NOT_GRABBABLE_USER_DATA; // helper factory to compose standardized, async metadata queries for "magic" Entity properties// like .script and .serverScripts.  This is used for automated testing of core scripting features// as well as to provide early adopters a self-discoverable, consistent way to diagnose common// problems with their own Entity scripts.class EntityPropertyMetadataRequest {public:    EntityPropertyMetadataRequest(ScriptManager* manager) : _scriptManager(manager){};    bool script(EntityItemID entityID, const ScriptValue& handler);    bool serverScripts(EntityItemID entityID, const ScriptValue& handler);private:    QPointer<ScriptManager> _scriptManager;}; /*@jsdoc * The result of a {@link Entities.findRayIntersection|findRayIntersection} search using a {@link PickRay}. * @typedef {object} Entities.RayToEntityIntersectionResult * @property {boolean} intersects - <code>true</code> if the {@link PickRay} intersected an entity, <code>false</code> if it  *     didn't. * @property {boolean} accurate - Is always <code>true</code>. * @property {Uuid} entityID - The ID if the entity intersected, if any, otherwise <code>null</code>. * @property {number} distance - The distance from the {@link PickRay} origin to the intersection point. * @property {Vec3} intersection - The intersection point. * @property {Vec3} surfaceNormal - The surface normal of the entity at the intersection point. * @property {BoxFace} face - The face of the entity's axis-aligned box that the ray intersects. * @property {object} extraInfo - Extra information depending on the entity intersected. Currently, only <code>Model</code>  *     entities provide extra information, and the information provided depends on the <code>precisionPicking</code> parameter  *     value that the search function was called with. */// "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock.class RayToEntityIntersectionResult {public:    bool intersects { false };    bool accurate { true };    QUuid entityID;    float distance { 0.0f };    BoxFace face { UNKNOWN_FACE };    glm::vec3 intersection { glm::vec3(0.0f, 0.0f, 0.0f) };    glm::vec3 surfaceNormal { glm::vec3(0.0f, 1.0f, 0.0f) };    QVariantMap extraInfo;};Q_DECLARE_METATYPE(RayToEntityIntersectionResult)ScriptValue RayToEntityIntersectionResultToScriptValue(ScriptEngine* engine, const RayToEntityIntersectionResult& results);bool RayToEntityIntersectionResultFromScriptValue(const ScriptValue& object, RayToEntityIntersectionResult& results); class ParabolaToEntityIntersectionResult {public:    bool intersects { false };    bool accurate { true };    QUuid entityID;    float distance { 0.0f };    float parabolicDistance { 0.0f };    BoxFace face { UNKNOWN_FACE };    glm::vec3 intersection;    glm::vec3 surfaceNormal;    QVariantMap extraInfo;}; /*@jsdoc * The <code>Entities</code> API provides facilities to create and interact with entities. Entities are 2D or 3D objects  * displayed in-world. Depending on their {@link Entities.EntityHostType|EntityHostType}, they may persist in the domain as  * "domain" entities, travel to different domains with a user as "avatar" entities, or be visible only to an individual user as  * "local" entities (a.k.a. "overlays"). * * <p>Note: For Interface, avatar, and client entity scripts, the entities available to scripts are those that Interface has  * displayed and so knows about. For assignment client scripts, the entities available are those that are "seen" by the  * {@link EntityViewer}. For entity server scripts, all entities are available.</p> * * <h3>Entity Types & Properties</h3> * * <p>For a list of the entity types that you can use, see {@link Entities.EntityType|Entity Types}.</p> *  * <p>For the properties of the different entity types, see {@link Entities.EntityProperties|Entity Properties}. Some properties are universal to all entity types, and some are specific to particular entity types.</p> * * <h3>Entity Methods</h3> * * <p>Some of the API's signals correspond to entity methods that are called, if present, in the entity being interacted with.  * The client or server entity script must expose them as a property. However, unlike {@link Entities.callEntityMethod}, server  * entity scripts do not need to list them in an <code>remotelyCallable</code> property. The entity methods are called with  * parameters per their corresponding signal.</p> * <table> *   <thead> *     <tr><th>Method Name</th><th>Corresponding Signal</th></tr> *   </thead> *   <tbody> *     <tr><td><code>clickDownOnEntity</code></td><td>{@link Entities.clickDownOnEntity}</td></tr> *     <tr><td><code>clickReleaseOnEntity</code></td><td>{@link Entities.clickReleaseOnEntity}</td></tr> *     <tr><td><code>collisionWithEntity</code></td><td>{@link Entities.collisionWithEntity}</td></tr> *     <tr><td><code>enterEntity</code></td><td>{@link Entities.enterEntity}</td></tr> *     <tr><td><code>holdingClickOnEntity</code></td><td>{@link Entities.holdingClickOnEntity}</td></tr> *     <tr><td><code>hoverEnterEntity</code></td><td>{@link Entities.hoverEnterEntity}</td></tr> *     <tr><td><code>hoverLeaveEntity</code></td><td>{@link Entities.hoverLeaveEntity}</td></tr> *     <tr><td><code>hoverOverEntity</code></td><td>{@link Entities.hoverOverEntity}</td></tr> *     <tr><td><code>leaveEntity</code></td><td>{@link Entities.leaveEntity}</td></tr> *     <tr><td><code>mouseDoublePressOnEntity</code></td><td>{@link Entities.mouseDoublePressOnEntity}</td></tr> *     <tr><td><code>mouseMoveOnEntity</code></td><td>{@link Entities.mouseMoveOnEntity}</td></tr> *     <tr><td><code>mouseMoveEvent</code></td><td><span class="important">Deprecated: Use <code>mouseMoveOnEntity</code>  *       instead.</span></td></tr> *     <tr><td><code>mousePressOnEntity</code></td><td>{@link Entities.mousePressOnEntity}</td></tr> *     <tr><td><code>mouseReleaseOnEntity</code></td><td>{@link Entities.mouseReleaseOnEntity}</td></tr> *   </tbody> * </table> * <p>See {@link Entities.clickDownOnEntity} for an example.</p> * * @namespace Entities * * @hifi-interface * @hifi-client-entity * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * * @property {Uuid} keyboardFocusEntity -  The {@link Entities.EntityProperties-Web|Web} entity that has keyboard focus. If no  *     Web entity has keyboard focus, returns <code>null</code>; set to <code>null</code> or {@link Uuid(0)|Uuid.NONE} to clear *     keyboard focus. *//// handles scripting of Entity commands from JS passed to assigned clientsclass EntityScriptingInterface : public OctreeScriptingInterface, public Dependency  {    Q_OBJECT    Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity)     friend EntityPropertyMetadataRequest;public:    EntityScriptingInterface(bool bidOnSimulationOwnership);     class ActivityTracking {    public:        int addedEntityCount { 0 };        int deletedEntityCount { 0 };        int editedEntityCount { 0 };    };     EntityEditPacketSender* getEntityPacketSender() const { return (EntityEditPacketSender*)getPacketSender(); }    virtual NodeType_t getServerNodeType() const override { return NodeType::EntityServer; }    virtual OctreeEditPacketSender* createPacketSender() override { return new EntityEditPacketSender(); }     void setEntityTree(EntityTreePointer modelTree);    EntityTreePointer getEntityTree() { return _entityTree; }    void setPersistentEntitiesScriptEngine(std::shared_ptr<EntitiesScriptEngineProvider> manager);    void setNonPersistentEntitiesScriptEngine(std::shared_ptr<EntitiesScriptEngineProvider> manager);     void resetActivityTracking();    ActivityTracking getActivityTracking() const { return _activityTracking; }     RayToEntityIntersectionResult evalRayIntersectionVector(const PickRay& ray, PickFilter searchFilter,        const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard);    ParabolaToEntityIntersectionResult evalParabolaIntersectionVector(const PickParabola& parabola, PickFilter searchFilter,        const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard);     /*@jsdoc     * Gets the properties of multiple entities.     * @function Entities.getMultipleEntityProperties     * @param {Uuid[]} entityIDs - The IDs of the entities to get the properties of.     * @param {string[]|string} [desiredProperties=[]] - The name or names of the properties to get. For properties that are      *     objects (e.g., the <code>"keyLight"</code> property), use the property and subproperty names in dot notation (e.g.,      *     <code>"keyLight.color"</code>). Getting all subproperties with the name of an object is currently not supported (e.g.,     *     passing the <code>"keyLight"</code> property only).     * @returns {Entities.EntityProperties[]} The specified properties of each entity for each entity that can be found. If      *     none of the entities can be found, then an empty array is returned. If no properties are specified, then all      *     properties are returned.     * @example <caption>Retrieve the names of the nearby entities</caption>     * var SEARCH_RADIUS = 50; // meters     * var entityIDs = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS);     * var propertySets = Entities.getMultipleEntityProperties(entityIDs, "name");     * print("Nearby entity names: " + JSON.stringify(propertySets));    */    static ScriptValue getMultipleEntityProperties(ScriptContext* context, ScriptEngine* engine);    ScriptValue getMultipleEntityPropertiesInternal(ScriptEngine* engine, QVector<QUuid> entityIDs, const ScriptValue& extendedDesiredProperties);     QUuid addEntityInternal(const EntityItemProperties& properties, entity::HostType entityHostType); public slots:     /*@jsdoc     * Checks whether or not the script can change the <code>locked</code> property of entities. Locked entities have their     * <code>locked</code> property set to <code>true</code> and cannot be edited or deleted.     * @function Entities.canAdjustLocks     * @returns {boolean} <code>true</code> if the domain server will allow the script to change the <code>locked</code>      *     property of entities, otherwise <code>false</code>.     * @example <caption>Lock an entity if you can.</caption>     * if (Entities.canAdjustLocks()) {     *     Entities.editEntity(entityID, { locked: true });     * } else {     *     Window.alert("You do not have the permissions to set an entity locked!");     * }     */    Q_INVOKABLE bool canAdjustLocks();     /*@jsdoc     * Checks whether or not the script can rez (create) new entities in the domain.     * @function Entities.canRez     * @returns {boolean} <code>true</code> if the domain server will allow the script to rez (create) new entities, otherwise      *     <code>false</code>.     */    Q_INVOKABLE bool canRez();     /*@jsdoc     * Checks whether or not the script can rez (create) new temporary entities in the domain. Temporary entities are entities      * with a finite <code>lifetime</code> property value set.     * @function Entities.canRezTmp     * @returns {boolean} <code>true</code> if the domain server will allow the script to rez (create) new temporary entities,     *     otherwise <code>false</code>.     */    Q_INVOKABLE bool canRezTmp();     /*@jsdoc     * Checks whether or not the script can make changes to the asset server's assets.     * @function Entities.canWriteAssets     * @returns {boolean} <code>true</code> if the domain server will allow the script to make changes to the asset server's      *     assets, otherwise <code>false</code>.     */    Q_INVOKABLE bool canWriteAssets();     /*@jsdoc     * Checks whether or not the script can replace the domain's content set.     * @function Entities.canReplaceContent     * @returns {boolean} <code>true</code> if the domain server will allow the script to replace the domain's content set,      *     otherwise <code>false</code>.     */    Q_INVOKABLE bool canReplaceContent();     /*@jsdoc     * Checks whether or not the script can get and set the <code>privateUserData</code> property of entities.     * @function Entities.canGetAndSetPrivateUserData     * @returns {boolean} <code>true</code> if the domain server will allow the script to get and set the      *     <code>privateUserData</code> property of entities, otherwise <code>false</code>.     */    Q_INVOKABLE bool canGetAndSetPrivateUserData();        /*@jsdoc     * Checks whether or not the script can rez avatar entities.     * @function Entities.canRezAvatarEntities     * @returns {boolean} <code>true</code> if the domain server will allow the script to rez avatar entities,     *     otherwise <code>false</code>.     */    Q_INVOKABLE bool canRezAvatarEntities();     /*@jsdoc     * Checks whether or not the script can view asset URLs     * @function Entities.canViewAssetURLs     * @returns {boolean} <code>true</code> if the domain server will allow the script to view asset URLs,     *     otherwise <code>false</code>.     */    Q_INVOKABLE bool canViewAssetURLs();     /*@jsdoc     * <p>How an entity is hosted and sent to others for display.</p>     * <table>     *   <thead>     *     <tr><th>Value</th><th>Description</th></tr>     *   </thead>     *   <tbody>     *     <tr><td><code>"domain"</code></td><td>Domain entities are stored on the domain, are visible to everyone, and are      *       sent to everyone by the entity server.</td></tr>     *     <tr><td><code>"avatar"</code></td><td>Avatar entities are stored on an Interface client, are visible to everyone,      *       and are sent to everyone by the avatar mixer. They follow the client to each domain visited, displaying at the      *       same domain coordinates unless parented to the client's avatar.</td></tr>     *     <tr><td><code>"local"</code></td><td>Local entities are ephemeral &mdash; they aren't stored anywhere &mdash; and      *       are visible only to the client. They follow the client to each domain visited, displaying at the same domain      *       coordinates unless parented to the client's avatar. Additionally, local entities are always      *       collisionless.</td></tr>     *   </tbody>     * </table>     * @typedef {string} Entities.EntityHostType     */     /*@jsdoc     * Adds a new domain, avatar, or local entity.     * @function Entities.addEntity     * @param {Entities.EntityProperties} properties - The properties of the entity to create.     * @param {Entities.EntityHostType} [entityHostType="domain"] - The type of entity to create.          * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NONE}.     * @example <caption>Create a box domain entity in front of your avatar.</caption>     * var entityID = Entities.addEntity({     *     type: "Box",     *     position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })),     *     rotation: MyAvatar.orientation,     *     dimensions: { x: 0.5, y: 0.5, z: 0.5 },     *     lifetime: 300  // Delete after 5 minutes.     * });     * print("Entity created: " + entityID);     */    Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString) {        entity::HostType entityHostType;        if (entityHostTypeString == "local") {            entityHostType = entity::HostType::LOCAL;        } else if (entityHostTypeString == "avatar") {            entityHostType = entity::HostType::AVATAR;        } else {            entityHostType = entity::HostType::DOMAIN;        }        return addEntityInternal(properties, entityHostType);    }     /*@jsdoc     * Adds a new avatar entity (<code>{@link Entities.EntityProperties|entityHostType}</code> property is      * <code>"avatar"</code>) or domain entity (<code>{@link Entities.EntityProperties|entityHostType}</code> property is      * <code>"domain"</code>).     * @function Entities.addEntity     * @param {Entities.EntityProperties} properties - The properties of the entity to create.     * @param {boolean} [avatarEntity=false] - <code>true</code> to create an avatar entity, <code>false</code> to create a      *     domain entity.     * @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid(0)|Uuid.NONE}.     */    Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool avatarEntity = false) {        entity::HostType entityHostType = avatarEntity ? entity::HostType::AVATAR : entity::HostType::DOMAIN;        return addEntityInternal(properties, entityHostType);    }     /// temporary method until addEntity can be used from QJSEngine    /// Deliberately not adding jsdoc, only used internally.    // FIXME: Deprecate and remove from the API.    Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic,                                     bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity);     /*@jsdoc     * Creates a clone of an entity. The clone has the same properties as the original except that: it has a modified     * <code>name</code> property, clone-related properties are set per the original entity's clone-related     * {@link Entities.EntityProperties|properties} (e.g., <code>cloneLifetime</code>), and its clone-related properties are      * set to their defaults.     * <p>Domain entities must have their <code>cloneable</code> property value be <code>true</code> in order to be cloned. A      * domain entity can be cloned by a client that doesn't have rez permissions in the domain.</p>     * <p>Avatar entities must have their <code>cloneable</code> and <code>cloneAvatarEntity</code> property values be      * <code>true</code> in order to be cloned.</p>     * @function Entities.cloneEntity     * @param {Uuid} entityID - The ID of the entity to clone.     * @returns {Uuid} The ID of the new entity if successfully cloned, otherwise {@link Uuid(0)|Uuid.NONE}.     */    Q_INVOKABLE QUuid cloneEntity(const QUuid& entityID);     /*@jsdoc     * Gets an entity's property values.     * @function Entities.getEntityProperties     * @param {Uuid} entityID - The ID of the entity to get the properties of.     * @param {string|string[]} [desiredProperties=[]] - The name or names of the properties to get. For properties that are      *     objects (e.g., the <code>"keyLight"</code> property), use the property and subproperty names in dot notation (e.g.,      *     <code>"keyLight.color"</code>).     * @returns {Entities.EntityProperties} The specified properties of the entity if the entity can be found, otherwise an      *     empty object. If no properties are specified, then all properties are returned.     * @example <caption>Report the color of a new box entity.</caption>     * var entityID = Entities.addEntity({     *     type: "Box",     *     position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })),     *     rotation: MyAvatar.orientation,     *     dimensions: { x: 0.5, y: 0.5, z: 0.5 },     *     lifetime: 300  // Delete after 5 minutes.     * });     * var properties = Entities.getEntityProperties(entityID, ["color"]);     * print("Entity color: " + JSON.stringify(properties.color));     */    Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID);    Q_INVOKABLE ScriptValue getEntityProperties(const QUuid& entityID, const ScriptValue &desiredProperties);    /**     * @brief Internal function to get entity properties.     *     * It's being called by EntityScriptingInterface::getEntityProperties     * and also from C++ side in several places in the source code.     *     * @param entityID The ID of the entity to get the properties of.     * @param desiredProperties Flags representing requested entity properties     * @param returnNothingOnEmptyPropertyFlags If this parameter is false and property flags are empty, then all possible     * properties will get returned. This is needed because we divide properties requested through getEntityProperties into     * real properties and pseudo properties. Only real properties are passed here as desiredProperties, so if user requests     * only pseudo properties, then desiredProperties will be empty. In such case we need to pass true     * as returnNothingOnEmptyPropertyFlags to avoid mistakenly returning all the properties.     * @return EntityItemProperties Requested properties of the entity if it can be found.     */     Q_INVOKABLE EntityItemProperties getEntityPropertiesInternal(const QUuid& entityID, EntityPropertyFlags desiredProperties,                                                                 bool returnNothingOnEmptyPropertyFlags);    //Q_INVOKABLE EntityItemProperties getEntityProperties(const QUuid& entityID, EntityPropertyFlags desiredProperties);     /*@jsdoc     * Edits an entity, changing one or more of its property values.     * @function Entities.editEntity     * @param {Uuid} entityID - The ID of the entity to edit.     * @param {Entities.EntityProperties} properties - The new property values.     * @returns {Uuid} The ID of the entity if the edit was successful, otherwise <code>null</code> or {@link Uuid|Uuid.NONE}.     * @example <caption>Change the color of an entity.</caption>     * var entityID = Entities.addEntity({     *     type: "Box",     *     position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })),     *     rotation: MyAvatar.orientation,     *     dimensions: { x: 0.5, y: 0.5, z: 0.5 },     *     lifetime: 300  // Delete after 5 minutes.     * });     * var properties = Entities.getEntityProperties(entityID, ["color"]);     * print("Entity color: " + JSON.stringify(properties.color));     *     * Script.setTimeout(function () { // Wait for the entity to be created before editing.     *     Entities.editEntity(entityID, {     *         color: { red: 255, green: 0, blue: 0 }     *     });     *     properties = Entities.getEntityProperties(entityID, ["color"]);     *     print("Entity color: " + JSON.stringify(properties.color));     * }, 50);     */    Q_INVOKABLE QUuid editEntity(const QUuid& entityID, const EntityItemProperties& properties);     /*@jsdoc     * Deletes an entity.     * @function Entities.deleteEntity     * @param {Uuid} entityID - The ID of the entity to delete.     * @example <caption>Delete an entity a few seconds after creating it.</caption>     * var entityID = Entities.addEntity({     *     type: "Box",     *     position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })),     *     rotation: MyAvatar.orientation,     *     dimensions: { x: 0.5, y: 0.5, z: 0.5 }     * });     *     * Script.setTimeout(function () {     *     Entities.deleteEntity(entityID);     * }, 3000);     */    Q_INVOKABLE void deleteEntity(const QUuid& entityID);     /*@jsdoc     * Gets an entity's type.     * @function Entities.getEntityType     * @param {Uuid} id - The ID of the entity to get the type of.     * @returns {Entities.EntityType} The type of the entity.     */    Q_INVOKABLE QString getEntityType(const QUuid& entityID);     /*@jsdoc     * Gets an entity's script object. In particular, this is useful for accessing a {@link Entities.EntityProperties-Web|Web}      * entity's HTML <code>EventBridge</code> script object to exchange messages with the web page script.     * <p>To send a message from an Interface script to a Web entity over its event bridge:</p>     * <pre class="prettyprint"><code>var entityObject = Entities.getEntityObject(entityID);     * entityObject.emitScriptEvent(message);</code></pre>     * <p>To receive a message from a Web entity over its event bridge in an Interface script:</p>     * <pre class="prettyprint"><code>var entityObject = Entities.getentityObject(entityID);     * entityObject.webEventReceived.connect(function(message) {     *     ...     * };</code></pre>     * <p>Alternatively, you can use {@link Entities.emitScriptEvent} and {@link Entities.webEventReceived} to exchange     * messages with a Web entity over its event bridge.</p>     * @function Entities.getEntityObject     * @param {Uuid} id - The ID of the entity to get the script object for.     * @returns {object} The script object for the entity if found.     * @example <caption>Exchange messages with a Web entity with an HTML source.</caption>     * // HTML file, name: "webEntity.html".     * <!DOCTYPE html>     * <html>

Lines 1–500 of 2781.