// // Created by Sam Gondelman 10/20/2017 // Copyright 2017 High Fidelity, Inc. // Copyright 2022-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 // #ifndef hifi_PickScriptingInterface_h #define hifi_PickScriptingInterface_h #include #include #include #include #include #include class ScriptEngine; class ScriptValue; /*@jsdoc * The Picks API lets you create and manage objects for repeatedly calculating intersections. * * @namespace Picks * * @hifi-interface * @hifi-client-entity * @hifi-avatar * * @property {FilterFlags} PICK_DOMAIN_ENTITIES - Include domain entities when intersecting. Read-only. * @property {FilterFlags} PICK_AVATAR_ENTITIES - Include avatar entities when intersecting. Read-only. * @property {FilterFlags} PICK_LOCAL_ENTITIES - Include local entities when intersecting. Read-only. * @property {FilterFlags} PICK_AVATARS - Include avatars when intersecting. Read-only. * @property {FilterFlags} PICK_HUD - Include the HUD surface when intersecting in HMD mode. Read-only. * * @property {FilterFlags} PICK_ENTITIES - Include domain and avatar entities when intersecting. Read-only. *

Deprecated: This property is deprecated and will be removed. Use PICK_DOMAIN_ENTITIES | * PICK_AVATAR_ENTITIES instead.

* @property {FilterFlags} PICK_OVERLAYS - Include local entities when intersecting. Read-only. *

Deprecated: This property is deprecated and will be removed. Use PICK_LOCAL_ENTITIES * instead.

* * @property {FilterFlags} PICK_INCLUDE_VISIBLE - Include visible objects when intersecting. Read-only. *

Warning: Is currently always enabled by default but may not be in the future.

* @property {FilterFlags} PICK_INCLUDE_INVISIBLE - Include invisible objects when intersecting. Read-only. * * @property {FilterFlags} PICK_INCLUDE_COLLIDABLE - Include collidable objects when intersecting. Read-only. *

Warning: Is currently always enabled by default but may not be in the future.

* @property {FilterFlags} PICK_INCLUDE_NONCOLLIDABLE - Include non-collidable objects when intersecting. Read-only. * * @property {FilterFlags} PICK_PRECISE - Pick against exact meshes. Read-only. * @property {FilterFlags} PICK_COARSE - Pick against coarse meshes. Read-only. * * @property {FilterFlags} PICK_ALL_INTERSECTIONS - If set, returns all intersections instead of just the closest. * Read-only. *

Warning: Not yet implemented.

* * @property {FilterFlags} PICK_BYPASS_IGNORE - Allows pick to intersect entities even when their * ignorePickIntersection property value is true. For debug purposes. * Read-only. * * @property {IntersectionType} INTERSECTED_NONE - Intersected nothing. Read-only. * @property {IntersectionType} INTERSECTED_ENTITY - Intersected an entity. Read-only. * @property {IntersectionType} INTERSECTED_LOCAL_ENTITY - Intersected a local entity. Read-only. * @property {IntersectionType} INTERSECTED_OVERLAY - Intersected a local entity. (3D overlays no longer exist.) * Read-only. *

Deprecated: This property is deprecated and will be removed. Use * INTERSECTED_LOCAL_ENTITY instead.

