diff --git a/.gitignore b/.gitignore
index 747f613a4b..4b0251156e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,9 @@ android/**/src/main/assets
android/**/gradle*
*.class
+# Visual Studio
+/.vs
+
# VSCode
# List taken from Github Global Ignores master@435c4d92
# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore
diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md
index 1559ece191..8820bda8f6 100644
--- a/BUILD_LINUX.md
+++ b/BUILD_LINUX.md
@@ -37,8 +37,14 @@ sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 li
Install build tools:
```bash
+# For Ubuntu 18.04
sudo apt-get install cmake
```
+```bash
+# For Ubuntu 16.04
+wget https://cmake.org/files/v3.9/cmake-3.9.5-Linux-x86_64.sh
+sudo sh cmake-3.9.5-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir
+```
Install Python 3:
```bash
@@ -61,7 +67,7 @@ git tags
Then checkout last tag with:
```bash
-git checkout tags/v0.71.0
+git checkout tags/v0.79.0
```
### Compiling
diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
index 78a4487284..d5b87af7fa 100644
--- a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
+++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
@@ -117,7 +117,8 @@ void RenderThread::setup() {
{ std::unique_lock For Interface, client entity, and avatar scripts, see {@link MyAvatar}. Note: In the examples, use " Warning: Potentially an expensive call. Do not use if possible. Warning: Potentially an expensive call. Do not use if possible.Agent
API enables an assignment client to emulate an avatar. Setting isAvatar = true
connects
+ * the assignment client to the avatar and audio mixers, and enables the {@link Avatar} API to be used.
+ *
* @namespace Agent
*
* @hifi-assignment-client
*
- * @property {boolean} isAvatar
- * @property {boolean} isPlayingAvatarSound Read-only.
- * @property {boolean} isListeningToAudioStream
- * @property {boolean} isNoiseGateEnabled
- * @property {number} lastReceivedAudioLoudness Read-only.
- * @property {Uuid} sessionUUID Read-only.
+ * @property {boolean} isAvatar - true
if the assignment client script is emulating an avatar, otherwise
+ * false
.
+ * @property {boolean} isPlayingAvatarSound - true
if the script has a sound to play, otherwise false
.
+ * Sounds are played when isAvatar
is true
, from the position and with the orientation of the
+ * scripted avatar's head. Read-only.
+ * @property {boolean} isListeningToAudioStream - true
if the agent is "listening" to the audio stream from the
+ * domain, otherwise false
.
+ * @property {boolean} isNoiseGateEnabled - true
if the noise gate is enabled, otherwise false
. When
+ * enabled, the input audio stream is blocked (fully attenuated) if it falls below an adaptive threshold.
+ * @property {number} lastReceivedAudioLoudness - The current loudness of the audio input. Nominal range [0.0
(no
+ * sound) – 1.0
(the onset of clipping)]. Read-only.
+ * @property {Uuid} sessionUUID - The unique ID associated with the agent's current session in the domain. Read-only.
*/
class AgentScriptingInterface : public QObject {
Q_OBJECT
@@ -54,20 +63,43 @@ public:
public slots:
/**jsdoc
+ * Sets whether the script should emulate an avatar.
* @function Agent.setIsAvatar
- * @param {boolean} isAvatar
+ * @param {boolean} isAvatar - true
if the script emulates an avatar, otherwise false
.
+ * @example true
if the script is emulating an avatar, otherwise false
.
+ * @example isAvatar == true
.
* @function Agent.playAvatarSound
- * @param {object} avatarSound
+ * @param {SoundObject} avatarSound - The sound played.
+ * @example Avatar
API is used to manipulate scriptable avatars on the domain. This API is a subset of the
- * {@link MyAvatar} API.
+ * {@link MyAvatar} API. To enable this API, set {@link Agent|Agent.isAvatar} to true
.
+ *
+ * Avatar
" instead of "MyAvatar
".0.005
and
+ * 1000.0
. When the scale value is fetched, it may temporarily be further limited by the domain's settings.
+ * @property {number} density - The density of the avatar in kg/m3. The density is used to work out its mass in
+ * the application of physics. Read-only.
+ * @property {Vec3} handPosition - A user-defined hand position, in world coordinates. The position moves with the avatar
+ * but is otherwise not used or changed by Interface.
+ * @property {number} bodyYaw - The left or right rotation about an axis running from the head to the feet of the avatar.
* Yaw is sometimes called "heading".
* @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
* sometimes called "elevation".
* @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
* sometimes called "bank".
- * @property {Quat} orientation
+ * @property {Quat} orientation - The orientation of the avatar.
* @property {Quat} headOrientation - The orientation of the avatar's head.
* @property {number} headPitch - The rotation about an axis running from ear to ear of the avatar's head. Pitch is
* sometimes called "elevation".
@@ -46,79 +50,37 @@
* head. Yaw is sometimes called "heading".
* @property {number} headRoll - The rotation about an axis running from the nose to the back of the avatar's head. Roll is
* sometimes called "bank".
- * @property {Vec3} velocity
- * @property {Vec3} angularVelocity
- * @property {number} audioLoudness
- * @property {number} audioAverageLoudness
- * @property {string} displayName
- * @property {string} sessionDisplayName - Sanitized, defaulted version displayName that is defined by the AvatarMixer
- * rather than by Interface clients. The result is unique among all avatars present at the time.
- * @property {boolean} lookAtSnappingEnabled
- * @property {string} skeletonModelURL
- * @property {AttachmentData[]} attachmentData
+ * @property {Vec3} velocity - The current velocity of the avatar.
+ * @property {Vec3} angularVelocity - The current angular velocity of the avatar.
+ * @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the
+ * domain.
+ * @property {number} audioAverageLoudness - The rolling average loudness of the audio input that the avatar is injecting
+ * into the domain.
+ * @property {string} displayName - The avatar's display name.
+ * @property {string} sessionDisplayName - displayName's
sanitized and default version defined by the avatar mixer
+ * rather than Interface clients. The result is unique among all avatars present in the domain at the time.
+ * @property {boolean} lookAtSnappingEnabled=true - true
if the avatar's eyes snap to look at another avatar's
+ * eyes when the other avatar is in the line of sight and also has lookAtSnappingEnabled == true
.
+ * @property {string} skeletonModelURL - The avatar's FST file.
+ * @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.
+ * Deprecated: Use avatar entities instead.
* @property {string[]} jointNames - The list of joints in the current avatar model. Read-only.
- * @property {Uuid} sessionUUID Read-only.
- * @property {Mat4} sensorToWorldMatrix Read-only.
- * @property {Mat4} controllerLeftHandMatrix Read-only.
- * @property {Mat4} controllerRightHandMatrix Read-only.
- * @property {number} sensorToWorldScale Read-only.
+ * @property {Uuid} sessionUUID - Unique ID of the avatar in the domain. Read-only.
+ * @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the
+ * avatar's size, orientation, and position in the virtual world. Read-only.
+ * @property {Mat4} controllerLeftHandMatrix - The rotation and translation of the left hand controller relative to the
+ * avatar. Read-only.
+ * @property {Mat4} controllerRightHandMatrix - The rotation and translation of the right hand controller relative to the
+ * avatar. Read-only.
+ * @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's
+ * size in the virtual world. Read-only.
+ * @property {boolean} hasPriority - is the avatar in a Hero zone? Read-only.
*
- * @borrows MyAvatar.getDomainMinScale as getDomainMinScale
- * @borrows MyAvatar.getDomainMaxScale as getDomainMaxScale
- * @borrows MyAvatar.canMeasureEyeHeight as canMeasureEyeHeight
- * @borrows MyAvatar.getEyeHeight as getEyeHeight
- * @borrows MyAvatar.getHeight as getHeight
- * @borrows MyAvatar.setHandState as setHandState
- * @borrows MyAvatar.getHandState as getHandState
- * @borrows MyAvatar.setRawJointData as setRawJointData
- * @borrows MyAvatar.setJointData as setJointData
- * @borrows MyAvatar.setJointRotation as setJointRotation
- * @borrows MyAvatar.setJointTranslation as setJointTranslation
- * @borrows MyAvatar.clearJointData as clearJointData
- * @borrows MyAvatar.isJointDataValid as isJointDataValid
- * @borrows MyAvatar.getJointRotation as getJointRotation
- * @borrows MyAvatar.getJointTranslation as getJointTranslation
- * @borrows MyAvatar.getJointRotations as getJointRotations
- * @borrows MyAvatar.getJointTranslations as getJointTranslations
- * @borrows MyAvatar.setJointRotations as setJointRotations
- * @borrows MyAvatar.setJointTranslations as setJointTranslations
- * @borrows MyAvatar.clearJointsData as clearJointsData
- * @borrows MyAvatar.getJointIndex as getJointIndex
- * @borrows MyAvatar.getJointNames as getJointNames
- * @borrows MyAvatar.setBlendshape as setBlendshape
- * @borrows MyAvatar.getAttachmentsVariant as getAttachmentsVariant
- * @borrows MyAvatar.setAttachmentsVariant as setAttachmentsVariant
- * @borrows MyAvatar.updateAvatarEntity as updateAvatarEntity
- * @borrows MyAvatar.clearAvatarEntity as clearAvatarEntity
- * @borrows MyAvatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected
- * @borrows MyAvatar.getAttachmentData as getAttachmentData
- * @borrows MyAvatar.setAttachmentData as setAttachmentData
- * @borrows MyAvatar.attach as attach
- * @borrows MyAvatar.detachOne as detachOne
- * @borrows MyAvatar.detachAll as detachAll
- * @borrows MyAvatar.getAvatarEntityData as getAvatarEntityData
- * @borrows MyAvatar.setAvatarEntityData as setAvatarEntityData
- * @borrows MyAvatar.getSensorToWorldMatrix as getSensorToWorldMatrix
- * @borrows MyAvatar.getSensorToWorldScale as getSensorToWorldScale
- * @borrows MyAvatar.getControllerLeftHandMatrix as getControllerLeftHandMatrix
- * @borrows MyAvatar.getControllerRightHandMatrix as getControllerRightHandMatrix
- * @borrows MyAvatar.getDataRate as getDataRate
- * @borrows MyAvatar.getUpdateRate as getUpdateRate
- * @borrows MyAvatar.displayNameChanged as displayNameChanged
- * @borrows MyAvatar.sessionDisplayNameChanged as sessionDisplayNameChanged
- * @borrows MyAvatar.skeletonModelURLChanged as skeletonModelURLChanged
- * @borrows MyAvatar.lookAtSnappingChanged as lookAtSnappingChanged
- * @borrows MyAvatar.sessionUUIDChanged as sessionUUIDChanged
- * @borrows MyAvatar.sendAvatarDataPacket as sendAvatarDataPacket
- * @borrows MyAvatar.sendIdentityPacket as sendIdentityPacket
- * @borrows MyAvatar.setJointMappingsFromNetworkReply as setJointMappingsFromNetworkReply
- * @borrows MyAvatar.setSessionUUID as setSessionUUID
- * @borrows MyAvatar.getAbsoluteJointRotationInObjectFrame as getAbsoluteJointRotationInObjectFrame
- * @borrows MyAvatar.getAbsoluteJointTranslationInObjectFrame as getAbsoluteJointTranslationInObjectFrame
- * @borrows MyAvatar.setAbsoluteJointRotationInObjectFrame as setAbsoluteJointRotationInObjectFrame
- * @borrows MyAvatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame
- * @borrows MyAvatar.getTargetScale as getTargetScale
- * @borrows MyAvatar.resetLastSent as resetLastSent
+ * @example true
if the animation should loop, false
if it shouldn't.
+ * @param {boolean} [hold=false] - Not used.
+ * @param {number} [firstFrame=0] - The frame at which the animation starts.
+ * @param {number} [lastFrame=3.403e+38] - The frame at which the animation stops.
+ * @param {string[]} [maskedJoints=[]] - The names of joints that should not be animated.
*/
/// Allows scripts to run animations.
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
@@ -148,39 +112,37 @@ public:
const QStringList& maskedJoints = QStringList());
/**jsdoc
+ * Stops playing the current animation.
* @function Avatar.stopAnimation
*/
Q_INVOKABLE void stopAnimation();
/**jsdoc
+ * Gets the details of the current avatar animation that is being or was recently played.
* @function Avatar.getAnimationDetails
- * @returns {Avatar.AnimationDetails}
+ * @returns {Avatar.AnimationDetails} The current or recent avatar animation.
+ * @example
The avatar animation system includes a set of default animations along with rules for how those animations are blended
+ * together with procedural data (such as look at vectors, hand sensors etc.). Playing your own custom animations will
+ * override the default animations. restoreAnimation()
is used to restore all motion from the default
+ * animation system including inverse kinematics for hand and head controllers. If you aren't currently playing an override
+ * animation, this function has no effect.
Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together
+ * with procedural data (such as look at vectors, hand sensors etc.). Each animation specified in the avatar-animation.json
+ * file is known as an animation role. Animation roles map to easily understandable actions that the avatar can perform,
+ * such as "idleStand"
, "idleTalk"
, or "walkFwd"
. getAnimationRoles()
+ * is used get the list of animation roles defined in the avatar-animation.json.
Each avatar has an avatar-animation.json file that defines a set of animation roles. Animation roles map to easily
+ * understandable actions that the avatar can perform, such as "idleStand"
, "idleTalk"
, or
+ * "walkFwd"
. To get the full list of roles, use {@ link MyAvatar.getAnimationRoles}.
+ * For each role, the avatar-animation.json defines when the animation is used, the animation clip (FBX) used, and how
+ * animations are blended together with procedural data (such as look at vectors, hand sensors etc.).
+ * overrideRoleAnimation()
is used to change the animation clip (FBX) associated with a specified animation
+ * role. To end the role animation and restore the default, use {@link MyAvatar.restoreRoleAnimation}.
Note: Hand roles only affect the hand. Other 'main' roles, like 'idleStand', 'idleTalk', 'takeoffStand' are full body.
*Note: When using pre-built animation data, it's critical that the joint orientation of the source animation and target * rig are equivalent, since the animation data applies absolute values onto the joints. If the orientations are different, * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see - * Avatar Standards. + * Avatar Standards. * @function MyAvatar.overrideRoleAnimation * @param role {string} The animation role to override - * @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the avatar skeleton and animation data. + * @param url {string} The URL to the animation file. Animation files need to be in FBX format, but only need to contain the avatar skeleton and animation data. * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. * @param loop {boolean} Set to true if the animation should loop * @param firstFrame {number} The frame the animation should start at @@ -470,13 +651,15 @@ public: Q_INVOKABLE void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); /**jsdoc - * Each avatar has an avatar-animation.json file that defines a set of animation roles. Animation roles map to easily understandable actions that - * the avatar can perform, such as "idleStand", "idleTalk", or "walkFwd". To get the full list of roles, use getAnimationRoles(). For each role, - * the avatar-animation.json defines when the animation is used, the animation clip (.FBX) used, and how animations are blended together with - * procedural data (such as look at vectors, hand sensors etc.). You can change the animation clip (.FBX) associated with a specified animation - * role using overrideRoleAnimation(). - * restoreRoleAnimation() is used to restore a specified animation role's default animation clip. If you have not specified an override animation - * for the specified role, this function will have no effect. + * Restores a default role animation. + *
Each avatar has an avatar-animation.json file that defines a set of animation roles. Animation roles map to easily
+ * understandable actions that the avatar can perform, such as "idleStand"
, "idleTalk"
, or
+ * "walkFwd"
. To get the full list of roles, use {@link MyAvatar.getAnimationRoles}. For each role,
+ * the avatar-animation.json defines when the animation is used, the animation clip (FBX) used, and how animations are
+ * blended together with procedural data (such as look-at vectors, hand sensors etc.). You can change the animation clip
+ * (FBX) associated with a specified animation role using {@link MyAvatar.overrideRoleAnimation}.
+ * restoreRoleAnimation()
is used to restore a specified animation role's default animation clip. If you have
+ * not specified an override animation for the specified role, this function has no effect.
* @function MyAvatar.restoreRoleAnimation
* @param role {string} The animation role clip to restore.
*/
@@ -491,90 +674,160 @@ public:
// adding one of the other handlers. While any handler may change a value in animStateDictionaryIn (or supply different values in animStateDictionaryOut)
// a handler must not remove properties from animStateDictionaryIn, nor change property values that it does not intend to change.
// It is not specified in what order multiple handlers are called.
+ /**jsdoc
+ * Adds an animation state handler function that is invoked just before each animation graph update. More than one
+ * animation state handler function may be added by calling addAnimationStateHandler
multiple times. It is not
+ * specified in what order multiple handlers are called.
+ *
The animation state handler function is called with an {@link MyAvatar.AnimStateDictionary|AnimStateDictionary}
+ * "animStateDictionaryIn
" parameter and is expected to return an
+ * {@link MyAvatar.AnimStateDictionary|AnimStateDictionary} "animStateDictionaryOut
" object. The
+ * animStateDictionaryOut
object can be the same object as animStateDictionaryIn
, or it can be a
+ * different object. The animStateDictionaryIn
may be shared among multiple handlers and thus may contain
+ * additional properties specified when adding the different handlers.
A handler may change a value from animStateDictionaryIn
or add different values in the
+ * animStateDictionaryOut
returned. Any property values set in animStateDictionaryOut
will
+ * override those of the internal animation machinery.|null} propertiesList - The list of {@link MyAvatar.AnimStateDictionary|AnimStateDictionary}
+ * properties that should be included in the parameter that the handler function is called with. If null
+ * then all properties are included in the call parameter.
+ * @returns {number} The ID of the animation state handler function if successfully added, undefined
if not.
+ * @example
true
if you do snap turns in HMD mode; false
if you do smooth turns in HMD
+ * mode.
*/
Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; }
/**jsdoc
+ * Sets whether your should do snap turns or smooth turns in HMD mode.
* @function MyAvatar.setSnapTurn
- * @param {boolean} on
+ * @param {boolean} on - true
to do snap turns in HMD mode; false
to do smooth turns in HMD mode.
*/
Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
/**jsdoc
+ * Sets the avatar's dominant hand.
* @function MyAvatar.setDominantHand
- * @param {string} hand
+ * @param {string} hand - The dominant hand: "left"
for the left hand or "right"
for the right
+ * hand. Any other value has no effect.
*/
Q_INVOKABLE void setDominantHand(const QString& hand);
+
/**jsdoc
+ * Gets the avatar's dominant hand.
* @function MyAvatar.getDominantHand
- * @returns {string}
+ * @returns {string} "left"
for the left hand, "right"
for the right hand.
*/
Q_INVOKABLE QString getDominantHand() const;
/**jsdoc
* @function MyAvatar.setHmdAvatarAlignmentType
- * @param {string} hand
+ * @param {string} type - "head"
to align your head and your avatar's head, "eyes"
to align your
+ * eyes and your avatar's eyes.
+ *
*/
- Q_INVOKABLE void setHmdAvatarAlignmentType(const QString& hand);
+ Q_INVOKABLE void setHmdAvatarAlignmentType(const QString& type);
+
/**jsdoc
- * @function MyAvatar.setHmdAvatarAlignmentType
- * @returns {string}
+ * Gets the HMD alignment for your avatar.
+ * @function MyAvatar.getHmdAvatarAlignmentType
+ * @returns {string} "head"
if aligning your head and your avatar's head, "eyes"
if aligning your
+ * eyes and your avatar's eyes.
*/
Q_INVOKABLE QString getHmdAvatarAlignmentType() const;
/**jsdoc
- * @function MyAvatar.setCenterOfGravityModelEnabled
- * @param {boolean} enabled
- */
+ * Sets whether the avatar's hips are balanced over the feet or positioned under the head.
+ * @function MyAvatar.setCenterOfGravityModelEnabled
+ * @param {boolean} enabled - true
to balance the hips over the feet, false
to position the hips
+ * under the head.
+ */
Q_INVOKABLE void setCenterOfGravityModelEnabled(bool value) { _centerOfGravityModelEnabled = value; }
+
/**jsdoc
- * @function MyAvatar.getCenterOfGravityModelEnabled
- * @returns {boolean}
- */
+ * Gets whether the avatar hips are being balanced over the feet or placed under the head.
+ * @function MyAvatar.getCenterOfGravityModelEnabled
+ * @returns {boolean} true
if the hips are being balanced over the feet, false
if the hips are
+ * being positioned under the head.
+ */
Q_INVOKABLE bool getCenterOfGravityModelEnabled() const { return _centerOfGravityModelEnabled; }
+
/**jsdoc
+ * Sets whether the avatar's position updates to recenter the avatar under the head. In room-scale VR, recentering
+ * causes your avatar to follow your HMD as you walk around the room. Disabling recentering is useful if you want to pin
+ * the avatar to a fixed position.
* @function MyAvatar.setHMDLeanRecenterEnabled
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to recenter the avatar under the head as it moves, false
to
+ * disable recentering.
*/
Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; }
+
/**jsdoc
+ * Gets whether the avatar's position updates to recenter the avatar under the head. In room-scale VR, recentering
+ * causes your avatar to follow your HMD as you walk around the room.
* @function MyAvatar.getHMDLeanRecenterEnabled
- * @returns {boolean}
+ * @returns {boolean} true
if recentering is enabled, false
if not.
*/
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
+
/**jsdoc
- * Request to enable hand touch effect globally
+ * Requests that the hand touch effect is disabled for your avatar. Any resulting change in the status of the hand touch
+ * effect will be signaled by {@link MyAvatar.shouldDisableHandTouchChanged}.
+ * The hand touch effect makes the avatar's fingers adapt to the shape of any object grabbed, creating the effect that + * it is really touching that object.
* @function MyAvatar.requestEnableHandTouch */ Q_INVOKABLE void requestEnableHandTouch(); + /**jsdoc - * Request to disable hand touch effect globally + * Requests that the hand touch effect is enabled for your avatar. Any resulting change in the status of the hand touch + * effect will be signaled by {@link MyAvatar.shouldDisableHandTouchChanged}. + *The hand touch effect makes the avatar's fingers adapt to the shape of any object grabbed, creating the effect that + * it is really touching that object.
* @function MyAvatar.requestDisableHandTouch */ Q_INVOKABLE void requestDisableHandTouch(); + /**jsdoc - * Disables hand touch effect on a specific entity + * Disables the hand touch effect on a specific entity. + *The hand touch effect makes the avatar's fingers adapt to the shape of any object grabbed, creating the effect that + * it is really touching that object.
* @function MyAvatar.disableHandTouchForID - * @param {Uuid} entityID - ID of the entity that will disable hand touch effect + * @param {Uuid} entityID - The entity that the hand touch effect will be disabled for. */ Q_INVOKABLE void disableHandTouchForID(const QUuid& entityID); + /**jsdoc - * Enables hand touch effect on a specific entity + * Enables the hand touch effect on a specific entity. + *The hand touch effect makes the avatar's fingers adapt to the shape of any object grabbed, creating the effect that + * it is really touching that object.
* @function MyAvatar.enableHandTouchForID - * @param {Uuid} entityID - ID of the entity that will enable hand touch effect + * @param {Uuid} entityID - The entity that the hand touch effect will be enabled for. */ Q_INVOKABLE void enableHandTouchForID(const QUuid& entityID); @@ -612,29 +865,43 @@ public: float getDriveKey(DriveKeys key) const; /**jsdoc + * Gets the value of a drive key, regardless of whether it is disabled. * @function MyAvatar.getRawDriveKey - * @param {DriveKeys} key - * @returns {number} + * @param {MyAvatar.DriveKeys} key - The drive key. + * @returns {number} The value of the drive key. */ Q_INVOKABLE float getRawDriveKey(DriveKeys key) const; void relayDriveKeysToCharacterController(); /**jsdoc + * Disables the action associated with a drive key. * @function MyAvatar.disableDriveKey - * @param {DriveKeys} key + * @param {MyAvatar.DriveKeys} key - The drive key to disable. + * @exampletrue
if the drive key is disabled, false
if it isn't.
*/
Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const;
@@ -649,7 +916,7 @@ public:
/**jsdoc
* Recenter the avatar in the horizontal direction, if {@link MyAvatar|MyAvatar.hmdLeanRecenterEnabled}
is
* false
.
- * @ function MyAvatar.triggerHorizontalRecenter
+ * @function MyAvatar.triggerHorizontalRecenter
*/
Q_INVOKABLE void triggerHorizontalRecenter();
@@ -660,10 +927,10 @@ public:
Q_INVOKABLE void triggerRotationRecenter();
/**jsdoc
- *The isRecenteringHorizontally function returns true if MyAvatar
- *is translating the root of the Avatar to keep the center of gravity under the head.
- *isActive(Horizontal) is returned.
- *@function MyAvatar.isRecenteringHorizontally
+ * Gets whether the avatar is configured to keep its center of gravity under its head.
+ * @function MyAvatar.isRecenteringHorizontally
+ * @returns {boolean} true
if the avatar is keeping its center of gravity under its head position,
+ * false
if not.
*/
Q_INVOKABLE bool isRecenteringHorizontally() const;
@@ -672,7 +939,7 @@ public:
const MyHead* getMyHead() const;
/**jsdoc
- * Get the current position of the avatar's "Head" joint.
+ * Gets the current position of the avatar's "Head" joint.
* @function MyAvatar.getHeadPosition
* @returns {Vec3} The current position of the avatar's "Head" joint.
* @example 0
, in degrees.
*/
Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); }
/**jsdoc
- * Get the current position of the point directly between the avatar's eyes.
+ * Gets the current position of the point directly between the avatar's eyes.
* @function MyAvatar.getEyePosition
* @returns {Vec3} The current position of the point directly between the avatar's eyes.
* @example Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints * for hand animation.)
* @function MyAvatar.getLeftHandPosition @@ -742,7 +1022,8 @@ public: Q_INVOKABLE glm::vec3 getLeftHandPosition() const; /**jsdoc - * Get the position of the avatar's right hand as positioned by a hand controller (e.g., Oculus Touch or Vive).Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints * for hand animation.)
* @function MyAvatar.getRightHandPosition @@ -754,53 +1035,71 @@ public: Q_INVOKABLE glm::vec3 getRightHandPosition() const; /**jsdoc + * Gets the position 0.3m in front of the left hand's position in the direction along the palm, in avatar coordinates, as + * positioned by a hand controller. * @function MyAvatar.getLeftHandTipPosition - * @returns {Vec3} + * @returns {Vec3} The position 0.3m in front of the left hand's position in the direction along the palm, in avatar + * coordinates. If the hand isn't being positioned by a controller,{@link Vec3(0)|Vec3.ZERO}
is returned.
*/
Q_INVOKABLE glm::vec3 getLeftHandTipPosition() const;
/**jsdoc
+ * Gets the position 0.3m in front of the right hand's position in the direction along the palm, in avatar coordinates, as
+ * positioned by a hand controller.
* @function MyAvatar.getRightHandTipPosition
- * @returns {Vec3}
+ * @returns {Vec3} The position 0.3m in front of the right hand's position in the direction along the palm, in avatar
+ * coordinates. If the hand isn't being positioned by a controller, {@link Vec3(0)|Vec3.ZERO}
is returned.
*/
Q_INVOKABLE glm::vec3 getRightHandTipPosition() const;
/**jsdoc
- * Get the pose (position, rotation, velocity, and angular velocity) of the avatar's left hand as positioned by a
- * hand controller (e.g., Oculus Touch or Vive).Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints
* for hand animation.) If you are using the Leap Motion, the return value's valid
property will be
* false
and any pose values returned will not be meaningful.
Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints
* for hand animation.) If you are using the Leap Motion, the return value's valid
property will be
* false
and any pose values returned will not be meaningful.
Note: Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints
+ * for hand animation.) If you are using Leap Motion, the return value's valid
property will be
+ * false
and any pose values returned will not be meaningful.
Note: Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints
+ * for hand animation.) If you are using Leap Motion, the return value's valid
property will be
+ * false
and any pose values returned will not be meaningful.
Note: Only works on the hips joint.
* @function MyAvatar.pinJoint - * @param {number} index - * @param {Vec3} position - * @param {Quat} orientation - * @returns {boolean} + * @param {number} index - The index of the joint. + * @param {Vec3} position - The position of the joint in world coordinates. + * @param {Quat} orientation - The orientation of the joint in world coordinates. + * @returns {boolean}true
if the joint was pinned, false
if it wasn't.
*/
Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation);
bool isJointPinned(int index);
/**jsdoc
+ * Clears a lock on a joint's position and orientation, as set by {@link MyAvatar.pinJoint|pinJoint}.
+ * Note: Only works on the hips joint.
* @function MyAvatar.clearPinOnJoint - * @param {number} index - * @returns {boolean} + * @param {number} index - The index of the joint. + * @returns {boolean}true
if the joint was unpinned, false
if it wasn't.
*/
Q_INVOKABLE bool clearPinOnJoint(int index);
/**jsdoc
+ * Gets the maximum error distance from the most recent inverse kinematics (IK) solution.
* @function MyAvatar.getIKErrorOnLastSolve
- * @returns {number}
+ * @returns {number} The maximum IK error distance.
*/
Q_INVOKABLE float getIKErrorOnLastSolve() const;
/**jsdoc
+ * Changes the user's avatar and associated descriptive name.
* @function MyAvatar.useFullAvatarURL
- * @param {string} fullAvatarURL
- * @param {string} [modelName=""]
+ * @param {string} fullAvatarURL - The URL of the avatar's .fst
file.
+ * @param {string} [modelName=""] - Descriptive name of the avatar.
*/
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
/**jsdoc
- * Get the complete URL for the current avatar.
+ * Gets the complete URL for the current avatar.
* @function MyAvatar.getFullAvatarURLFromPreferences
* @returns {string} The full avatar model name.
* @example true
if your avatar is flying and not taking off or falling, otherwise
- * false
.
+ * @returns {boolean} true
if your avatar is flying and not taking off or falling, false
if not.
*/
Q_INVOKABLE bool isFlying();
/**jsdoc
- * Check whether your avatar is in the air or not.
+ * Checks whether your avatar is in the air.
* @function MyAvatar.isInAir
* @returns {boolean} true
if your avatar is taking off, flying, or falling, otherwise false
* because your avatar is on the ground.
@@ -959,7 +1264,7 @@ public:
Q_INVOKABLE bool isInAir();
/**jsdoc
- * Set your preference for flying in your current desktop or HMD display mode. Note that your ability to fly also depends
+ * Sets your preference for flying in your current desktop or HMD display mode. Note that your ability to fly also depends
* on whether the domain you're in allows you to fly.
* @function MyAvatar.setFlyingEnabled
* @param {boolean} enabled - Set true
if you want to enable flying in your current desktop or HMD display
@@ -968,7 +1273,7 @@ public:
Q_INVOKABLE void setFlyingEnabled(bool enabled);
/**jsdoc
- * Get your preference for flying in your current desktop or HMD display mode. Note that your ability to fly also depends
+ * Gets your preference for flying in your current desktop or HMD display mode. Note that your ability to fly also depends
* on whether the domain you're in allows you to fly.
* @function MyAvatar.getFlyingEnabled
* @returns {boolean} true
if your preference is to enable flying in your current desktop or HMD display mode,
@@ -977,7 +1282,7 @@ public:
Q_INVOKABLE bool getFlyingEnabled();
/**jsdoc
- * Set your preference for flying in desktop display mode. Note that your ability to fly also depends on whether the domain
+ * Sets your preference for flying in desktop display mode. Note that your ability to fly also depends on whether the domain
* you're in allows you to fly.
* @function MyAvatar.setFlyingDesktopPref
* @param {boolean} enabled - Set true
if you want to enable flying in desktop display mode, otherwise set
@@ -986,7 +1291,7 @@ public:
Q_INVOKABLE void setFlyingDesktopPref(bool enabled);
/**jsdoc
- * Get your preference for flying in desktop display mode. Note that your ability to fly also depends on whether the domain
+ * Gets your preference for flying in desktop display mode. Note that your ability to fly also depends on whether the domain
* you're in allows you to fly.
* @function MyAvatar.getFlyingDesktopPref
* @returns {boolean} true
if your preference is to enable flying in desktop display mode, otherwise
@@ -995,7 +1300,7 @@ public:
Q_INVOKABLE bool getFlyingDesktopPref();
/**jsdoc
- * Set your preference for flying in HMD display mode. Note that your ability to fly also depends on whether the domain
+ * Sets your preference for flying in HMD display mode. Note that your ability to fly also depends on whether the domain
* you're in allows you to fly.
* @function MyAvatar.setFlyingHMDPref
* @param {boolean} enabled - Set true
if you want to enable flying in HMD display mode, otherwise set
@@ -1004,7 +1309,7 @@ public:
Q_INVOKABLE void setFlyingHMDPref(bool enabled);
/**jsdoc
- * Get your preference for flying in HMD display mode. Note that your ability to fly also depends on whether the domain
+ * Gets your preference for flying in HMD display mode. Note that your ability to fly also depends on whether the domain
* you're in allows you to fly.
* @function MyAvatar.getFlyingHMDPref
* @returns {boolean} true
if your preference is to enable flying in HMD display mode, otherwise
@@ -1012,65 +1317,104 @@ public:
*/
Q_INVOKABLE bool getFlyingHMDPref();
-
/**jsdoc
+ * Gets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on
+ * permissible scale values imposed by the domain.
* @function MyAvatar.getAvatarScale
- * @returns {number}
+ * @returns {number} The target scale for the avatar, range 0.005
– 1000.0
.
*/
Q_INVOKABLE float getAvatarScale();
/**jsdoc
+ * Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on
+ * permissible scale values imposed by the domain.
* @function MyAvatar.setAvatarScale
- * @param {number} scale
+ * @param {number} scale - The target scale for the avatar, range 0.005
– 1000.0
.
*/
Q_INVOKABLE void setAvatarScale(float scale);
-
/**jsdoc
+ * Sets whether the avatar should collide with entities.
+ * Note: A false
value won't disable collisions if the avatar is in a zone that disallows
+ * collisionless avatars. However, the false
value will be set so that collisions are disabled as soon as the
+ * avatar moves to a position where collisionless avatars are allowed.
* @function MyAvatar.setCollisionsEnabled
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to enable the avatar to collide with entities, false
to
+ * disable.
*/
Q_INVOKABLE void setCollisionsEnabled(bool enabled);
/**jsdoc
+ * Gets whether the avatar will currently collide with entities.
+ *
Note: The avatar will always collide with entities if in a zone that disallows collisionless avatars.
* @function MyAvatar.getCollisionsEnabled
- * @returns {boolean}
+ * @returns {boolean} true
if the avatar will currently collide with entities, false
if it won't.
*/
Q_INVOKABLE bool getCollisionsEnabled();
/**jsdoc
- * @function MyAvatar.setOtherAvatarsCollisionsEnabled
- * @param {boolean} enabled
- */
+ * Sets whether the avatar should collide with other avatars.
+ * @function MyAvatar.setOtherAvatarsCollisionsEnabled
+ * @param {boolean} enabled - true
to enable the avatar to collide with other avatars, false
+ * to disable.
+ */
Q_INVOKABLE void setOtherAvatarsCollisionsEnabled(bool enabled);
/**jsdoc
- * @function MyAvatar.getOtherAvatarsCollisionsEnabled
- * @returns {boolean}
- */
+ * Gets whether the avatar will collide with other avatars.
+ * @function MyAvatar.getOtherAvatarsCollisionsEnabled
+ * @returns {boolean} true
if the avatar will collide with other avatars, false
if it won't.
+ */
Q_INVOKABLE bool getOtherAvatarsCollisionsEnabled();
/**jsdoc
- * @function MyAvatar.getCollisionCapsule
- * @returns {object}
- */
+ * Gets the avatar's collision capsule: a cylinder with hemispherical ends that approximates the extents or the avatar.
+ *
Warning: The values returned are in world coordinates but aren't necessarily up to date with the + * avatar's current position.
+ * @function MyAvatar.getCollisionCapsule + * @returns {MyAvatar.CollisionCapsule} The avatar's collision capsule. + */ Q_INVOKABLE QVariantMap getCollisionCapsule() const; /**jsdoc * @function MyAvatar.setCharacterControllerEnabled - * @param {boolean} enabled - * @deprecated + * @param {boolean} enabled -true
to enable the avatar to collide with entities, false
to
+ * disable.
+ * @deprecated Use {@link MyAvatar.setCollisionsEnabled} instead.
*/
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated
/**jsdoc
* @function MyAvatar.getCharacterControllerEnabled
- * @returns {boolean}
- * @deprecated
+ * @returns {boolean} true
if the avatar will currently collide with entities, false
if it won't.
+ * @deprecated Use {@link MyAvatar.getCollisionsEnabled} instead.
*/
Q_INVOKABLE bool getCharacterControllerEnabled(); // deprecated
+ /**jsdoc
+ * @comment Different behavior to the Avatar version of this method.
+ * Gets the rotation of a joint relative to the avatar.
+ * @function MyAvatar.getAbsoluteJointRotationInObjectFrame
+ * @param {number} index - The index of the joint.
+ * @returns {Quat} The rotation of the joint relative to the avatar.
+ * @example > 0
).
* @function MyAvatar.isUp
- * @param {Vec3} direction
- * @returns {boolean}
+ * @param {Vec3} direction - The vector to test.
+ * @returns {boolean} true
if the direction vector is pointing generally in the direction of the avatar's "up"
+ * direction.
*/
Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up.
/**jsdoc
+ * Tests whether a vector is pointing in the general direction of the avatar's "down" direction (i.e., dot product of
+ * vectors is < 0
).
* @function MyAvatar.isDown
- * @param {Vec3} direction
- * @returns {boolean}
+ * @param {Vec3} direction - The vector to test.
+ * @returns {boolean} true
if the direction vector is pointing generally in the direction of the avatar's
+ * "down" direction.
*/
Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; };
@@ -1122,6 +1472,7 @@ public:
float getUserEyeHeight() const;
virtual SpatialParentTree* getParentTree() const override;
+ virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getSensorToWorldScale()); }
const QUuid& getSelfID() const { return AVATAR_SELF_ID; }
@@ -1160,110 +1511,164 @@ public:
void prepareAvatarEntityDataForReload();
/**jsdoc
- * Create a new grab.
+ * Creates a new grab that grabs an entity.
* @function MyAvatar.grab
- * @param {Uuid} targetID - id of grabbed thing
- * @param {number} parentJointIndex - avatar joint being used to grab
- * @param {Vec3} offset - target's positional offset from joint
- * @param {Quat} rotationalOffset - target's rotational offset from joint
- * @returns {Uuid} id of the new grab
+ * @param {Uuid} targetID - The ID of the entity to grab.
+ * @param {number} parentJointIndex - The avatar joint to use to grab the entity.
+ * @param {Vec3} offset - The target's local position relative to the joint.
+ * @param {Quat} rotationalOffset - The target's local rotation relative to the joint.
+ * @returns {Uuid} The ID of the new grab.
+ * @example true
to activate flow simulation.
- * @param {boolean} - Set to true
to activate collisions.
- * @param {Object} physicsConfig - object with the customized physic parameters
- * i.e. {"hair": {"active": true, "stiffness": 0.0, "radius": 0.04, "gravity": -0.035, "damping": 0.8, "inertia": 0.8, "delta": 0.35}}
- * @param {Object} collisionsConfig - object with the customized collision parameters
- * i.e. {"Spine2": {"type": "sphere", "radius": 0.14, "offset": {"x": 0.0, "y": 0.2, "z": 0.0}}}
- */
+ * Enables and disables flow simulation of physics on the avatar's hair, clothes, and body parts. See
+ * {@link https://docs.highfidelity.com/create/avatars/add-flow.html|Add Flow to Your Avatar} for more
+ * information.
+ * @function MyAvatar.useFlow
+ * @param {boolean} isActive - true
if flow simulation is enabled on the joint, false
if it isn't.
+ * @param {boolean} isCollidable - true
to enable collisions in the flow simulation, false
to
+ * disable.
+ * @param {Object1000
.
+ * Increases the avatar's scale by five percent, up to a minimum scale of 1000
.
* @function MyAvatar.increaseSize
* @example 0.25
.
+ * Decreases the avatar's scale by five percent, down to a minimum scale of 0.25
.
* @function MyAvatar.decreaseSize
* @example 1.0
.
+ * Resets the avatar's scale back to the default scale of 1.0
.
* @function MyAvatar.resetSize
*/
void resetSize();
/**jsdoc
* @function MyAvatar.animGraphLoaded
+ * @deprecated This function is deprecated and will be removed.
*/
void animGraphLoaded();
/**jsdoc
+ * Sets the amount of gravity applied to the avatar in the y-axis direction. (Negative values are downward.)
* @function MyAvatar.setGravity
- * @param {number} gravity
+ * @param {number} gravity - The amount of gravity to be applied to the avatar, in m/s2.
*/
void setGravity(float gravity);
/**jsdoc
+ * Sets the amount of gravity applied to the avatar in the y-axis direction. (Negative values are downward.) The default
+ * value is -5
m/s2.
* @function MyAvatar.getGravity
- * @returns {number}
+ * @returns {number} The amount of gravity currently applied to the avatar, in m/s2.
*/
float getGravity();
/**jsdoc
- * Move the avatar to a new position and/or orientation in the domain, while taking into account Avatar leg-length.
+ * Moves the avatar to a new position and/or orientation in the domain, while taking into account Avatar leg-length.
* @function MyAvatar.goToFeetLocation
* @param {Vec3} position - The new position for the avatar, in world coordinates.
* @param {boolean} [hasOrientation=false] - Set to true
to set the orientation of the avatar.
@@ -1271,13 +1676,12 @@ public slots:
* @param {boolean} [shouldFaceLocation=false] - Set to true
to position the avatar a short distance away from
* the new position and orientate the avatar to face the position.
*/
-
void goToFeetLocation(const glm::vec3& newPosition,
bool hasOrientation, const glm::quat& newOrientation,
bool shouldFaceLocation);
/**jsdoc
- * Move the avatar to a new position and/or orientation in the domain.
+ * Moves the avatar to a new position and/or orientation in the domain.
* @function MyAvatar.goToLocation
* @param {Vec3} position - The new position for the avatar, in world coordinates.
* @param {boolean} [hasOrientation=false] - Set to true
to set the orientation of the avatar.
@@ -1290,133 +1694,183 @@ public slots:
bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
bool shouldFaceLocation = false, bool withSafeLanding = true);
/**jsdoc
+ * Moves the avatar to a new position and (optional) orientation in the domain.
* @function MyAvatar.goToLocation
- * @param {object} properties
+ * @param {MyAvatar.GoToProperties} target - The goto target.
*/
void goToLocation(const QVariant& properties);
/**jsdoc
+ * Moves the avatar to a new position and then enables collisions.
* @function MyAvatar.goToLocationAndEnableCollisions
- * @param {Vec3} position
+ * @param {Vec3} position - The new position for the avatar, in world coordinates.
*/
void goToLocationAndEnableCollisions(const glm::vec3& newPosition);
/**jsdoc
* @function MyAvatar.safeLanding
- * @param {Vec3} position
- * @returns {boolean}
+ * @param {Vec3} position -The new position for the avatar, in world coordinates.
+ * @returns {boolean} true
if the avatar was moved, false
if it wasn't.
+ * @deprecated This function is deprecated and will be removed.
*/
bool safeLanding(const glm::vec3& position);
/**jsdoc
* @function MyAvatar.restrictScaleFromDomainSettings
- * @param {objecct} domainSettingsObject
+ * @param {object} domainSettings - Domain settings.
+ * @deprecated This function is deprecated and will be removed.
*/
void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject);
/**jsdoc
* @function MyAvatar.clearScaleRestriction
+ * @deprecated This function is deprecated and will be removed from the API.
*/
void clearScaleRestriction();
/**jsdoc
+ * Adds a thrust to your avatar's current thrust to be applied for a short while.
* @function MyAvatar.addThrust
- * @param {Vec3} thrust
+ * @param {Vec3} thrust - The thrust direction and magnitude.
+ * @deprecated Use {@link MyAvatar|MyAvatar.motorVelocity} and related properties instead.
*/
// Set/Get update the thrust that will move the avatar around
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
/**jsdoc
+ * Gets the thrust currently being applied to your avatar.
* @function MyAvatar.getThrust
- * @returns {vec3}
+ * @returns {Vec3} The thrust currently being applied to your avatar.
+ * @deprecated Use {@link MyAvatar|MyAvatar.motorVelocity} and related properties instead.
*/
glm::vec3 getThrust() { return _thrust; };
/**jsdoc
+ * Sets the thrust to be applied to your avatar for a short while.
* @function MyAvatar.setThrust
- * @param {Vec3} thrust
+ * @param {Vec3} thrust - The thrust direction and magnitude.
+ * @deprecated Use {@link MyAvatar|MyAvatar.motorVelocity} and related properties instead.
*/
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
/**jsdoc
+ * Updates avatar motion behavior from the Developer > Avatar > Enable Default Motor Control and Enable Scripted
+ * Motor Control menu items.
* @function MyAvatar.updateMotionBehaviorFromMenu
*/
Q_INVOKABLE void updateMotionBehaviorFromMenu();
/**jsdoc
- * @function MyAvatar.setToggleHips
- * @param {boolean} enabled
- */
+ * @function MyAvatar.setToggleHips
+ * @param {boolean} enabled - Enabled.
+ * @deprecated This function is deprecated and will be removed.
+ */
void setToggleHips(bool followHead);
+
/**jsdoc
- * @function MyAvatar.setEnableDebugDrawBaseOfSupport
- * @param {boolean} enabled
- */
+ * Displays the base of support area debug graphics if in HMD mode. If your head goes outside this area your avatar's hips
+ * are moved to counterbalance your avatar, and if your head moves too far then your avatar's position is moved (i.e., a
+ * step happens).
+ * @function MyAvatar.setEnableDebugDrawBaseOfSupport
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
+ */
void setEnableDebugDrawBaseOfSupport(bool isEnabled);
+
/**jsdoc
+ * Displays default pose debug graphics.
* @function MyAvatar.setEnableDebugDrawDefaultPose
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawDefaultPose(bool isEnabled);
+
/**jsdoc
+ * Displays animation debug graphics.
* @function MyAvatar.setEnableDebugDrawAnimPose
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawAnimPose(bool isEnabled);
+
/**jsdoc
+ * Displays position debug graphics.
* @function MyAvatar.setEnableDebugDrawPosition
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawPosition(bool isEnabled);
+
/**jsdoc
+ * Displays controller hand target debug graphics.
* @function MyAvatar.setEnableDebugDrawHandControllers
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawHandControllers(bool isEnabled);
+
/**jsdoc
+ * Displays sensor-to-world matrix debug graphics.
* @function MyAvatar.setEnableDebugDrawSensorToWorldMatrix
- * @param {boolean} enabled
+ * @param {boolean} enable - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled);
+
/**jsdoc
+ * Displays inverse kinematics targets debug graphics.
* @function MyAvatar.setEnableDebugDrawIKTargets
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawIKTargets(bool isEnabled);
+
/**jsdoc
+ * Displays inverse kinematics constraints debug graphics.
* @function MyAvatar.setEnableDebugDrawIKConstraints
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawIKConstraints(bool isEnabled);
+
/**jsdoc
+ * Displays inverse kinematics chains debug graphics.
* @function MyAvatar.setEnableDebugDrawIKChains
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawIKChains(bool isEnabled);
+
/**jsdoc
+ * Displays detailed collision debug graphics.
* @function MyAvatar.setEnableDebugDrawDetailedCollision
- * @param {boolean} enabled
+ * @param {boolean} enabled - true
to show the debug graphics, false
to hide.
*/
void setEnableDebugDrawDetailedCollision(bool isEnabled);
/**jsdoc
- * Get whether or not your avatar mesh is visible.
+ * Gets whether your avatar mesh is visible.
* @function MyAvatar.getEnableMeshVisible
* @returns {boolean} true
if your avatar's mesh is visible, otherwise false
.
*/
bool getEnableMeshVisible() const override;
+ /**jsdoc
+ * @function MyAvatar.storeAvatarEntityDataPayload
+ * @deprecated This function is deprecated and will be removed.
+ */
void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) override;
+
+ /**jsdoc
+ * @comment Uses the base class's JSDoc.
+ */
void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true) override;
+
+ /**jsdoc
+ * @function MyAvatar.sanitizeAvatarEntityProperties
+ * @param {EntityItemProperties} properties - Properties.
+ * @deprecated This function is deprecated and will be removed.
+ */
void sanitizeAvatarEntityProperties(EntityItemProperties& properties) const;
/**jsdoc
- * Set whether or not your avatar mesh is visible.
+ * Sets whether your avatar mesh is visible to you.
* @function MyAvatar.setEnableMeshVisible
- * @param {boolean} visible - true
to set your avatar mesh visible; false
to set it invisible.
+ * @param {boolean} enabled - true
to show your avatar mesh, false
to hide.
* @example true
to enable IK, false
to disable.
*/
void setEnableInverseKinematics(bool isEnabled);
/**jsdoc
+ * Gets the URL of the override animation graph.
+ * See {@link https://docs.highfidelity.com/create/avatars/custom-animations.html|Custom Avatar Animations} for + * information on animation graphs.
* @function MyAvatar.getAnimGraphOverrideUrl - * @returns {string} + * @returns {string} The URL of the override animation graph JSON file.""
if there is no override animation
+ * graph.
*/
QUrl getAnimGraphOverrideUrl() const; // thread-safe
/**jsdoc
+ * Sets the animation graph to use in preference to the default animation graph.
+ * See {@link https://docs.highfidelity.com/create/avatars/custom-animations.html|Custom Avatar Animations} for + * information on animation graphs.
* @function MyAvatar.setAnimGraphOverrideUrl - * @param {string} url + * @param {string} url - The URL of the animation graph JSON file to use. Set to""
to clear an override.
*/
void setAnimGraphOverrideUrl(QUrl value); // thread-safe
/**jsdoc
+ * Gets the URL of animation graph (i.e., the avatar animation JSON) that's currently being used for avatar animations.
+ * See {@link https://docs.highfidelity.com/create/avatars/custom-animations.html|Custom Avatar Animations} for + * information on animation graphs.
* @function MyAvatar.getAnimGraphUrl - * @returns {string} + * @returns {string} The URL of the current animation graph JSON file. + * @ExampleSee {@link https://docs.highfidelity.com/create/avatars/custom-animations.html|Custom Avatar Animations} for + * information on animation graphs.
* @function MyAvatar.setAnimGraphUrl - * @param {string} url + * @param {string} url - The URL of the animation graph JSON file to use. */ void setAnimGraphUrl(const QUrl& url); // thread-safe /**jsdoc + * Gets your listening position for spatialized audio. The position depends on the value of the + * {@link Myavatar|audioListenerMode} property. * @function MyAvatar.getPositionForAudio - * @returns {Vec3} + * @returns {Vec3} Your listening position. */ glm::vec3 getPositionForAudio(); /**jsdoc + * Gets the orientation of your listening position for spatialized audio. The orientation depends on the value of the + * {@link Myavatar|audioListenerMode} property. * @function MyAvatar.getOrientationForAudio - * @returns {Quat} + * @returns {Quat} The orientation of your listening position. */ glm::quat getOrientationForAudio(); /**jsdoc * @function MyAvatar.setModelScale - * @param {number} scale + * @param {number} scale - The scale. + * @deprecated This function is deprecated and will be removed. */ virtual void setModelScale(float scale) override; signals: /**jsdoc + * Triggered when the {@link MyAvatar|audioListenerMode} property value changes. * @function MyAvatar.audioListenerModeChanged * @returns {Signal} */ @@ -1485,12 +1961,14 @@ signals: /**jsdoc * @function MyAvatar.transformChanged * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void transformChanged(); /**jsdoc + * Triggered when the {@link MyAvatar|collisionSoundURL} property value changes. * @function MyAvatar.newCollisionSoundURL - * @param {string} url + * @param {string} url - The URL of the new collision sound. * @returns {Signal} */ void newCollisionSoundURL(const QUrl& url); @@ -1498,7 +1976,7 @@ signals: /**jsdoc * Triggered when the avatar collides with an entity. * @function MyAvatar.collisionWithEntity - * @param {Collision} collision + * @param {Collision} collision - Details of the collision. * @returns {Signal} * @exampletrue
if collisions with the environment are enabled, false
if
+ * they're not.
* @returns {Signal}
*/
void collisionsEnabledChanged(bool enabled);
/**jsdoc
- * Triggered when collisions with other avatars enabled or disabled
- * @function MyAvatar.otherAvatarsCollisionsEnabledChanged
- * @param {boolean} enabled
- * @returns {Signal}
- */
+ * Triggered when collisions with other avatars are enabled or disabled.
+ * @function MyAvatar.otherAvatarsCollisionsEnabledChanged
+ * @param {boolean} enabled - true
if collisions with other avatars are enabled, false
if they're
+ * not.
+ * @returns {Signal}
+ */
void otherAvatarsCollisionsEnabledChanged(bool enabled);
/**jsdoc
- * Triggered when avatar's animation url changes
+ * Triggered when the avatar's animation graph being used changes.
* @function MyAvatar.animGraphUrlChanged
- * @param {url} url
+ * @param {string} url - The URL of the new animation graph JSON file.
* @returns {Signal}
- */
+ * @example Synonym of {@link MyAvatar.skeletonModelURLChanged|skeletonModelURLChanged}.
* @function MyAvatar.skeletonChanged * @returns {Signal} */ void skeletonChanged(); /**jsdoc + * Triggered when the avatar's dominant hand changes. * @function MyAvatar.dominantHandChanged - * @param {string} hand + * @param {string} hand - The dominant hand:"left"
for the left hand, "right"
for the right hand.
* @returns {Signal}
*/
void dominantHandChanged(const QString& hand);
/**jsdoc
+ * Triggered when the HMD alignment for your avatar changes.
* @function MyAvatar.hmdAvatarAlignmentTypeChanged
- * @param {string} type
+ * @param {string} type - "head"
if aligning your head and your avatar's head, "eyes"
if aligning
+ * your eyes and your avatar's eyes.
* @returns {Signal}
*/
void hmdAvatarAlignmentTypeChanged(const QString& type);
/**jsdoc
+ * Triggered when the avatar's sensorToWorldScale
property value changes.
* @function MyAvatar.sensorToWorldScaleChanged
- * @param {number} scale
+ * @param {number} scale - The scale that transforms dimensions in the user's real world to the avatar's size in the virtual
+ * world.
* @returns {Signal}
*/
void sensorToWorldScaleChanged(float sensorToWorldScale);
/**jsdoc
+ * Triggered when the a model is attached to or detached from one of the avatar's joints using one of
+ * {@link MyAvatar.attach|attach}, {@link MyAvatar.detachOne|detachOne}, {@link MyAvatar.detachAll|detachAll}, or
+ * {@link MyAvatar.setAttachmentData|setAttachmentData}.
* @function MyAvatar.attachmentsChanged
* @returns {Signal}
+ * @deprecated Use avatar entities instead.
*/
void attachmentsChanged();
/**jsdoc
+ * Triggered when the avatar's size changes. This can be due to the user changing the size of their avatar or the domain
+ * limiting the size of their avatar.
* @function MyAvatar.scaleChanged
* @returns {Signal}
*/
void scaleChanged();
/**jsdoc
- * Triggered when hand touch is globally enabled or disabled
+ * Triggered when the hand touch effect is enabled or disabled for the avatar.
+ * The hand touch effect makes the avatar's fingers adapt to the shape of any object grabbed, creating the effect that + * it is really touching that object.
* @function MyAvatar.shouldDisableHandTouchChanged - * @param {boolean} shouldDisable + * @param {boolean} disabled -true
if the hand touch effect is disabled for the avatar,
+ * false
if it isn't disabled.
* @returns {Signal}
*/
void shouldDisableHandTouchChanged(bool shouldDisable);
/**jsdoc
- * Triggered when hand touch is enabled or disabled for an specific entity
+ * Triggered when the hand touch is enabled or disabled on a specific entity.
+ * The hand touch effect makes the avatar's fingers adapt to the shape of any object grabbed, creating the effect that + * it is really touching that object.
* @function MyAvatar.disableHandTouchForIDChanged - * @param {Uuid} entityID - ID of the entity that will enable hand touch effect - * @param {boolean} disable + * @param {Uuid} entityID - The entity that the hand touch effect has been enabled or disabled for. + * @param {boolean} disabled -true
if the hand touch effect is disabled for the entity,
+ * false
if it isn't disabled.
* @returns {Signal}
*/
void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
@@ -1659,7 +2171,7 @@ private:
bool getEnableStepResetRotation() const { return _stepResetRotationEnabled; }
void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; }
bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; }
- bool isMyAvatar() const override { return true; }
+ virtual bool isMyAvatar() const override { return true; }
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
virtual glm::vec3 getSkeletonPosition() const override;
int _skeletonModelChangeCount { 0 };
@@ -1912,7 +2424,7 @@ private:
bool didTeleport();
bool getIsAway() const { return _isAway; }
void setAway(bool value);
- void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const override;
+ void sendPacket(const QUuid& entityID) const override;
std::mutex _pinnedJointsMutex;
std::vectortrue
if the audio input is muted, otherwise false
.
- * @property {boolean} noiseReduction - true
if noise reduction is enabled, otherwise false
. When
- * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
+ * @property {boolean} mutedDesktop - true
if the audio input is muted, otherwise false
.
+ * @property {boolean} noiseReduction - true
if noise reduction is enabled, otherwise false
. When
+ * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just
* above the noise floor.
- * @property {number} inputLevel - The loudness of the audio input, range 0.0
(no sound) –
+ * @property {number} inputLevel - The loudness of the audio input, range 0.0
(no sound) –
* 1.0
(the onset of clipping). Read-only.
* @property {boolean} clipping - true
if the audio input is clipping, otherwise false
.
- * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.0
– 1.0
.
- * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
+ * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.0
– 1.0
.
+ * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some
* devices, and others might only support values of 0.0
and 1.0
.
- * @property {boolean} isStereoInput - true
if the input audio is being used in stereo, otherwise
+ * @property {boolean} isStereoInput - true
if the input audio is being used in stereo, otherwise
* false
. Some devices do not support stereo, in which case the value is always false
.
* @property {string} context - The current context of the audio: either "Desktop"
or "HMD"
.
* Read-only.
@@ -58,11 +63,18 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
+ Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged)
Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged)
Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
Q_PROPERTY(QString context READ getContext NOTIFY contextChanged)
Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop)
+ Q_PROPERTY(bool mutedDesktop READ getMutedDesktop WRITE setMutedDesktop NOTIFY mutedDesktopChanged)
+ Q_PROPERTY(bool mutedHMD READ getMutedHMD WRITE setMutedHMD NOTIFY mutedHMDChanged)
+ Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged);
+ Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged)
+ Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged)
+ Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged)
public:
static QString AUDIO;
@@ -75,6 +87,7 @@ public:
bool isMuted() const;
bool noiseReductionEnabled() const;
+ bool warnWhenMutedEnabled() const;
float getInputVolume() const;
float getInputLevel() const;
bool isClipping() const;
@@ -82,10 +95,30 @@ public:
void showMicMeter(bool show);
+ // Mute setting setters and getters
+ void setMutedDesktop(bool isMuted);
+ bool getMutedDesktop() const;
+ void setMutedHMD(bool isMuted);
+ bool getMutedHMD() const;
+ void setPTT(bool enabled);
+ bool getPTT();
+ void setPushingToTalk(bool pushingToTalk);
+ bool getPushingToTalk() const;
+
+ // Push-To-Talk setters and getters
+ void setPTTDesktop(bool enabled);
+ bool getPTTDesktop() const;
+ void setPTTHMD(bool enabled);
+ bool getPTTHMD() const;
+
+ // Settings handlers
+ void saveData();
+ void loadData();
+
/**jsdoc
* @function Audio.setInputDevice
* @param {object} device
- * @param {boolean} isHMD
+ * @param {boolean} isHMD
* @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD);
@@ -99,8 +132,8 @@ public:
Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD);
/**jsdoc
- * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options
- * come from either the domain's audio zone if used — configured on the server — or as scripted by
+ * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options
+ * come from either the domain's audio zone if used — configured on the server — or as scripted by
* {@link Audio.setReverbOptions|setReverbOptions}.
* @function Audio.setReverb
* @param {boolean} enable - true
to enable reverberation, false
to disable.
@@ -110,13 +143,13 @@ public:
* var injectorOptions = {
* position: MyAvatar.position
* };
- *
+ *
* Script.setTimeout(function () {
* print("Reverb OFF");
* Audio.setReverb(false);
* injector = Audio.playSound(sound, injectorOptions);
* }, 1000);
- *
+ *
* Script.setTimeout(function () {
* var reverbOptions = new AudioEffectOptions();
* reverbOptions.roomSize = 100;
@@ -124,26 +157,86 @@ public:
* print("Reverb ON");
* Audio.setReverb(true);
* }, 4000);
- *
+ *
* Script.setTimeout(function () {
* print("Reverb OFF");
* Audio.setReverb(false);
* }, 8000); */
Q_INVOKABLE void setReverb(bool enable);
-
+
/**jsdoc
* Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation.
* @function Audio.setReverbOptions
* @param {AudioEffectOptions} options - The reverberation options.
*/
Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options);
-
+
+ /**jsdoc
+ * Sets the avatar gain at the server.
+ * Units are Decibels (dB)
+ * @function Audio.setAvatarGain
+ * @param {number} gain (in dB)
+ */
+ Q_INVOKABLE void setAvatarGain(float gain);
+
+ /**jsdoc
+ * Gets the avatar gain at the server.
+ * @function Audio.getAvatarGain
+ * @returns {number} gain (in dB)
+ */
+ Q_INVOKABLE float getAvatarGain();
+
+ /**jsdoc
+ * Sets the injector gain at the server.
+ * Units are Decibels (dB)
+ * @function Audio.setInjectorGain
+ * @param {number} gain (in dB)
+ */
+ Q_INVOKABLE void setInjectorGain(float gain);
+
+ /**jsdoc
+ * Gets the injector gain at the server.
+ * @function Audio.getInjectorGain
+ * @returns {number} gain (in dB)
+ */
+ Q_INVOKABLE float getInjectorGain();
+
+ /**jsdoc
+ * Sets the local injector gain in the client.
+ * Units are Decibels (dB)
+ * @function Audio.setLocalInjectorGain
+ * @param {number} gain (in dB)
+ */
+ Q_INVOKABLE void setLocalInjectorGain(float gain);
+
+ /**jsdoc
+ * Gets the local injector gain in the client.
+ * @function Audio.getLocalInjectorGain
+ * @returns {number} gain (in dB)
+ */
+ Q_INVOKABLE float getLocalInjectorGain();
+
+ /**jsdoc
+ * Sets the injector gain for system sounds.
+ * Units are Decibels (dB)
+ * @function Audio.setSystemInjectorGain
+ * @param {number} gain (in dB)
+ */
+ Q_INVOKABLE void setSystemInjectorGain(float gain);
+
+ /**jsdoc
+ * Gets the injector gain for system sounds.
+ * @function Audio.getSystemInjectorGain
+ * @returns {number} gain (in dB)
+ */
+ Q_INVOKABLE float getSystemInjectorGain();
+
/**jsdoc
* Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format.
* @function Audio.startRecording
- * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav
+ * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav
* extension. The file is overwritten if it already exists.
- * @returns {boolean} true
if the specified file could be opened and audio recording has started, otherwise
+ * @returns {boolean} true
if the specified file could be opened and audio recording has started, otherwise
* false
.
* @example true
if the audio input is muted for desktop mode, otherwise false
.
+ * @returns {Signal}
+ */
+ void mutedDesktopChanged(bool isMuted);
+
+ /**jsdoc
+ * Triggered when HMD audio input is muted or unmuted.
+ * @function Audio.mutedHMDChanged
+ * @param {boolean} isMuted - true
if the audio input is muted for HMD mode, otherwise false
.
+ * @returns {Signal}
+ */
+ void mutedHMDChanged(bool isMuted);
+
+ /**
+ * Triggered when Push-to-Talk has been enabled or disabled.
+ * @function Audio.pushToTalkChanged
+ * @param {boolean} enabled - true
if Push-to-Talk is enabled, otherwise false
.
+ * @returns {Signal}
+ */
+ void pushToTalkChanged(bool enabled);
+
+ /**
+ * Triggered when Push-to-Talk has been enabled or disabled for desktop mode.
+ * @function Audio.pushToTalkDesktopChanged
+ * @param {boolean} enabled - true
if Push-to-Talk is emabled for Desktop mode, otherwise false
.
+ * @returns {Signal}
+ */
+ void pushToTalkDesktopChanged(bool enabled);
+
+ /**
+ * Triggered when Push-to-Talk has been enabled or disabled for HMD mode.
+ * @function Audio.pushToTalkHMDChanged
+ * @param {boolean} enabled - true
if Push-to-Talk is emabled for HMD mode, otherwise false
.
+ * @returns {Signal}
+ */
+ void pushToTalkHMDChanged(bool enabled);
+
/**jsdoc
* Triggered when the audio input noise reduction is enabled or disabled.
* @function Audio.noiseReductionChanged
@@ -201,12 +334,20 @@ signals:
*/
void noiseReductionChanged(bool isEnabled);
+ /**jsdoc
+ * Triggered when "warn when muted" is enabled or disabled.
+ * @function Audio.warnWhenMutedChanged
+ * @param {boolean} isEnabled - true
if "warn when muted" is enabled, otherwise false
.
+ * @returns {Signal}
+ */
+ void warnWhenMutedChanged(bool isEnabled);
+
/**jsdoc
* Triggered when the input audio volume changes.
* @function Audio.inputVolumeChanged
- * @param {number} volume - The requested volume to be applied to the audio input, range 0.0
–
- * 1.0
. The resulting value of Audio.inputVolume
depends on the capabilities of the device:
- * for example, the volume can't be changed on some devices, and others might only support values of 0.0
+ * @param {number} volume - The requested volume to be applied to the audio input, range 0.0
–
+ * 1.0
. The resulting value of Audio.inputVolume
depends on the capabilities of the device:
+ * for example, the volume can't be changed on some devices, and others might only support values of 0.0
* and 1.0
.
* @returns {Signal}
*/
@@ -215,7 +356,7 @@ signals:
/**jsdoc
* Triggered when the input audio level changes.
* @function Audio.inputLevelChanged
- * @param {number} level - The loudness of the input audio, range 0.0
(no sound) – 1.0
(the
+ * @param {number} level - The loudness of the input audio, range 0.0
(no sound) – 1.0
(the
* onset of clipping).
* @returns {Signal}
*/
@@ -237,6 +378,14 @@ signals:
*/
void contextChanged(const QString& context);
+ /**jsdoc
+ * Triggered when pushing to talk.
+ * @function Audio.pushingToTalkChanged
+ * @param {boolean} talking - true
if broadcasting with PTT, false
otherwise.
+ * @returns {Signal}
+ */
+ void pushingToTalkChanged(bool talking);
+
public slots:
/**jsdoc
@@ -245,9 +394,12 @@ public slots:
*/
void onContextChanged();
+ void handlePushedToTalk(bool enabled);
+
private slots:
void setMuted(bool muted);
void enableNoiseReduction(bool enable);
+ void enableWarnWhenMuted(bool enable);
void setInputVolume(float volume);
void onInputLoudnessChanged(float loudness, bool isClipping);
@@ -259,12 +411,23 @@ private:
float _inputVolume { 1.0f };
float _inputLevel { 0.0f };
+ float _localInjectorGain { 0.0f }; // in dB
+ float _systemInjectorGain { 0.0f }; // in dB
bool _isClipping { false };
- bool _isMuted { false };
bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled.
+ bool _enableWarnWhenMuted { true };
bool _contextIsHMD { false };
AudioDevices* getDevices() { return &_devices; }
AudioDevices _devices;
+ Setting::HandleheroAvatarCount
property changes.
+ * @function Stats.heroAvatarCountChanged
+ * @returns {Signal}
+ */
+ void heroAvatarCountChanged();
+
/**jsdoc
* Triggered when the value of the updatedAvatarCount
property changes.
* @function Stats.updatedAvatarCountChanged
@@ -443,6 +456,13 @@ signals:
*/
void updatedAvatarCountChanged();
+ /**jsdoc
+ * Triggered when the value of the updatedHeroAvatarCount
property changes.
+ * @function Stats.updatedHeroAvatarCountChanged
+ * @returns {Signal}
+ */
+ void updatedHeroAvatarCountChanged();
+
/**jsdoc
* Triggered when the value of the notUpdatedAvatarCount
property changes.
* @function Stats.notUpdatedAvatarCountChanged
@@ -674,6 +694,13 @@ signals:
*/
void audioNoiseGateChanged();
+ /**jsdoc
+ * Triggered when the value of the audioInjectors
property changes.
+ * @function Stats.audioInjectorsChanged
+ * @returns {Signal}
+ */
+ void audioInjectorsChanged();
+
/**jsdoc
* Triggered when the value of the entityPacketsInKbps
property changes.
* @function Stats.entityPacketsInKbpsChanged
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp
index e5cec70f64..1c8a9019ea 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.cpp
+++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp
@@ -292,7 +292,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
setLastInspectedEntity(entityID);
- EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
+ EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityID, _entityPropertyFlags);
auto nodeList = DependencyManager::getparentID
is an avatar skeleton. A value of 65535
means "no joint".
*
- * @property {boolean} isFacingAvatar - If true< / code>, the overlay is rotated to face the user's camera about an axis
+ * @property {boolean} isFacingAvatar - If true
, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
- * @property {string} text="" - The text to display.Text does not automatically wrap; use \n< / code> for a line break.
+ * @property {string} text="" - The text to display.Text does not automatically wrap; use \n
for a line break.
* @property {number} textAlpha=1 - The text alpha value.
* @property {Color} backgroundColor=0,0,0 - The background color.
* @property {number} backgroundAlpha=0.7 - The background alpha value.
@@ -1876,7 +1876,7 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* parentID
set, otherwise the same value as position
.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
- * parentID
set, otherwise the same value as rotation
. Synonym: localOrientation
.
+ * parentID
set, otherwise the same value as rotation
. Synonym: localOrientation
.
* @property {boolean} ignorePickIntersection=false - If true
, picks ignore the overlay. ignoreRayIntersection
is a synonym.
* @property {boolean} drawInFront=false - If true
, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If true
, the overlay is rendered in front of everything, including the HUD.
@@ -1916,7 +1916,7 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* parentID
set, otherwise the same value as position
.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
- * parentID
set, otherwise the same value as rotation
. Synonym: localOrientation
.
+ * parentID
set, otherwise the same value as rotation
. Synonym: localOrientation
.
* @property {boolean} isSolid=false - Synonyms: solid
, isFilled
, and filled
.
* Antonyms: isWire
and wire
.
* @property {boolean} ignorePickIntersection=false - If true
, picks ignore the overlay. ignoreRayIntersection
is a synonym.
@@ -1927,46 +1927,46 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* parentID
is an avatar skeleton. A value of 65535
means "no joint".
*
- * @property {number} startAt = 0 - The counter - clockwise angle from the overlay's x-axis that drawing starts at, in degrees.
- * @property {number} endAt = 360 - The counter - clockwise angle from the overlay's x-axis that drawing ends at, in degrees.
- * @property {number} outerRadius = 1 - The outer radius of the overlay, in meters.Synonym: radius< / code>.
- * @property {number} innerRadius = 0 - The inner radius of the overlay, in meters.
- * @property {Color} color = 255, 255, 255 - The color of the overlay.Setting this value also sets the values of
- * innerStartColor< / code>, innerEndColor< / code>, outerStartColor< / code>, and outerEndColor< / code>.
- * @property {Color} startColor - Sets the values of innerStartColor< / code> and outerStartColor< / code>.
- * Write - only.< / em>
- * @property {Color} endColor - Sets the values of innerEndColor< / code> and outerEndColor< / code>.
- * Write - only.< / em>
- * @property {Color} innerColor - Sets the values of innerStartColor< / code> and innerEndColor< / code>.
- * Write - only.< / em>
- * @property {Color} outerColor - Sets the values of outerStartColor< / code> and outerEndColor< / code>.
- * Write - only.< / em>
+ * @property {number} startAt = 0 - The counter - clockwise angle from the overlay's x-axis that drawing starts at in degrees.
+ * @property {number} endAt = 360 - The counter - clockwise angle from the overlay's x-axis that drawing ends at in degrees.
+ * @property {number} outerRadius = 1 - The outer radius of the overlay in meters. Synonym: radius
.
+ * @property {number} innerRadius = 0 - The inner radius of the overlay in meters.
+ * @property {Color} color = 255, 255, 255 - The color of the overlay. Setting this value also sets the values of
+ * innerStartColor
, innerEndColor
, outerStartColor
, and outerEndColor
.
+ * @property {Color} startColor - Sets the values of innerStartColor
and outerStartColor
.
+ * Write - only.
+ * @property {Color} endColor - Sets the values of innerEndColor
and outerEndColor
.
+ * Write - only.
+ * @property {Color} innerColor - Sets the values of innerStartColor
and innerEndColor
.
+ * Write - only.
+ * @property {Color} outerColor - Sets the values of outerStartColor
and outerEndColor
.
+ * Write - only.
* @property {Color} innerStartcolor - The color at the inner start point of the overlay.
* @property {Color} innerEndColor - The color at the inner end point of the overlay.
* @property {Color} outerStartColor - The color at the outer start point of the overlay.
* @property {Color} outerEndColor - The color at the outer end point of the overlay.
- * @property {number} alpha = 0.5 - The opacity of the overlay, 0.0< / code> -1.0< / code>.Setting this value also sets
- * the values of innerStartAlpha< / code>, innerEndAlpha< / code>, outerStartAlpha< / code>, and
- * outerEndAlpha< / code>.Synonym: Alpha< / code>; write - only< / em>.
- * @property {number} startAlpha - Sets the values of innerStartAlpha< / code> and outerStartAlpha< / code>.
- * Write - only.< / em>
- * @property {number} endAlpha - Sets the values of innerEndAlpha< / code> and outerEndAlpha< / code>.
- * Write - only.< / em>
- * @property {number} innerAlpha - Sets the values of innerStartAlpha< / code> and innerEndAlpha< / code>.
- * Write - only.< / em>
- * @property {number} outerAlpha - Sets the values of outerStartAlpha< / code> and outerEndAlpha< / code>.
- * Write - only.< / em>
+ * @property {number} alpha = 0.5 - The opacity of the overlay, 0.0
-1.0
. Setting this value also sets
+ * the values of innerStartAlpha
, innerEndAlpha
, outerStartAlpha
, and
+ * outerEndAlpha
. Synonym: Alpha
; write - only.
+ * @property {number} startAlpha - Sets the values of innerStartAlpha
and outerStartAlpha
.
+ * Write - only.
+ * @property {number} endAlpha - Sets the values of innerEndAlpha
and outerEndAlpha
.
+ * Write - only.
+ * @property {number} innerAlpha - Sets the values of innerStartAlpha
and innerEndAlpha
.
+ * Write - only.
+ * @property {number} outerAlpha - Sets the values of outerStartAlpha
and outerEndAlpha
.
+ * Write - only.
* @property {number} innerStartAlpha = 0 - The alpha at the inner start point of the overlay.
* @property {number} innerEndAlpha = 0 - The alpha at the inner end point of the overlay.
* @property {number} outerStartAlpha = 0 - The alpha at the outer start point of the overlay.
* @property {number} outerEndAlpha = 0 - The alpha at the outer end point of the overlay.
*
- * @property {boolean} hasTickMarks = false - If true< / code>, tick marks are drawn.
+ * @property {boolean} hasTickMarks = false - If true
, tick marks are drawn.
* @property {number} majorTickMarksAngle = 0 - The angle between major tick marks, in degrees.
* @property {number} minorTickMarksAngle = 0 - The angle between minor tick marks, in degrees.
- * @property {number} majorTickMarksLength = 0 - The length of the major tick marks, in meters.A positive value draws tick marks
+ * @property {number} majorTickMarksLength = 0 - The length of the major tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
- * @property {number} minorTickMarksLength = 0 - The length of the minor tick marks, in meters.A positive value draws tick marks
+ * @property {number} minorTickMarksLength = 0 - The length of the minor tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {Color} majorTickMarksColor = 0, 0, 0 - The color of the major tick marks.
* @property {Color} minorTickMarksColor = 0, 0, 0 - The color of the minor tick marks.
diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp
index 4fe02e9307..1a922e507d 100644
--- a/libraries/animation/src/AnimClip.cpp
+++ b/libraries/animation/src/AnimClip.cpp
@@ -124,41 +124,45 @@ void AnimClip::copyFromNetworkAnim() {
_anim.resize(animFrameCount);
// find the size scale factor for translation in the animation.
- const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarSkeleton->nameToJointIndex("Hips"));
- const int animHipsParentIndex = animSkeleton.getParentIndex(animSkeleton.nameToJointIndex("Hips"));
- const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarSkeleton->nameToJointIndex("Hips"));
- const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animSkeleton.nameToJointIndex("Hips"));
-
- // the get the units and the heights for the animation and the avatar
- const float avatarUnitScale = extractScale(avatarSkeleton->getGeometryOffset()).y;
- const float animationUnitScale = extractScale(animModel.offset).y;
- const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y;
- const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y;
-
- // get the parent scales for the avatar and the animation
- float avatarHipsParentScale = 1.0f;
- if (avatarHipsParentIndex >= 0) {
- const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex);
- avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y;
- }
- float animHipsParentScale = 1.0f;
- if (animHipsParentIndex >= 0) {
- const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex);
- animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y;
- }
-
- const float EPSILON = 0.0001f;
float boneLengthScale = 1.0f;
- // compute the ratios for the units, the heights in meters, and the parent scales
- if ((fabsf(animHeightInMeters) > EPSILON) && (animationUnitScale > EPSILON) && (animHipsParentScale > EPSILON)) {
- const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters;
- const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale);
- const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale);
+ const int avatarHipsIndex = avatarSkeleton->nameToJointIndex("Hips");
+ const int animHipsIndex = animSkeleton.nameToJointIndex("Hips");
+ if (avatarHipsIndex != -1 && animHipsIndex != -1) {
+ const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarHipsIndex);
+ const int animHipsParentIndex = animSkeleton.getParentIndex(animHipsIndex);
- boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio;
+ const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsIndex);
+ const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsIndex);
+
+ // the get the units and the heights for the animation and the avatar
+ const float avatarUnitScale = extractScale(avatarSkeleton->getGeometryOffset()).y;
+ const float animationUnitScale = extractScale(animModel.offset).y;
+ const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y;
+ const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y;
+
+ // get the parent scales for the avatar and the animation
+ float avatarHipsParentScale = 1.0f;
+ if (avatarHipsParentIndex != -1) {
+ const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex);
+ avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y;
+ }
+ float animHipsParentScale = 1.0f;
+ if (animHipsParentIndex != -1) {
+ const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex);
+ animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y;
+ }
+
+ const float EPSILON = 0.0001f;
+ // compute the ratios for the units, the heights in meters, and the parent scales
+ if ((fabsf(animHeightInMeters) > EPSILON) && (animationUnitScale > EPSILON) && (animHipsParentScale > EPSILON)) {
+ const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters;
+ const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale);
+ const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale);
+
+ boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio;
+ }
}
-
for (int frame = 0; frame < animFrameCount; frame++) {
const HFMAnimationFrame& animFrame = animModel.animationFrames[frame];
diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h
index 0136b7d125..6d58ecedec 100644
--- a/libraries/animation/src/AnimInverseKinematics.h
+++ b/libraries/animation/src/AnimInverseKinematics.h
@@ -59,6 +59,46 @@ public:
float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; }
+ /**jsdoc
+ * Specifies the initial conditions of the IK solver.
+ *
+ *
+ * Value Name Description
+ *
+ *
+ * 0
RelaxToUnderPoses This is a blend: it is 15/16 PreviousSolution
+ * and 1/16 UnderPoses
. This provides some of the benefits of using UnderPoses
so that the
+ * underlying animation is still visible, while at the same time converging faster then using the
+ * UnderPoses
as the only initial solution.
+ * 1
RelaxToLimitCenterPoses This is a blend: it is 15/16
+ * PreviousSolution
and 1/16 LimitCenterPoses
. This should converge quickly because it is
+ * close to the previous solution, but still provides the benefits of avoiding limb locking.
+ * 2
PreviousSolution The IK system will begin to solve from the same position and
+ * orientations for each joint that was the result from the previous frame.
+ * Pros: As the end effectors typically do not move much from frame to frame, this is likely to converge quickly
+ * to a valid solution.
+ * Cons: If the previous solution resulted in an awkward or uncomfortable posture, the next frame will also be
+ * awkward and uncomfortable. It can also result in locked elbows and knees.
+ * 3
UnderPoses The IK occurs at one of the top-most layers. It has access to the
+ * full posture that was computed via canned animations and blends. We call this animated set of poses the "under
+ * pose". The under poses are what would be visible if IK was completely disabled. Using the under poses as the
+ * initial conditions of the CCD solve will cause some of the animated motion to be blended into the result of the
+ * IK. This can result in very natural results, especially if there are only a few IK targets enabled. On the other
+ * hand, because the under poses might be quite far from the desired end effector, it can converge slowly in some
+ * cases, causing it to never reach the IK target in the allotted number of iterations. Also, in situations where all
+ * of the IK targets are being controlled by external sensors, sometimes starting from the under poses can cause
+ * awkward motions from the underlying animations to leak into the IK result.
+ * 4
LimitCenterPoses This pose is taken to be the center of all the joint
+ * constraints. This can prevent the IK solution from getting locked or stuck at a particular constraint. For
+ * example, if the arm is pointing straight outward from the body, as the end effector moves towards the body, at
+ * some point the elbow should bend to accommodate. However, because the CCD solver is stuck at a local maximum, it
+ * will not rotate the elbow, unless the initial conditions already have the elbow bent, which is the case for
+ * LimitCenterPoses
. When all the IK targets are enabled, this result will provide a consistent starting
+ * point for each IK solve, hopefully resulting in a consistent, natural result.
+ *
+ *
+ * @typedef {number} MyAvatar.AnimIKSolutionSource
+ */
enum class SolutionSource {
RelaxToUnderPoses = 0,
RelaxToLimitCenterPoses,
diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h
index 70929bd4e4..1ad4e100db 100644
--- a/libraries/animation/src/AnimOverlay.h
+++ b/libraries/animation/src/AnimOverlay.h
@@ -24,6 +24,37 @@ class AnimOverlay : public AnimNode {
public:
friend class AnimTests;
+ /**jsdoc
+ * Specifies sets of joints.
+ *
+ *
+ * Value Name Description
+ *
+ *
+ * 0
FullBodyBoneSet All joints.
+ * 1
UpperBodyBoneSet Only the "Spine" joint and its children.
+ * 2
LowerBodyBoneSet Only the leg joints and their children.
+ * 3
LeftArmBoneSet Joints that are the children of the "LeftShoulder"
+ * joint.
+ * 4
RightArmBoneSet Joints that are the children of the "RightShoulder"
+ * joint.
+ * 5
AboveTheHeadBoneSet Joints that are the children of the "Head"
+ * joint.
+ * 6
BelowTheHeadBoneSet Joints that are NOT the children of the "head"
+ * joint.
+ * 7
HeadOnlyBoneSet The "Head" joint.
+ * 8
SpineOnlyBoneSet The "Spine" joint.
+ * 9
EmptyBoneSet No joints.
+ * 10
LeftHandBoneSet joints that are the children of the "LeftHand"
+ * joint.
+ * 11
RightHandBoneSet Joints that are the children of the "RightHand"
+ * joint.
+ * 12
HipsOnlyBoneSet The "Hips" joint.
+ * 13
BothFeetBoneSet The "LeftFoot" and "RightFoot" joints.
+ *
+ *
+ * @typedef {number} MyAvatar.AnimOverlayBoneSet
+ */
enum BoneSet {
FullBodyBoneSet = 0,
UpperBodyBoneSet,
diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp
index 5bc2021e5e..1f9e72bf28 100644
--- a/libraries/animation/src/Flow.cpp
+++ b/libraries/animation/src/Flow.cpp
@@ -67,17 +67,23 @@ void FlowCollisionSystem::addCollisionSphere(int jointIndex, const FlowCollision
auto collision = FlowCollisionSphere(jointIndex, settings, isTouch);
collision.setPosition(position);
if (isSelfCollision) {
- _selfCollisions.push_back(collision);
+ if (!isTouch) {
+ _selfCollisions.push_back(collision);
+ } else {
+ _selfTouchCollisions.push_back(collision);
+ }
} else {
_othersCollisions.push_back(collision);
}
-
};
+
void FlowCollisionSystem::resetCollisions() {
_allCollisions.clear();
_othersCollisions.clear();
+ _selfTouchCollisions.clear();
_selfCollisions.clear();
}
+
FlowCollisionResult FlowCollisionSystem::computeCollision(const std::vector collisions) {
FlowCollisionResult result;
if (collisions.size() > 1) {
@@ -106,6 +112,10 @@ void FlowCollisionSystem::setScale(float scale) {
_selfCollisions[j]._radius = _selfCollisions[j]._initialRadius * scale;
_selfCollisions[j]._offset = _selfCollisions[j]._initialOffset * scale;
}
+ for (size_t j = 0; j < _selfTouchCollisions.size(); j++) {
+ _selfTouchCollisions[j]._radius = _selfTouchCollisions[j]._initialRadius * scale;
+ _selfTouchCollisions[j]._offset = _selfTouchCollisions[j]._initialOffset * scale;
+ }
};
std::vector FlowCollisionSystem::checkFlowThreadCollisions(FlowThread* flowThread) {
@@ -178,9 +188,9 @@ void FlowCollisionSystem::setCollisionSettingsByJoint(int jointIndex, const Flow
}
void FlowCollisionSystem::prepareCollisions() {
_allCollisions.clear();
- _allCollisions.resize(_selfCollisions.size() + _othersCollisions.size());
- std::copy(_selfCollisions.begin(), _selfCollisions.begin() + _selfCollisions.size(), _allCollisions.begin());
- std::copy(_othersCollisions.begin(), _othersCollisions.begin() + _othersCollisions.size(), _allCollisions.begin() + _selfCollisions.size());
+ _allCollisions.insert(_allCollisions.end(), _selfCollisions.begin(), _selfCollisions.end());
+ _allCollisions.insert(_allCollisions.end(), _othersCollisions.begin(), _othersCollisions.end());
+ _allCollisions.insert(_allCollisions.end(), _selfTouchCollisions.begin(), _selfTouchCollisions.end());
_othersCollisions.clear();
}
@@ -273,18 +283,20 @@ void FlowJoint::setRecoveryPosition(const glm::vec3& recoveryPosition) {
}
void FlowJoint::update(float deltaTime) {
- glm::vec3 accelerationOffset = glm::vec3(0.0f);
- if (_settings._stiffness > 0.0f) {
- glm::vec3 recoveryVector = _recoveryPosition - _currentPosition;
- float recoveryFactor = powf(_settings._stiffness, 3.0f);
- accelerationOffset = recoveryVector * recoveryFactor;
- }
- FlowNode::update(deltaTime, accelerationOffset);
- if (_anchored) {
- if (!_isHelper) {
- _currentPosition = _updatedPosition;
- } else {
- _currentPosition = _parentPosition;
+ if (_settings._active) {
+ glm::vec3 accelerationOffset = glm::vec3(0.0f);
+ if (_settings._stiffness > 0.0f) {
+ glm::vec3 recoveryVector = _recoveryPosition - _currentPosition;
+ float recoveryFactor = powf(_settings._stiffness, 3.0f);
+ accelerationOffset = recoveryVector * recoveryFactor;
+ }
+ FlowNode::update(deltaTime, accelerationOffset);
+ if (_anchored) {
+ if (!_isHelper) {
+ _currentPosition = _updatedPosition;
+ } else {
+ _currentPosition = _parentPosition;
+ }
}
}
};
@@ -674,6 +686,14 @@ bool Flow::updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t thr
return true;
}
+void Flow::updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses) {
+ glm::quat jointRotation;
+ getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation);
+ getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation);
+ glm::vec3 worldOffset = jointRotation * collision._offset;
+ collision._position = collision._position + worldOffset;
+}
+
void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses) {
updateAbsolutePoses(relativePoses, absolutePoses);
for (auto &jointData : _flowJointData) {
@@ -695,11 +715,11 @@ void Flow::updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses)
}
auto &selfCollisions = _collisionSystem.getSelfCollisions();
for (auto &collision : selfCollisions) {
- glm::quat jointRotation;
- getJointPositionInWorldFrame(absolutePoses, collision._jointIndex, collision._position, _entityPosition, _entityRotation);
- getJointRotationInWorldFrame(absolutePoses, collision._jointIndex, jointRotation, _entityRotation);
- glm::vec3 worldOffset = jointRotation * collision._offset;
- collision._position = collision._position + worldOffset;
+ updateCollisionJoint(collision, absolutePoses);
+ }
+ auto &selfTouchCollisions = _collisionSystem.getSelfTouchCollisions();
+ for (auto &collision : selfTouchCollisions) {
+ updateCollisionJoint(collision, absolutePoses);
}
_collisionSystem.prepareCollisions();
}
@@ -710,7 +730,7 @@ void Flow::setJoints(AnimPoseVec& relativePoses, const std::vector& overri
for (int jointIndex : joints) {
auto &joint = _flowJointData[jointIndex];
if (jointIndex >= 0 && jointIndex < (int)relativePoses.size() && !overrideFlags[jointIndex]) {
- relativePoses[jointIndex].rot() = joint.getCurrentRotation();
+ relativePoses[jointIndex].rot() = joint.getSettings()._active ? joint.getCurrentRotation() : joint.getInitialRotation();
}
}
}
diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h
index ad81c2be77..5dc1a3ba3e 100644
--- a/libraries/animation/src/Flow.h
+++ b/libraries/animation/src/Flow.h
@@ -140,6 +140,7 @@ public:
std::vector checkFlowThreadCollisions(FlowThread* flowThread);
std::vector& getSelfCollisions() { return _selfCollisions; };
+ std::vector& getSelfTouchCollisions() { return _selfTouchCollisions; };
void setOthersCollisions(const std::vector& othersCollisions) { _othersCollisions = othersCollisions; }
void prepareCollisions();
void resetCollisions();
@@ -150,9 +151,11 @@ public:
void setActive(bool active) { _active = active; }
bool getActive() const { return _active; }
const std::vector& getCollisions() const { return _selfCollisions; }
+ void clearSelfCollisions() { _selfCollisions.clear(); }
protected:
std::vector _selfCollisions;
std::vector _othersCollisions;
+ std::vector _selfTouchCollisions;
std::vector _allCollisions;
float _scale { 1.0f };
bool _active { false };
@@ -210,7 +213,7 @@ public:
bool isHelper() const { return _isHelper; }
const FlowPhysicsSettings& getSettings() { return _settings; }
- void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; }
+ void setSettings(const FlowPhysicsSettings& settings) { _settings = settings; _initialRadius = _settings._radius; }
const glm::vec3& getCurrentPosition() const { return _currentPosition; }
int getIndex() const { return _index; }
@@ -222,6 +225,7 @@ public:
const glm::quat& getCurrentRotation() const { return _currentRotation; }
const glm::vec3& getCurrentTranslation() const { return _initialTranslation; }
const glm::vec3& getInitialPosition() const { return _initialPosition; }
+ const glm::quat& getInitialRotation() const { return _initialRotation; }
bool isColliding() const { return _colliding; }
protected:
@@ -297,6 +301,7 @@ public:
void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings);
const std::map& getGroupSettings() const { return _groupSettings; }
void cleanUp();
+ void updateScale() { setScale(_scale); }
signals:
void onCleanup();
@@ -311,6 +316,7 @@ private:
void setJoints(AnimPoseVec& relativePoses, const std::vector& overrideFlags);
void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses);
+ void updateCollisionJoint(FlowCollisionSphere& collision, AnimPoseVec& absolutePoses);
bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex);
void updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings);
void setScale(float scale);
diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h
index 57eaff9c30..564dba7f05 100644
--- a/libraries/animation/src/IKTarget.h
+++ b/libraries/animation/src/IKTarget.h
@@ -16,6 +16,27 @@ const float HACK_HMD_TARGET_WEIGHT = 8.0f;
class IKTarget {
public:
+ /**jsdoc
+ * An IK target type.
+ *
+ *
+ * Value Name Description
+ *
+ *
+ * 0
RotationAndPosition Attempt to reach the rotation and position end
+ * effector.
+ * 1
RotationOnly Attempt to reach the end effector rotation only.
+ * 2
HmdHead Deprecated: A special mode of IK that would attempt
+ * to prevent unnecessary bending of the spine.
+ * 3
HipsRelativeRotationAndPosition Attempt to reach a rotation and position end
+ * effector that is not in absolute rig coordinates but is offset by the avatar hips translation.
+ * 4
Spline Use a cubic Hermite spline to model the human spine. This prevents
+ * kinks in the spine and allows for a small amount of stretch and squash.
+ * 5
Unknown IK is disabled.
+ *
+ *
+ * @typedef {number} MyAvatar.IKTargetType
+ */
enum class Type {
RotationAndPosition,
RotationOnly,
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 07bdfde189..43e94d23e8 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -88,6 +88,218 @@ static const QString MAIN_STATE_MACHINE_RIGHT_HAND_ROTATION("mainStateMachineRig
static const QString MAIN_STATE_MACHINE_RIGHT_HAND_POSITION("mainStateMachineRightHandPosition");
+/**jsdoc
+ * An AnimStateDictionary
object may have the following properties. It may also have other properties, set by
+ * scripts.
+ * Warning: These properties are subject to change.
+ *
+ *
+ * Name Type Description
+ *
+ *
+ * userAnimNone
boolean true
when no user overrideAnimation is
+ * playing.
+ * userAnimA
boolean true
when a user overrideAnimation is
+ * playing.
+ * userAnimB
boolean true
when a user overrideAnimation is
+ * playing.
+ *
+ * sine
number Oscillating sine wave.
+ * moveForwardSpeed
number Controls the blend between the various forward walking
+ * & running animations.
+ * moveBackwardSpeed
number Controls the blend between the various backward walking
+ * & running animations.
+ * moveLateralSpeed
number Controls the blend between the various sidestep walking
+ * & running animations.
+ *
+ * isMovingForward
boolean true
if the avatar is moving
+ * forward.
+ * isMovingBackward
boolean true
if the avatar is moving
+ * backward.
+ * isMovingRight
boolean true
if the avatar is moving to the
+ * right.
+ * isMovingLeft
boolean true
if the avatar is moving to the
+ * left.
+ * isMovingRightHmd
boolean true
if the avatar is moving to the right
+ * while the user is in HMD mode.
+ * isMovingLeftHmd
boolean true
if the avatar is moving to the left while
+ * the user is in HMD mode.
+ * isNotMoving
boolean true
if the avatar is stationary.
+ *
+ * isTurningRight
boolean true
if the avatar is turning
+ * clockwise.
+ * isTurningLeft
boolean true
if the avatar is turning
+ * counter-clockwise.
+ * isNotTurning
boolean true
if the avatar is not turning.
+ * isFlying
boolean true
if the avatar is flying.
+ * isNotFlying
boolean true
if the avatar is not flying.
+ * isTakeoffStand
boolean true
if the avatar is about to execute a
+ * standing jump.
+ * isTakeoffRun
boolean true
if the avatar is about to execute a running
+ * jump.
+ * isNotTakeoff
boolean true
if the avatar is not jumping.
+ * isInAirStand
boolean true
if the avatar is in the air after a standing
+ * jump.
+ * isInAirRun
boolean true
if the avatar is in the air after a running
+ * jump.
+ * isNotInAir
boolean true
if the avatar on the ground.
+ *
+ * inAirAlpha
number Used to interpolate between the up, apex, and down in-air
+ * animations.
+ * ikOverlayAlpha
number The blend between upper body and spline IK versus the
+ * underlying animation
+ *
+ * headPosition
{@link Vec3} The desired position of the Head
joint in
+ * rig coordinates.
+ * headRotation
{@link Quat} The desired orientation of the Head
joint in
+ * rig coordinates.
+ * headType
{@link MyAvatar.IKTargetType|IKTargetType} The type of IK used for the
+ * head.
+ * headWeight
number How strongly the head chain blends with the other IK
+ * chains.
+ *
+ * leftHandPosition
{@link Vec3} The desired position of the LeftHand
+ * joint in rig coordinates.
+ * leftHandRotation
{@link Quat} The desired orientation of the LeftHand
+ * joint in rig coordinates.
+ * leftHandType
{@link MyAvatar.IKTargetType|IKTargetType} The type of IK used for the
+ * left arm.
+ * leftHandPoleVectorEnabled
boolean When true
, the elbow angle is
+ * controlled by the rightHandPoleVector
property value. Otherwise the elbow direction comes from the
+ * underlying animation.
+ * leftHandPoleReferenceVector
{@link Vec3} The direction of the elbow in the local
+ * coordinate system of the elbow.
+ * leftHandPoleVector
{@link Vec3} The direction the elbow should point in rig
+ * coordinates.
+ *
+ * rightHandPosition
{@link Vec3} The desired position of the RightHand
+ * joint in rig coordinates.
+ * rightHandRotation
{@link Quat} The desired orientation of the
+ * RightHand
joint in rig coordinates.
+ * rightHandType
{@link MyAvatar.IKTargetType|IKTargetType} The type of IK used for
+ * the right arm.
+ * rightHandPoleVectorEnabled
boolean When true
, the elbow angle is
+ * controlled by the rightHandPoleVector
property value. Otherwise the elbow direction comes from the
+ * underlying animation.
+ * rightHandPoleReferenceVector
{@link Vec3} The direction of the elbow in the local
+ * coordinate system of the elbow.
+ * rightHandPoleVector
{@link Vec3} The direction the elbow should point in rig
+ * coordinates.
+ *
+ * leftFootIKEnabled
boolean true
if IK is enabled for the left
+ * foot.
+ * rightFootIKEnabled
boolean true
if IK is enabled for the right
+ * foot.
+ *
+ * leftFootIKPositionVar
string The name of the source for the desired position
+ * of the LeftFoot
joint. If not set, the foot rotation of the underlying animation will be used.
+ * leftFootIKRotationVar
string The name of the source for the desired rotation
+ * of the LeftFoot
joint. If not set, the foot rotation of the underlying animation will be used.
+ * leftFootPoleVectorEnabled
boolean When true
, the knee angle is
+ * controlled by the leftFootPoleVector
property value. Otherwise the knee direction comes from the
+ * underlying animation.
+ * leftFootPoleVector
{@link Vec3} The direction the knee should face in rig
+ * coordinates.
+ * rightFootIKPositionVar
string The name of the source for the desired position
+ * of the RightFoot
joint. If not set, the foot rotation of the underlying animation will be used.
+ * rightFootIKRotationVar
string The name of the source for the desired rotation
+ * of the RightFoot
joint. If not set, the foot rotation of the underlying animation will be used.
+ * rightFootPoleVectorEnabled
boolean When true
, the knee angle is
+ * controlled by the rightFootPoleVector
property value. Otherwise the knee direction comes from the
+ * underlying animation.
+ * rightFootPoleVector
{@link Vec3} The direction the knee should face in rig
+ * coordinates.
+ *
+ * isTalking
boolean true
if the avatar is talking.
+ * notIsTalking
boolean true
if the avatar is not talking.
+ *
+ * solutionSource
{@link MyAvatar.AnimIKSolutionSource|AnimIKSolutionSource}
+ * Determines the initial conditions of the IK solver.
+ * defaultPoseOverlayAlpha
number Controls the blend between the main animation state
+ * machine and the default pose. Mostly used during full body tracking so that walking & jumping animations do not
+ * affect the IK of the figure.
+ * defaultPoseOverlayBoneSet
{@link MyAvatar.AnimOverlayBoneSet|AnimOverlayBoneSet}
+ * Specifies which bones will be replace by the source overlay.
+ * hipsType
{@link MyAvatar.IKTargetType|IKTargetType} The type of IK used for the
+ * hips.
+ * hipsPosition
{@link Vec3} The desired position of Hips
joint in rig
+ * coordinates.
+ * hipsRotation
{@link Quat} the desired orientation of the Hips
joint in
+ * rig coordinates.
+ * spine2Type
{@link MyAvatar.IKTargetType|IKTargetType} The type of IK used for the
+ * Spine2
joint.
+ * spine2Position
{@link Vec3} The desired position of the Spine2
joint
+ * in rig coordinates.
+ * spine2Rotation
{@link Quat} The desired orientation of the Spine2
+ * joint in rig coordinates.
+ *
+ * leftFootIKAlpha
number Blends between full IK for the leg and the underlying
+ * animation.
+ * rightFootIKAlpha
number Blends between full IK for the leg and the underlying
+ * animation.
+ * hipsWeight
number How strongly the hips target blends with the IK solution for
+ * other IK chains.
+ * leftHandWeight
number How strongly the left hand blends with IK solution of other
+ * IK chains.
+ * rightHandWeight
number How strongly the right hand blends with IK solution of other
+ * IK chains.
+ * spine2Weight
number How strongly the spine2 chain blends with the rest of the IK
+ * solution.
+ *
+ * leftHandOverlayAlpha
number Used to blend in the animated hand gesture poses, such
+ * as point and thumbs up.
+ * leftHandGraspAlpha
number Used to blend between an open hand and a closed hand.
+ * Usually changed as you squeeze the trigger of the hand controller.
+ * rightHandOverlayAlpha
number Used to blend in the animated hand gesture poses,
+ * such as point and thumbs up.
+ * rightHandGraspAlpha
number Used to blend between an open hand and a closed hand.
+ * Usually changed as you squeeze the trigger of the hand controller.
+ * isLeftIndexPoint
boolean true
if the left hand should be
+ * pointing.
+ * isLeftThumbRaise
boolean true
if the left hand should be
+ * thumbs-up.
+ * isLeftIndexPointAndThumbRaise
boolean true
if the left hand should be
+ * pointing and thumbs-up.
+ * isLeftHandGrasp
boolean true
if the left hand should be at rest,
+ * grasping the controller.
+ * isRightIndexPoint
boolean true
if the right hand should be
+ * pointing.
+ * isRightThumbRaise
boolean true
if the right hand should be
+ * thumbs-up.
+ * isRightIndexPointAndThumbRaise
boolean true
if the right hand should
+ * be pointing and thumbs-up.
+ * isRightHandGrasp
boolean true
if the right hand should be at rest,
+ * grasping the controller.
+ *
+ *
+ *
+ * Note: Rig coordinates are +z
forward and +y
up.
+ * @typedef {object} MyAvatar.AnimStateDictionary
+ */
+// Note: The following animVars are intentionally not documented:
+// - leftFootPosition
+// - leftFootRotation
+// - rightFooKPosition
+// - rightFooKRotation
+// Note: The following items aren't set in the code below but are still intentionally documented:
+// - leftFootIKAlpha
+// - rightFootIKAlpha
+// - hipsWeight
+// - leftHandWeight
+// - rightHandWeight
+// - spine2Weight
+// - rightHandOverlayAlpha
+// - rightHandGraspAlpha
+// - leftHandOverlayAlpha
+// - leftHandGraspAlpha
+// - isRightIndexPoint
+// - isRightThumbRaise
+// - isRightIndexPointAndThumbRaise
+// - isRightHandGrasp
+// - isLeftIndexPoint
+// - isLeftThumbRaise
+// - isLeftIndexPointAndThumbRaise
+// - isLeftHandGrasp
Rig::Rig() {
// Ensure thread-safe access to the rigRegistry.
std::lock_guard guard(rigRegistryMutex);
@@ -1210,7 +1422,8 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
_networkAnimState.blendTime += deltaTime;
alpha = _computeNetworkAnimation ? (_networkAnimState.blendTime / TOTAL_BLEND_TIME) : (1.0f - (_networkAnimState.blendTime / TOTAL_BLEND_TIME));
alpha = glm::clamp(alpha, 0.0f, 1.0f);
- for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) {
+ size_t numJoints = std::min(_networkPoseSet._relativePoses.size(), _internalPoseSet._relativePoses.size());
+ for (size_t i = 0; i < numJoints; i++) {
_networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], alpha);
}
}
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index b2e6167ffa..c537fea646 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -1052,7 +1052,7 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
- if (_muted || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) {
+ if ((_muted && !_shouldEchoLocally) || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) {
return;
}
@@ -1354,26 +1354,30 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
for (const AudioInjectorPointer& injector : _activeLocalAudioInjectors) {
// the lock guarantees that injectorBuffer, if found, is invariant
- AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
+ auto injectorBuffer = injector->getLocalBuffer();
if (injectorBuffer) {
+ auto options = injector->getOptions();
+
static const int HRTF_DATASET_INDEX = 1;
- int numChannels = injector->isAmbisonic() ? AudioConstants::AMBISONIC : (injector->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
+ int numChannels = options.ambisonic ? AudioConstants::AMBISONIC : (options.stereo ? AudioConstants::STEREO : AudioConstants::MONO);
size_t bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
// get one frame from the injector
memset(_localScratchBuffer, 0, bytesToRead);
if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) {
- float gain = injector->getVolume();
+ bool isSystemSound = !options.positionSet && !options.ambisonic;
- if (injector->isAmbisonic()) {
+ float gain = options.volume * (isSystemSound ? _systemInjectorGain : _localInjectorGain);
- if (injector->isPositionSet()) {
+ if (options.ambisonic) {
+
+ if (options.positionSet) {
// distance attenuation
- glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
+ glm::vec3 relativePosition = options.position - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
}
@@ -1382,7 +1386,7 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
// Calculate the soundfield orientation relative to the listener.
// Injector orientation can be used to align a recording to our world coordinates.
//
- glm::quat relativeOrientation = injector->getOrientation() * glm::inverse(_orientationGetter());
+ glm::quat relativeOrientation = options.orientation * glm::inverse(_orientationGetter());
// convert from Y-up (OpenGL) to Z-up (Ambisonic) coordinate system
float qw = relativeOrientation.w;
@@ -1394,12 +1398,12 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
- } else if (injector->isStereo()) {
+ } else if (options.stereo) {
- if (injector->isPositionSet()) {
+ if (options.positionSet) {
// distance attenuation
- glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
+ glm::vec3 relativePosition = options.position - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
}
@@ -1412,10 +1416,10 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
} else { // injector is mono
- if (injector->isPositionSet()) {
+ if (options.positionSet) {
// distance attenuation
- glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
+ glm::vec3 relativePosition = options.position - _positionGetter();
float distance = glm::max(glm::length(relativePosition), EPSILON);
gain = gainForSource(distance, gain);
@@ -1437,21 +1441,21 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
} else {
- qCDebug(audioclient) << "injector has no more data, marking finished for removal";
+ //qCDebug(audioclient) << "injector has no more data, marking finished for removal";
injector->finishLocalInjection();
injectorsToRemove.append(injector);
}
} else {
- qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal";
+ //qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal";
injector->finishLocalInjection();
injectorsToRemove.append(injector);
}
}
for (const AudioInjectorPointer& injector : injectorsToRemove) {
- qCDebug(audioclient) << "removing injector";
+ //qCDebug(audioclient) << "removing injector";
_activeLocalAudioInjectors.removeOne(injector);
}
@@ -1531,6 +1535,15 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) {
}
}
+void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) {
+ if (_warnWhenMuted != enable) {
+ _warnWhenMuted = enable;
+ if (emitSignal) {
+ emit warnWhenMutedChanged(_warnWhenMuted);
+ }
+ }
+}
+
bool AudioClient::setIsStereoInput(bool isStereoInput) {
bool stereoInputChanged = false;
if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) {
@@ -1562,15 +1575,13 @@ bool AudioClient::setIsStereoInput(bool isStereoInput) {
}
bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) {
- AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer();
+ auto injectorBuffer = injector->getLocalBuffer();
if (injectorBuffer) {
// local injectors are on the AudioInjectorsThread, so we must guard access
Lock lock(_injectorsMutex);
if (!_activeLocalAudioInjectors.contains(injector)) {
- qCDebug(audioclient) << "adding new injector";
+ //qCDebug(audioclient) << "adding new injector";
_activeLocalAudioInjectors.append(injector);
- // move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
- injectorBuffer->setParent(nullptr);
// update the flag
_localInjectorsAvailable.exchange(true, std::memory_order_release);
@@ -1586,6 +1597,11 @@ bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) {
}
}
+int AudioClient::getNumLocalInjectors() {
+ Lock lock(_injectorsMutex);
+ return _activeLocalAudioInjectors.size();
+}
+
void AudioClient::outputFormatChanged() {
_outputFrameSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * OUTPUT_CHANNEL_COUNT * _outputFormat.sampleRate()) /
_desiredOutputFormat.sampleRate();
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 87e0f68e72..7608bf5cdb 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -181,6 +181,8 @@ public:
bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; }
#endif
+ int getNumLocalInjectors();
+
public slots:
void start();
void stop();
@@ -210,6 +212,9 @@ public slots:
void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true);
bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; }
+ void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
+ bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
+
virtual bool getLocalEcho() override { return _shouldEchoLocally; }
virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; }
virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; }
@@ -236,6 +241,8 @@ public slots:
void setInputVolume(float volume, bool emitSignal = true);
void setReverb(bool reverb);
void setReverbOptions(const AudioEffectOptions* options);
+ void setLocalInjectorGain(float gain) { _localInjectorGain = gain; };
+ void setSystemInjectorGain(float gain) { _systemInjectorGain = gain; };
void outputNotify();
@@ -246,6 +253,7 @@ signals:
void inputVolumeChanged(float volume);
void muteToggled(bool muted);
void noiseReductionChanged(bool noiseReductionEnabled);
+ void warnWhenMutedChanged(bool warnWhenMutedEnabled);
void mutedByMixer();
void inputReceived(const QByteArray& inputSamples);
void inputLoudnessChanged(float loudness, bool isClipping);
@@ -365,6 +373,7 @@ private:
bool _shouldEchoLocally;
bool _shouldEchoToServer;
bool _isNoiseGateEnabled;
+ bool _warnWhenMuted;
bool _reverb;
AudioEffectOptions _scriptReverbOptions;
@@ -388,6 +397,8 @@ private:
int16_t* _outputScratchBuffer { NULL };
// for local audio (used by audio injectors thread)
+ std::atomic _localInjectorGain { 1.0f };
+ std::atomic _systemInjectorGain { 1.0f };
float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
float* _localOutputMixBuffer { NULL };
diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp
index 1581990e0c..4911917bf0 100644
--- a/libraries/audio/src/AudioInjector.cpp
+++ b/libraries/audio/src/AudioInjector.cpp
@@ -24,9 +24,10 @@
#include "AudioRingBuffer.h"
#include "AudioLogging.h"
#include "SoundCache.h"
-#include "AudioSRC.h"
#include "AudioHelpers.h"
+int metaType = qRegisterMetaType("AudioInjectorPointer");
+
AbstractAudioInterface* AudioInjector::_localAudioInterface{ nullptr };
AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs) {
@@ -51,26 +52,30 @@ AudioInjector::AudioInjector(AudioDataPointer audioData, const AudioInjectorOpti
{
}
-AudioInjector::~AudioInjector() {
- deleteLocalBuffer();
-}
+AudioInjector::~AudioInjector() {}
bool AudioInjector::stateHas(AudioInjectorState state) const {
- return (_state & state) == state;
+ return resultWithReadLock([&] {
+ return (_state & state) == state;
+ });
}
void AudioInjector::setOptions(const AudioInjectorOptions& options) {
// since options.stereo is computed from the audio stream,
// we need to copy it from existing options just in case.
- bool currentlyStereo = _options.stereo;
- bool currentlyAmbisonic = _options.ambisonic;
- _options = options;
- _options.stereo = currentlyStereo;
- _options.ambisonic = currentlyAmbisonic;
+ withWriteLock([&] {
+ bool currentlyStereo = _options.stereo;
+ bool currentlyAmbisonic = _options.ambisonic;
+ _options = options;
+ _options.stereo = currentlyStereo;
+ _options.ambisonic = currentlyAmbisonic;
+ });
}
void AudioInjector::finishNetworkInjection() {
- _state |= AudioInjectorState::NetworkInjectionFinished;
+ withWriteLock([&] {
+ _state |= AudioInjectorState::NetworkInjectionFinished;
+ });
// if we are already finished with local
// injection, then we are finished
@@ -80,35 +85,31 @@ void AudioInjector::finishNetworkInjection() {
}
void AudioInjector::finishLocalInjection() {
- _state |= AudioInjectorState::LocalInjectionFinished;
- if(_options.localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) {
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(this, "finishLocalInjection");
+ return;
+ }
+
+ bool localOnly = false;
+ withWriteLock([&] {
+ _state |= AudioInjectorState::LocalInjectionFinished;
+ localOnly = _options.localOnly;
+ });
+
+ if(localOnly || stateHas(AudioInjectorState::NetworkInjectionFinished)) {
finish();
}
}
void AudioInjector::finish() {
- _state |= AudioInjectorState::Finished;
-
+ withWriteLock([&] {
+ _state |= AudioInjectorState::Finished;
+ });
emit finished();
-
- deleteLocalBuffer();
+ _localBuffer = nullptr;
}
void AudioInjector::restart() {
- // grab the AudioInjectorManager
- auto injectorManager = DependencyManager::get();
-
- if (thread() != QThread::currentThread()) {
- QMetaObject::invokeMethod(this, "restart");
-
- if (!_options.localOnly) {
- // notify the AudioInjectorManager to wake up in case it's waiting for new injectors
- injectorManager->notifyInjectorReadyCondition();
- }
-
- return;
- }
-
// reset the current send offset to zero
_currentSendOffset = 0;
@@ -121,19 +122,23 @@ void AudioInjector::restart() {
// check our state to decide if we need extra handling for the restart request
if (stateHas(AudioInjectorState::Finished)) {
- if (!inject(&AudioInjectorManager::restartFinishedInjector)) {
+ if (!inject(&AudioInjectorManager::threadInjector)) {
qWarning() << "AudioInjector::restart failed to thread injector";
}
}
}
bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) {
- _state = AudioInjectorState::NotFinished;
+ AudioInjectorOptions options;
+ withWriteLock([&] {
+ _state = AudioInjectorState::NotFinished;
+ options = _options;
+ });
int byteOffset = 0;
- if (_options.secondOffset > 0.0f) {
- int numChannels = _options.ambisonic ? 4 : (_options.stereo ? 2 : 1);
- byteOffset = (int)(AudioConstants::SAMPLE_RATE * _options.secondOffset * numChannels);
+ if (options.secondOffset > 0.0f) {
+ int numChannels = options.ambisonic ? 4 : (options.stereo ? 2 : 1);
+ byteOffset = (int)(AudioConstants::SAMPLE_RATE * options.secondOffset * numChannels);
byteOffset *= AudioConstants::SAMPLE_SIZE;
}
_currentSendOffset = byteOffset;
@@ -143,7 +148,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInj
}
bool success = true;
- if (!_options.localOnly) {
+ if (!options.localOnly) {
auto injectorManager = DependencyManager::get();
if (!(*injectorManager.*injection)(sharedFromThis())) {
success = false;
@@ -158,7 +163,8 @@ bool AudioInjector::injectLocally() {
if (_localAudioInterface) {
if (_audioData->getNumBytes() > 0) {
- _localBuffer = new AudioInjectorLocalBuffer(_audioData);
+ _localBuffer = QSharedPointer(new AudioInjectorLocalBuffer(_audioData), &AudioInjectorLocalBuffer::deleteLater);
+ _localBuffer->moveToThread(thread());
_localBuffer->open(QIODevice::ReadOnly);
_localBuffer->setShouldLoop(_options.loop);
@@ -181,14 +187,6 @@ bool AudioInjector::injectLocally() {
return success;
}
-void AudioInjector::deleteLocalBuffer() {
- if (_localBuffer) {
- _localBuffer->stop();
- _localBuffer->deleteLater();
- _localBuffer = nullptr;
- }
-}
-
const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f);
static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1;
static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
@@ -220,6 +218,10 @@ int64_t AudioInjector::injectNextFrame() {
static int volumeOptionOffset = -1;
static int audioDataOffset = -1;
+ AudioInjectorOptions options = resultWithReadLock([&] {
+ return _options;
+ });
+
if (!_currentPacket) {
if (_currentSendOffset < 0 ||
_currentSendOffset >= (int)_audioData->getNumBytes()) {
@@ -253,7 +255,7 @@ int64_t AudioInjector::injectNextFrame() {
audioPacketStream << QUuid::createUuid();
// pack the stereo/mono type of the stream
- audioPacketStream << _options.stereo;
+ audioPacketStream << options.stereo;
// pack the flag for loopback, if requested
loopbackOptionOffset = _currentPacket->pos();
@@ -262,15 +264,16 @@ int64_t AudioInjector::injectNextFrame() {
// pack the position for injected audio
positionOptionOffset = _currentPacket->pos();
- audioPacketStream.writeRawData(reinterpret_cast(&_options.position),
- sizeof(_options.position));
+ audioPacketStream.writeRawData(reinterpret_cast(&options.position),
+ sizeof(options.position));
// pack our orientation for injected audio
- audioPacketStream.writeRawData(reinterpret_cast(&_options.orientation),
- sizeof(_options.orientation));
+ audioPacketStream.writeRawData(reinterpret_cast(&options.orientation),
+ sizeof(options.orientation));
+
+ audioPacketStream.writeRawData(reinterpret_cast(&options.position),
+ sizeof(options.position));
- audioPacketStream.writeRawData(reinterpret_cast(&_options.position),
- sizeof(_options.position));
glm::vec3 boxCorner = glm::vec3(0);
audioPacketStream.writeRawData(reinterpret_cast(&boxCorner),
sizeof(glm::vec3));
@@ -283,7 +286,7 @@ int64_t AudioInjector::injectNextFrame() {
volumeOptionOffset = _currentPacket->pos();
quint8 volume = MAX_INJECTOR_VOLUME;
audioPacketStream << volume;
- audioPacketStream << _options.ignorePenumbra;
+ audioPacketStream << options.ignorePenumbra;
audioDataOffset = _currentPacket->pos();
@@ -313,10 +316,10 @@ int64_t AudioInjector::injectNextFrame() {
_currentPacket->writePrimitive((uchar)(_localAudioInterface && _localAudioInterface->shouldLoopbackInjectors()));
_currentPacket->seek(positionOptionOffset);
- _currentPacket->writePrimitive(_options.position);
- _currentPacket->writePrimitive(_options.orientation);
+ _currentPacket->writePrimitive(options.position);
+ _currentPacket->writePrimitive(options.orientation);
- quint8 volume = packFloatGainToByte(_options.volume);
+ quint8 volume = packFloatGainToByte(options.volume);
_currentPacket->seek(volumeOptionOffset);
_currentPacket->writePrimitive(volume);
@@ -326,8 +329,8 @@ int64_t AudioInjector::injectNextFrame() {
// Might be a reasonable place to do the encode step here.
QByteArray decodedAudio;
- int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
- if (!_options.loop) {
+ int totalBytesLeftToCopy = (options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
+ if (!options.loop) {
// If we aren't looping, let's make sure we don't read past the end
int bytesLeftToRead = _audioData->getNumBytes() - _currentSendOffset;
totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, bytesLeftToRead);
@@ -342,14 +345,16 @@ int64_t AudioInjector::injectNextFrame() {
auto samplesOut = reinterpret_cast(decodedAudio.data());
// Copy and Measure the loudness of this frame
- _loudness = 0.0f;
- for (int i = 0; i < samplesLeftToCopy; ++i) {
- auto index = (currentSample + i) % _audioData->getNumSamples();
- auto sample = samples[index];
- samplesOut[i] = sample;
- _loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
- }
- _loudness /= (float)samplesLeftToCopy;
+ withWriteLock([&] {
+ _loudness = 0.0f;
+ for (int i = 0; i < samplesLeftToCopy; ++i) {
+ auto index = (currentSample + i) % _audioData->getNumSamples();
+ auto sample = samples[index];
+ samplesOut[i] = sample;
+ _loudness += abs(sample) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f);
+ }
+ _loudness /= (float)samplesLeftToCopy;
+ });
_currentSendOffset = (_currentSendOffset + totalBytesLeftToCopy) %
_audioData->getNumBytes();
@@ -371,7 +376,7 @@ int64_t AudioInjector::injectNextFrame() {
_outgoingSequenceNumber++;
}
- if (_currentSendOffset == 0 && !_options.loop) {
+ if (_currentSendOffset == 0 && !options.loop) {
finishNetworkInjection();
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
}
@@ -391,134 +396,10 @@ int64_t AudioInjector::injectNextFrame() {
// If we are falling behind by more frames than our threshold, let's skip the frames ahead
qCDebug(audio) << this << "injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames";
_nextFrame = currentFrameBasedOnElapsedTime;
- _currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData->getNumBytes();
+ _currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (options.stereo ? 2 : 1) % _audioData->getNumBytes();
}
int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS;
return std::max(INT64_C(0), playNextFrameAt - currentTime);
-}
-
-void AudioInjector::stop() {
- // trigger a call on the injector's thread to change state to finished
- QMetaObject::invokeMethod(this, "finish");
-}
-
-void AudioInjector::triggerDeleteAfterFinish() {
- // make sure this fires on the AudioInjector thread
- if (thread() != QThread::currentThread()) {
- QMetaObject::invokeMethod(this, "triggerDeleteAfterFinish", Qt::QueuedConnection);
- return;
- }
-
- if (stateHas(AudioInjectorState::Finished)) {
- stop();
- } else {
- _state |= AudioInjectorState::PendingDelete;
- }
-}
-
-AudioInjectorPointer AudioInjector::playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options) {
- AudioInjectorPointer injector = playSound(sound, options);
-
- if (injector) {
- injector->_state |= AudioInjectorState::PendingDelete;
- }
-
- return injector;
-}
-
-
-AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const AudioInjectorOptions& options) {
- if (!sound || !sound->isReady()) {
- return AudioInjectorPointer();
- }
-
- if (options.pitch == 1.0f) {
-
- AudioInjectorPointer injector = AudioInjectorPointer::create(sound, options);
-
- if (!injector->inject(&AudioInjectorManager::threadInjector)) {
- qWarning() << "AudioInjector::playSound failed to thread injector";
- }
- return injector;
-
- } else {
- using AudioConstants::AudioSample;
- using AudioConstants::SAMPLE_RATE;
- const int standardRate = SAMPLE_RATE;
- // limit pitch to 4 octaves
- const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
- const int resampledRate = glm::round(SAMPLE_RATE / pitch);
-
- auto audioData = sound->getAudioData();
- auto numChannels = audioData->getNumChannels();
- auto numFrames = audioData->getNumFrames();
-
- AudioSRC resampler(standardRate, resampledRate, numChannels);
-
- // create a resampled buffer that is guaranteed to be large enough
- const int maxOutputFrames = resampler.getMaxOutput(numFrames);
- const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
- QByteArray resampledBuffer(maxOutputSize, '\0');
- auto bufferPtr = reinterpret_cast(resampledBuffer.data());
-
- resampler.render(audioData->data(), bufferPtr, numFrames);
-
- int numSamples = maxOutputFrames * numChannels;
- auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
-
- AudioInjectorPointer injector = AudioInjectorPointer::create(newAudioData, options);
-
- if (!injector->inject(&AudioInjectorManager::threadInjector)) {
- qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
- }
- return injector;
- }
-}
-
-AudioInjectorPointer AudioInjector::playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options) {
- AudioInjectorPointer injector = playSound(audioData, options);
-
- if (injector) {
- injector->_state |= AudioInjectorState::PendingDelete;
- }
-
- return injector;
-}
-
-AudioInjectorPointer AudioInjector::playSound(AudioDataPointer audioData, const AudioInjectorOptions& options) {
- if (options.pitch == 1.0f) {
- AudioInjectorPointer injector = AudioInjectorPointer::create(audioData, options);
-
- if (!injector->inject(&AudioInjectorManager::threadInjector)) {
- qWarning() << "AudioInjector::playSound failed to thread pitch-shifted injector";
- }
- return injector;
- } else {
- using AudioConstants::AudioSample;
- using AudioConstants::SAMPLE_RATE;
- const int standardRate = SAMPLE_RATE;
- // limit pitch to 4 octaves
- const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
- const int resampledRate = glm::round(SAMPLE_RATE / pitch);
-
- auto numChannels = audioData->getNumChannels();
- auto numFrames = audioData->getNumFrames();
-
- AudioSRC resampler(standardRate, resampledRate, numChannels);
-
- // create a resampled buffer that is guaranteed to be large enough
- const int maxOutputFrames = resampler.getMaxOutput(numFrames);
- const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
- QByteArray resampledBuffer(maxOutputSize, '\0');
- auto bufferPtr = reinterpret_cast(resampledBuffer.data());
-
- resampler.render(audioData->data(), bufferPtr, numFrames);
-
- int numSamples = maxOutputFrames * numChannels;
- auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
-
- return AudioInjector::playSound(newAudioData, options);
- }
}
\ No newline at end of file
diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h
index 3c21d2eccf..555af84025 100644
--- a/libraries/audio/src/AudioInjector.h
+++ b/libraries/audio/src/AudioInjector.h
@@ -19,6 +19,8 @@
#include
#include
+#include
+
#include
#include
@@ -49,7 +51,7 @@ AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs)
// In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object
// until it dies.
-class AudioInjector : public QObject, public QEnableSharedFromThis {
+class AudioInjector : public QObject, public QEnableSharedFromThis, public ReadWriteLockable {
Q_OBJECT
public:
AudioInjector(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions);
@@ -61,40 +63,34 @@ public:
int getCurrentSendOffset() const { return _currentSendOffset; }
void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; }
- AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; }
+ QSharedPointer getLocalBuffer() const { return _localBuffer; }
AudioHRTF& getLocalHRTF() { return _localHRTF; }
AudioFOA& getLocalFOA() { return _localFOA; }
- bool isLocalOnly() const { return _options.localOnly; }
- float getVolume() const { return _options.volume; }
- bool isPositionSet() const { return _options.positionSet; }
- glm::vec3 getPosition() const { return _options.position; }
- glm::quat getOrientation() const { return _options.orientation; }
- bool isStereo() const { return _options.stereo; }
- bool isAmbisonic() const { return _options.ambisonic; }
+ float getLoudness() const { return resultWithReadLock([&] { return _loudness; }); }
+ bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); }
+
+ bool isLocalOnly() const { return resultWithReadLock([&] { return _options.localOnly; }); }
+ float getVolume() const { return resultWithReadLock([&] { return _options.volume; }); }
+ bool isPositionSet() const { return resultWithReadLock([&] { return _options.positionSet; }); }
+ glm::vec3 getPosition() const { return resultWithReadLock([&] { return _options.position; }); }
+ glm::quat getOrientation() const { return resultWithReadLock([&] { return _options.orientation; }); }
+ bool isStereo() const { return resultWithReadLock([&] { return _options.stereo; }); }
+ bool isAmbisonic() const { return resultWithReadLock([&] { return _options.ambisonic; }); }
+
+ AudioInjectorOptions getOptions() const { return resultWithReadLock([&] { return _options; }); }
+ void setOptions(const AudioInjectorOptions& options);
bool stateHas(AudioInjectorState state) const ;
static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; }
- static AudioInjectorPointer playSoundAndDelete(SharedSoundPointer sound, const AudioInjectorOptions& options);
- static AudioInjectorPointer playSound(SharedSoundPointer sound, const AudioInjectorOptions& options);
- static AudioInjectorPointer playSoundAndDelete(AudioDataPointer audioData, const AudioInjectorOptions& options);
- static AudioInjectorPointer playSound(AudioDataPointer audioData, const AudioInjectorOptions& options);
+ void restart();
+ void finish();
+
+ void finishNetworkInjection();
public slots:
- void restart();
-
- void stop();
- void triggerDeleteAfterFinish();
-
- const AudioInjectorOptions& getOptions() const { return _options; }
- void setOptions(const AudioInjectorOptions& options);
-
- float getLoudness() const { return _loudness; }
- bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); }
- void finish();
void finishLocalInjection();
- void finishNetworkInjection();
signals:
void finished();
@@ -104,7 +100,6 @@ private:
int64_t injectNextFrame();
bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&));
bool injectLocally();
- void deleteLocalBuffer();
static AbstractAudioInterface* _localAudioInterface;
@@ -116,7 +111,7 @@ private:
float _loudness { 0.0f };
int _currentSendOffset { 0 };
std::unique_ptr _currentPacket { nullptr };
- AudioInjectorLocalBuffer* _localBuffer { nullptr };
+ QSharedPointer _localBuffer { nullptr };
int64_t _nextFrame { 0 };
std::unique_ptr _frameTimer { nullptr };
@@ -128,4 +123,6 @@ private:
friend class AudioInjectorManager;
};
+Q_DECLARE_METATYPE(AudioInjectorPointer)
+
#endif // hifi_AudioInjector_h
diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.cpp b/libraries/audio/src/AudioInjectorLocalBuffer.cpp
index 015d87e03b..680513abf5 100644
--- a/libraries/audio/src/AudioInjectorLocalBuffer.cpp
+++ b/libraries/audio/src/AudioInjectorLocalBuffer.cpp
@@ -16,6 +16,10 @@ AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(AudioDataPointer audioData) :
{
}
+AudioInjectorLocalBuffer::~AudioInjectorLocalBuffer() {
+ stop();
+}
+
void AudioInjectorLocalBuffer::stop() {
_isStopped = true;
@@ -30,9 +34,8 @@ bool AudioInjectorLocalBuffer::seek(qint64 pos) {
}
}
-
qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
- if (!_isStopped) {
+ if (!_isStopped && _audioData) {
// first copy to the end of the raw audio
int bytesToEnd = (int)_audioData->getNumBytes() - _currentOffset;
diff --git a/libraries/audio/src/AudioInjectorLocalBuffer.h b/libraries/audio/src/AudioInjectorLocalBuffer.h
index e0f8847883..2f73e5b313 100644
--- a/libraries/audio/src/AudioInjectorLocalBuffer.h
+++ b/libraries/audio/src/AudioInjectorLocalBuffer.h
@@ -22,6 +22,7 @@ class AudioInjectorLocalBuffer : public QIODevice {
Q_OBJECT
public:
AudioInjectorLocalBuffer(AudioDataPointer audioData);
+ ~AudioInjectorLocalBuffer();
void stop();
diff --git a/libraries/audio/src/AudioInjectorManager.cpp b/libraries/audio/src/AudioInjectorManager.cpp
index f30d3093ec..e5ffc77798 100644
--- a/libraries/audio/src/AudioInjectorManager.cpp
+++ b/libraries/audio/src/AudioInjectorManager.cpp
@@ -14,11 +14,14 @@
#include
#include
+#include
#include "AudioConstants.h"
#include "AudioInjector.h"
#include "AudioLogging.h"
+#include "AudioSRC.h"
+
AudioInjectorManager::~AudioInjectorManager() {
_shouldStop = true;
@@ -30,7 +33,7 @@ AudioInjectorManager::~AudioInjectorManager() {
auto& timePointerPair = _injectors.top();
// ask it to stop and be deleted
- timePointerPair.second->stop();
+ timePointerPair.second->finish();
_injectors.pop();
}
@@ -46,6 +49,8 @@ AudioInjectorManager::~AudioInjectorManager() {
_thread->quit();
_thread->wait();
}
+
+ moveToThread(qApp->thread());
}
void AudioInjectorManager::createThread() {
@@ -55,6 +60,8 @@ void AudioInjectorManager::createThread() {
// when the thread is started, have it call our run to handle injection of audio
connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection);
+ moveToThread(_thread);
+
// start the thread
_thread->start();
}
@@ -141,36 +148,7 @@ bool AudioInjectorManager::wouldExceedLimits() { // Should be called inside of a
bool AudioInjectorManager::threadInjector(const AudioInjectorPointer& injector) {
if (_shouldStop) {
- qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
- return false;
- }
-
- // guard the injectors vector with a mutex
- Lock lock(_injectorsMutex);
-
- if (wouldExceedLimits()) {
- return false;
- } else {
- if (!_thread) {
- createThread();
- }
-
- // move the injector to the QThread
- injector->moveToThread(_thread);
-
- // add the injector to the queue with a send timestamp of now
- _injectors.emplace(usecTimestampNow(), injector);
-
- // notify our wait condition so we can inject two frames for this injector immediately
- _injectorReady.notify_one();
-
- return true;
- }
-}
-
-bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& injector) {
- if (_shouldStop) {
- qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
+ qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
return false;
}
@@ -188,3 +166,192 @@ bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& i
}
return true;
}
+
+AudioInjectorPointer AudioInjectorManager::playSound(const SharedSoundPointer& sound, const AudioInjectorOptions& options, bool setPendingDelete) {
+ if (_shouldStop) {
+ qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
+ return nullptr;
+ }
+
+ AudioInjectorPointer injector = nullptr;
+ if (sound && sound->isReady()) {
+ if (options.pitch == 1.0f) {
+ injector = QSharedPointer(new AudioInjector(sound, options), &AudioInjector::deleteLater);
+ } else {
+ using AudioConstants::AudioSample;
+ using AudioConstants::SAMPLE_RATE;
+ const int standardRate = SAMPLE_RATE;
+ // limit pitch to 4 octaves
+ const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
+ const int resampledRate = glm::round(SAMPLE_RATE / pitch);
+
+ auto audioData = sound->getAudioData();
+ auto numChannels = audioData->getNumChannels();
+ auto numFrames = audioData->getNumFrames();
+
+ AudioSRC resampler(standardRate, resampledRate, numChannels);
+
+ // create a resampled buffer that is guaranteed to be large enough
+ const int maxOutputFrames = resampler.getMaxOutput(numFrames);
+ const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
+ QByteArray resampledBuffer(maxOutputSize, '\0');
+ auto bufferPtr = reinterpret_cast(resampledBuffer.data());
+
+ resampler.render(audioData->data(), bufferPtr, numFrames);
+
+ int numSamples = maxOutputFrames * numChannels;
+ auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
+
+ injector = QSharedPointer(new AudioInjector(newAudioData, options), &AudioInjector::deleteLater);
+ }
+ }
+
+ if (!injector) {
+ return nullptr;
+ }
+
+ if (setPendingDelete) {
+ injector->_state |= AudioInjectorState::PendingDelete;
+ }
+
+ injector->moveToThread(_thread);
+ injector->inject(&AudioInjectorManager::threadInjector);
+
+ return injector;
+}
+
+AudioInjectorPointer AudioInjectorManager::playSound(const AudioDataPointer& audioData, const AudioInjectorOptions& options, bool setPendingDelete) {
+ if (_shouldStop) {
+ qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down.";
+ return nullptr;
+ }
+
+ AudioInjectorPointer injector = nullptr;
+ if (options.pitch == 1.0f) {
+ injector = QSharedPointer(new AudioInjector(audioData, options), &AudioInjector::deleteLater);
+ } else {
+ using AudioConstants::AudioSample;
+ using AudioConstants::SAMPLE_RATE;
+ const int standardRate = SAMPLE_RATE;
+ // limit pitch to 4 octaves
+ const float pitch = glm::clamp(options.pitch, 1 / 16.0f, 16.0f);
+ const int resampledRate = glm::round(SAMPLE_RATE / pitch);
+
+ auto numChannels = audioData->getNumChannels();
+ auto numFrames = audioData->getNumFrames();
+
+ AudioSRC resampler(standardRate, resampledRate, numChannels);
+
+ // create a resampled buffer that is guaranteed to be large enough
+ const int maxOutputFrames = resampler.getMaxOutput(numFrames);
+ const int maxOutputSize = maxOutputFrames * numChannels * sizeof(AudioSample);
+ QByteArray resampledBuffer(maxOutputSize, '\0');
+ auto bufferPtr = reinterpret_cast(resampledBuffer.data());
+
+ resampler.render(audioData->data(), bufferPtr, numFrames);
+
+ int numSamples = maxOutputFrames * numChannels;
+ auto newAudioData = AudioData::make(numSamples, numChannels, bufferPtr);
+
+ injector = QSharedPointer(new AudioInjector(newAudioData, options), &AudioInjector::deleteLater);
+ }
+
+ if (!injector) {
+ return nullptr;
+ }
+
+ if (setPendingDelete) {
+ injector->_state |= AudioInjectorState::PendingDelete;
+ }
+
+ injector->moveToThread(_thread);
+ injector->inject(&AudioInjectorManager::threadInjector);
+
+ return injector;
+}
+
+void AudioInjectorManager::setOptionsAndRestart(const AudioInjectorPointer& injector, const AudioInjectorOptions& options) {
+ if (!injector) {
+ return;
+ }
+
+ if (QThread::currentThread() != _thread) {
+ QMetaObject::invokeMethod(this, "setOptionsAndRestart", Q_ARG(const AudioInjectorPointer&, injector), Q_ARG(const AudioInjectorOptions&, options));
+ _injectorReady.notify_one();
+ return;
+ }
+
+ injector->setOptions(options);
+ injector->restart();
+}
+
+void AudioInjectorManager::restart(const AudioInjectorPointer& injector) {
+ if (!injector) {
+ return;
+ }
+
+ if (QThread::currentThread() != _thread) {
+ QMetaObject::invokeMethod(this, "restart", Q_ARG(const AudioInjectorPointer&, injector));
+ _injectorReady.notify_one();
+ return;
+ }
+
+ injector->restart();
+}
+
+void AudioInjectorManager::setOptions(const AudioInjectorPointer& injector, const AudioInjectorOptions& options) {
+ if (!injector) {
+ return;
+ }
+
+ if (QThread::currentThread() != _thread) {
+ QMetaObject::invokeMethod(this, "setOptions", Q_ARG(const AudioInjectorPointer&, injector), Q_ARG(const AudioInjectorOptions&, options));
+ _injectorReady.notify_one();
+ return;
+ }
+
+ injector->setOptions(options);
+}
+
+AudioInjectorOptions AudioInjectorManager::getOptions(const AudioInjectorPointer& injector) {
+ if (!injector) {
+ return AudioInjectorOptions();
+ }
+
+ return injector->getOptions();
+}
+
+float AudioInjectorManager::getLoudness(const AudioInjectorPointer& injector) {
+ if (!injector) {
+ return 0.0f;
+ }
+
+ return injector->getLoudness();
+}
+
+bool AudioInjectorManager::isPlaying(const AudioInjectorPointer& injector) {
+ if (!injector) {
+ return false;
+ }
+
+ return injector->isPlaying();
+}
+
+void AudioInjectorManager::stop(const AudioInjectorPointer& injector) {
+ if (!injector) {
+ return;
+ }
+
+ if (QThread::currentThread() != _thread) {
+ QMetaObject::invokeMethod(this, "stop", Q_ARG(const AudioInjectorPointer&, injector));
+ _injectorReady.notify_one();
+ return;
+ }
+
+ injector->finish();
+}
+
+size_t AudioInjectorManager::getNumInjectors() {
+ Lock lock(_injectorsMutex);
+ return _injectors.size();
+}
\ No newline at end of file
diff --git a/libraries/audio/src/AudioInjectorManager.h b/libraries/audio/src/AudioInjectorManager.h
index 9aca3014e3..cb243f23de 100644
--- a/libraries/audio/src/AudioInjectorManager.h
+++ b/libraries/audio/src/AudioInjectorManager.h
@@ -30,8 +30,27 @@ class AudioInjectorManager : public QObject, public Dependency {
SINGLETON_DEPENDENCY
public:
~AudioInjectorManager();
+
+ AudioInjectorPointer playSound(const SharedSoundPointer& sound, const AudioInjectorOptions& options, bool setPendingDelete = false);
+ AudioInjectorPointer playSound(const AudioDataPointer& audioData, const AudioInjectorOptions& options, bool setPendingDelete = false);
+
+ size_t getNumInjectors();
+
+public slots:
+ void setOptionsAndRestart(const AudioInjectorPointer& injector, const AudioInjectorOptions& options);
+ void restart(const AudioInjectorPointer& injector);
+
+ void setOptions(const AudioInjectorPointer& injector, const AudioInjectorOptions& options);
+ AudioInjectorOptions getOptions(const AudioInjectorPointer& injector);
+
+ float getLoudness(const AudioInjectorPointer& injector);
+ bool isPlaying(const AudioInjectorPointer& injector);
+
+ void stop(const AudioInjectorPointer& injector);
+
private slots:
void run();
+
private:
using TimeInjectorPointerPair = std::pair;
@@ -49,11 +68,10 @@ private:
using Lock = std::unique_lock;
bool threadInjector(const AudioInjectorPointer& injector);
- bool restartFinishedInjector(const AudioInjectorPointer& injector);
void notifyInjectorReadyCondition() { _injectorReady.notify_one(); }
bool wouldExceedLimits();
- AudioInjectorManager() {};
+ AudioInjectorManager() { createThread(); }
AudioInjectorManager(const AudioInjectorManager&) = delete;
AudioInjectorManager& operator=(const AudioInjectorManager&) = delete;
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index f3e671143b..74940b44cc 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -43,7 +43,6 @@
using namespace std;
-const int NUM_BODY_CONE_SIDES = 9;
const float CHAT_MESSAGE_SCALE = 0.0015f;
const float CHAT_MESSAGE_HEIGHT = 0.1f;
const float DISPLAYNAME_FADE_TIME = 0.5f;
@@ -373,13 +372,6 @@ bool Avatar::applyGrabChanges() {
target->removeGrab(grab);
_avatarGrabs.erase(itr);
grabAddedOrRemoved = true;
- if (isMyAvatar()) {
- const EntityItemPointer& entity = std::dynamic_pointer_cast(target);
- if (entity && entity->getEntityHostType() == entity::HostType::AVATAR && entity->getSimulationOwner().getID() == getID()) {
- EntityItemProperties properties = entity->getProperties();
- sendPacket(entity->getID(), properties);
- }
- }
} else {
undeleted.push_back(id);
}
@@ -660,9 +652,8 @@ void Avatar::fadeIn(render::ScenePointer scene) {
scene->enqueueTransaction(transaction);
}
-void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) {
+void Avatar::fadeOut(render::Transaction& transaction, KillAvatarReason reason) {
render::Transition::Type transitionType = render::Transition::USER_LEAVE_DOMAIN;
- render::Transaction transaction;
if (reason == KillAvatarReason::YourAvatarEnteredTheirBubble) {
transitionType = render::Transition::BUBBLE_ISECT_TRESPASSER;
@@ -670,7 +661,6 @@ void Avatar::fadeOut(render::ScenePointer scene, KillAvatarReason reason) {
transitionType = render::Transition::BUBBLE_ISECT_OWNER;
}
fade(transaction, transitionType);
- scene->enqueueTransaction(transaction);
}
void Avatar::fade(render::Transaction& transaction, render::Transition::Type type) {
@@ -680,19 +670,6 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ
transaction.addTransitionToItem(itemId, type, _renderItemID);
}
}
- _isFading = true;
-}
-
-void Avatar::updateFadingStatus() {
- if (_isFading) {
- render::Transaction transaction;
- transaction.queryTransitionOnItem(_renderItemID, [this](render::ItemID id, const render::Transition* transition) {
- if (!transition || transition->isFinished) {
- _isFading = false;
- }
- });
- AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
- }
}
void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
@@ -1661,60 +1638,6 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) {
return bytesRead;
}
-int Avatar::_jointConesID = GeometryCache::UNKNOWN_ID;
-
-// render a makeshift cone section that serves as a body part connecting joint spheres
-void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
- float radius1, float radius2, const glm::vec4& color) {
-
- auto geometryCache = DependencyManager::get();
-
- if (_jointConesID == GeometryCache::UNKNOWN_ID) {
- _jointConesID = geometryCache->allocateID();
- }
-
- glm::vec3 axis = position2 - position1;
- float length = glm::length(axis);
-
- if (length > 0.0f) {
-
- axis /= length;
-
- glm::vec3 perpSin = glm::vec3(1.0f, 0.0f, 0.0f);
- glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin));
- perpSin = glm::cross(perpCos, axis);
-
- float angleb = 0.0f;
- QVector points;
-
- for (int i = 0; i < NUM_BODY_CONE_SIDES; i ++) {
-
- // the rectangles that comprise the sides of the cone section are
- // referenced by "a" and "b" in one dimension, and "1", and "2" in the other dimension.
- int anglea = angleb;
- angleb = ((float)(i+1) / (float)NUM_BODY_CONE_SIDES) * TWO_PI;
-
- float sa = sinf(anglea);
- float sb = sinf(angleb);
- float ca = cosf(anglea);
- float cb = cosf(angleb);
-
- glm::vec3 p1a = position1 + perpSin * sa * radius1 + perpCos * ca * radius1;
- glm::vec3 p1b = position1 + perpSin * sb * radius1 + perpCos * cb * radius1;
- glm::vec3 p2a = position2 + perpSin * sa * radius2 + perpCos * ca * radius2;
- glm::vec3 p2b = position2 + perpSin * sb * radius2 + perpCos * cb * radius2;
-
- points << p1a << p1b << p2a << p1b << p2a << p2b;
- }
-
- PROFILE_RANGE_BATCH(batch, __FUNCTION__);
- // TODO: this is really inefficient constantly recreating these vertices buffers. It would be
- // better if the avatars cached these buffers for each of the joints they are rendering
- geometryCache->updateVertices(_jointConesID, points, color);
- geometryCache->renderVertices(batch, gpu::TRIANGLES, _jointConesID);
- }
-}
-
float Avatar::getSkeletonHeight() const {
Extents extents = _skeletonModel->getBindExtents();
return extents.maximum.y - extents.minimum.y;
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
index 6c31f9fc93..aef5ac09e9 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
@@ -127,7 +127,12 @@ private:
class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaModelPayload {
Q_OBJECT
- // This property has JSDoc in MyAvatar.h.
+ /*jsdoc
+ * @comment IMPORTANT: The JSDoc for the following properties should be copied to MyAvatar.h.
+ *
+ * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the
+ * registration point of the 3D model.
+ */
Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset)
public:
@@ -175,7 +180,6 @@ public:
/// Returns the distance to use as a LOD parameter.
float getLODDistance() const;
- virtual bool isMyAvatar() const override { return false; }
virtual void createOrb() { }
enum class LoadingStatus {
@@ -196,36 +200,52 @@ public:
virtual QStringList getJointNames() const override;
/**jsdoc
+ * Gets the default rotation of a joint (in the current avatar) relative to its parent.
+ * For information on the joint hierarchy used, see
+ * Avatar Standards.
* @function MyAvatar.getDefaultJointRotation
- * @param {number} index
- * @returns {Quat}
+ * @param {number} index - The joint index.
+ * @returns {Quat} The default rotation of the joint if the joint index is valid, otherwise {@link Quat(0)|Quat.IDENTITY}.
*/
Q_INVOKABLE virtual glm::quat getDefaultJointRotation(int index) const;
/**jsdoc
+ * Gets the default translation of a joint (in the current avatar) relative to its parent, in model coordinates.
+ * Warning: These coordinates are not necessarily in meters.
+ * For information on the joint hierarchy used, see
+ * Avatar Standards.
* @function MyAvatar.getDefaultJointTranslation
- * @param {number} index
- * @returns {Vec3}
+ * @param {number} index - The joint index.
+ * @returns {Vec3} The default translation of the joint (in model coordinates) if the joint index is valid, otherwise
+ * {@link Vec3(0)|Vec3.ZERO}.
*/
Q_INVOKABLE virtual glm::vec3 getDefaultJointTranslation(int index) const;
/**jsdoc
- * Provides read only access to the default joint rotations in avatar coordinates.
+ * Gets the default joint rotations in avatar coordinates.
* The default pose of the avatar is defined by the position and orientation of all bones
* in the avatar's model file. Typically this is a T-pose.
* @function MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame
- * @param index {number} index number
- * @returns {Quat} The rotation of this joint in avatar coordinates.
+ * @param index {number} - The joint index.
+ * @returns {Quat} The default rotation of the joint in avatar coordinates.
+ * @example Report the default rotation of your avatar's head joint relative to your avatar.
+ * var headIndex = MyAvatar.getJointIndex("Head");
+ * var defaultHeadRotation = MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(headIndex);
+ * print("Default head rotation: " + JSON.stringify(Quat.safeEulerAngles(defaultHeadRotation))); // Degrees
*/
Q_INVOKABLE virtual glm::quat getAbsoluteDefaultJointRotationInObjectFrame(int index) const;
/**jsdoc
- * Provides read only access to the default joint translations in avatar coordinates.
+ * Gets the default joint translations in avatar coordinates.
* The default pose of the avatar is defined by the position and orientation of all bones
* in the avatar's model file. Typically this is a T-pose.
* @function MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame
- * @param index {number} index number
- * @returns {Vec3} The position of this joint in avatar coordinates.
+ * @param index {number} - The joint index.
+ * @returns {Vec3} The default position of the joint in avatar coordinates.
+ * @example Report the default translation of your avatar's head joint relative to your avatar.
+ * var headIndex = MyAvatar.getJointIndex("Head");
+ * var defaultHeadTranslation = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(headIndex);
+ * print("Default head translation: " + JSON.stringify(defaultHeadTranslation));
*/
Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const;
@@ -233,59 +253,88 @@ public:
virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const override;
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
+
+ /**jsdoc
+ * Sets the rotation of a joint relative to the avatar.
+ * Warning: Not able to be used in the MyAvatar
API.
+ * @function MyAvatar.setAbsoluteJointRotationInObjectFrame
+ * @param {number} index - The index of the joint. Not used.
+ * @param {Quat} rotation - The rotation of the joint relative to the avatar. Not used.
+ * @returns {boolean} false
.
+ */
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
+
+ /**jsdoc
+ * Sets the translation of a joint relative to the avatar.
+ * Warning: Not able to be used in the MyAvatar
API.
+ * @function MyAvatar.setAbsoluteJointTranslationInObjectFrame
+ * @param {number} index - The index of the joint. Not used.
+ * @param {Vec3} translation - The translation of the joint relative to the avatar. Not used.
+ * @returns {boolean} false
.
+ */
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; }
virtual glm::vec3 getSpine2SplineOffset() const { return _spine2SplineOffset; }
virtual float getSpine2SplineRatio() const { return _spine2SplineRatio; }
// world-space to avatar-space rigconversion functions
/**jsdoc
- * @function MyAvatar.worldToJointPoint
- * @param {Vec3} position
- * @param {number} [jointIndex=-1]
- * @returns {Vec3}
- */
+ * Transforms a position in world coordinates to a position in a joint's coordinates, or avatar coordinates if no joint is
+ * specified.
+ * @function MyAvatar.worldToJointPoint
+ * @param {Vec3} position - The position in world coordinates.
+ * @param {number} [jointIndex=-1] - The index of the joint.
+ * @returns {Vec3} The position in the joint's coordinate system, or avatar coordinate system if no joint is specified.
+ */
Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
- * @function MyAvatar.worldToJointDirection
- * @param {Vec3} direction
- * @param {number} [jointIndex=-1]
- * @returns {Vec3}
- */
+ * Transforms a direction in world coordinates to a direction in a joint's coordinates, or avatar coordinates if no joint
+ * is specified.
+ * @function MyAvatar.worldToJointDirection
+ * @param {Vec3} direction - The direction in world coordinates.
+ * @param {number} [jointIndex=-1] - The index of the joint.
+ * @returns {Vec3} The direction in the joint's coordinate system, or avatar coordinate system if no joint is specified.
+ */
Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
- * @function MyAvatar.worldToJointRotation
- * @param {Quat} rotation
- * @param {number} [jointIndex=-1]
- * @returns {Quat}
+ * Transforms a rotation in world coordinates to a rotation in a joint's coordinates, or avatar coordinates if no joint is
+ * specified.
+ * @function MyAvatar.worldToJointRotation
+ * @param {Quat} rotation - The rotation in world coordinates.
+ * @param {number} [jointIndex=-1] - The index of the joint.
+ * @returns {Quat} The rotation in the joint's coordinate system, or avatar coordinate system if no joint is specified.
*/
Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const;
-
/**jsdoc
- * @function MyAvatar.jointToWorldPoint
- * @param {vec3} position
- * @param {number} [jointIndex=-1]
- * @returns {Vec3}
- */
+ * Transforms a position in a joint's coordinates, or avatar coordinates if no joint is specified, to a position in world
+ * coordinates.
+ * @function MyAvatar.jointToWorldPoint
+ * @param {Vec3} position - The position in joint coordinates, or avatar coordinates if no joint is specified.
+ * @param {number} [jointIndex=-1] - The index of the joint.
+ * @returns {Vec3} The position in world coordinates.
+ */
Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const;
/**jsdoc
- * @function MyAvatar.jointToWorldDirection
- * @param {Vec3} direction
- * @param {number} [jointIndex=-1]
- * @returns {Vec3}
- */
+ * Transforms a direction in a joint's coordinates, or avatar coordinates if no joint is specified, to a direction in world
+ * coordinates.
+ * @function MyAvatar.jointToWorldDirection
+ * @param {Vec3} direction - The direction in joint coordinates, or avatar coordinates if no joint is specified.
+ * @param {number} [jointIndex=-1] - The index of the joint.
+ * @returns {Vec3} The direction in world coordinates.
+ */
Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const;
/**jsdoc
- * @function MyAvatar.jointToWorldRotation
- * @param {Quat} rotation
- * @param {number} [jointIndex=-1]
- * @returns {Quat}
- */
+ * Transforms a rotation in a joint's coordinates, or avatar coordinates if no joint is specified, to a rotation in world
+ * coordinates.
+ * @function MyAvatar.jointToWorldRotation
+ * @param {Quat} rotation - The rotation in joint coordinates, or avatar coordinates if no joint is specified.
+ * @param {number} [jointIndex=-1] - The index of the joint.
+ * @returns {Quat} The rotation in world coordinates.
+ */
Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const;
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
@@ -296,11 +345,8 @@ public:
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
- static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
- float radius1, float radius2, const glm::vec4& color);
-
/**jsdoc
- * Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
+ * Sets the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* with an offset of { x: 0, y: 0.1, z: 0 }
, your avatar will appear to be raised off the ground slightly.
* @function MyAvatar.setSkeletonOffset
* @param {Vec3} offset - The skeleton offset to set.
@@ -316,7 +362,7 @@ public:
Q_INVOKABLE void setSkeletonOffset(const glm::vec3& offset);
/**jsdoc
- * Get the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
+ * Gets the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* with an offset of { x: 0, y: 0.1, z: 0 }
, your avatar will appear to be raised off the ground slightly.
* @function MyAvatar.getSkeletonOffset
* @returns {Vec3} The current skeleton offset.
@@ -328,7 +374,7 @@ public:
virtual glm::vec3 getSkeletonPosition() const;
/**jsdoc
- * Get the position of a joint in the current avatar.
+ * Gets the position of a joint in the current avatar.
* @function MyAvatar.getJointPosition
* @param {number} index - The index of the joint.
* @returns {Vec3} The position of the joint in world coordinates.
@@ -336,7 +382,7 @@ public:
Q_INVOKABLE glm::vec3 getJointPosition(int index) const;
/**jsdoc
- * Get the position of a joint in the current avatar.
+ * Gets the position of a joint in the current avatar.
* @function MyAvatar.getJointPosition
* @param {string} name - The name of the joint.
* @returns {Vec3} The position of the joint in world coordinates.
@@ -346,7 +392,7 @@ public:
Q_INVOKABLE glm::vec3 getJointPosition(const QString& name) const;
/**jsdoc
- * Get the position of the current avatar's neck in world coordinates.
+ * Gets the position of the current avatar's neck in world coordinates.
* @function MyAvatar.getNeckPosition
* @returns {Vec3} The position of the neck in world coordinates.
* @example Report the position of your avatar's neck.
@@ -355,8 +401,9 @@ public:
Q_INVOKABLE glm::vec3 getNeckPosition() const;
/**jsdoc
+ * Gets the current acceleration of the avatar.
* @function MyAvatar.getAcceleration
- * @returns {Vec3}
+ * @returns {Vec3} The current acceleration of the avatar.
*/
Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; }
@@ -380,47 +427,55 @@ public:
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
float computeMass();
/**jsdoc
- * Get the position of the current avatar's feet (or rather, bottom of its collision capsule) in world coordinates.
+ * Gets the position of the current avatar's feet (or rather, bottom of its collision capsule) in world coordinates.
* @function MyAvatar.getWorldFeetPosition
* @returns {Vec3} The position of the avatar's feet in world coordinates.
- */
+ */
Q_INVOKABLE glm::vec3 getWorldFeetPosition();
void setPositionViaScript(const glm::vec3& position) override;
void setOrientationViaScript(const glm::quat& orientation) override;
/**jsdoc
+ * Gets the ID of the entity of avatar that the avatar is parented to.
* @function MyAvatar.getParentID
- * @returns {Uuid}
+ * @returns {Uuid} The ID of the entity or avatar that the avatar is parented to. {@link Uuid|Uuid.NULL} if not parented.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual const QUuid getParentID() const override { return SpatiallyNestable::getParentID(); }
/**jsdoc
+ * Sets the ID of the entity of avatar that the avatar is parented to.
* @function MyAvatar.setParentID
- * @param {Uuid} parentID
+ * @param {Uuid} parentID - The ID of the entity or avatar that the avatar should be parented to. Set to
+ * {@link Uuid|Uuid.NULL} to unparent.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual void setParentID(const QUuid& parentID) override;
/**jsdoc
+ * Gets the joint of the entity or avatar that the avatar is parented to.
* @function MyAvatar.getParentJointIndex
- * @returns {number}
+ * @returns {number} The joint of the entity or avatar that the avatar is parented to. 65535
or
+ * -1
if parented to the entity or avatar's position and orientation rather than a joint.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual quint16 getParentJointIndex() const override { return SpatiallyNestable::getParentJointIndex(); }
/**jsdoc
+ * Sets the joint of the entity or avatar that the avatar is parented to.
* @function MyAvatar.setParentJointIndex
- * @param {number} parentJointIndex
+ * @param {number} parentJointIndex - he joint of the entity or avatar that the avatar should be parented to. Use
+ * 65535
or -1
to parent to the entity or avatar's position and orientation rather than a
+ * joint.
*/
// This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript.
Q_INVOKABLE virtual void setParentJointIndex(quint16 parentJointIndex) override;
/**jsdoc
- * Returns an array of joints, where each joint is an object containing name, index, and parentIndex fields.
+ * Gets information on all the joints in the avatar's skeleton.
* @function MyAvatar.getSkeleton
- * @returns {MyAvatar.SkeletonJoint[]} A list of information about each joint in this avatar's skeleton.
+ * @returns {MyAvatar.SkeletonJoint[]} Information about each joint in the avatar's skeleton.
*/
/**jsdoc
* Information about a single joint in an Avatar's skeleton hierarchy.
@@ -446,8 +501,9 @@ public:
/**jsdoc
* @function MyAvatar.getSimulationRate
- * @param {string} [rateName=""]
- * @returns {number}
+ * @param {string} [rateName=""] - Rate name.
+ * @returns {number} Simulation rate.
+ * @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE float getSimulationRate(const QString& rateName = QString("")) const;
@@ -464,9 +520,7 @@ public:
bool isMoving() const { return _moving; }
void fadeIn(render::ScenePointer scene);
- void fadeOut(render::ScenePointer scene, KillAvatarReason reason);
- bool isFading() const { return _isFading; }
- void updateFadingStatus();
+ void fadeOut(render::Transaction& transaction, KillAvatarReason reason);
// JSDoc is in AvatarData.h.
Q_INVOKABLE virtual float getEyeHeight() const override;
@@ -503,6 +557,13 @@ public:
uint32_t appendSubMetaItems(render::ItemIDs& subItems);
signals:
+ /**jsdoc
+ * Triggered when the avatar's target scale is changed. The target scale is the desired scale of the avatar without any
+ * restrictions on permissible scale values imposed by the domain.
+ * @function MyAvatar.targetScaleChanged
+ * @param {number} targetScale - The avatar's target scale.
+ * @returns Signal
+ */
void targetScaleChanged(float targetScale);
public slots:
@@ -511,7 +572,7 @@ public slots:
// thread safe, will return last valid palm from cache
/**jsdoc
- * Get the position of the left palm in world coordinates.
+ * Gets the position of the left palm in world coordinates.
* @function MyAvatar.getLeftPalmPosition
* @returns {Vec3} The position of the left palm in world coordinates.
* @example Report the position of your avatar's left palm.
@@ -520,15 +581,16 @@ public slots:
glm::vec3 getLeftPalmPosition() const;
/**jsdoc
- * Get the rotation of the left palm in world coordinates.
+ * Gets the rotation of the left palm in world coordinates.
* @function MyAvatar.getLeftPalmRotation
* @returns {Quat} The rotation of the left palm in world coordinates.
* @example Report the rotation of your avatar's left palm.
* print(JSON.stringify(MyAvatar.getLeftPalmRotation()));
*/
glm::quat getLeftPalmRotation() const;
+
/**jsdoc
- * Get the position of the right palm in world coordinates.
+ * Gets the position of the right palm in world coordinates.
* @function MyAvatar.getRightPalmPosition
* @returns {Vec3} The position of the right palm in world coordinates.
* @example Report the position of your avatar's right palm.
@@ -545,21 +607,26 @@ public slots:
*/
glm::quat getRightPalmRotation() const;
+ /**jsdoc
+ * @function MyAvatar.setModelURLFinished
+ * @param {boolean} success
+ * @deprecated This function is deprecated and will be removed.
+ */
// hooked up to Model::setURLFinished signal
void setModelURLFinished(bool success);
/**jsdoc
* @function MyAvatar.rigReady
- * @returns {Signal}
+ * @deprecated This function is deprecated and will be removed.
*/
// Hooked up to Model::rigReady signal
void rigReady();
/**jsdoc
* @function MyAvatar.rigReset
- * @returns {Signal}
+ * @deprecated This function is deprecated and will be removed.
*/
- // Jooked up to Model::rigReset signal
+ // Hooked up to Model::rigReset signal
void rigReset();
protected:
@@ -608,7 +675,7 @@ protected:
// protected methods...
bool isLookingAtMe(AvatarSharedPointer avatar) const;
- virtual void sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { }
+ virtual void sendPacket(const QUuid& entityID) const { }
bool applyGrabChanges();
void relayJointDataToChildren();
@@ -658,15 +725,12 @@ protected:
bool _initialized { false };
bool _isAnimatingScale { false };
bool _mustFadeIn { false };
- bool _isFading { false };
bool _reconstructSoftEntitiesJointMap { false };
float _modelScale { 1.0f };
AvatarTransit _transit;
std::mutex _transitLock;
- static int _jointConesID;
-
int _voiceSphereID;
float _displayNameTargetAlpha { 1.0f };
diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
index ea71ff128c..fbcf36a8c9 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
@@ -338,24 +338,20 @@ void SkeletonModel::computeBoundingShape() {
void SkeletonModel::renderBoundingCollisionShapes(RenderArgs* args, gpu::Batch& batch, float scale, float alpha) {
auto geometryCache = DependencyManager::get();
// draw a blue sphere at the capsule top point
- glm::vec3 topPoint = _translation + getRotation() * (scale * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * Vectors::UNIT_Y));
-
+ glm::vec3 topPoint = _translation + _rotation * (scale * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * Vectors::UNIT_Y));
batch.setModelTransform(Transform().setTranslation(topPoint).postScale(scale * _boundingCapsuleRadius));
geometryCache->renderSolidSphereInstance(args, batch, glm::vec4(0.6f, 0.6f, 0.8f, alpha));
// draw a yellow sphere at the capsule bottom point
- glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, scale * _boundingCapsuleHeight, 0.0f);
- glm::vec3 axis = topPoint - bottomPoint;
-
+ glm::vec3 bottomPoint = topPoint - _rotation * glm::vec3(0.0f, scale * _boundingCapsuleHeight, 0.0f);
batch.setModelTransform(Transform().setTranslation(bottomPoint).postScale(scale * _boundingCapsuleRadius));
geometryCache->renderSolidSphereInstance(args, batch, glm::vec4(0.8f, 0.8f, 0.6f, alpha));
// draw a green cylinder between the two points
- glm::vec3 origin(0.0f);
- batch.setModelTransform(Transform().setTranslation(bottomPoint));
- geometryCache->bindSimpleProgram(batch);
- Avatar::renderJointConnectingCone(batch, origin, axis, scale * _boundingCapsuleRadius, scale * _boundingCapsuleRadius,
- glm::vec4(0.6f, 0.8f, 0.6f, alpha));
+ float capsuleDiameter = 2.0f * _boundingCapsuleRadius;
+ glm::vec3 cylinderDimensions = glm::vec3(capsuleDiameter, _boundingCapsuleHeight, capsuleDiameter);
+ batch.setModelTransform(Transform().setScale(scale * cylinderDimensions).setRotation(_rotation).setTranslation(0.5f * (topPoint + bottomPoint)));
+ geometryCache->renderSolidShapeInstance(args, batch, GeometryCache::Shape::Cylinder, glm::vec4(0.6f, 0.8f, 0.6f, alpha));
}
bool SkeletonModel::hasSkeleton() {
diff --git a/libraries/avatars/src/AssociatedTraitValues.h b/libraries/avatars/src/AssociatedTraitValues.h
index e3060a8097..0df8cd9bb5 100644
--- a/libraries/avatars/src/AssociatedTraitValues.h
+++ b/libraries/avatars/src/AssociatedTraitValues.h
@@ -28,9 +28,10 @@
namespace AvatarTraits {
template
class AssociatedTraitValues {
+ using SimpleTypesArray = std::array;
public:
// constructor that pre-fills _simpleTypes with the default value specified by the template
- AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {}
+ AssociatedTraitValues() { std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue); }
/// inserts the given value for the given simple trait type
void insert(TraitType type, T value) { _simpleTypes[type] = value; }
@@ -71,12 +72,12 @@ namespace AvatarTraits {
}
/// const iterators for the vector of simple type values
- typename std::vector::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
- typename std::vector::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
+ typename SimpleTypesArray::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
+ typename SimpleTypesArray::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
/// non-const iterators for the vector of simple type values
- typename std::vector::iterator simpleBegin() { return _simpleTypes.begin(); }
- typename std::vector::iterator simpleEnd() { return _simpleTypes.end(); }
+ typename SimpleTypesArray::iterator simpleBegin() { return _simpleTypes.begin(); }
+ typename SimpleTypesArray::iterator simpleEnd() { return _simpleTypes.end(); }
struct TraitWithInstances {
TraitType traitType;
@@ -96,7 +97,7 @@ namespace AvatarTraits {
typename std::vector::iterator instancedEnd() { return _instancedTypes.end(); }
private:
- std::vector _simpleTypes;
+ SimpleTypesArray _simpleTypes;
/// return the iterator to the matching TraitWithInstances object for a given instanced trait type
typename std::vector::iterator instancesForTrait(TraitType traitType) {
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index c733cfa291..a2b0b808ba 100755
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -564,6 +564,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
setAtBit16(flags, COLLIDE_WITH_OTHER_AVATARS);
}
+ // Avatar has hero priority
+ if (getHasPriority()) {
+ setAtBit16(flags, HAS_HERO_PRIORITY);
+ }
+
data->flags = flags;
destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
@@ -1138,10 +1143,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
// we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split
// into two sections to maintain backward compatibility. The bits are ordered as such (0-7 left to right).
// AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled
- // +---+-----+-----+--+--+--+--+-----+
- // |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|xxxxx|
- // +---+-----+-----+--+--+--+--+-----+
+ // +---+-----+-----+--+--+--+--+--+----+
+ // |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|He|xxxx|
+ // +---+-----+-----+--+--+--+--+--+----+
// Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits
+ // Hero-avatar status (He) - 12th bit
auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT)
+ (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0);
@@ -1152,7 +1158,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT);
auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT);
auto newCollideWithOtherAvatars = oneAtBit16(bitItems, COLLIDE_WITH_OTHER_AVATARS);
-
+ auto newHasPriority = oneAtBit16(bitItems, HAS_HERO_PRIORITY);
+
bool keyStateChanged = (_keyState != newKeyState);
bool handStateChanged = (_handState != newHandState);
bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected);
@@ -1161,8 +1168,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement);
bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement);
bool collideWithOtherAvatarsChanged = (_collideWithOtherAvatars != newCollideWithOtherAvatars);
+ bool hasPriorityChanged = (getHasPriority() != newHasPriority);
bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged ||
- proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged;
+ proceduralEyeFaceMovementChanged ||
+ proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged || hasPriorityChanged;
_keyState = newKeyState;
_handState = newHandState;
@@ -1172,6 +1181,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
_headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement);
_headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement);
_collideWithOtherAvatars = newCollideWithOtherAvatars;
+ setHasPriorityWithoutTimestampReset(newHasPriority);
sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
@@ -1425,6 +1435,47 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
return numBytesRead;
}
+/**jsdoc
+ * The avatar mixer data comprises different types of data, with the data rates of each being tracked in kbps.
+ *
+ *
+ *
+ * Rate Name Description
+ *
+ *
+ * "globalPosition"
Incoming global position.
+ * "localPosition"
Incoming local position.
+ * "avatarBoundingBox"
Incoming avatar bounding box.
+ * "avatarOrientation"
Incoming avatar orientation.
+ * "avatarScale"
Incoming avatar scale.
+ * "lookAtPosition"
Incoming look-at position.
+ * "audioLoudness"
Incoming audio loudness.
+ * "sensorToWorkMatrix"
Incoming sensor-to-world matrix.
+ * "additionalFlags"
Incoming additional avatar flags.
+ * "parentInfo"
Incoming parent information.
+ * "faceTracker"
Incoming face tracker data.
+ * "jointData"
Incoming joint data.
+ * "jointDefaultPoseFlagsRate"
Incoming joint default pose flags.
+ * "farGrabJointRate"
Incoming far grab joint.
+ * "globalPositionOutbound"
Outgoing global position.
+ * "localPositionOutbound"
Outgoing local position.
+ * "avatarBoundingBoxOutbound"
Outgoing avatar bounding box.
+ * "avatarOrientationOutbound"
Outgoing avatar orientation.
+ * "avatarScaleOutbound"
Outgoing avatar scale.
+ * "lookAtPositionOutbound"
Outgoing look-at position.
+ * "audioLoudnessOutbound"
Outgoing audio loudness.
+ * "sensorToWorkMatrixOutbound"
Outgoing sensor-to-world matrix.
+ * "additionalFlagsOutbound"
Outgoing additional avatar flags.
+ * "parentInfoOutbound"
Outgoing parent information.
+ * "faceTrackerOutbound"
Outgoing face tracker data.
+ * "jointDataOutbound"
Outgoing joint data.
+ * "jointDefaultPoseFlagsOutbound"
Outgoing joint default pose flags.
+ * ""
When no rate name is specified, the total incoming data rate is provided.
+ *
+ *
+ *
+ * @typedef {string} AvatarDataRate
+ */
float AvatarData::getDataRate(const QString& rateName) const {
if (rateName == "") {
return _parseBufferRate.rate() / BYTES_PER_KILOBIT;
@@ -1486,6 +1537,35 @@ float AvatarData::getDataRate(const QString& rateName) const {
return 0.0f;
}
+/**jsdoc
+ * The avatar mixer data comprises different types of data updated at different rates, in Hz.
+ *
+ *
+ *
+ * Rate Name Description
+ *
+ *
+
+ * "globalPosition"
Global position.
+ * "localPosition"
Local position.
+ * "avatarBoundingBox"
Avatar bounding box.
+ * "avatarOrientation"
Avatar orientation.
+ * "avatarScale"
Avatar scale.
+ * "lookAtPosition"
Look-at position.
+ * "audioLoudness"
Audio loudness.
+ * "sensorToWorkMatrix"
Sensor-to-world matrix.
+ * "additionalFlags"
Additional avatar flags.
+ * "parentInfo"
Parent information.
+ * "faceTracker"
Face tracker data.
+ * "jointData"
Joint data.
+ * "farGrabJointData"
Far grab joint data.
+
+ * ""
When no rate name is specified, the overall update rate is provided.
+ *
+ *
+ *
+ * @typedef {string} AvatarUpdateRate
+ */
float AvatarData::getUpdateRate(const QString& rateName) const {
if (rateName == "") {
return _parseBufferUpdateRate.rate();
@@ -1911,42 +1991,16 @@ QUrl AvatarData::getWireSafeSkeletonModelURL() const {
}
}
-qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
- AvatarTraits::TraitVersion traitVersion) {
-
- qint64 bytesWritten = 0;
-
- if (traitType == AvatarTraits::SkeletonModelURL) {
-
- QByteArray encodedSkeletonURL = getWireSafeSkeletonModelURL().toEncoded();
-
- if (encodedSkeletonURL.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
- qWarning() << "Refusing to pack simple trait" << traitType << "of size" << encodedSkeletonURL.size()
- << "bytes since it exceeds the maximum size" << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
- return 0;
- }
-
- bytesWritten += destination.writePrimitive(traitType);
-
- if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
- bytesWritten += destination.writePrimitive(traitVersion);
- }
-
- AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size();
- bytesWritten += destination.writePrimitive(encodedURLSize);
-
- bytesWritten += destination.write(encodedSkeletonURL);
- }
-
- return bytesWritten;
+QByteArray AvatarData::packSkeletonModelURL() const {
+ return getWireSafeSkeletonModelURL().toEncoded();
}
+void AvatarData::unpackSkeletonModelURL(const QByteArray& data) {
+ auto skeletonModelURL = QUrl::fromEncoded(data);
+ setSkeletonModelURL(skeletonModelURL);
+}
-qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType,
- AvatarTraits::TraitInstanceID traitInstanceID,
- ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
- qint64 bytesWritten = 0;
-
+QByteArray AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
// grab a read lock on the avatar entities and check for entity data for the given ID
QByteArray entityBinaryData;
_avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] {
@@ -1955,104 +2009,48 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy
}
});
- if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
- qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size()
- << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
- return 0;
- }
-
- bytesWritten += destination.writePrimitive(traitType);
-
- if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
- bytesWritten += destination.writePrimitive(traitVersion);
- }
-
- bytesWritten += destination.write(traitInstanceID.toRfc4122());
-
- if (!entityBinaryData.isNull()) {
- AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size();
-
- bytesWritten += destination.writePrimitive(entityBinarySize);
- bytesWritten += destination.write(entityBinaryData);
- } else {
- bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
- }
-
- return bytesWritten;
+ return entityBinaryData;
}
-
-qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType,
- AvatarTraits::TraitInstanceID traitInstanceID,
- ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
- qint64 bytesWritten = 0;
-
+QByteArray AvatarData::packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID) {
// grab a read lock on the avatar grabs and check for grab data for the given ID
QByteArray grabBinaryData;
-
_avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] {
if (_avatarGrabData.contains(traitInstanceID)) {
grabBinaryData = _avatarGrabData[traitInstanceID];
}
});
- if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
- qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << grabBinaryData.size()
- << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
- return 0;
- }
-
- bytesWritten += destination.writePrimitive(traitType);
-
- if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
- bytesWritten += destination.writePrimitive(traitVersion);
- }
-
- bytesWritten += destination.write(traitInstanceID.toRfc4122());
-
- if (!grabBinaryData.isNull()) {
- AvatarTraits::TraitWireSize grabBinarySize = grabBinaryData.size();
-
- bytesWritten += destination.writePrimitive(grabBinarySize);
- bytesWritten += destination.write(grabBinaryData);
- } else {
- bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
- }
-
- return bytesWritten;
+ return grabBinaryData;
}
-qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID,
- ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
- qint64 bytesWritten = 0;
+QByteArray AvatarData::packTrait(AvatarTraits::TraitType traitType) const {
+ QByteArray traitBinaryData;
+ // Call packer function
+ if (traitType == AvatarTraits::SkeletonModelURL) {
+ traitBinaryData = packSkeletonModelURL();
+ }
+
+ return traitBinaryData;
+}
+
+QByteArray AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID) {
+ QByteArray traitBinaryData;
+
+ // Call packer function
if (traitType == AvatarTraits::AvatarEntity) {
- bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion);
+ traitBinaryData = packAvatarEntityTraitInstance(traitInstanceID);
} else if (traitType == AvatarTraits::Grab) {
- bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion);
+ traitBinaryData = packGrabTraitInstance(traitInstanceID);
}
- return bytesWritten;
-}
-
-void AvatarData::prepareResetTraitInstances() {
- if (_clientTraitsHandler) {
- _avatarEntitiesLock.withReadLock([this]{
- foreach (auto entityID, _packedAvatarEntityData.keys()) {
- _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
- }
- foreach (auto grabID, _avatarGrabData.keys()) {
- _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
- }
- });
- }
+ return traitBinaryData;
}
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
if (traitType == AvatarTraits::SkeletonModelURL) {
- // get the URL from the binary data
- auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData);
- setSkeletonModelURL(skeletonModelURL);
+ unpackSkeletonModelURL(traitBinaryData);
}
}
@@ -2073,6 +2071,19 @@ void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType,
}
}
+void AvatarData::prepareResetTraitInstances() {
+ if (_clientTraitsHandler) {
+ _avatarEntitiesLock.withReadLock([this]{
+ foreach (auto entityID, _packedAvatarEntityData.keys()) {
+ _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
+ }
+ foreach (auto grabID, _avatarGrabData.keys()) {
+ _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
+ }
+ });
+ }
+}
+
QByteArray AvatarData::identityByteArray(bool setIsReplicated) const {
QByteArray identityData;
QDataStream identityStream(&identityData, QIODevice::Append);
@@ -2721,13 +2732,16 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const
}
/**jsdoc
+ * Information on an attachment worn by the avatar.
* @typedef {object} AttachmentData
- * @property {string} modelUrl
- * @property {string} jointName
- * @property {Vec3} translation
- * @property {Vec3} rotation
- * @property {number} scale
- * @property {boolean} soft
+ * @property {string} modelUrl - The URL of the model file. Models can be FBX or OBJ format.
+ * @property {string} jointName - The offset to apply to the model relative to the joint position.
+ * @property {Vec3} translation - The offset from the joint that the attachment is positioned at.
+ * @property {Vec3} rotation - The rotation applied to the model relative to the joint orientation.
+ * @property {number} scale - The scale applied to the attachment model.
+ * @property {boolean} soft - If true
and the model has a skeleton, the bones of the attached model's skeleton are
+ * rotated to fit the avatar's current pose. If true
, the translation
, rotation
, and
+ * scale
parameters are ignored.
*/
QVariant AttachmentData::toVariant() const {
QVariantMap result;
@@ -2933,6 +2947,10 @@ float AvatarData::_avatarSortCoefficientSize { 8.0f };
float AvatarData::_avatarSortCoefficientCenter { 0.25f };
float AvatarData::_avatarSortCoefficientAge { 1.0f };
+/**jsdoc
+ * An object with the UUIDs of avatar entities as keys and avatar entity properties objects as values.
+ * @typedef {Object.} AvatarEntityMap
+ */
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
QScriptValue obj = engine->newObject();
for (auto entityID : value.keys()) {
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 63396a59ac..1c4b0cfc53 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -100,6 +100,9 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
// Procedural audio to mouth movement is enabled 8th bit
// Procedural Blink is enabled 9th bit
// Procedural Eyelid is enabled 10th bit
+// Procedural PROCEDURAL_BLINK_FACE_MOVEMENT is enabled 11th bit
+// Procedural Collide with other avatars is enabled 12th bit
+// Procedural Has Hero Priority is enabled 13th bit
const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits
const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits
@@ -111,8 +114,26 @@ const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit
const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit
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:
+
+ *
+ *
+ * Value Description
+ *
+ *
+ * 0
No hand is pointing.
+ * 1
The left hand is pointing.
+ * 2
The right hand is pointing.
+ * 4
It 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
+ */
const char HAND_STATE_NULL = 0;
const char LEFT_HAND_POINTING_FLAG = 1;
const char RIGHT_HAND_POINTING_FLAG = 2;
@@ -411,7 +432,55 @@ class ClientTraitsHandler;
class AvatarData : public QObject, public SpatiallyNestable {
Q_OBJECT
- // The following properties have JSDoc in MyAvatar.h and ScriptableAvatar.h
+ // IMPORTANT: The JSDoc for the following properties should be copied to MyAvatar.h and ScriptableAvatar.h.
+ /*
+ * @property {Vec3} position - The position of the avatar.
+ * @property {number} scale=1.0 - The scale of the avatar. The value can be set to anything between 0.005
and
+ * 1000.0
. When the scale value is fetched, it may temporarily be further limited by the domain's settings.
+ * @property {number} density - The density of the avatar in kg/m3. The density is used to work out its mass in
+ * the application of physics. Read-only.
+ * @property {Vec3} handPosition - A user-defined hand position, in world coordinates. The position moves with the avatar
+ * but is otherwise not used or changed by Interface.
+ * @property {number} bodyYaw - The left or right rotation about an axis running from the head to the feet of the avatar.
+ * Yaw is sometimes called "heading".
+ * @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
+ * sometimes called "elevation".
+ * @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
+ * sometimes called "bank".
+ * @property {Quat} orientation - The orientation of the avatar.
+ * @property {Quat} headOrientation - The orientation of the avatar's head.
+ * @property {number} headPitch - The rotation about an axis running from ear to ear of the avatar's head. Pitch is
+ * sometimes called "elevation".
+ * @property {number} headYaw - The rotation left or right about an axis running from the base to the crown of the avatar's
+ * head. Yaw is sometimes called "heading".
+ * @property {number} headRoll - The rotation about an axis running from the nose to the back of the avatar's head. Roll is
+ * sometimes called "bank".
+ * @property {Vec3} velocity - The current velocity of the avatar.
+ * @property {Vec3} angularVelocity - The current angular velocity of the avatar.
+ * @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the
+ * domain.
+ * @property {number} audioAverageLoudness - The rolling average loudness of the audio input that the avatar is injecting
+ * into the domain.
+ * @property {string} displayName - The avatar's display name.
+ * @property {string} sessionDisplayName - displayName's
sanitized and default version defined by the avatar
+ * mixer rather than Interface clients. The result is unique among all avatars present in the domain at the time.
+ * @property {boolean} lookAtSnappingEnabled=true - true
if the avatar's eyes snap to look at another avatar's
+ * eyes when the other avatar is in the line of sight and also has lookAtSnappingEnabled == true
.
+ * @property {string} skeletonModelURL - The avatar's FST file.
+ * @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.
+ * Deprecated: Use avatar entities instead.
+ * @property {string[]} jointNames - The list of joints in the current avatar model. Read-only.
+ * @property {Uuid} sessionUUID - Unique ID of the avatar in the domain. Read-only.
+ * @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the
+ * avatar's size, orientation, and position in the virtual world. Read-only.
+ * @property {Mat4} controllerLeftHandMatrix - The rotation and translation of the left hand controller relative to the
+ * avatar. Read-only.
+ * @property {Mat4} controllerRightHandMatrix - The rotation and translation of the right hand controller relative to the
+ * avatar. Read-only.
+ * @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's
+ * size in the virtual world. Read-only.
+ * @property {boolean} hasPriority - is the avatar in a Hero zone? Read-only.
+ */
Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript)
Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale)
Q_PROPERTY(float density READ getDensity)
@@ -450,6 +519,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(float sensorToWorldScale READ getSensorToWorldScale)
+ Q_PROPERTY(bool hasPriority READ getHasPriority)
+
public:
virtual QString getName() const override { return QString("Avatar:") + _displayName; }
@@ -565,18 +636,18 @@ public:
virtual bool getHasAudioEnabledFaceMovement() const { return false; }
/**jsdoc
- * Returns the minimum scale allowed for this avatar in the current domain.
+ * Gets the minimum scale allowed for this avatar in the current domain.
* This value can change as the user changes avatars or when changing domains.
- * @function MyAvatar.getDomainMinScale
- * @returns {number} minimum scale allowed for this avatar in the current domain.
+ * @function Avatar.getDomainMinScale
+ * @returns {number} The minimum scale allowed for this avatar in the current domain.
*/
Q_INVOKABLE float getDomainMinScale() const;
/**jsdoc
- * Returns the maximum scale allowed for this avatar in the current domain.
+ * Gets the maximum scale allowed for this avatar in the current domain.
* This value can change as the user changes avatars or when changing domains.
- * @function MyAvatar.getDomainMaxScale
- * @returns {number} maximum scale allowed for this avatar in the current domain.
+ * @function Avatar.getDomainMaxScale
+ * @returns {number} The maximum scale allowed for this avatar in the current domain.
*/
Q_INVOKABLE float getDomainMaxScale() const;
@@ -589,18 +660,18 @@ public:
virtual bool canMeasureEyeHeight() const { return false; }
/**jsdoc
- * Provides read only access to the current eye height of the avatar.
+ * Gets the current eye height of the avatar.
* This height is only an estimate and might be incorrect for avatars that are missing standard joints.
- * @function MyAvatar.getEyeHeight
- * @returns {number} Eye height of avatar in meters.
+ * @function Avatar.getEyeHeight
+ * @returns {number} The eye height of the avatar.
*/
Q_INVOKABLE virtual float getEyeHeight() const { return _targetScale * getUnscaledEyeHeight(); }
/**jsdoc
- * Provides read only access to the current height of the avatar.
+ * Gets the current height of the avatar.
* This height is only an estimate and might be incorrect for avatars that are missing standard joints.
- * @function MyAvatar.getHeight
- * @returns {number} Height of avatar in meters.
+ * @function Avatar.getHeight
+ * @returns {number} The height of the avatar.
*/
Q_INVOKABLE virtual float getHeight() const;
@@ -610,36 +681,43 @@ public:
void setDomainMaximumHeight(float domainMaximumHeight);
/**jsdoc
- * @function MyAvatar.setHandState
- * @param {string} state
+ * 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.
*/
Q_INVOKABLE void setHandState(char s) { _handState = s; }
/**jsdoc
- * @function MyAvatar.getHandState
- * @returns {string}
+ * 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.
*/
Q_INVOKABLE char getHandState() const { return _handState; }
const QVector& getRawJointData() const { return _jointData; }
/**jsdoc
- * @function MyAvatar.setRawJointData
- * @param {JointData[]} data
+ * Sets joint translations and rotations from raw joint data.
+ * @function Avatar.setRawJointData
+ * @param {JointData[]} data - The raw joint data.
+ * @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE void setRawJointData(QVector data);
/**jsdoc
- * Set a specific joint's rotation and position relative to its parent.
- * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
+ * Sets a specific joint's rotation and position relative to its parent, in model coordinates.
+ *
Warning: These coordinates are not necessarily in meters.
+ * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
* kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
* the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
* joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
- * @function MyAvatar.setJointData
+ * @function Avatar.setJointData
* @param {number} index - The index of the joint.
* @param {Quat} rotation - The rotation of the joint relative to its parent.
- * @param {Vec3} translation - The translation of the joint relative to its parent.
+ * @param {Vec3} translation - The translation of the joint relative to its parent, in model coordinates.
* @example Set your avatar to it's default T-pose for a while.
* 
* // Set all joint translations and rotations to defaults.
@@ -654,91 +732,98 @@ public:
* Script.setTimeout(function () {
* MyAvatar.clearJointsData();
* }, 5000);
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation);
/**jsdoc
- * Set a specific joint's rotation relative to its parent.
+ * Sets a specific joint's rotation relative to its parent.
* Setting joint data completely overrides/replaces all motion from the default animation system including inverse
* kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
* the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
* joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
- * @function MyAvatar.setJointRotation
+ * @function Avatar.setJointRotation
* @param {number} index - The index of the joint.
* @param {Quat} rotation - The rotation of the joint relative to its parent.
*/
Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation);
/**jsdoc
- * Set a specific joint's translation relative to its parent.
- * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
+ * Sets a specific joint's translation relative to its parent, in model coordinates.
+ *
Warning: These coordinates are not necessarily in meters.
+ * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
* kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
* the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
* joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
- * @function MyAvatar.setJointTranslation
+ * @function Avatar.setJointTranslation
* @param {number} index - The index of the joint.
- * @param {Vec3} translation - The translation of the joint relative to its parent.
+ * @param {Vec3} translation - The translation of the joint relative to its parent, in model coordinates.
*/
Q_INVOKABLE virtual void setJointTranslation(int index, const glm::vec3& translation);
/**jsdoc
- * Clear joint translations and rotations set by script for a specific joint. This restores all motion from the default
+ * Clears joint translations and rotations set by script for a specific joint. This restores all motion from the default
* animation system including inverse kinematics for that joint.
* Note: This is slightly faster than the function variation that specifies the joint name.
- * @function MyAvatar.clearJointData
+ * @function Avatar.clearJointData
* @param {number} index - The index of the joint.
*/
Q_INVOKABLE virtual void clearJointData(int index);
/**jsdoc
- * @function MyAvatar.isJointDataValid
- * @param {number} index
- * @returns {boolean}
+ * Checks that the data for a joint are valid.
+ * @function Avatar.isJointDataValid
+ * @param {number} index - The index of the joint.
+ * @returns {boolean} true
if the joint data are valid, false
if not.
*/
Q_INVOKABLE bool isJointDataValid(int index) const;
/**jsdoc
- * Get the rotation of a joint relative to its parent. For information on the joint hierarchy used, see
- * Avatar Standards.
- * @function MyAvatar.getJointRotation
+ * Gets the rotation of a joint relative to its parent. For information on the joint hierarchy used, see
+ * Avatar Standards.
+ * @function Avatar.getJointRotation
* @param {number} index - The index of the joint.
* @returns {Quat} The rotation of the joint relative to its parent.
*/
Q_INVOKABLE virtual glm::quat getJointRotation(int index) const;
/**jsdoc
- * Get the translation of a joint relative to its parent. For information on the joint hierarchy used, see
- * Avatar Standards.
- * @function MyAvatar.getJointTranslation
+ * Gets the translation of a joint relative to its parent, in model coordinates.
+ * Warning: These coordinates are not necessarily in meters.
+ * For information on the joint hierarchy used, see
+ * Avatar Standards.
+ * @function Avatar.getJointTranslation
* @param {number} index - The index of the joint.
- * @returns {Vec3} The translation of the joint relative to its parent.
+ * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates.
*/
Q_INVOKABLE virtual glm::vec3 getJointTranslation(int index) const;
/**jsdoc
- * Set a specific joint's rotation and position relative to its parent.
- * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
+ * Sets a specific joint's rotation and position relative to its parent, in model coordinates.
+ *
Warning: These coordinates are not necessarily in meters.
+ * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
* kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
* the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
* joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
- * @function MyAvatar.setJointData
+ * @function Avatar.setJointData
* @param {string} name - The name of the joint.
* @param {Quat} rotation - The rotation of the joint relative to its parent.
- * @param {Vec3} translation - The translation of the joint relative to its parent.
+ * @param {Vec3} translation - The translation of the joint relative to its parent, in model coordinates.
*/
Q_INVOKABLE virtual void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation);
/**jsdoc
- * Set a specific joint's rotation relative to its parent.
+ * Sets a specific joint's rotation relative to its parent.
* Setting joint data completely overrides/replaces all motion from the default animation system including inverse
* kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
* the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
* joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
- * @function MyAvatar.setJointRotation
+ * @function Avatar.setJointRotation
* @param {string} name - The name of the joint.
* @param {Quat} rotation - The rotation of the joint relative to its parent.
* @example Set your avatar to its default T-pose then rotate its right arm.
@@ -759,104 +844,125 @@ public:
* Script.setTimeout(function () {
* MyAvatar.clearJointsData();
* }, 5000);
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void setJointRotation(const QString& name, const glm::quat& rotation);
/**jsdoc
- * Set a specific joint's translation relative to its parent.
- * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
+ * Sets a specific joint's translation relative to its parent, in model coordinates.
+ *
Warning: These coordinates are not necessarily in meters.
+ * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
* kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
* the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
* joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
- * @function MyAvatar.setJointTranslation
+ * @function Avatar.setJointTranslation
* @param {string} name - The name of the joint.
- * @param {Vec3} translation - The translation of the joint relative to its parent.
+ * @param {Vec3} translation - The translation of the joint relative to its parent, in model coordinates.
* @example Stretch your avatar's neck. Depending on the avatar you are using, you will either see a gap between
* the head and body or you will see the neck stretched.
* 
* // Stretch your avatar's neck.
- * MyAvatar.setJointTranslation("Neck", { x: 0, y: 25, z: 0 });
+ * MyAvatar.setJointTranslation("Neck", Vec3.multiply(2, MyAvatar.getJointTranslation("Neck")));
*
* // Restore your avatar's neck after 5s.
* Script.setTimeout(function () {
* MyAvatar.clearJointData("Neck");
* }, 5000);
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void setJointTranslation(const QString& name, const glm::vec3& translation);
/**jsdoc
- * Clear joint translations and rotations set by script for a specific joint. This restores all motion from the default
+ * Clears joint translations and rotations set by script for a specific joint. This restores all motion from the default
* animation system including inverse kinematics for that joint.
* Note: This is slightly slower than the function variation that specifies the joint index.
- * @function MyAvatar.clearJointData
+ * @function Avatar.clearJointData
* @param {string} name - The name of the joint.
* @example Offset and restore the position of your avatar's head.
- * // Move your avatar's head up by 25cm from where it should be.
- * MyAvatar.setJointTranslation("Neck", { x: 0, y: 0.25, z: 0 });
+ * // Stretch your avatar's neck.
+ * MyAvatar.setJointTranslation("Neck", Vec3.multiply(2, MyAvatar.getJointTranslation("Neck")));
*
- * // Restore your avatar's head to its default position after 5s.
+ * // Restore your avatar's neck after 5s.
* Script.setTimeout(function () {
* MyAvatar.clearJointData("Neck");
* }, 5000);
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void clearJointData(const QString& name);
/**jsdoc
- * @function MyAvatar.isJointDataValid
- * @param {string} name
- * @returns {boolean}
+ * Checks if the data for a joint are valid.
+ * @function Avatar.isJointDataValid
+ * @param {string} name - The name of the joint.
+ * @returns {boolean} true
if the joint data are valid, false
if not.
*/
Q_INVOKABLE virtual bool isJointDataValid(const QString& name) const;
/**jsdoc
- * Get the rotation of a joint relative to its parent. For information on the joint hierarchy used, see
- * Avatar Standards.
- * @function MyAvatar.getJointRotation
+ * Gets the rotation of a joint relative to its parent. For information on the joint hierarchy used, see
+ * Avatar Standards.
+ * @function Avatar.getJointRotation
* @param {string} name - The name of the joint.
* @returns {Quat} The rotation of the joint relative to its parent.
* @example Report the rotation of your avatar's hips joint.
* print(JSON.stringify(MyAvatar.getJointRotation("Hips")));
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual glm::quat getJointRotation(const QString& name) const;
/**jsdoc
- * Get the translation of a joint relative to its parent. For information on the joint hierarchy used, see
- * Avatar Standards.
- * @function MyAvatar.getJointTranslation
+ * Gets the translation of a joint relative to its parent, in model coordinates.
+ * Warning: These coordinates are not necessarily in meters.
+ * For information on the joint hierarchy used, see
+ * Avatar Standards.
+ * @function Avatar.getJointTranslation
* @param {number} name - The name of the joint.
- * @returns {Vec3} The translation of the joint relative to its parent.
+ * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates.
* @example Report the translation of your avatar's hips joint.
* print(JSON.stringify(MyAvatar.getJointRotation("Hips")));
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual glm::vec3 getJointTranslation(const QString& name) const;
/**jsdoc
- * Get the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint.
- * @function MyAvatar.getJointRotations
+ * Gets the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint.
+ * @function Avatar.getJointRotations
* @returns {Quat[]} The rotations of all joints relative to each's parent. The values are in the same order as the array
- * returned by {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
+ * returned by {@link MyAvatar.getJointNames}, or {@link Avatar.getJointNames} if using the Avatar
API.
* @example Report the rotations of all your avatar's joints.
* print(JSON.stringify(MyAvatar.getJointRotations()));
+ *
+ * // Note: If using from the Avatar API, replace all "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual QVector getJointRotations() const;
/**jsdoc
- * @function MyAvatar.getJointTranslations
- * @returns {Vec3[]}
+ * Gets the translations of all joints in the current avatar. Each joint's translation is relative to its parent joint, in
+ * model coordinates.
+ * Warning: These coordinates are not necessarily in meters.
+ * @function Avatar.getJointTranslations
+ * @returns {Vec3[]} The translations of all joints relative to each's parent, in model coordinates. The values are in the
+ * same order as the array returned by {@link MyAvatar.getJointNames}, or {@link Avatar.getJointNames} if using the
+ * Avatar
API.
*/
Q_INVOKABLE virtual QVector getJointTranslations() const;
/**jsdoc
- * Set the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint.
+ * Sets the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint.
* Setting joint data completely overrides/replaces all motion from the default animation system including inverse
* kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
* the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
* joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
* the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
- * @function MyAvatar.setJointRotations
+ * @function Avatar.setJointRotations
* @param {Quat[]} jointRotations - The rotations for all joints in the avatar. The values are in the same order as the
- * array returned by {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
+ * array returned by {@link MyAvatar.getJointNames}, or {@link Avatar.getJointNames} if using the Avatar
API.
* @example Set your avatar to its default T-pose then rotate its right arm.
* 
* // Set all joint translations and rotations to defaults.
@@ -880,19 +986,31 @@ public:
* Script.setTimeout(function () {
* MyAvatar.clearJointsData();
* }, 5000);
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void setJointRotations(const QVector& jointRotations);
/**jsdoc
- * @function MyAvatar.setJointTranslations
- * @param {Vec3[]} translations
+ * Sets the translations of all joints in the current avatar. Each joint's translation is relative to its parent joint, in
+ * model coordinates.
+ * Warning: These coordinates are not necessarily in meters.
+ * Setting joint data completely overrides/replaces all motion from the default animation system including inverse
+ * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints,
+ * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate
+ * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set
+ * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.
+ * @function Avatar.setJointTranslations
+ * @param {Vec3[]} translations - The translations for all joints in the avatar, in model coordinates. The values are in
+ * the same order as the array returned by {@link MyAvatar.getJointNames}, or {@link Avatar.getJointNames} if using the
+ * Avatar
API.
*/
Q_INVOKABLE virtual void setJointTranslations(const QVector& jointTranslations);
/**jsdoc
- * Clear all joint translations and rotations that have been set by script. This restores all motion from the default
+ * Clears all joint translations and rotations that have been set by script. This restores all motion from the default
* animation system including inverse kinematics for all joints.
- * @function MyAvatar.clearJointsData
+ * @function Avatar.clearJointsData
* @example Set your avatar to it's default T-pose for a while.
* // Set all joint translations and rotations to defaults.
* var i, length, rotation, translation;
@@ -906,49 +1024,69 @@ public:
* Script.setTimeout(function () {
* MyAvatar.clearJointsData();
* }, 5000);
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void clearJointsData();
/**jsdoc
- * Get the joint index for a named joint. The joint index value is the position of the joint in the array returned by
- * {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
- * @function MyAvatar.getJointIndex
+ * Gets the joint index for a named joint. The joint index value is the position of the joint in the array returned by
+ * {@link MyAvatar.getJointNames}, or {@link Avatar.getJointNames} if using the Avatar
API.
+ * @function Avatar.getJointIndex
* @param {string} name - The name of the joint.
- * @returns {number} The index of the joint.
+ * @returns {number} The index of the joint if valid, otherwise -1
.
* @example Report the index of your avatar's left arm joint.
- * print(JSON.stringify(MyAvatar.getJointIndex("LeftArm"));
+ * print(JSON.stringify(MyAvatar.getJointIndex("LeftArm")));
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
/// Returns the index of the joint with the specified name, or -1 if not found/unknown.
Q_INVOKABLE virtual int getJointIndex(const QString& name) const;
/**jsdoc
- * Get the names of all the joints in the current avatar.
- * @function MyAvatar.getJointNames
+ * Gets the names of all the joints in the current avatar.
+ * @function Avatar.getJointNames
* @returns {string[]} The joint names.
* @example Report the names of all the joints in your current avatar.
* print(JSON.stringify(MyAvatar.getJointNames()));
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual QStringList getJointNames() const;
/**jsdoc
- * @function MyAvatar.setBlendshape
- * @param {string} name
- * @param {number} value
+ * Sets the value of a blendshape to animate your avatar's face. To enable other users to see the resulting animation of
+ * your avatar's face, use {@link Avatar.setForceFaceTrackerConnected} or {@link MyAvatar.setForceFaceTrackerConnected}.
+ * @function Avatar.setBlendshape
+ * @param {string} name - The name of the blendshape, per the
+ * {@link https://docs.highfidelity.com/create/avatars/avatar-standards.html#blendshapes Avatar Standards}.
+ * @param {number} value - A value between 0.0
and 1.0
.
+ * @example Open your avatar's mouth wide.
+ * MyAvatar.setForceFaceTrackerConnected(true);
+ * MyAvatar.setBlendshape("JawOpen", 1.0);
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
Q_INVOKABLE void setBlendshape(QString name, float val) { _headData->setBlendshape(name, val); }
/**jsdoc
- * @function MyAvatar.getAttachmentsVariant
- * @returns {object}
+ * Gets information about the models currently attached to your avatar.
+ * @function Avatar.getAttachmentsVariant
+ * @returns {AttachmentData[]} Information about all models attached to your avatar.
+ * @deprecated Use avatar entities instead.
*/
// FIXME: Can this name be improved? Can it be deprecated?
Q_INVOKABLE virtual QVariantList getAttachmentsVariant() const;
/**jsdoc
- * @function MyAvatar.setAttachmentsVariant
- * @param {object} variant
+ * Sets all models currently attached to your avatar. For example, if you retrieve attachment data using
+ * {@link MyAvatar.getAttachmentsVariant} or {@link Avatar.getAttachmentsVariant}, make changes to it, and then want to
+ * update your avatar's attachments per the changed data.
+ * @function Avatar.setAttachmentsVariant
+ * @param {AttachmentData[]} variant - The attachment data defining the models to have attached to your avatar.
+ * @deprecated Use avatar entities instead.
*/
// FIXME: Can this name be improved? Can it be deprecated?
Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant);
@@ -956,22 +1094,28 @@ public:
virtual void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload);
/**jsdoc
- * @function MyAvatar.updateAvatarEntity
- * @param {Uuid} entityID
- * @param {string} entityData
+ * @function Avatar.updateAvatarEntity
+ * @param {Uuid} entityID - The entity ID.
+ * @param {Array.} entityData - Entity data.
+ * @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE virtual void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
/**jsdoc
- * @function MyAvatar.clearAvatarEntity
- * @param {Uuid} entityID
+ * @function Avatar.clearAvatarEntity
+ * @param {Uuid} entityID - The entity ID.
+ * @param {boolean} [requiresRemovalFromTree=true] - Requires removal from tree.
+ * @deprecated This function is deprecated and will be removed.
*/
Q_INVOKABLE virtual void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true);
/**jsdoc
- * @function MyAvatar.setForceFaceTrackerConnected
- * @param {boolean} connected
+ * Enables blendshapes set using {@link Avatar.setBlendshape} or {@link MyAvatar.setBlendshape} to be transmitted to other
+ * users so that they can see the animation of your avatar's face.
+ * @function Avatar.setForceFaceTrackerConnected
+ * @param {boolean} connected - true
to enable blendshape changes to be transmitted to other users,
+ * false
to disable.
*/
Q_INVOKABLE void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; }
@@ -993,18 +1137,16 @@ public:
// identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged);
- qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
- AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
- qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID,
- ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
-
- void prepareResetTraitInstances();
+ QByteArray packTrait(AvatarTraits::TraitType traitType) const;
+ QByteArray packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
void processTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData);
void processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
+ void prepareResetTraitInstances();
+
QByteArray identityByteArray(bool setIsReplicated = false) const;
QUrl getWireSafeSkeletonModelURL() const;
@@ -1022,24 +1164,28 @@ public:
}
/**jsdoc
- * Get information about all models currently attached to your avatar.
- * @function MyAvatar.getAttachmentData
+ * Gets information about the models currently attached to your avatar.
+ * @function Avatar.getAttachmentData
* @returns {AttachmentData[]} Information about all models attached to your avatar.
+ * @deprecated Use avatar entities instead.
* @example Report the URLs of all current attachments.
* var attachments = MyAvatar.getaAttachmentData();
* for (var i = 0; i < attachments.length; i++) {
- * print (attachments[i].modelURL);
+ * print(attachments[i].modelURL);
* }
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual QVector getAttachmentData() const;
/**jsdoc
- * Set all models currently attached to your avatar. For example, if you retrieve attachment data using
+ * Sets all models currently attached to your avatar. For example, if you retrieve attachment data using
* {@link MyAvatar.getAttachmentData} or {@link Avatar.getAttachmentData}, make changes to it, and then want to update your avatar's attachments per the
* changed data. You can also remove all attachments by using setting attachmentData
to null
.
- * @function MyAvatar.setAttachmentData
- * @param {AttachmentData[]} attachmentData - The attachment data defining the models to have attached to your avatar. Use
+ * @function Avatar.setAttachmentData
+ * @param {AttachmentData[]} attachmentData - The attachment data defining the models to have attached to your avatar. Use
* null
to remove all attachments.
+ * @deprecated Use avatar entities instead.
* @example Remove a hat attachment if your avatar is wearing it.
* var hatURL = "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx";
* var attachments = MyAvatar.getAttachmentData();
@@ -1051,15 +1197,17 @@ public:
* break;
* }
* }
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void setAttachmentData(const QVector& attachmentData);
/**jsdoc
- * Attach a model to your avatar. For example, you can give your avatar a hat to wear, a guitar to hold, or a surfboard to
+ * Attaches a model to your avatar. For example, you can give your avatar a hat to wear, a guitar to hold, or a surfboard to
* stand on.
* Note: Attached models are models only; they are not entities and can not be manipulated using the {@link Entities} API.
* Nor can you use this function to attach an entity (such as a sphere or a box) to your avatar.
- * @function MyAvatar.attach
+ * @function Avatar.attach
* @param {string} modelURL - The URL of the model to attach. Models can be .FBX or .OBJ format.
* @param {string} [jointName=""] - The name of the avatar joint (see {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}) to attach the model
* to.
@@ -1067,12 +1215,14 @@ public:
* @param {Quat} [rotation=Quat.IDENTITY] - The rotation to apply to the model relative to the joint orientation.
* @param {number} [scale=1.0] - The scale to apply to the model.
* @param {boolean} [isSoft=false] - If the model has a skeleton, set this to true
so that the bones of the
- * attached model's skeleton are be rotated to fit the avatar's current pose. isSoft
is used, for example,
+ * attached model's skeleton are rotated to fit the avatar's current pose. isSoft
is used, for example,
* to have clothing that moves with the avatar.
* If true
, the translation
, rotation
, and scale
parameters are
* ignored.
- * @param {boolean} [allowDuplicates=false]
- * @param {boolean} [useSaved=true]
+ * @param {boolean} [allowDuplicates=false] - If true
then more than one copy of any particular model may be
+ * attached to the same joint; if false
then the same model cannot be attached to the same joint.
+ * @param {boolean} [useSaved=true] - Not used.
+ * @deprecated Use avatar entities instead.
* @example Attach a cowboy hat to your avatar's head.
* var attachment = {
* modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx",
@@ -1089,6 +1239,8 @@ public:
* attachment.rotation,
* attachment.scale,
* attachment.isSoft);
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(),
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(),
@@ -1096,20 +1248,22 @@ public:
bool allowDuplicates = false, bool useSaved = true);
/**jsdoc
- * Detach the most recently attached instance of a particular model from either a specific joint or any joint.
- * @function MyAvatar.detachOne
+ * Detaches the most recently attached instance of a particular model from either a specific joint or any joint.
+ * @function Avatar.detachOne
* @param {string} modelURL - The URL of the model to detach.
* @param {string} [jointName=""] - The name of the joint to detach the model from. If ""
, then the most
* recently attached model is removed from which ever joint it was attached to.
+ * @deprecated Use avatar entities instead.
*/
Q_INVOKABLE virtual void detachOne(const QString& modelURL, const QString& jointName = QString());
/**jsdoc
- * Detach all instances of a particular model from either a specific joint or all joints.
- * @function MyAvatar.detachAll
+ * Detaches all instances of a particular model from either a specific joint or all joints.
+ * @function Avatar.detachAll
* @param {string} modelURL - The URL of the model to detach.
* @param {string} [jointName=""] - The name of the joint to detach the model from. If ""
, then the model is
* detached from all joints.
+ * @deprecated Use avatar entities instead.
*/
Q_INVOKABLE virtual void detachAll(const QString& modelURL, const QString& jointName = QString());
@@ -1121,6 +1275,18 @@ public:
int getAverageBytesReceivedPerSecond() const;
int getReceiveRate() const;
+ // An Avatar can be set Priority from the AvatarMixer side.
+ bool getHasPriority() const { return _hasPriority; }
+ // regular setHasPriority does a check of state changed and if true reset 'additionalFlagsChanged' timestamp
+ void setHasPriority(bool hasPriority) {
+ if (_hasPriority != hasPriority) {
+ _additionalFlagsChanged = usecTimestampNow();
+ _hasPriority = hasPriority;
+ }
+ }
+ // In some cases, we want to assign the hasPRiority flag without reseting timestamp
+ void setHasPriorityWithoutTimestampReset(bool hasPriority) { _hasPriority = hasPriority; }
+
const glm::vec3& getTargetVelocity() const { return _targetVelocity; }
void clearRecordingBasis();
@@ -1136,14 +1302,12 @@ public:
AABox getDefaultBubbleBox() const;
/**jsdoc
- * @function MyAvatar.getAvatarEntityData
- * @returns {object}
+ * @comment Documented in derived classes' JSDoc because implementations are different.
*/
Q_INVOKABLE virtual AvatarEntityMap getAvatarEntityData() const;
/**jsdoc
- * @function MyAvatar.setAvatarEntityData
- * @param {object} avatarEntityData
+ * @comment Documented in derived classes' JSDoc because implementations are different.
*/
Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
@@ -1151,45 +1315,69 @@ public:
AvatarEntityIDs getAndClearRecentlyRemovedIDs();
/**jsdoc
- * @function MyAvatar.getSensorToWorldMatrix
- * @returns {Mat4}
+ * Gets the transform from the user's real world to the avatar's size, orientation, and position in the virtual world.
+ * @function Avatar.getSensorToWorldMatrix
+ * @returns {Mat4} The scale, rotation, and translation transform from the user's real world to the avatar's size,
+ * orientation, and position in the virtual world.
+ * @example Report the sensor to world matrix.
+ * var sensorToWorldMatrix = MyAvatar.getSensorToWorldMatrix();
+ * print("Sensor to woprld matrix: " + JSON.stringify(sensorToWorldMatrix));
+ * print("Rotation: " + JSON.stringify(Mat4.extractRotation(sensorToWorldMatrix)));
+ * print("Translation: " + JSON.stringify(Mat4.extractTranslation(sensorToWorldMatrix)));
+ * print("Scale: " + JSON.stringify(Mat4.extractScale(sensorToWorldMatrix)));
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
/**jsdoc
- * @function MyAvatar.getSensorToWorldScale
- * @returns {number}
+ * Gets the scale that transforms dimensions in the user's real world to the avatar's size in the virtual world.
+ * @function Avatar.getSensorToWorldScale
+ * @returns {number} The scale that transforms dimensions in the user's real world to the avatar's size in the virtual
+ * world.
*/
// thread safe
Q_INVOKABLE float getSensorToWorldScale() const;
/**jsdoc
- * @function MyAvatar.getControllerLeftHandMatrix
- * @returns {Mat4}
+ * Gets the rotation and translation of the left hand controller relative to the avatar.
+ * @function Avatar.getControllerLeftHandMatrix
+ * @returns {Mat4} The rotation and translation of the left hand controller relative to the avatar.
+ * @example Report the left hand controller matrix.
+ * var leftHandMatrix = MyAvatar.getControllerLeftHandMatrix();
+ * print("Controller left hand matrix: " + JSON.stringify(leftHandMatrix));
+ * print("Rotation: " + JSON.stringify(Mat4.extractRotation(leftHandMatrix)));
+ * print("Translation: " + JSON.stringify(Mat4.extractTranslation(leftHandMatrix)));
+ * print("Scale: " + JSON.stringify(Mat4.extractScale(leftHandMatrix))); // Always 1,1,1.
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
// thread safe
Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const;
/**jsdoc
- * @function MyAvatar.getControllerRightHandMatrix
- * @returns {Mat4}
+ * Gets the rotation and translation of the right hand controller relative to the avatar.
+ * @function Avatar.getControllerRightHandMatrix
+ * @returns {Mat4} The rotation and translation of the right hand controller relative to the avatar.
*/
// thread safe
Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const;
/**jsdoc
- * @function MyAvatar.getDataRate
- * @param {string} [rateName=""]
- * @returns {number}
+ * Gets the amount of avatar mixer data being generated by the avatar.
+ * @function Avatar.getDataRate
+ * @param {AvatarDataRate} [rateName=""] - The type of avatar mixer data to get the data rate of.
+ * @returns {number} The data rate in kbps.
*/
Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const;
/**jsdoc
- * @function MyAvatar.getUpdateRate
- * @param {string} [rateName=""]
- * @returns {number}
+ * Gets the update rate of avatar mixer data being generated by the avatar.
+ * @function Avatar.getUpdateRate
+ * @param {AvatarUpdateRate} [rateName=""] - The type of avatar mixer data to get the update rate of.
+ * @returns {number} The update rate in Hz.
*/
Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const;
@@ -1235,52 +1423,92 @@ public:
signals:
/**jsdoc
- * @function MyAvatar.displayNameChanged
+ * Triggered when the avatar's displayName
property value changes.
+ * @function Avatar.displayNameChanged
* @returns {Signal}
+ * @example Report when your avatar display name changes.
+ * MyAvatar.displayNameChanged.connect(function () {
+ * print("Avatar display name changed to: " + MyAvatar.displayName);
+ * });
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
void displayNameChanged();
/**jsdoc
- * @function MyAvatar.sessionDisplayNameChanged
+ * Triggered when the avatar's sessionDisplayName
property value changes.
+ * @function Avatar.sessionDisplayNameChanged
* @returns {Signal}
+ * @example Report when your avatar's session display name changes.
+ * MyAvatar.sessionDisplayNameChanged.connect(function () {
+ * print("Avatar session display name changed to: " + MyAvatar.sessionDisplayName);
+ * });
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
void sessionDisplayNameChanged();
/**jsdoc
- * @function MyAvatar.skeletonModelURLChanged
+ * Triggered when the avatar's model (i.e., skeletonModelURL
property value) is changed.
+ * @function Avatar.skeletonModelURLChanged
* @returns {Signal}
+ * @example Report when your avatar's skeleton model changes.
+ * MyAvatar.skeletonModelURLChanged.connect(function () {
+ * print("Skeleton model changed to: " + MyAvatar.skeletonModelURL);
+ * });
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
void skeletonModelURLChanged();
/**jsdoc
- * @function MyAvatar.lookAtSnappingChanged
- * @param {boolean} enabled
+ * Triggered when the avatar's lookAtSnappingEnabled
property value changes.
+ * @function Avatar.lookAtSnappingChanged
+ * @param {boolean} enabled - true
if look-at snapping is enabled, false
if not.
* @returns {Signal}
+ * @example Report when your look-at snapping setting changes.
+ * MyAvatar.lookAtSnappingChanged.connect(function () {
+ * print("Avatar look-at snapping changed to: " + MyAvatar.lookAtSnappingEnabled);
+ * });
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
void lookAtSnappingChanged(bool enabled);
/**jsdoc
- * @function MyAvatar.sessionUUIDChanged
+ * Triggered when the avatar's sessionUUID
property value changes.
+ * @function Avatar.sessionUUIDChanged
* @returns {Signal}
+ * @example Report when your avatar's session UUID changes.
+ * MyAvatar.sessionUUIDChanged.connect(function () {
+ * print("Avatar session UUID changed to: " + MyAvatar.sessionUUID);
+ * });
+ *
+ * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar".
*/
void sessionUUIDChanged();
public slots:
/**jsdoc
- * @function MyAvatar.sendAvatarDataPacket
- * @param {boolean} [sendAll=false]
+ * @function Avatar.sendAvatarDataPacket
+ * @param {boolean} [sendAll=false] - Send all.
+ * @returns {number}
+ * @deprecated This function is deprecated and will be removed.
*/
virtual int sendAvatarDataPacket(bool sendAll = false);
/**jsdoc
- * @function MyAvatar.sendIdentityPacket
+ * @function Avatar.sendIdentityPacket
+ * @returns {number}
+ * @deprecated This function is deprecated and will be removed.
*/
int sendIdentityPacket();
/**jsdoc
- * @function MyAvatar.setSessionUUID
- * @param {Uuid} sessionUUID
+ * @function Avatar.setSessionUUID
+ * @param {Uuid} sessionUUID - Session UUID.
+ * @deprecated This function is deprecated and will be removed.
*/
virtual void setSessionUUID(const QUuid& sessionUUID) {
if (sessionUUID != getID()) {
@@ -1293,44 +1521,61 @@ public slots:
}
}
+
/**jsdoc
- * @function MyAvatar.getAbsoluteJointRotationInObjectFrame
- * @param {number} index
- * @returns {Quat}
+ * Gets the rotation of a joint relative to the avatar.
+ * Warning: Not able to be used in the Avatar
API.
+ * @function Avatar.getAbsoluteJointRotationInObjectFrame
+ * @param {number} index - The index of the joint. Not used.
+ * @returns {Quat} Quat.IDENTITY
.
*/
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
/**jsdoc
- * @function MyAvatar.getAbsoluteJointTranslationInObjectFrame
- * @param {number} index
- * @returns {Vec3}
+ * Gets the translation of a joint relative to the avatar.
+ * Warning: Not able to be used in the Avatar
API.
+ * @function Avatar.getAbsoluteJointTranslationInObjectFrame
+ * @param {number} index - The index of the joint. Not used.
+ * @returns {Vec3} Vec3.ZERO
.
*/
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
/**jsdoc
- * @function MyAvatar.setAbsoluteJointRotationInObjectFrame
- * @param {number} index
- * @param {Quat} rotation
- * @returns {boolean}
+ * Sets the rotation of a joint relative to the avatar.
+ * Warning: Not able to be used in the Avatar
API.
+ * @function Avatar.setAbsoluteJointRotationInObjectFrame
+ * @param {number} index - The index of the joint. Not used.
+ * @param {Quat} rotation - The rotation of the joint relative to the avatar. Not used.
+ * @returns {boolean} false
.
*/
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; }
/**jsdoc
- * @function MyAvatar.setAbsoluteJointTranslationInObjectFrame
- * @param {number} index
- * @param {Vec3} translation
- * @returns {boolean}
+ * Sets the translation of a joint relative to the avatar.
+ * Warning: Not able to be used in the Avatar
API.
+ * @function Avatar.setAbsoluteJointTranslationInObjectFrame
+ * @param {number} index - The index of the joint. Not used.
+ * @param {Vec3} translation - The translation of the joint relative to the avatar. Not used.
+ * @returns {boolean} false
.
*/
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; }
/**jsdoc
- * @function MyAvatar.getTargetScale
- * @returns {number}
+ * Gets the target scale of the avatar without any restrictions on permissible values imposed by the domain. In contrast, the
+ * scale
property's value may be limited by the domain's settings.
+ * @function Avatar.getTargetScale
+ * @returns {number} The target scale of the avatar.
+ * @example Compare the target and current avatar scales.
+ * print("Current avatar scale: " + MyAvatar.scale);
+ * print("Target avatar scale: " + MyAvatar.getTargetScale());
+ *
+ * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar".
*/
float getTargetScale() const { return _targetScale; } // why is this a slot?
/**jsdoc
- * @function MyAvatar.resetLastSent
+ * @function Avatar.resetLastSent
+ * @deprecated This function is deprecated and will be removed.
*/
void resetLastSent() { _lastToByteArray = 0; }
@@ -1352,13 +1597,13 @@ protected:
bool hasParent() const { return !getParentID().isNull(); }
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
- qint64 packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType,
- AvatarTraits::TraitInstanceID traitInstanceID,
- ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion);
- qint64 packGrabTraitInstance(AvatarTraits::TraitType traitType,
- AvatarTraits::TraitInstanceID traitInstanceID,
- ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion);
+ QByteArray packSkeletonModelURL() const;
+ QByteArray packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
+ QByteArray packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
+ void unpackSkeletonModelURL(const QByteArray& data);
+
+
// isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master"
// Audio Mixer that the replicated avatar is connected to.
bool _isReplicated{ false };
@@ -1462,6 +1707,7 @@ protected:
glm::vec3 _globalBoundingBoxOffset;
AABox _defaultBubbleBox;
+ AABox _fitBoundingBox;
mutable ReadWriteLockable _avatarEntitiesLock;
AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids
@@ -1498,6 +1744,7 @@ protected:
bool _isNewAvatar { true };
bool _isClientAvatar { false };
bool _collideWithOtherAvatars { true };
+ bool _hasPriority{ false };
// null unless MyAvatar or ScriptableAvatar sending traits data to mixer
std::unique_ptr _clientTraitsHandler;
diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp
index c16d65506a..3abd352778 100644
--- a/libraries/avatars/src/AvatarHashMap.cpp
+++ b/libraries/avatars/src/AvatarHashMap.cpp
@@ -439,7 +439,6 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
}
auto removedAvatar = _avatarHash.take(sessionUUID);
-
if (removedAvatar) {
removedAvatars.push_back(removedAvatar);
}
diff --git a/libraries/avatars/src/AvatarTraits.cpp b/libraries/avatars/src/AvatarTraits.cpp
new file mode 100644
index 0000000000..724f30e2f3
--- /dev/null
+++ b/libraries/avatars/src/AvatarTraits.cpp
@@ -0,0 +1,135 @@
+//
+// AvatarTraits.cpp
+// libraries/avatars/src
+//
+// Created by Clement Brisset on 3/19/19.
+// Copyright 2019 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 "AvatarTraits.h"
+
+#include
+
+#include "AvatarData.h"
+
+namespace AvatarTraits {
+
+ qint64 packTrait(TraitType traitType, ExtendedIODevice& destination, const AvatarData& avatar) {
+ // Call packer function
+ auto traitBinaryData = avatar.packTrait(traitType);
+ auto traitBinaryDataSize = traitBinaryData.size();
+
+ // Verify packed data
+ if (traitBinaryDataSize > MAXIMUM_TRAIT_SIZE) {
+ qWarning() << "Refusing to pack simple trait" << traitType << "of size" << traitBinaryDataSize
+ << "bytes since it exceeds the maximum size" << MAXIMUM_TRAIT_SIZE << "bytes";
+ return 0;
+ }
+
+ // Write packed data to stream
+ qint64 bytesWritten = 0;
+ bytesWritten += destination.writePrimitive((TraitType)traitType);
+ bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
+ bytesWritten += destination.write(traitBinaryData);
+ return bytesWritten;
+ }
+
+ qint64 packVersionedTrait(TraitType traitType, ExtendedIODevice& destination,
+ TraitVersion traitVersion, const AvatarData& avatar) {
+ // Call packer function
+ auto traitBinaryData = avatar.packTrait(traitType);
+ auto traitBinaryDataSize = traitBinaryData.size();
+
+ // Verify packed data
+ if (traitBinaryDataSize > MAXIMUM_TRAIT_SIZE) {
+ qWarning() << "Refusing to pack simple trait" << traitType << "of size" << traitBinaryDataSize
+ << "bytes since it exceeds the maximum size" << MAXIMUM_TRAIT_SIZE << "bytes";
+ return 0;
+ }
+
+ // Write packed data to stream
+ qint64 bytesWritten = 0;
+ bytesWritten += destination.writePrimitive((TraitType)traitType);
+ bytesWritten += destination.writePrimitive((TraitVersion)traitVersion);
+ bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
+ bytesWritten += destination.write(traitBinaryData);
+ return bytesWritten;
+ }
+
+
+ qint64 packTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
+ ExtendedIODevice& destination, AvatarData& avatar) {
+ // Call packer function
+ auto traitBinaryData = avatar.packTraitInstance(traitType, traitInstanceID);
+ auto traitBinaryDataSize = traitBinaryData.size();
+
+
+ // Verify packed data
+ if (traitBinaryDataSize > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
+ qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << traitBinaryDataSize
+ << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
+ return 0;
+ }
+
+ // Write packed data to stream
+ qint64 bytesWritten = 0;
+ bytesWritten += destination.writePrimitive((TraitType)traitType);
+ bytesWritten += destination.write(traitInstanceID.toRfc4122());
+
+ if (!traitBinaryData.isNull()) {
+ bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
+ bytesWritten += destination.write(traitBinaryData);
+ } else {
+ bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
+ }
+
+ return bytesWritten;
+ }
+
+ qint64 packVersionedTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
+ ExtendedIODevice& destination, TraitVersion traitVersion,
+ AvatarData& avatar) {
+ // Call packer function
+ auto traitBinaryData = avatar.packTraitInstance(traitType, traitInstanceID);
+ auto traitBinaryDataSize = traitBinaryData.size();
+
+
+ // Verify packed data
+ if (traitBinaryDataSize > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
+ qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << traitBinaryDataSize
+ << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
+ return 0;
+ }
+
+ // Write packed data to stream
+ qint64 bytesWritten = 0;
+ bytesWritten += destination.writePrimitive((TraitType)traitType);
+ bytesWritten += destination.writePrimitive((TraitVersion)traitVersion);
+ bytesWritten += destination.write(traitInstanceID.toRfc4122());
+
+ if (!traitBinaryData.isNull()) {
+ bytesWritten += destination.writePrimitive((TraitWireSize)traitBinaryDataSize);
+ bytesWritten += destination.write(traitBinaryData);
+ } else {
+ bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
+ }
+
+ return bytesWritten;
+ }
+
+
+ qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
+ TraitVersion traitVersion) {
+ qint64 bytesWritten = 0;
+ bytesWritten += destination.writePrimitive(traitType);
+ if (traitVersion > DEFAULT_TRAIT_VERSION) {
+ bytesWritten += destination.writePrimitive(traitVersion);
+ }
+ bytesWritten += destination.write(instanceID.toRfc4122());
+ bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
+ return bytesWritten;
+ }
+};
diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h
index 4516572e42..13d64ec225 100644
--- a/libraries/avatars/src/AvatarTraits.h
+++ b/libraries/avatars/src/AvatarTraits.h
@@ -14,20 +14,35 @@
#include
#include
+#include
#include
#include
+class ExtendedIODevice;
+class AvatarData;
+
namespace AvatarTraits {
enum TraitType : int8_t {
+ // Null trait
NullTrait = -1,
- SkeletonModelURL,
+
+ // Simple traits
+ SkeletonModelURL = 0,
+
+ // Instanced traits
FirstInstancedTrait,
AvatarEntity = FirstInstancedTrait,
Grab,
+
+ // Traits count
TotalTraitTypes
};
+ const int NUM_SIMPLE_TRAITS = (int)FirstInstancedTrait;
+ const int NUM_INSTANCED_TRAITS = (int)TotalTraitTypes - (int)FirstInstancedTrait;
+ const int NUM_TRAITS = (int)TotalTraitTypes;
+
using TraitInstanceID = QUuid;
inline bool isSimpleTrait(TraitType traitType) {
@@ -46,22 +61,19 @@ namespace AvatarTraits {
const TraitMessageSequence FIRST_TRAIT_SEQUENCE = 0;
const TraitMessageSequence MAX_TRAIT_SEQUENCE = INT64_MAX;
- inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
- TraitVersion traitVersion = NULL_TRAIT_VERSION) {
- qint64 bytesWritten = 0;
+ qint64 packTrait(TraitType traitType, ExtendedIODevice& destination, const AvatarData& avatar);
+ qint64 packVersionedTrait(TraitType traitType, ExtendedIODevice& destination,
+ TraitVersion traitVersion, const AvatarData& avatar);
- bytesWritten += destination.writePrimitive(traitType);
+ qint64 packTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
+ ExtendedIODevice& destination, AvatarData& avatar);
+ qint64 packVersionedTraitInstance(TraitType traitType, TraitInstanceID traitInstanceID,
+ ExtendedIODevice& destination, TraitVersion traitVersion,
+ AvatarData& avatar);
- if (traitVersion > DEFAULT_TRAIT_VERSION) {
- bytesWritten += destination.writePrimitive(traitVersion);
- }
+ qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
+ TraitVersion traitVersion = NULL_TRAIT_VERSION);
- bytesWritten += destination.write(instanceID.toRfc4122());
-
- bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
-
- return bytesWritten;
- }
};
#endif // hifi_AvatarTraits_h
diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp
index bcbe5308c7..f6bd66e89a 100644
--- a/libraries/avatars/src/ClientTraitsHandler.cpp
+++ b/libraries/avatars/src/ClientTraitsHandler.cpp
@@ -106,9 +106,10 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
auto traitType = static_cast(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt));
if (initialSend || *simpleIt == Updated) {
- if (traitType == AvatarTraits::SkeletonModelURL) {
- bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList);
+ bytesWritten += AvatarTraits::packTrait(traitType, *traitsPacketList, *_owningAvatar);
+
+ if (traitType == AvatarTraits::SkeletonModelURL) {
// keep track of our skeleton version in case we get an override back
_currentSkeletonVersion = _currentTraitVersion;
}
@@ -124,7 +125,9 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
|| instanceIDValuePair.value == Updated) {
// this is a changed trait we need to send or we haven't send out trait information yet
// ask the owning avatar to pack it
- bytesWritten += _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList);
+ bytesWritten += AvatarTraits::packTraitInstance(instancedIt->traitType, instanceIDValuePair.id,
+ *traitsPacketList, *_owningAvatar);
+
} else if (!initialSend && instanceIDValuePair.value == Deleted) {
// pack delete for this trait instance
bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id,
@@ -162,11 +165,11 @@ void ClientTraitsHandler::processTraitOverride(QSharedPointer m
// override the skeleton URL but do not mark the trait as having changed
// so that we don't unecessarily send a new trait packet to the mixer with the overriden URL
- auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize));
auto hasChangesBefore = _hasChangedTraits;
- _owningAvatar->setSkeletonModelURL(encodedSkeletonURL);
+ auto traitBinaryData = message->readWithoutCopy(traitBinarySize);
+ _owningAvatar->processTrait(traitType, traitBinaryData);
// setSkeletonModelURL will flag us for changes to the SkeletonModelURL so we reset some state here to
// avoid unnecessarily sending the overriden skeleton model URL back to the mixer
diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp
index a716a40ad8..18717c8ca3 100644
--- a/libraries/avatars/src/ScriptAvatarData.cpp
+++ b/libraries/avatars/src/ScriptAvatarData.cpp
@@ -343,6 +343,14 @@ glm::mat4 ScriptAvatarData::getControllerRightHandMatrix() const {
// END
//
+bool ScriptAvatarData::getHasPriority() const {
+ if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
+ return sharedAvatarData->getHasPriority();
+ } else {
+ return false;
+ }
+}
+
glm::quat ScriptAvatarData::getAbsoluteJointRotationInObjectFrame(int index) const {
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
return sharedAvatarData->getAbsoluteJointRotationInObjectFrame(index);
diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h
index 91bac61728..01f7ff360a 100644
--- a/libraries/avatars/src/ScriptAvatarData.h
+++ b/libraries/avatars/src/ScriptAvatarData.h
@@ -68,6 +68,8 @@ class ScriptAvatarData : public QObject {
Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix)
Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix)
+ Q_PROPERTY(bool hasPriority READ getHasPriority)
+
public:
ScriptAvatarData(AvatarSharedPointer avatarData);
@@ -133,6 +135,8 @@ public:
glm::mat4 getControllerLeftHandMatrix() const;
glm::mat4 getControllerRightHandMatrix() const;
+ bool getHasPriority() const;
+
signals:
void displayNameChanged();
void sessionDisplayNameChanged();
diff --git a/libraries/baking/CMakeLists.txt b/libraries/baking/CMakeLists.txt
index cce76f152f..73618427f6 100644
--- a/libraries/baking/CMakeLists.txt
+++ b/libraries/baking/CMakeLists.txt
@@ -1,8 +1,6 @@
set(TARGET_NAME baking)
setup_hifi_library(Concurrent)
-link_hifi_libraries(shared graphics networking ktx image fbx)
+link_hifi_libraries(shared shaders graphics networking material-networking graphics-scripting ktx image fbx model-baker task)
include_hifi_library_headers(gpu)
include_hifi_library_headers(hfm)
-
-target_draco()
diff --git a/libraries/baking/src/Baker.h b/libraries/baking/src/Baker.h
index c1b2ddf959..611f992c96 100644
--- a/libraries/baking/src/Baker.h
+++ b/libraries/baking/src/Baker.h
@@ -52,7 +52,7 @@ protected:
void handleErrors(const QStringList& errors);
// List of baked output files. For instance, for an FBX this would
- // include the .fbx and all of its texture files.
+ // include the .fbx, a .fst pointing to the fbx, and all of the fbx texture files.
std::vector _outputFiles;
QStringList _errorList;
diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp
index afaca1dd62..2189e7bdc3 100644
--- a/libraries/baking/src/FBXBaker.cpp
+++ b/libraries/baking/src/FBXBaker.cpp
@@ -33,29 +33,19 @@
#include "ModelBakingLoggingCategory.h"
#include "TextureBaker.h"
-#ifdef HIFI_DUMP_FBX
-#include "FBXToJSON.h"
-#endif
-
-void FBXBaker::bake() {
- qDebug() << "FBXBaker" << _modelURL << "bake starting";
-
- // setup the output folder for the results of this bake
- setupOutputFolder();
-
- if (shouldStop()) {
- return;
+FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
+ const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
+ ModelBaker(inputModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
+ if (hasBeenBaked) {
+ // Look for the original model file one directory higher. Perhaps this is an oven output directory.
+ QUrl originalRelativePath = QUrl("../original/" + inputModelURL.fileName().replace(BAKED_FBX_EXTENSION, FBX_EXTENSION));
+ QUrl newInputModelURL = inputModelURL.adjusted(QUrl::RemoveFilename).resolved(originalRelativePath);
+ _modelURL = newInputModelURL;
}
-
- connect(this, &FBXBaker::sourceCopyReadyToLoad, this, &FBXBaker::bakeSourceCopy);
-
- // make a local copy of the FBX file
- loadSourceFBX();
}
-void FBXBaker::bakeSourceCopy() {
- // load the scene from the FBX file
- importScene();
+void FBXBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) {
+ _hfmModel = hfmModel;
if (shouldStop()) {
return;
@@ -68,222 +58,100 @@ void FBXBaker::bakeSourceCopy() {
return;
}
- rewriteAndBakeSceneModels();
+ rewriteAndBakeSceneModels(hfmModel->meshes, dracoMeshes, dracoMaterialLists);
+}
- if (shouldStop()) {
+void FBXBaker::replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList) {
+ // Compress mesh information and store in dracoMeshNode
+ FBXNode dracoMeshNode;
+ bool success = buildDracoMeshNode(dracoMeshNode, dracoMeshBytes, dracoMaterialList);
+
+ if (!success) {
return;
- }
-
- // check if we're already done with textures (in case we had none to re-write)
- checkIfTexturesFinished();
-}
-
-void FBXBaker::setupOutputFolder() {
- // make sure there isn't already an output directory using the same name
- if (QDir(_bakedOutputDir).exists()) {
- qWarning() << "Output path" << _bakedOutputDir << "already exists. Continuing.";
} else {
- qCDebug(model_baking) << "Creating FBX output folder" << _bakedOutputDir;
+ meshNode.children.push_back(dracoMeshNode);
- // attempt to make the output folder
- if (!QDir().mkpath(_bakedOutputDir)) {
- handleError("Failed to create FBX output folder " + _bakedOutputDir);
- return;
- }
- // attempt to make the output folder
- if (!QDir().mkpath(_originalOutputDir)) {
- handleError("Failed to create FBX output folder " + _originalOutputDir);
- return;
- }
- }
-}
+ static const std::vector nodeNamesToDelete {
+ // Node data that is packed into the draco mesh
+ "Vertices",
+ "PolygonVertexIndex",
+ "LayerElementNormal",
+ "LayerElementColor",
+ "LayerElementUV",
+ "LayerElementMaterial",
+ "LayerElementTexture",
-void FBXBaker::loadSourceFBX() {
- // check if the FBX is local or first needs to be downloaded
- if (_modelURL.isLocalFile()) {
- // load up the local file
- QFile localFBX { _modelURL.toLocalFile() };
-
- qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath;
-
- if (!localFBX.exists()) {
- //QMessageBox::warning(this, "Could not find " + _fbxURL.toString(), "");
- handleError("Could not find " + _modelURL.toString());
- return;
- }
-
- // make a copy in the output folder
- if (!_originalOutputDir.isEmpty()) {
- qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName();
- localFBX.copy(_originalOutputDir + "/" + _modelURL.fileName());
- }
-
- localFBX.copy(_originalModelFilePath);
-
- // emit our signal to start the import of the FBX source copy
- emit sourceCopyReadyToLoad();
- } else {
- // remote file, kick off a download
- auto& networkAccessManager = NetworkAccessManager::getInstance();
-
- QNetworkRequest networkRequest;
-
- // setup the request to follow re-directs and always hit the network
- networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
- networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
- networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
-
- networkRequest.setUrl(_modelURL);
-
- qCDebug(model_baking) << "Downloading" << _modelURL;
- auto networkReply = networkAccessManager.get(networkRequest);
-
- connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply);
- }
-}
-
-void FBXBaker::handleFBXNetworkReply() {
- auto requestReply = qobject_cast(sender());
-
- if (requestReply->error() == QNetworkReply::NoError) {
- qCDebug(model_baking) << "Downloaded" << _modelURL;
-
- // grab the contents of the reply and make a copy in the output folder
- QFile copyOfOriginal(_originalModelFilePath);
-
- qDebug(model_baking) << "Writing copy of original FBX to" << _originalModelFilePath << copyOfOriginal.fileName();
-
- if (!copyOfOriginal.open(QIODevice::WriteOnly)) {
- // add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made
- handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")");
- return;
- }
- if (copyOfOriginal.write(requestReply->readAll()) == -1) {
- handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)");
- return;
- }
-
- // close that file now that we are done writing to it
- copyOfOriginal.close();
-
- if (!_originalOutputDir.isEmpty()) {
- copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName());
- }
-
- // emit our signal to start the import of the FBX source copy
- emit sourceCopyReadyToLoad();
- } else {
- // add an error to our list stating that the FBX could not be downloaded
- handleError("Failed to download " + _modelURL.toString());
- }
-}
-
-void FBXBaker::importScene() {
- qDebug() << "file path: " << _originalModelFilePath.toLocal8Bit().data() << QDir(_originalModelFilePath).exists();
-
- QFile fbxFile(_originalModelFilePath);
- if (!fbxFile.open(QIODevice::ReadOnly)) {
- handleError("Error opening " + _originalModelFilePath + " for reading");
- return;
- }
-
- FBXSerializer fbxSerializer;
-
- qCDebug(model_baking) << "Parsing" << _modelURL;
- _rootNode = fbxSerializer._rootNode = fbxSerializer.parseFBX(&fbxFile);
-
-#ifdef HIFI_DUMP_FBX
- {
- FBXToJSON fbxToJSON;
- fbxToJSON << _rootNode;
- QFileInfo modelFile(_originalModelFilePath);
- QString outFilename(_bakedOutputDir + "/" + modelFile.completeBaseName() + "_FBX.json");
- QFile jsonFile(outFilename);
- if (jsonFile.open(QIODevice::WriteOnly)) {
- jsonFile.write(fbxToJSON.str().c_str(), fbxToJSON.str().length());
- jsonFile.close();
- }
- }
-#endif
-
- _hfmModel = fbxSerializer.extractHFMModel({}, _modelURL.toString());
- _textureContentMap = fbxSerializer._textureContent;
-}
-
-void FBXBaker::rewriteAndBakeSceneModels() {
- unsigned int meshIndex = 0;
- bool hasDeformers { false };
- for (FBXNode& rootChild : _rootNode.children) {
- if (rootChild.name == "Objects") {
- for (FBXNode& objectChild : rootChild.children) {
- if (objectChild.name == "Deformer") {
- hasDeformers = true;
- break;
- }
+ // Node data that we don't support
+ "Edges",
+ "LayerElementTangent",
+ "LayerElementBinormal",
+ "LayerElementSmoothing"
+ };
+ auto& children = meshNode.children;
+ auto it = children.begin();
+ while (it != children.end()) {
+ auto begin = nodeNamesToDelete.begin();
+ auto end = nodeNamesToDelete.end();
+ if (find(begin, end, it->name) != end) {
+ it = children.erase(it);
+ } else {
+ ++it;
}
}
- if (hasDeformers) {
- break;
- }
}
+}
+
+void FBXBaker::rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) {
+ std::vector meshIndexToRuntimeOrder;
+ auto meshCount = (int)meshes.size();
+ meshIndexToRuntimeOrder.resize(meshCount);
+ for (int i = 0; i < meshCount; i++) {
+ meshIndexToRuntimeOrder[meshes[i].meshIndex] = i;
+ }
+
+ // The meshIndex represents the order in which the meshes are loaded from the FBX file
+ // We replicate this order by iterating over the meshes in the same way that FBXSerializer does
+ int meshIndex = 0;
for (FBXNode& rootChild : _rootNode.children) {
if (rootChild.name == "Objects") {
- for (FBXNode& objectChild : rootChild.children) {
- if (objectChild.name == "Geometry") {
-
- // TODO Pull this out of _hfmModel instead so we don't have to reprocess it
- auto extractedMesh = FBXSerializer::extractMesh(objectChild, meshIndex, false);
-
- // Callback to get MaterialID
- GetMaterialIDCallback materialIDcallback = [&extractedMesh](int partIndex) {
- return extractedMesh.partMaterialTextures[partIndex].first;
- };
-
- // Compress mesh information and store in dracoMeshNode
- FBXNode dracoMeshNode;
- bool success = compressMesh(extractedMesh.mesh, hasDeformers, dracoMeshNode, materialIDcallback);
-
- // if bake fails - return, if there were errors and continue, if there were warnings.
- if (!success) {
- if (hasErrors()) {
- return;
- } else if (hasWarnings()) {
- continue;
- }
- } else {
- objectChild.children.push_back(dracoMeshNode);
-
- static const std::vector nodeNamesToDelete {
- // Node data that is packed into the draco mesh
- "Vertices",
- "PolygonVertexIndex",
- "LayerElementNormal",
- "LayerElementColor",
- "LayerElementUV",
- "LayerElementMaterial",
- "LayerElementTexture",
-
- // Node data that we don't support
- "Edges",
- "LayerElementTangent",
- "LayerElementBinormal",
- "LayerElementSmoothing"
- };
- auto& children = objectChild.children;
- auto it = children.begin();
- while (it != children.end()) {
- auto begin = nodeNamesToDelete.begin();
- auto end = nodeNamesToDelete.end();
- if (find(begin, end, it->name) != end) {
- it = children.erase(it);
- } else {
- ++it;
+ for (FBXNode& object : rootChild.children) {
+ if (object.name == "Geometry") {
+ if (object.properties.at(2) == "Mesh") {
+ int meshNum = meshIndexToRuntimeOrder[meshIndex];
+ replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
+ meshIndex++;
+ }
+ } else if (object.name == "Model") {
+ for (FBXNode& modelChild : object.children) {
+ if (modelChild.name == "Properties60" || modelChild.name == "Properties70") {
+ // This is a properties node
+ // Remove the geometric transform because that has been applied directly to the vertices in FBXSerializer
+ static const QVariant GEOMETRIC_TRANSLATION = hifi::ByteArray("GeometricTranslation");
+ static const QVariant GEOMETRIC_ROTATION = hifi::ByteArray("GeometricRotation");
+ static const QVariant GEOMETRIC_SCALING = hifi::ByteArray("GeometricScaling");
+ for (int i = 0; i < modelChild.children.size(); i++) {
+ const auto& prop = modelChild.children[i];
+ const auto& propertyName = prop.properties.at(0);
+ if (propertyName == GEOMETRIC_TRANSLATION ||
+ propertyName == GEOMETRIC_ROTATION ||
+ propertyName == GEOMETRIC_SCALING) {
+ modelChild.children.removeAt(i);
+ --i;
+ }
}
+ } else if (modelChild.name == "Vertices") {
+ // This model is also a mesh
+ int meshNum = meshIndexToRuntimeOrder[meshIndex];
+ replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
+ meshIndex++;
}
}
- } // Geometry Object
+ }
- } // foreach root child
+ if (hasErrors()) {
+ return;
+ }
+ }
}
}
}
diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h
index 2af51b2190..59ef5e349d 100644
--- a/libraries/baking/src/FBXBaker.h
+++ b/libraries/baking/src/FBXBaker.h
@@ -31,31 +31,18 @@ using TextureBakerThreadGetter = std::function;
class FBXBaker : public ModelBaker {
Q_OBJECT
public:
- using ModelBaker::ModelBaker;
+ FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
+ const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
-public slots:
- virtual void bake() override;
-
-signals:
- void sourceCopyReadyToLoad();
-
-private slots:
- void bakeSourceCopy();
- void handleFBXNetworkReply();
+protected:
+ virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists) override;
private:
- void setupOutputFolder();
-
- void loadSourceFBX();
-
- void importScene();
- void embedTextureMetaData();
- void rewriteAndBakeSceneModels();
+ void rewriteAndBakeSceneModels(const QVector& meshes, const std::vector& dracoMeshes, const std::vector>& dracoMaterialLists);
void rewriteAndBakeSceneTextures();
+ void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector& dracoMaterialList);
- HFMModel* _hfmModel;
- QHash