getSkeletonDefaultData();
+
/**jsdoc
* Gets the default rotation of a joint (in the current avatar) relative to its parent.
* For information on the joint hierarchy used, see
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index f460881a45..95f46b3ddc 100755
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -1633,6 +1633,13 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v
data.translationIsDefaultPose = false;
}
+QVector AvatarData::getJointData() const {
+ QVector jointData;
+ QReadLocker readLock(&_jointDataLock);
+ jointData = _jointData;
+ return jointData;
+}
+
void AvatarData::clearJointData(int index) {
if (index < 0 || index >= LOWEST_PSEUDO_JOINT_INDEX) {
return;
@@ -1987,11 +1994,98 @@ QUrl AvatarData::getWireSafeSkeletonModelURL() const {
return QUrl();
}
}
+QByteArray AvatarData::packSkeletonData() const {
+ // Send an avatar trait packet with the skeleton data before the mesh is loaded
+ int avatarDataSize = 0;
+ QByteArray avatarDataByteArray;
+ _avatarSkeletonDataLock.withReadLock([&] {
+ // Add header
+ AvatarSkeletonTrait::Header header;
+ header.maxScaleDimension = 0.0f;
+ header.maxTranslationDimension = 0.0f;
+ header.numJoints = (uint8_t)_avatarSkeletonData.size();
+ header.stringTableLength = 0;
+
+ for (size_t i = 0; i < _avatarSkeletonData.size(); i++) {
+ header.stringTableLength += (uint16_t)_avatarSkeletonData[i].jointName.size();
+ auto& translation = _avatarSkeletonData[i].defaultTranslation;
+ header.maxTranslationDimension = std::max(header.maxTranslationDimension, std::max(std::max(translation.x, translation.y), translation.z));
+ header.maxScaleDimension = std::max(header.maxScaleDimension, _avatarSkeletonData[i].defaultScale);
+ }
+
+ const int byteArraySize = (int)sizeof(AvatarSkeletonTrait::Header) + (int)(header.numJoints * sizeof(AvatarSkeletonTrait::JointData)) + header.stringTableLength;
+ avatarDataByteArray = QByteArray(byteArraySize, 0);
+ unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data());
+ const unsigned char* const startPosition = destinationBuffer;
+
+ memcpy(destinationBuffer, &header, sizeof(header));
+ destinationBuffer += sizeof(AvatarSkeletonTrait::Header);
+
+ QString stringTable = "";
+ for (size_t i = 0; i < _avatarSkeletonData.size(); i++) {
+ AvatarSkeletonTrait::JointData jdata;
+ jdata.boneType = _avatarSkeletonData[i].boneType;
+ jdata.parentIndex = _avatarSkeletonData[i].parentIndex;
+ packFloatRatioToTwoByte((uint8_t*)(&jdata.defaultScale), _avatarSkeletonData[i].defaultScale / header.maxScaleDimension);
+ packOrientationQuatToSixBytes(jdata.defaultRotation, _avatarSkeletonData[i].defaultRotation);
+ packFloatVec3ToSignedTwoByteFixed(jdata.defaultTranslation, _avatarSkeletonData[i].defaultTranslation / header.maxTranslationDimension, TRANSLATION_COMPRESSION_RADIX);
+ jdata.jointIndex = (uint16_t)i;
+ jdata.stringStart = (uint16_t)_avatarSkeletonData[i].stringStart;
+ jdata.stringLength = (uint8_t)_avatarSkeletonData[i].stringLength;
+ stringTable += _avatarSkeletonData[i].jointName;
+ memcpy(destinationBuffer, &jdata, sizeof(AvatarSkeletonTrait::JointData));
+ destinationBuffer += sizeof(AvatarSkeletonTrait::JointData);
+ }
+
+ memcpy(destinationBuffer, stringTable.toUtf8(), header.stringTableLength);
+ destinationBuffer += header.stringTableLength;
+
+ avatarDataSize = destinationBuffer - startPosition;
+ });
+ return avatarDataByteArray.left(avatarDataSize);
+}
QByteArray AvatarData::packSkeletonModelURL() const {
return getWireSafeSkeletonModelURL().toEncoded();
}
+void AvatarData::unpackSkeletonData(const QByteArray& data) {
+
+ const unsigned char* startPosition = reinterpret_cast(data.data());
+ const unsigned char* sourceBuffer = startPosition;
+
+ auto header = reinterpret_cast(sourceBuffer);
+ sourceBuffer += sizeof(const AvatarSkeletonTrait::Header);
+
+ std::vector joints;
+ for (uint8_t i = 0; i < header->numJoints; i++) {
+ auto jointData = reinterpret_cast(sourceBuffer);
+ sourceBuffer += sizeof(const AvatarSkeletonTrait::JointData);
+ AvatarSkeletonTrait::UnpackedJointData uJointData;
+ uJointData.boneType = (int)jointData->boneType;
+ uJointData.jointIndex = (int)i;
+ uJointData.stringLength = (int)jointData->stringLength;
+ uJointData.stringStart = (int)jointData->stringStart;
+ uJointData.parentIndex = ((uJointData.boneType == AvatarSkeletonTrait::BoneType::SkeletonRoot) ||
+ (uJointData.boneType == AvatarSkeletonTrait::BoneType::NonSkeletonRoot)) ? -1 : (int)jointData->parentIndex;
+ unpackOrientationQuatFromSixBytes(reinterpret_cast(&jointData->defaultRotation), uJointData.defaultRotation);
+ unpackFloatVec3FromSignedTwoByteFixed(reinterpret_cast(&jointData->defaultTranslation), uJointData.defaultTranslation, TRANSLATION_COMPRESSION_RADIX);
+ unpackFloatRatioFromTwoByte(reinterpret_cast(&jointData->defaultScale), uJointData.defaultScale);
+ uJointData.defaultTranslation *= header->maxTranslationDimension;
+ uJointData.defaultScale *= header->maxScaleDimension;
+ joints.push_back(uJointData);
+ }
+ QString table = QString::fromUtf8(reinterpret_cast(sourceBuffer), (int)header->stringTableLength);
+ for (size_t i = 0; i < joints.size(); i++) {
+ QStringRef subString(&table, joints[i].stringStart, joints[i].stringLength);
+ joints[i].jointName = subString.toString();
+ }
+ if (_clientTraitsHandler) {
+ _clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonData);
+ }
+ setSkeletonData(joints);
+}
+
void AvatarData::unpackSkeletonModelURL(const QByteArray& data) {
auto skeletonModelURL = QUrl::fromEncoded(data);
setSkeletonModelURL(skeletonModelURL);
@@ -2027,6 +2121,8 @@ QByteArray AvatarData::packTrait(AvatarTraits::TraitType traitType) const {
// Call packer function
if (traitType == AvatarTraits::SkeletonModelURL) {
traitBinaryData = packSkeletonModelURL();
+ } else if (traitType == AvatarTraits::SkeletonData) {
+ traitBinaryData = packSkeletonData();
}
return traitBinaryData;
@@ -2048,6 +2144,8 @@ QByteArray AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, Avat
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
if (traitType == AvatarTraits::SkeletonModelURL) {
unpackSkeletonModelURL(traitBinaryData);
+ } else if (traitType == AvatarTraits::SkeletonData) {
+ unpackSkeletonData(traitBinaryData);
}
}
@@ -2110,7 +2208,6 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
}
_skeletonModelURL = expanded;
-
if (_clientTraitsHandler) {
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL);
}
@@ -3008,6 +3105,26 @@ AABox AvatarData::computeBubbleBox(float bubbleScale) const {
return box;
}
+void AvatarData::setSkeletonData(const std::vector& skeletonData) {
+ _avatarSkeletonDataLock.withWriteLock([&] {
+ _avatarSkeletonData = skeletonData;
+ });
+}
+
+std::vector AvatarData::getSkeletonData() const {
+ std::vector skeletonData;
+ _avatarSkeletonDataLock.withReadLock([&] {
+ skeletonData = _avatarSkeletonData;
+ });
+ return skeletonData;
+}
+
+void AvatarData::sendSkeletonData() const{
+ if (_clientTraitsHandler) {
+ _clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonData);
+ }
+}
+
AABox AvatarData::getDefaultBubbleBox() const {
AABox bubbleBox(_defaultBubbleBox);
bubbleBox.translate(_globalPosition);
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index d73573272d..2add2f9881 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -145,6 +145,45 @@ const char AVATARDATA_FLAGS_MINIMUM = 0;
using SmallFloat = uint16_t; // a compressed float with less precision, user defined radix
+namespace AvatarSkeletonTrait {
+ enum BoneType {
+ SkeletonRoot = 0,
+ SkeletonChild,
+ NonSkeletonRoot,
+ NonSkeletonChild
+ };
+
+ PACKED_BEGIN struct Header {
+ float maxTranslationDimension;
+ float maxScaleDimension;
+ uint8_t numJoints;
+ uint16_t stringTableLength;
+ } PACKED_END;
+
+ PACKED_BEGIN struct JointData {
+ uint16_t stringStart;
+ uint8_t stringLength;
+ uint8_t boneType;
+ uint8_t defaultTranslation[6];
+ uint8_t defaultRotation[6];
+ uint16_t defaultScale;
+ uint16_t jointIndex;
+ uint16_t parentIndex;
+ } PACKED_END;
+
+ struct UnpackedJointData {
+ int stringStart;
+ int stringLength;
+ int boneType;
+ glm::vec3 defaultTranslation;
+ glm::quat defaultRotation;
+ float defaultScale;
+ int jointIndex;
+ int parentIndex;
+ QString jointName;
+ };
+}
+
namespace AvatarDataPacket {
// NOTE: every time AvatarData is sent from mixer to client, it also includes the GUIID for the session
@@ -258,6 +297,7 @@ namespace AvatarDataPacket {
PACKED_BEGIN struct AvatarLocalPosition {
float localPosition[3]; // parent frame translation of the avatar
} PACKED_END;
+
const size_t AVATAR_LOCAL_POSITION_SIZE = 12;
static_assert(sizeof(AvatarLocalPosition) == AVATAR_LOCAL_POSITION_SIZE, "AvatarDataPacket::AvatarLocalPosition size doesn't match.");
@@ -1420,6 +1460,10 @@ public:
void setIsNewAvatar(bool isNewAvatar) { _isNewAvatar = isNewAvatar; }
bool getIsNewAvatar() { return _isNewAvatar; }
void setIsClientAvatar(bool isClientAvatar) { _isClientAvatar = isClientAvatar; }
+ void setSkeletonData(const std::vector& skeletonData);
+ std::vector getSkeletonData() const;
+ void sendSkeletonData() const;
+ QVector getJointData() const;
signals:
@@ -1598,12 +1642,13 @@ protected:
bool hasParent() const { return !getParentID().isNull(); }
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
+ QByteArray packSkeletonData() const;
QByteArray packSkeletonModelURL() const;
QByteArray packAvatarEntityTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
QByteArray packGrabTraitInstance(AvatarTraits::TraitInstanceID traitInstanceID);
void unpackSkeletonModelURL(const QByteArray& data);
-
+ void unpackSkeletonData(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.
@@ -1720,6 +1765,9 @@ protected:
AvatarGrabDataMap _avatarGrabData;
bool _avatarGrabDataChanged { false }; // by network
+ mutable ReadWriteLockable _avatarSkeletonDataLock;
+ std::vector _avatarSkeletonData;
+
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
ThreadSafeValueCache _sensorToWorldMatrixCache { glm::mat4() };
ThreadSafeValueCache _controllerLeftHandMatrixCache { glm::mat4() };
diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h
index 13d64ec225..007b0418a9 100644
--- a/libraries/avatars/src/AvatarTraits.h
+++ b/libraries/avatars/src/AvatarTraits.h
@@ -29,7 +29,7 @@ namespace AvatarTraits {
// Simple traits
SkeletonModelURL = 0,
-
+ SkeletonData,
// Instanced traits
FirstInstancedTrait,
AvatarEntity = FirstInstancedTrait,
diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp
index f6bd66e89a..8f6871f0e9 100644
--- a/libraries/avatars/src/ClientTraitsHandler.cpp
+++ b/libraries/avatars/src/ClientTraitsHandler.cpp
@@ -107,8 +107,7 @@ int ClientTraitsHandler::sendChangedTraitsToMixer() {
if (initialSend || *simpleIt == Updated) {
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;