* @property {IntersectionType} INTERSECTED_AVATAR - Intersected an avatar. Read-only. * @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD surface. Read-only. * * @property {number} perFrameTimeBudget - The maximum time, in microseconds, to spend per frame updating pick results. * @property {number} handLaserDelay - The delay, in seconds, applied to the hand lasers to smooth their movement. */ class PickScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(unsigned int PICK_ENTITIES READ getPickEntities CONSTANT) Q_PROPERTY(unsigned int PICK_OVERLAYS READ getPickOverlays CONSTANT) Q_PROPERTY(unsigned int PICK_DOMAIN_ENTITIES READ getPickDomainEntities CONSTANT) Q_PROPERTY(unsigned int PICK_AVATAR_ENTITIES READ getPickAvatarEntities CONSTANT) Q_PROPERTY(unsigned int PICK_LOCAL_ENTITIES READ getPickLocalEntities CONSTANT) Q_PROPERTY(unsigned int PICK_AVATARS READ getPickAvatars CONSTANT) Q_PROPERTY(unsigned int PICK_HUD READ getPickHud CONSTANT) Q_PROPERTY(unsigned int PICK_INCLUDE_VISIBLE READ getPickIncludeVisible CONSTANT) Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ getPickIncludeInvisible CONSTANT) Q_PROPERTY(unsigned int PICK_INCLUDE_COLLIDABLE READ getPickIncludeCollidable CONSTANT) Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ getPickIncludeNoncollidable CONSTANT) Q_PROPERTY(unsigned int PICK_PRECISE READ getPickPrecise CONSTANT) Q_PROPERTY(unsigned int PICK_COARSE READ getPickCoarse CONSTANT) Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ getPickAllIntersections CONSTANT) Q_PROPERTY(unsigned int PICK_BYPASS_IGNORE READ getPickBypassIgnore CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_NONE READ getIntersectedNone CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ getIntersectedEntity CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_LOCAL_ENTITY READ getIntersectedLocalEntity CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ getIntersectedOverlay CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ getIntersectedAvatar CONSTANT) Q_PROPERTY(unsigned int INTERSECTED_HUD READ getIntersectedHud CONSTANT) Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget) Q_PROPERTY(float handLaserDelay READ getHandLaserDelay WRITE setHandLaserDelay) SINGLETON_DEPENDENCY public: static void registerMetaTypes(ScriptEngine* engine); void registerProperties(ScriptEngine* engine); /*@jsdoc * Creates a new pick. Different {@link PickType}s use different properties, and within one PickType the properties you * choose can lead to a wide range of behaviors. For example, with PickType.Ray, the properties could * configure a mouse ray pick, an avatar head ray pick, or a joint ray pick. *

Warning: Picks created using this method currently always intersect at least visible and collidable * things but this may not always be the case.

* @function Picks.createPick * @param {PickType} type - The type of picking to use. * @param {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties} * properties - Properties of the pick, per the pick type. * @returns {number} The ID of the pick created. 0 if invalid. */ // TODO: expand Pointers to be able to be fully configurable with PickFilters Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties); /*@jsdoc * Enables a pick. Enabled picks update their pick results. * @function Picks.enablePick * @param {number} id - The ID of the pick. */ Q_INVOKABLE void enablePick(unsigned int uid); /*@jsdoc * Disables a pick. Disabled picks do not update their pick results. * @function Picks.disablePick * @param {number} id - The ID of the pick. */ Q_INVOKABLE void disablePick(unsigned int uid); /*@jsdoc * Get the enabled status of a pick. Enabled picks update their pick results. * @function Picks.isPickEnabled * @param {number} id - The ID of the pick. * @returns {boolean} enabled - Whether or not the pick is enabled. */ Q_INVOKABLE bool isPickEnabled(unsigned int uid) const; /*@jsdoc * Removes (deletes) a pick. * @function Picks.removePick * @param {number} id - The ID of the pick. */ Q_INVOKABLE void removePick(unsigned int uid); /*@jsdoc * Gets the current properties of the pick. * @function Picks.getPickProperties * @param {number} id - The ID of the pick. * @returns {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties} * Properties of the pick, per the pick type. */ Q_INVOKABLE QVariantMap getPickProperties(unsigned int uid) const; /*@jsdoc * Gets the parameters that were passed in to {@link Picks.createPick} to create the pick, if the pick was created through * a script. Note that these properties do not reflect the current state of the pick. * See {@link Picks.getPickProperties}. * @function Picks.getPickScriptParameters * @param {number} id - The ID of the pick. * @returns {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties} * Script-provided properties, per the pick type. */ Q_INVOKABLE QVariantMap getPickScriptParameters(unsigned int uid) const; /*@jsdoc * Gets all picks which currently exist, including disabled picks. * @function Picks.getPicks * @returns {number[]} picks - The IDs of the picks. */ Q_INVOKABLE QVector getPicks() const; /*@jsdoc * Gets the most recent result from a pick. A pick continues to be updated ready to return a result, as long as it is * enabled. *

Note: Stylus picks only intersect with objects in their include list, set using * {@link Picks.setIncludeItems|setIncludeItems}.

