diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 001ddd877a2..961b151b28d 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -5,7 +5,7 @@ // Created by Stephen Birarda on 4/9/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2021 Vircadia contributors. -// Copyright 2022-2023 Overte e.V. +// Copyright 2022-2026 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 @@ -72,6 +72,7 @@ using AvatarGrabDataMap = QMap; using AvatarDataSequenceNumber = uint16_t; +/// XXX: why is there an avatar entity limit and why is it specifically 42 const int MAX_NUM_AVATAR_ENTITIES = 42; // avatar motion behaviors @@ -119,23 +120,8 @@ const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit const int COLLIDE_WITH_OTHER_AVATARS = 11; // 12th bit const int HAS_HERO_PRIORITY = 12; // 13th bit (be scared) -/*@jsdoc - *

The pointing state of the hands is specified by the following values:

- * - * - * - * - * - * - * - * - * - * - *
ValueDescription
0No hand is pointing.
1The left hand is pointing.
2The right hand is pointing.
4It is the index finger that is pointing.
- *

The values for the hand states are added together to give the HandState value. For example, if the left - * hand's finger is pointing, the value is 1 + 4 == 5. - * @typedef {number} HandState - */ +// FIXME: Unused prehistoric cruft, still technically used in Avatar +// for rendering magenta laser pointers in C++ const char HAND_STATE_NULL = 0; const char LEFT_HAND_POINTING_FLAG = 1; const char RIGHT_HAND_POINTING_FLAG = 2; @@ -248,6 +234,8 @@ namespace AvatarDataPacket { const size_t AVATAR_SCALE_SIZE = 2; static_assert(sizeof(AvatarScale) == AVATAR_SCALE_SIZE, "AvatarDataPacket::AvatarScale size doesn't match."); + // TODO: Is this necessary? Wouldn't the eye + // bones in the joint data already cover this? PACKED_BEGIN struct LookAtPosition { float lookAtPosition[3]; // world space position that eyes are focusing on. // FIXME - unless the person has an eye tracker, this is simulated... @@ -295,6 +283,9 @@ namespace AvatarDataPacket { const size_t PARENT_INFO_SIZE = 18; static_assert(sizeof(ParentInfo) == PARENT_INFO_SIZE, "AvatarDataPacket::ParentInfo size doesn't match."); + // FIXME: Why is the local position a separately packed field, + // especially when it's the exact same size as the global position + // and a local position with no parent is just a global position anyway? // will only ever be included if the avatar has a parent but can change independent of changes to parent info // and so we keep it a separate record PACKED_BEGIN struct AvatarLocalPosition { @@ -304,6 +295,9 @@ namespace AvatarDataPacket { const size_t AVATAR_LOCAL_POSITION_SIZE = 12; static_assert(sizeof(AvatarLocalPosition) == AVATAR_LOCAL_POSITION_SIZE, "AvatarDataPacket::AvatarLocalPosition size doesn't match."); + // FIXME: In practice it looks like only the avatar-relative + // controller joints of OtherAvatars aren't done properly, and + // the "camera-relative" ones fall back to the avatar-relative ones. PACKED_BEGIN struct HandControllers { SixByteQuat leftHandRotation; SixByteTrans leftHandTranslation; @@ -328,6 +322,9 @@ namespace AvatarDataPacket { // variable length structure follows + // FIXME: This has nothing to do with face tracking. Are these fields + // even used? I thought these blendshapes were driven procedurally + // just from the audio loudness // only present if HAS_SCRIPTED_BLENDSHAPES flag is set in AvatarInfo.flags PACKED_BEGIN struct FaceTrackerInfo { float leftEyeBlink; @@ -420,6 +417,7 @@ const float AVATAR_DISTANCE_LEVEL_5 = 200.0f; // meters // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found). // This is the start location in the Sandbox (xyz: 6270, 211, 6000). +// FIXME: Unused prehistoric cruft const glm::vec3 START_LOCATION(6270, 211, 6000); // Avatar Transit Constants @@ -433,6 +431,7 @@ const float AVATAR_TRANSIT_FRAMES_PER_SECOND = 30.0f; const float AVATAR_PRE_TRANSIT_FRAME_COUNT = 10.0f; const float AVATAR_POST_TRANSIT_FRAME_COUNT = 27.0f; +// FIXME: Unused prehistoric cruft enum KeyState { NO_KEY_DOWN = 0, INSERT_KEY_DOWN, @@ -484,6 +483,10 @@ class AvatarPriority { class ClientTraitsHandler; +/** + * The parent class of and Avatar, ScriptableAvatar, MixerAvatar, + * and the grandparent class of MyAvatar and OtherAvatar. + */ class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT @@ -600,7 +603,18 @@ class AvatarData : public QObject, public SpatiallyNestable { static const QString FRAME_NAME; + /** + * Deserializes a recorded joint data frame and applies it to an avatar. + * @param frameData The serialized frame data + * @param avatar The avatar to modify + * @param useFrameSkeleton true if the avatar's model URL should be overwritten by the recorded one + */ static void fromFrame(const QByteArray& frameData, AvatarData& avatar, bool useFrameSkeleton = true); + /** + * Serializes an avatar's joint data for the avatar recording format. + * @param avatar The avatar to serialize + * @returns The serialized joint data + */ static QByteArray toFrame(const AvatarData& avatar); AvatarData(); @@ -608,36 +622,74 @@ class AvatarData : public QObject, public SpatiallyNestable { virtual bool isMyAvatarURLProtected() const { return false; } // This needs to be here because both MyAvatar and AvatarData inherit from MyAvatar + /** @return PathUtils::resourcesUrl("/meshes/defaultAvatar_full.fst") */ static const QUrl& defaultFullAvatarModelUrl(); + // TODO: Why is this its own function? It's always getID const QUuid getSessionUUID() const { return getID(); } + // FIXME: Unused prehistoric cruft glm::vec3 getHandPosition() const; void setHandPosition(const glm::vec3& handPosition); + /// Specifies how much avatar data to include in the output of AvatarData::toByteArray. typedef enum { + /// Only the avatar UUID and empty flag bitset header NoData, + /// Global position and audio loudness PALMinimum, + /// Any dirty properties, except joint data MinimumData, + /// Any dirty properties, and only joint data that has changed significantly CullSmallData, + /// Any dirty properties, and any joint data IncludeSmallData, + /// Sends all available data, even if it's not dirty SendAllData } AvatarDataDetail; + /** + * Similar to toByteArary, but automatically fills in most of its arguments and + * updates _lastToByteArray to usecTimestampNow. + */ virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false); + // FIXME: This function is a horrible mess + /** + * Packs avatar data into a byte array. What data is included is determined by dataDetail. + * @param dataDetail How much data should be included + * @param lastSentTime Microsecond timestamp of when the last update was sent + * @param lastSendJointData + * @param sendStatus + * @param dropFaceTracking Doesn't actually have anything to do with face tracking. + * @param distanceAdjust Whether to ignore small joint differences that might be too far away to see + * @param viewerPosition Used for determining the distance to the avatar for culling small joint deltas + * @param sentJointDataOut TODO + * @param maxDataSize The maximum size the serialised packet can be. If a property would overfill the buffer, it and the following properties are ignored. + * @param outboundDataRateOut Bandwidth counter + * @returns The serialized avatar data + */ virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize = 0, AvatarDataRate* outboundDataRateOut = nullptr) const; + /** + * Updates _lastSendJointData. + * @param cullSmallChanges + */ virtual void doneEncoding(bool cullSmallChanges); - /// \return true if an error should be logged + /** + * Used for throttling the output of error messages + * @return true if an error should be logged + */ bool shouldLogError(const quint64& now); - /// \param packet byte array of data - /// \param offset number of bytes into packet where data starts - /// \return number of bytes parsed + /** + * Deserializes avatar data from a network packet buffer. + * @param buffer The serialized avatar data + * @return The number of bytes that were consumed + */ virtual int parseDataFromBuffer(const QByteArray& buffer); virtual void setCollisionWithOtherAvatarsFlags() {}; @@ -650,9 +702,15 @@ class AvatarData : public QObject, public SpatiallyNestable { float getBodyRoll() const; void setBodyRoll(float bodyRoll); + /** An alias for setWorldPosition. */ virtual void setPositionViaScript(const glm::vec3& position); + /** An alias for setWorldOrientation. */ virtual void setOrientationViaScript(const glm::quat& orientation); + /** + * Sets the avatar's rotation and up vector. + * Unused by AvatarData, but is used by Avatar. + */ virtual void updateAttitude(const glm::quat& orientation) {} glm::quat getHeadOrientation() const { @@ -760,20 +818,10 @@ class AvatarData : public QObject, public SpatiallyNestable { void setDomainMinimumHeight(float domainMinimumHeight); void setDomainMaximumHeight(float domainMaximumHeight); - /*@jsdoc - * Sets the pointing state of the hands to control where the laser emanates from. If the right index finger is pointing, the - * laser emanates from the tip of that finger, otherwise it emanates from the palm. - * @function Avatar.setHandState - * @param {HandState} state - The pointing state of the hand. - */ + // FIXME: Unused legacy cruft, remove this Q_INVOKABLE void setHandState(char s) { _handState = s; } - /*@jsdoc - * Gets the pointing state of the hands to control where the laser emanates from. If the right index finger is pointing, the - * laser emanates from the tip of that finger, otherwise it emanates from the palm. - * @function Avatar.getHandState - * @returns {HandState} The pointing state of the hand. - */ + // FIXME: Unused legacy cruft, remove this Q_INVOKABLE char getHandState() const { return _handState; } /*@jsdoc @@ -1177,7 +1225,7 @@ class AvatarData : public QObject, public SpatiallyNestable { */ Q_INVOKABLE void setForceFaceTrackerConnected(bool connected) { setHasScriptedBlendshapes(connected); } - // key state + // FIXME: Unused legacy cruft, remove these void setKeyState(KeyState s) { _keyState = s; } KeyState keyState() const { return _keyState; } @@ -1207,7 +1255,9 @@ class AvatarData : public QObject, public SpatiallyNestable { QByteArray identityByteArray(bool setIsReplicated = false) const; + /** @return The skeleton model URL, or an empty QUrl if it's a file:// or qrc:/ URL. */ QUrl getWireSafeSkeletonModelURL() const; + /** @return The skeleton model URL, or an empty QUrl if this avatar is someone else's. */ virtual const QUrl& getSkeletonModelURL() const; const QString& getDisplayName() const { return _displayName; } @@ -1589,14 +1639,14 @@ public slots: float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; - // Hand state (are we grabbing something or not) + // FIXME: Unused prehistoric cruft char _handState; QVector _jointData; ///< the state of the skeleton joints QVector _lastSentJointData; ///< the state of the skeleton joints last time we transmitted mutable QReadWriteLock _jointDataLock; - // key state + // FIXME: Unused prehistoric cruft KeyState _keyState; std::atomic _hasNewJointData { true }; // set in AvatarData, cleared in Avatar @@ -1606,12 +1656,15 @@ public slots: std::vector _hasNewJointDataVec; std::array, 2> _orientationHistory; + // FIXME: Prehistoric cruft that makes no sense and needs to be removed mutable HeadData* _headData { nullptr }; QUrl _skeletonModelURL; QString _displayName; QString _sessionDisplayName { }; bool _lookAtSnappingEnabled { true }; + // FIXME: Unused, we don't have centralised avatar verification and + // the "verification failed" model doesn't even exist anymore bool _verificationFailed { false }; quint64 _errorLogExpiry; ///< time in future when to log an error diff --git a/libraries/entities/src/EntityItem.h.in b/libraries/entities/src/EntityItem.h.in index 4c4841c31e1..42a4169f1e0 100644 --- a/libraries/entities/src/EntityItem.h.in +++ b/libraries/entities/src/EntityItem.h.in @@ -73,8 +73,12 @@ class MeshProxyList; namespace entity { enum class HostType : uint8_t { + /// Owned and saved by the domain, networked by the entity mixer. DOMAIN = 0, + /// Owned and saved by a specific user. Networked by + /// the avatar mixer and follows the user across domains. AVATAR, + /// Unsaved and not networked to other clients, follows the user across domains. LOCAL }; } @@ -197,16 +201,27 @@ public: void requiresRecalcBoxes(); - /// Dimensions in meters (0.0 - TREE_SCALE) + /** + * Gets this EntityItem's global scale. + * @return getUnscaledDimensions() * getSNScale() + */ virtual glm::vec3 getScaledDimensions() const; + /** + * Sets this EntityItem's global scale. + * setUnscaledDimensions(value / getSNScale()) + */ virtual void setScaledDimensions(const glm::vec3& value); virtual glm::vec3 getPivot() const { return glm::vec3(0.0f); } // pivot offset for positioning, mainly for model entities + /// @return This EntityItem's local scale glm::vec3 getUnscaledDimensions() const; + /// Sets this EntityItem's local scale. virtual void setUnscaledDimensions(const glm::vec3& value); + /// Calculates an appropriate mass for the EntityItem's density and volume. float computeMass() const; + /// Sets the EntityItem's density to change its mass for the same volume. void setMass(float mass); bool hasVelocity() const { return getWorldVelocity() != ENTITY_ITEM_ZERO_VEC3; } @@ -223,6 +238,7 @@ public: /// age of this entity in seconds float getAge() const { return (float)(usecTimestampNow() - getCreated()) / (float)USECS_PER_SECOND; } bool lifetimeHasExpired() const; + /// @return Microsecond timestamp of when the EntityItem is due to be automatically deleted quint64 getExpiry() const; // position, size, and bounds related helpers @@ -250,6 +266,10 @@ public: bool getCullWithParent() const; void setCullWithParent(bool value); + // FIXME: "Cauterize" feels unusually violent. + // This should just be called joint hiding/clipping. + /// Sets whether this EntityItem will inherit any cauterization clipping from its parent, + /// i.e. an avatar's head and neck when in first person void setCauterized(bool value); bool getCauterized() const; @@ -260,7 +280,9 @@ public: void computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask) const; + /// @returns Whether this entity can be collided with by avatars or other entities virtual bool shouldBePhysical() const { return !isDead() && getShapeType() != SHAPE_TYPE_NONE && !isLocalEntity(); } + /// @returns true if this entity's model and/or textures have been fully loaded bool isVisuallyReady() const { return _visuallyReady; } // FIXME not thread safe? @@ -285,20 +307,29 @@ public: float getRadius() const; virtual void adjustShapeInfoByRegistration(ShapeInfo& info, bool includePivot = true) const; + // FIXME: Why is this here and why does it only work on Shapes? + // It should either be moved into ShapeEntityItem or made more + // generic to be a point-bounding box test. + /// @returns true if point is within a Shape entity's bounds virtual bool contains(const glm::vec3& point) const; virtual bool isReadyToComputeShape() const { return !isDead(); } virtual void computeShapeInfo(ShapeInfo& info); virtual float getVolumeEstimate() const; + // FIXME: Why is this here? It should be moved to ShapeEntityItem /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } + /// Sets an entity's global position void setPosition(const glm::vec3& value); virtual void setParentID(const QUuid& parentID) override; + // FIXME: Why is this here? It should be moved to ShapeEntityItem virtual void setShapeType(ShapeType type) { /* do nothing */ } + /// Sets an entity's global rotation void setRotation(glm::quat orientation); + /// Sets an entity's global linear velocity void setVelocity(const glm::vec3& velocity); uint32_t getDirtyFlags() const; @@ -309,9 +340,12 @@ public: void markSpecialFlags(uint32_t mask); void clearSpecialFlags(uint32_t mask = 0xffff0000); + /// @return true if this entity has any velocity, relative to the world bool isMoving() const; + /// @return true if this entity has any velocity relative to its parent bool isMovingRelativeToParent() const; + /// @return true if this entity is being simulated by EntitySimulation bool isSimulated() const { return _simulated; } bool isInPhysicsSimulation() const { return (bool)(_flags & Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION); } @@ -326,7 +360,9 @@ public: glm::mat4 getEntityToWorldMatrix() const; glm::mat4 getWorldToEntityMatrix() const; + /// @return getWorldToEntityMatrix() * point glm::vec3 worldToEntity(const glm::vec3& point) const; + /// @return getEntityToWorldMatrix() * point glm::vec3 entityToWorld(const glm::vec3& point) const; quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; } @@ -391,13 +427,14 @@ public: void setScriptHasFinishedPreload(bool value); bool isScriptPreloadFinished(); QString getLoadedScript() const { return _loadedScript; } + // FIXME: The term "wearable" is used inconsistently and should just be removed virtual bool isWearable() const; bool isDomainEntity() const { return _entityHostType == entity::HostType::DOMAIN; } bool isAvatarEntity() const { return _entityHostType == entity::HostType::AVATAR; } bool isMyAvatarEntity() const; bool isLocalEntity() const { return _entityHostType == entity::HostType::LOCAL; } - // if this entity is an avatar entity, which avatar is it associated with? + /// if this entity is an avatar entity, which avatar is it associated with? QUuid getOwningAvatarIDForProperties() const; virtual bool wantsHandControllerPointerEvents() const { return false; } diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index d8395c8739e..580defc8ca4 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -778,6 +778,8 @@ const Transform SpatiallyNestable::getTransform() const { return result; } +// FIXME: This doesn't seem to work on avatars. Two avatars parented to eachother +// will forever spam this function without ever breaking the loop. void SpatiallyNestable::breakParentingLoop() const { // someone created a loop. break it... qCDebug(shared) << "Parenting loop detected: " << getID(); diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index a04ad62a3a4..f8e0ab9a629 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -4,6 +4,7 @@ // // Created by Seth Alves on 2015-10-18 // Copyright 2015 High Fidelity, Inc. +// Copyright 2026 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 @@ -33,86 +34,273 @@ enum class NestableType { Avatar }; +// FIXME: SpatiallyNestable is very inconsistent and +// has an **enormous** amount of code duplication. +// Most of its member functions could be replaced +// with ones that take/return `Transform &`. +/** + * The base "entity" class, with a transform and parent-child hierarchy. + * The common ancestor of EntityItem and AvatarData. + */ class SpatiallyNestable : public std::enable_shared_from_this { public: SpatiallyNestable(NestableType nestableType, QUuid id); virtual ~SpatiallyNestable(); + // FIXME: Is there a good reason these are virtual? + // It doesn't look like anything actually overrides them virtual const QUuid getID() const; virtual void setID(const QUuid& id); + // FIXME: SpatiallyNestables are always either an EntityItem + // or an AvatarData, so why isn't this a pure virtual function? + /** + * @return The entity's name property for EntityItem, and "Avatar: displayName" for avatars. + */ virtual QString getName() const { return "SpatiallyNestable"; } + /** @return The UUID of this SpatiallyNestable's parent. A null UUID if there is no parent. */ virtual const QUuid getParentID() const; + /** @param parentID The new parent UUID. Can be a null UUID for no parent. */ virtual void setParentID(const QUuid& parentID); virtual bool isMyAvatar() const { return false; } + /** @return This SpatiallyNestable's parent joint index, or -1 if it has none set. */ virtual quint16 getParentJointIndex() const { return _parentJointIndex; } + /** @param parentJointIndex A parent joint index, or -1 for none. */ virtual void setParentJointIndex(quint16 parentJointIndex); + /** + * @param position Global position vector + * @param parentID SpatiallyNestable that position will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that position will be transformed relative to + * @param scalesWithParent Whether the global-to-local transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + // FIXME: What does "inheriting scale" mean on a rotation? + /** + * @param orientation Global orientation quaternion + * @param parentID SpatiallyNestable that orientation will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that orientation will be transformed relative to + * @param scalesWithParent Whether the global-to-local transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::quat worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param velocity Global linear velocity vector + * @param parentID SpatiallyNestable that velocity will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that velocity will be transformed relative to + * @param scalesWithParent Whether the global-to-local transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 worldToLocalVelocity(const glm::vec3& velocity, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param angularVelocity Global angular velocity vector + * @param parentID SpatiallyNestable that angularVelocity will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that angularVelocity will be transformed relative to + * @param scalesWithParent Whether the global-to-local transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 worldToLocalAngularVelocity(const glm::vec3& angularVelocity, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param dimensions Global dimensions + * @param parentID SpatiallyNestable that dimensions will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that dimensions will be transformed relative to + * @param scalesWithParent Whether the global-to-local transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 worldToLocalDimensions(const glm::vec3& dimensions, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param position Position vector local to parentID + * @param parentID SpatiallyNestable that position will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that position will be transformed relative to + * @param scalesWithParent Whether the local-to-global transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param orientation Orientation quaternion local to parentID + * @param parentID SpatiallyNestable that orientation will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that orientation will be transformed relative to + * @param scalesWithParent Whether the local-to-global transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::quat localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param velocity Linear velocity vector local to parentID + * @param parentID SpatiallyNestable that velocity will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that velocity will be transformed relative to + * @param scalesWithParent Whether the local-to-global transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 localToWorldVelocity(const glm::vec3& velocity, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param angularVelocity Angular velocity vector local to parentID + * @param parentID SpatiallyNestable that angularVelocity will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that angularVelocity will be transformed relative to + * @param scalesWithParent Whether the local-to-global transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 localToWorldAngularVelocity(const glm::vec3& angularVelocity, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + /** + * @param dimensions Dimensions vector local to parentID + * @param parentID SpatiallyNestable that dimensions will be transformed relative to + * @param parentJointIndex The joint of the parent SpatiallyNestable that dimensions will be transformed relative to + * @param scalesWithParent Whether the local-to-global transformation takes scaling into account + * @param success false if parentID or parentJointIndex is invalid + */ static glm::vec3 localToWorldDimensions(const glm::vec3& dimensions, const QUuid& parentID, int parentJointIndex, bool scalesWithParent, bool& success); + // FIXME: There is no valid variant of NestableType that would produce "unknown" + /** + * @param nestableType + * @return A string matching nestableType: "entity", "avatar", or "unknown". + */ static QString nestableTypeToString(NestableType nestableType); - + /** + * @param depth How many parents to check before assuming it's looping, Defaults to 0, which will mean this function only ever returns true if the SpatiallyNestable has no parent. + * @return true if this SpatiallyNestable's parent tree is complete and non-recursive + */ virtual bool isParentPathComplete(int depth = 0) const; - // world frame + /** + * @param success true if this SpatiallyNestable has a complete, non-recursive parent tree. + * @param depth How many parent transforms in the parent tree to account for. + * @return A copy of this SpatiallyNestable's global transform if depth is > 0, otherwise its local transform. + */ virtual const Transform getTransform(bool& success, int depth = 0) const; + // FIXME: This looks like a hack. It's only used by Model entities when billboarding, + // and ModelEntityItem just overrides this anyway. + /** + * @param success true if this SpatiallyNestable has a complete, non-recursive parent tree. + * @param depth How many parent transforms in the parent tree to account for. + * @return A copy of this SpatiallyNestable's global transform, but with a local rotation. + */ virtual const Transform getTransformWithOnlyLocalRotation(bool& success, int depth = 0) const; + /** + * @return A copy of this SpatiallyNestable's local transform. + */ virtual const Transform getTransform() const; + /** + * @param transform A new global transform for this SpatiallyNestable. + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + */ virtual void setTransform(const Transform& transform, bool& success); + /** + * @param transform A new global transform for this SpatiallyNestable. + */ virtual bool setTransform(const Transform& transform); + /** + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @param depth How many parent transforms in the parent tree to take into account. + * @returns A copy of this SpatiallyNestable's global transform, excluding its own local transform. + */ virtual Transform getParentTransform(bool& success, int depth = 0) const; + /** + * @param position Global position + * @param orientation Global rotation + */ void setWorldTransform(const glm::vec3& position, const glm::quat& orientation); + /** + * @param success true if this SpatiallyNestable has a complete, non-recursive parent tree. + * @return This SpatiallyNestable's global position + */ virtual glm::vec3 getWorldPosition(bool& success) const; + /** + * @return This SpatiallyNestable's global position + */ virtual glm::vec3 getWorldPosition() const; + /** + * @param position Global position vector + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @param tellPhysics + */ virtual void setWorldPosition(const glm::vec3& position, bool& success, bool tellPhysics = true); + /** @param position Global position vector */ virtual void setWorldPosition(const glm::vec3& position); + /** + * @param success true if this SpatiallyNestable has a complete, non-recursive parent tree. + * @return This SpatiallyNestable's global rotation + */ virtual glm::quat getWorldOrientation(bool& success) const; + /** @return This SpatiallyNestable's global rotation */ virtual glm::quat getWorldOrientation() const; + // FIXME: This should be called getJointWorldOrientation + /** + * @param jointIndex The joint index to get the global rotation of + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return The global rotation of the joint + */ virtual glm::quat getWorldOrientation(int jointIndex, bool& success) const; + /** + * @param orientation Global rotation quaternion + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @param tellPhysics + */ virtual void setWorldOrientation(const glm::quat& orientation, bool& success, bool tellPhysics = true); + /** @param orientation Global rotation quaternion */ virtual void setWorldOrientation(const glm::quat& orientation); + /** + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return The global linear velocity vector + */ virtual glm::vec3 getWorldVelocity(bool& success) const; + /** @return The global linear velocity vector */ virtual glm::vec3 getWorldVelocity() const; + /** + * @param velocity Global linear velocity vector + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + */ virtual void setWorldVelocity(const glm::vec3& velocity, bool& success); + /** @param velocity Global linear velocity vector */ virtual void setWorldVelocity(const glm::vec3& velocity); + /** + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return The global linear velocity of this SpatiallyNestable's parent tree, excluding its own local velocity. + */ virtual glm::vec3 getParentVelocity(bool& success) const; + /** + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return The global angular velocity vector + */ virtual glm::vec3 getWorldAngularVelocity(bool& success) const; + /** @return The global angular velocity vector */ virtual glm::vec3 getWorldAngularVelocity() const; + /** + * @param angularVelocity Global angular velocity vector + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + */ virtual void setWorldAngularVelocity(const glm::vec3& angularVelocity, bool& success); + /** @param angularVelocity Global angular velocity vector */ virtual void setWorldAngularVelocity(const glm::vec3& angularVelocity); + /** + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return The global angular velocity of this SpatiallyNestable's parent tree, excluding its own local velocity. + */ virtual glm::vec3 getParentAngularVelocity(bool& success) const; + // TODO: Document these, I don't understand what they're doing virtual AACube getMaximumAACube(bool& success) const; virtual AACube calculateInitialQueryAACube(bool& success); @@ -126,14 +314,39 @@ class SpatiallyNestable : public std::enable_shared_from_this virtual AACube getQueryAACube(bool& success) const; virtual AACube getQueryAACube() const; + /** @return This SpatiallyNestable's global scale. */ virtual glm::vec3 getSNScale() const; + /** + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return This SpatiallyNestable's global scale. + */ virtual glm::vec3 getSNScale(bool& success) const; + /** @param scale The new global global scale. */ virtual void setSNScale(const glm::vec3& scale); + /** + * @param scale The new global global scale. + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + */ virtual void setSNScale(const glm::vec3& scale, bool& success); - // get world-frame values for a specific joint + /** + * @param jointIndex The joint index to get the global transform of + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @param depth How many parent transforms in the parent tree to take into account. + * @return The global transform of the specified joint, or identity if the joint is invalid + */ virtual const Transform getJointTransform(int jointIndex, bool& success, int depth = 0) const; + /** + * @param jointIndex The joint index to get the global position of + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return The global position of the specified joint, or zero if the joint is invalid + */ virtual glm::vec3 getJointWorldPosition(int jointIndex, bool& success) const; + /** + * @param jointIndex The joint index to get the global scale of + * @param success true if this SpatiallyNestable has a valid parent transform or no parent. + * @return The global position of the specified joint, or zero if the joint is invalid + */ virtual glm::vec3 getJointSNScale(int jointIndex, bool& success) const; // object's parent's frame @@ -155,7 +368,12 @@ class SpatiallyNestable : public std::enable_shared_from_this virtual glm::vec3 getLocalSNScale() const; virtual void setLocalSNScale(const glm::vec3& scale); + /** @return true if this SpatiallyNestable should inherit its parents' scales */ virtual bool getScalesWithParent() const { return false; } + // FIXME: This looks like a bad hack. Children should just inherit the SNScale + // of their parent joint, but Avatar and MyAvatar override this in different ways. + // https://github.com/overte-org/overte/issues/2071 + /** @return Scale vector for child nodes to scale themselves with, only used by avatars for scaling attached avatar entities */ virtual glm::vec3 scaleForChildren() const { return glm::vec3(1.0f); } QList getChildren() const; @@ -168,34 +386,83 @@ class SpatiallyNestable : public std::enable_shared_from_this virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const { return glm::vec3(1.0f); } virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const { return glm::quat(); } virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const { return glm::vec3(); } + /** + * @param index The joint to get the parent of + * @return The parent joint index of index, or -1 if it is invalid or the root joint. + */ virtual int getJointParent(int index) const { return -1; } virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) {return false; } + /** + * Not implemented on SpatiallyNestable. + * @param index Joint index + * @return Local joint rotation quaternion, relative to the joint's parent + */ virtual glm::quat getLocalJointRotation(int index) const {return glm::quat(); } + /** + * Not implemented on SpatiallyNestable. + * @param index Joint index + * @return Local joint translation vector, relative to the joint's parent + */ virtual glm::vec3 getLocalJointTranslation(int index) const {return glm::vec3(); } + /** + * Not implemented on SpatiallyNestable. + * @param index Joint index + * @param rotation Local rotation quaternion + * @return true if the joint index is valid, relative to the joint's parent + */ virtual bool setLocalJointRotation(int index, const glm::quat& rotation) { return false; } + /** + * Not implemented on SpatiallyNestable. + * @param index Joint index + * @param translation Local translation vector + * @return true if the joint index is valid, relative to the joint's parent + */ virtual bool setLocalJointTranslation(int index, const glm::vec3& translation) { return false; } + /** @return shared_from_this() const-casted back to a mutable pointer */ SpatiallyNestablePointer getThisPointer() const; using ChildLambda = std::function; using ChildLambdaTest = std::function; + /** @param actor A function that will run on each of this SpatiallyNestable's children */ void forEachChild(const ChildLambda& actor) const; + /** + * @param actor A function that will run on each of this SpatiallyNestable's children, and their children + */ void forEachDescendant(const ChildLambda& actor) const; + /** + * @param actor A function that will run on each of this SpatiallyNestable's children, but will stop iterating if the function returns false + */ void forEachChildTest(const ChildLambdaTest& actor) const; + /** + * @param actor A function that will run on each of this SpatiallyNestable's children, and their children, but will stop iterating if the function returns false + */ void forEachDescendantTest(const ChildLambdaTest& actor) const; + /** Marks this SpatiallyNestable as safe to remove. */ void die() { _isDead = true; } + /** @return true if this SpatiallyNestable is safe to remove. */ bool isDead() const { return _isDead; } + /** @return true if this SpatiallyNestable's direct parent is valid and exists, or if it has no parent. */ bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; } + + /** + * Only implemented in EntityItem. + * @return nullptr + */ virtual SpatialParentTree* getParentTree() const { return nullptr; } bool hasAncestorOfType(NestableType nestableType, int depth = 0) const; const QUuid findAncestorOfType(NestableType nestableType, int depth = 0) const; + /** + * @param success true if this SpatiallyNestable has a valid parent tree, or if it has no parent. + * @returns The direct parent, or nullptr if there is no parent. + */ SpatiallyNestablePointer getParentPointer(bool& success) const; static SpatiallyNestablePointer findByID(QUuid id, bool& success); @@ -208,15 +475,36 @@ class SpatiallyNestable : public std::enable_shared_from_this const glm::vec3& localVelocity, const glm::vec3& localAngularVelocity); + /** + * @param time Microseconds since the engine started + * @return true if this SpatiallyNestable's scale has changed since time + */ bool scaleChangedSince(quint64 time) const { return _scaleChanged > time; } + /** + * @param time Microseconds since the engine started + * @return true if this SpatiallyNestable's position has changed since time + */ bool tranlationChangedSince(quint64 time) const { return _translationChanged > time; } + /** + * @param time Microseconds since the engine started + * @return true if this SpatiallyNestable's rotation has changed since time + */ bool rotationChangedSince(quint64 time) const { return _rotationChanged > time; } + /** + * Dumps this SpatiallyNestable's state to the log. + * @param prefix Prefix string before the dump log lines + */ void dump(const QString& prefix = "") const; - virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true); // called when a this object's location has changed - virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed - virtual void parentDeleted() { } // called on children of a deleted parent + /** Called when this SpatiallyNestable's position has changed. */ + virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true); + // FIXME: This should probably be moved to EntityItem. + // SpatiallyNestable doesn't have dimensions. + /** Called when this SpatiallyNestable's dimensions have changed. */ + virtual void dimensionsChanged() { _queryAACubeSet = false; } + /** Called when this SpatiallyNestable's parent has been deleted. */ + virtual void parentDeleted() { } virtual void addGrab(GrabPointer grab); virtual void removeGrab(GrabPointer grab); @@ -230,8 +518,11 @@ class SpatiallyNestable : public std::enable_shared_from_this virtual void beParentOfChild(SpatiallyNestablePointer newChild) const; virtual void forgetChild(SpatiallyNestablePointer newChild) const; + // TODO: "Cauterization" is a weirdly violent term for this. + // It should instead be called something like joint hiding or culling. virtual void recalculateChildCauterization() const { } + // TODO: Is it necessary to reference both the parent *and* child nodes? mutable ReadWriteLockable _childrenLock; mutable QHash _children; @@ -239,8 +530,11 @@ class SpatiallyNestable : public std::enable_shared_from_this mutable AACube _queryAACube; mutable bool _queryAACubeSet { false }; + /** Microsecond timestamp of when this SpatiallyNestable's SNScale was changed */ quint64 _scaleChanged { 0 }; + /** Microsecond timestamp of when this SpatiallyNestable's position was changed */ quint64 _translationChanged { 0 }; + /** Microsecond timestamp of when this SpatiallyNestable's rotation was changed */ quint64 _rotationChanged { 0 }; mutable ReadWriteLockable _grabsLock; @@ -256,9 +550,9 @@ class SpatiallyNestable : public std::enable_shared_from_this mutable ReadWriteLockable _idLock; mutable ReadWriteLockable _velocityLock; mutable ReadWriteLockable _angularVelocityLock; - Transform _transform; // this is to be combined with parent's world-transform to produce this' world-transform. - glm::vec3 _velocity; - glm::vec3 _angularVelocity; + Transform _transform; /**< This SpatiallyNestable's local transform */ + glm::vec3 _velocity; /**< This SpatiallyNestable's local linear velocity */ + glm::vec3 _angularVelocity; /**< This SpatiallyNestable's local angular velocity */ mutable bool _parentKnowsMe { false }; bool _isDead { false }; bool _queryAACubeIsPuffed { false };