Merge pull request #15438 from luiscuenca/skeletonTrait

Skeleton Trait Implementation
This commit is contained in:
Anthony Thibault 2019-04-26 10:14:47 -07:00 committed by GitHub
commit 4761a69a4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 278 additions and 7 deletions

View file

@ -179,7 +179,6 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
if (packetTraitVersion > _lastReceivedTraitVersions[traitType]) {
_avatar->processTrait(traitType, message.read(traitSize));
_lastReceivedTraitVersions[traitType] = packetTraitVersion;
if (traitType == AvatarTraits::SkeletonModelURL) {
// special handling for skeleton model URL, since we need to make sure it is in the whitelist
checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion);

View file

@ -625,6 +625,8 @@ Menu::Menu() {
avatar.get(), SLOT(setEnableDebugDrawAnimPose(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawPosition, 0, false,
avatar.get(), SLOT(setEnableDebugDrawPosition(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawOtherSkeletons, 0, false,
avatarManager.data(), SLOT(setEnableDebugDrawOtherSkeletons(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MeshVisible, 0, true,
avatar.get(), SLOT(setEnableMeshVisible(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);

View file

@ -33,6 +33,7 @@ namespace MenuOption {
const QString AnimDebugDrawBaseOfSupport = "Debug Draw Base of Support";
const QString AnimDebugDrawDefaultPose = "Debug Draw Default Pose";
const QString AnimDebugDrawPosition= "Debug Draw Position";
const QString AnimDebugDrawOtherSkeletons = "Debug Draw Other Skeletons";
const QString AskToResetSettings = "Ask To Reset Settings on Start";
const QString AssetMigration = "ATP Asset Migration";
const QString AssetServer = "Asset Browser";

View file

@ -120,6 +120,8 @@ void AvatarManager::init() {
_myAvatar->addToScene(_myAvatar, scene, transaction);
scene->enqueueTransaction(transaction);
}
setEnableDebugDrawOtherSkeletons(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawOtherSkeletons));
}
void AvatarManager::setSpace(workload::SpacePointer& space ) {
@ -334,9 +336,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) {
_myAvatar->addAvatarHandsToFlow(avatar);
}
if (_drawOtherAvatarSkeletons) {
avatar->debugJointData();
}
avatar->setEnableMeshVisible(!_drawOtherAvatarSkeletons);
avatar->updateRenderItem(renderTransaction);
avatar->updateSpaceProxy(workloadTransaction);
avatar->setLastRenderUpdateTime(startTime);
} else {
// we've spent our time budget for this priority bucket
// let's deal with the reminding avatars if this pass and BREAK from the for loop

View file

@ -262,6 +262,15 @@ public slots:
*/
void updateAvatarRenderStatus(bool shouldRenderAvatars);
/**jsdoc
* Displays other avatars skeletons debug graphics.
* @function AvatarManager.setEnableDebugDrawOtherSkeletons
* @param {boolean} enabled - <code>true</code> to show the debug graphics, <code>false</code> to hide.
*/
void setEnableDebugDrawOtherSkeletons(bool isEnabled) {
_drawOtherAvatarSkeletons = isEnabled;
}
protected:
AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
@ -299,6 +308,7 @@ private:
workload::SpacePointer _space;
AvatarTransit::TransitConfig _transitConfig;
bool _drawOtherAvatarSkeletons { false };
};
#endif // hifi_AvatarManager_h

View file

@ -16,6 +16,7 @@
#include "Application.h"
#include "AvatarMotionState.h"
#include "DetailedMotionState.h"
#include "DebugDraw.h"
const float DISPLAYNAME_FADE_TIME = 0.5f;
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
@ -358,6 +359,58 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
}
}
void OtherAvatar::debugJointData() const {
// Get a copy of the joint data
auto jointData = getJointData();
auto skeletonData = getSkeletonData();
if ((int)skeletonData.size() == jointData.size() && jointData.size() != 0) {
const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
const vec4 LIGHT_RED(1.0f, 0.5f, 0.5f, 1.0f);
const vec4 LIGHT_GREEN(0.5f, 1.0f, 0.5f, 1.0f);
const vec4 LIGHT_BLUE(0.5f, 0.5f, 1.0f, 1.0f);
const vec4 GREY(0.3f, 0.3f, 0.3f, 1.0f);
const vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
const float AXIS_LENGTH = 0.1f;
AnimPoseVec absoluteJointPoses;
AnimPose rigToAvatar = AnimPose(Quaternions::Y_180 * getWorldOrientation(), getWorldPosition());
bool drawBones = false;
for (int i = 0; i < jointData.size(); i++) {
float jointScale = skeletonData[i].defaultScale * getTargetScale() * METERS_PER_CENTIMETER;
auto absoluteRotation = jointData[i].rotationIsDefaultPose ? skeletonData[i].defaultRotation : jointData[i].rotation;
auto localJointTranslation = jointScale * (jointData[i].translationIsDefaultPose ? skeletonData[i].defaultTranslation : jointData[i].translation);
bool isHips = skeletonData[i].jointName == "Hips";
if (isHips) {
localJointTranslation = glm::vec3(0.0f);
drawBones = true;
}
AnimPose absoluteParentPose;
int parentIndex = skeletonData[i].parentIndex;
if (parentIndex != -1 && parentIndex < (int)absoluteJointPoses.size()) {
absoluteParentPose = absoluteJointPoses[parentIndex];
}
AnimPose absoluteJointPose = AnimPose(absoluteRotation, absoluteParentPose.trans() + absoluteParentPose.rot() * localJointTranslation);
auto jointPose = rigToAvatar * absoluteJointPose;
auto parentPose = rigToAvatar * absoluteParentPose;
if (drawBones) {
glm::vec3 xAxis = jointPose.rot() * Vectors::UNIT_X;
glm::vec3 yAxis = jointPose.rot() * Vectors::UNIT_Y;
glm::vec3 zAxis = jointPose.rot() * Vectors::UNIT_Z;
DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * xAxis, jointData[i].rotationIsDefaultPose ? LIGHT_RED : RED);
DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * yAxis, jointData[i].rotationIsDefaultPose ? LIGHT_GREEN : GREEN);
DebugDraw::getInstance().drawRay(jointPose.trans(), jointPose.trans() + AXIS_LENGTH * zAxis, jointData[i].rotationIsDefaultPose ? LIGHT_BLUE : BLUE);
if (!isHips) {
DebugDraw::getInstance().drawRay(jointPose.trans(), parentPose.trans(), jointData[i].translationIsDefaultPose ? WHITE : GREY);
}
}
absoluteJointPoses.push_back(absoluteJointPose);
}
}
}
void OtherAvatar::handleChangedAvatarEntityData() {
PerformanceTimer perfTimer("attachments");

View file

@ -66,7 +66,7 @@ public:
void setCollisionWithOtherAvatarsFlags() override;
void simulate(float deltaTime, bool inView) override;
void debugJointData() const;
friend AvatarManager;
protected:

View file

@ -1469,6 +1469,37 @@ QStringList Avatar::getJointNames() const {
return result;
}
std::vector<AvatarSkeletonTrait::UnpackedJointData> Avatar::getSkeletonDefaultData() {
std::vector<AvatarSkeletonTrait::UnpackedJointData> defaultSkeletonData;
if (_skeletonModel->isLoaded()) {
auto& model = _skeletonModel->getHFMModel();
auto& rig = _skeletonModel->getRig();
float geometryToRigScale = extractScale(rig.getGeometryToRigTransform())[0];
QStringList jointNames = getJointNames();
int sizeCount = 0;
for (int i = 0; i < jointNames.size(); i++) {
AvatarSkeletonTrait::UnpackedJointData jointData;
jointData.jointIndex = i;
jointData.parentIndex = rig.getJointParentIndex(i);
if (jointData.parentIndex == -1) {
jointData.boneType = model.joints[i].isSkeletonJoint ? AvatarSkeletonTrait::BoneType::SkeletonRoot : AvatarSkeletonTrait::BoneType::NonSkeletonRoot;
} else {
jointData.boneType = model.joints[i].isSkeletonJoint ? AvatarSkeletonTrait::BoneType::SkeletonChild : AvatarSkeletonTrait::BoneType::NonSkeletonChild;
}
jointData.defaultRotation = rig.getAbsoluteDefaultPose(i).rot();
jointData.defaultTranslation = getDefaultJointTranslation(i);
float jointLocalScale = extractScale(model.joints[i].transform)[0];
jointData.defaultScale = jointLocalScale / geometryToRigScale;
jointData.jointName = jointNames[i];
jointData.stringLength = jointNames[i].size();
jointData.stringStart = sizeCount;
sizeCount += jointNames[i].size();
defaultSkeletonData.push_back(jointData);
}
}
return defaultSkeletonData;
}
glm::vec3 Avatar::getJointPosition(int index) const {
glm::vec3 position;
_skeletonModel->getJointPositionInWorldFrame(index, position);
@ -1535,6 +1566,8 @@ void Avatar::rigReady() {
buildSpine2SplineRatioCache();
computeMultiSphereShapes();
buildSpine2SplineRatioCache();
setSkeletonData(getSkeletonDefaultData());
sendSkeletonData();
}
// rig has been reset.

View file

@ -199,6 +199,8 @@ public:
virtual int getJointIndex(const QString& name) const override;
virtual QStringList getJointNames() const override;
std::vector<AvatarSkeletonTrait::UnpackedJointData> getSkeletonDefaultData();
/**jsdoc
* Gets the default rotation of a joint (in the current avatar) relative to its parent.
* <p>For information on the joint hierarchy used, see

View file

@ -1633,6 +1633,13 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v
data.translationIsDefaultPose = false;
}
QVector<JointData> AvatarData::getJointData() const {
QVector<JointData> 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<unsigned char*>(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<const unsigned char*>(data.data());
const unsigned char* sourceBuffer = startPosition;
auto header = reinterpret_cast<const AvatarSkeletonTrait::Header*>(sourceBuffer);
sourceBuffer += sizeof(const AvatarSkeletonTrait::Header);
std::vector<AvatarSkeletonTrait::UnpackedJointData> joints;
for (uint8_t i = 0; i < header->numJoints; i++) {
auto jointData = reinterpret_cast<const AvatarSkeletonTrait::JointData*>(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<const unsigned char*>(&jointData->defaultRotation), uJointData.defaultRotation);
unpackFloatVec3FromSignedTwoByteFixed(reinterpret_cast<const unsigned char*>(&jointData->defaultTranslation), uJointData.defaultTranslation, TRANSLATION_COMPRESSION_RADIX);
unpackFloatRatioFromTwoByte(reinterpret_cast<const unsigned char*>(&jointData->defaultScale), uJointData.defaultScale);
uJointData.defaultTranslation *= header->maxTranslationDimension;
uJointData.defaultScale *= header->maxScaleDimension;
joints.push_back(uJointData);
}
QString table = QString::fromUtf8(reinterpret_cast<const char*>(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<AvatarSkeletonTrait::UnpackedJointData>& skeletonData) {
_avatarSkeletonDataLock.withWriteLock([&] {
_avatarSkeletonData = skeletonData;
});
}
std::vector<AvatarSkeletonTrait::UnpackedJointData> AvatarData::getSkeletonData() const {
std::vector<AvatarSkeletonTrait::UnpackedJointData> 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);

View file

@ -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<AvatarSkeletonTrait::UnpackedJointData>& skeletonData);
std::vector<AvatarSkeletonTrait::UnpackedJointData> getSkeletonData() const;
void sendSkeletonData() const;
QVector<JointData> 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<AvatarSkeletonTrait::UnpackedJointData> _avatarSkeletonData;
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
ThreadSafeValueCache<glm::mat4> _controllerLeftHandMatrixCache { glm::mat4() };

View file

@ -29,7 +29,7 @@ namespace AvatarTraits {
// Simple traits
SkeletonModelURL = 0,
SkeletonData,
// Instanced traits
FirstInstancedTrait,
AvatarEntity = FirstInstancedTrait,

View file

@ -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;