* @function Picks.getPrevPickResult * @param {number} id - The ID of the pick. * @returns {RayPickResult|ParabolaPickResult|StylusPickResult|CollisionPickResult} The most recent intersection result. * @example Highlight entities under your mouse in desktop mode or that you're looking at in HMD mode. * // Highlight. * var HIGHLIGHT_LIST_NAME = "highlightEntitiesExampleList"; * var HIGHLIGHT_LIST_TYPE = "entity"; * Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, {}); * * // Ray pick. * var PICK_FILTER = Picks.PICK_DOMAIN_ENTITIES | Picks.PICK_AVATAR_ENTITIES * | Picks.PICK_INCLUDE_COLLIDABLE | Picks.PICK_INCLUDE_NONCOLLIDABLE; * var rayPick = Picks.createPick(PickType.Ray, { * enabled: true, * filter: PICK_FILTER, * joint: HMD.active ? "Avatar" : "Mouse" * }); * * // Highlight intersected entity. * var highlightedEntityID = null; * Script.update.connect(function () { * var rayPickResult = Picks.getPrevPickResult(rayPick); * if (rayPickResult.intersects) { * if (rayPickResult.objectID !== highlightedEntityID) { * if (highlightedEntityID) { * Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID); * } * highlightedEntityID = rayPickResult.objectID; * Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID); * } * } else { * if (highlightedEntityID) { * Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID); * highlightedEntityID = null; * } * } * }); * * // Clean up. * Script.scriptEnding.connect(function () { * if (highlightedEntityID) { * Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID); * } * }); */ Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid); /*@jsdoc * Sets whether or not a pick should use precision picking, i.e., whether it should pick against precise meshes or coarse * meshes. * This has the same effect as using the PICK_PRECISE or PICK_COARSE filter flags. * @function Picks.setPrecisionPicking * @param {number} id - The ID of the pick. * @param {boolean} precisionPicking - true to use precision picking, false to use coarse picking. */ Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking); /*@jsdoc * Sets a list of entity and avatar IDs that a pick should ignore during intersection. *

Note: Not used by stylus picks.

* @function Picks.setIgnoreItems * @param {number} id - The ID of the pick. * @param {Uuid[]} ignoreItems - The list of IDs to ignore. */ Q_INVOKABLE void setIgnoreItems(unsigned int uid, const ScriptValue& ignoreItems); /*@jsdoc * Sets a list of entity and avatar IDs that a pick should include during intersection, instead of intersecting with * everything. *

Note: Stylus picks only intersect with items in their include list.

* @function Picks.setIncludeItems * @param {number} id - The ID of the pick. * @param {Uuid[]} includeItems - The list of IDs to include. */ Q_INVOKABLE void setIncludeItems(unsigned int uid, const ScriptValue& includeItems); /*@jsdoc * Sets the delay of a Ray pick. *

Note: Not used by other pick types.

* @function Picks.setDelay * @param {number} id - The ID of the pointer. * @param {number} delay - The desired delay in seconds. */ Q_INVOKABLE void setDelay(unsigned int uid, float delay); /*@jsdoc * Checks if a pick is associated with the left hand: a ray or parabola pick with joint property set to * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a stylus pick with * hand property set to 0. * @function Picks.isLeftHand * @param {number} id - The ID of the pick. * @returns {boolean} true if the pick is associated with the left hand, false if it isn't. */ Q_INVOKABLE bool isLeftHand(unsigned int uid); /*@jsdoc * Checks if a pick is associated with the right hand: a ray or parabola pick with joint property set to * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a stylus pick with * hand property set to 1. * @function Picks.isRightHand * @param {number} id - The ID of the pick. * @returns {boolean} true if the pick is associated with the right hand, false if it isn't. */ Q_INVOKABLE bool isRightHand(unsigned int uid); /*@jsdoc * Checks if a pick is associated with the system mouse: a ray or parabola pick with joint property set to * "Mouse". * @function Picks.isMouse * @param {number} id - The ID of the pick. * @returns {boolean} true if the pick is associated with the system mouse, false if it isn't. */ Q_INVOKABLE bool isMouse(unsigned int uid); unsigned int getPerFrameTimeBudget() const; void setPerFrameTimeBudget(unsigned int numUsecs); float getHandLaserDelay() const; void setHandLaserDelay(float delay); public slots: static constexpr unsigned int getPickBypassIgnore() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_BYPASS_IGNORE); } /*@jsdoc * @function Picks.PICK_ENTITIES * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_DOMAIN_ENTITIES | * Picks.PICK_AVATAR_ENTITIES properties expression instead. * @returns {number} */ static constexpr unsigned int getPickEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); } /*@jsdoc * @function Picks.PICK_OVERLAYS * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_LOCAL_ENTITIES property * instead. * @returns {number} */ static constexpr unsigned int getPickOverlays() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); } /*@jsdoc * @function Picks.PICK_DOMAIN_ENTITIES * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_DOMAIN_ENTITIES property * instead. * @returns {number} */ static constexpr unsigned int getPickDomainEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES); } /*@jsdoc * @function Picks.PICK_AVATAR_ENTITIES * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_AVATAR_ENTITIES property * instead. * @returns {number} */ static constexpr unsigned int getPickAvatarEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); } /*@jsdoc * @function Picks.PICK_LOCAL_ENTITIES * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_LOCAL_ENTITIES property * instead. * @returns {number} */ static constexpr unsigned int getPickLocalEntities() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); } /*@jsdoc * @function Picks.PICK_AVATARS * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_AVATARS property * instead. * @returns {number} */ static constexpr unsigned int getPickAvatars() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATARS); } /*@jsdoc * @function Picks.PICK_HUD * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_HUD property instead. * @returns {number} */ static constexpr unsigned int getPickHud() { return PickFilter::getBitMask(PickFilter::FlagBit::HUD); } /*@jsdoc * @function Picks.PICK_INCLUDE_VISIBLE * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_INCLUDE_VISIBLE property * instead. * @returns {number} */ static constexpr unsigned int getPickIncludeVisible() { return PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); } /*@jsdoc * @function Picks.PICK_INCLUDE_INVISIBLE * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_INCLUDE_INVISIBLE property * instead. * @returns {number} */ static constexpr unsigned int getPickIncludeInvisible() { return PickFilter::getBitMask(PickFilter::FlagBit::INVISIBLE); } /*@jsdoc * @function Picks.PICK_INCLUDE_COLLIDABLE * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_INCLUDE_COLLIDABLE property * instead. * @returns {number} */ static constexpr unsigned int getPickIncludeCollidable() { return PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); } /*@jsdoc * @function Picks.PICK_INCLUDE_NONCOLLIDABLE * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_INCLUDE_NONCOLLIDABLE * property instead. * @returns {number} */ static constexpr unsigned int getPickIncludeNoncollidable() { return PickFilter::getBitMask(PickFilter::FlagBit::NONCOLLIDABLE); } /*@jsdoc * @function Picks.PICK_PRECISE * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_PRECISE property instead. * @returns {number} */ static constexpr unsigned int getPickPrecise() { return PickFilter::getBitMask(PickFilter::FlagBit::PRECISE); } /*@jsdoc * @function Picks.PICK_COARSE * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_COARSE property instead. * @returns {number} */ static constexpr unsigned int getPickCoarse() { return PickFilter::getBitMask(PickFilter::FlagBit::COARSE); } /*@jsdoc * @function Picks.PICK_ALL_INTERSECTIONS * @deprecated This function is deprecated and will be removed. Use the Picks.PICK_ALL_INTERSECTIONS property * instead. * @returns {number} */ static constexpr unsigned int getPickAllIntersections() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ALL_INTERSECTIONS); } /*@jsdoc * @function Picks.INTERSECTED_NONE * @deprecated This function is deprecated and will be removed. Use the Picks.INTERSECTED_NONE property * instead. * @returns {number} */ static constexpr unsigned int getIntersectedNone() { return IntersectionType::NONE; } /*@jsdoc * @function Picks.INTERSECTED_ENTITY * @deprecated This function is deprecated and will be removed. Use the Picks.INTERSECTED_ENTITY property * instead. * @returns {number} */ static constexpr unsigned int getIntersectedEntity() { return IntersectionType::ENTITY; } /*@jsdoc * @function Picks.INTERSECTED_LOCAL_ENTITY * @deprecated This function is deprecated and will be removed. Use the Picks.INTERSECTED_LOCAL_ENTITY * property instead. * @returns {number} */ static constexpr unsigned int getIntersectedLocalEntity() { return IntersectionType::LOCAL_ENTITY; } /*@jsdoc * @function Picks.INTERSECTED_OVERLAY * @deprecated This function is deprecated and will be removed. Use the Picks.INTERSECTED_LOCAL_ENTITY * property instead. * @returns {number} */ static constexpr unsigned int getIntersectedOverlay() { return getIntersectedLocalEntity(); } /*@jsdoc * @function Picks.INTERSECTED_AVATAR * @deprecated This function is deprecated and will be removed. Use the Picks.INTERSECTED_AVATAR property * instead. * @returns {number} */ static constexpr unsigned int getIntersectedAvatar() { return IntersectionType::AVATAR; } /*@jsdoc * @function Picks.INTERSECTED_HUD * @deprecated This function is deprecated and will be removed. Use the Picks.INTERSECTED_HUD property * instead. * @returns {number} */ static constexpr unsigned int getIntersectedHud() { return IntersectionType::HUD; } signals: void handLaserDelayChanged(float delay); protected: static std::shared_ptr buildRayPick(const QVariantMap& properties); static std::shared_ptr buildStylusPick(const QVariantMap& properties); static std::shared_ptr buildCollisionPick(const QVariantMap& properties); static std::shared_ptr buildParabolaPick(const QVariantMap& properties); static void setParentTransform(std::shared_ptr pick, const QVariantMap& propMap); private: Setting::Handle _handLaserDelaySetting { "handLaserDelay", 0.35f }; }; #endif // hifi_PickScriptingInterface_h