From 6b5b272cd755a0f79561780433b44d1584702747 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Dec 2015 09:52:11 -0800 Subject: [PATCH 1/4] Added SoftAttachmentModel class. Is a subclass of Model, it overrides the updateClusterMatrices so it will pull the actual joint matrices from a different rig override. For the avatar soft attachment system, this override will be the Avatar::_skeletonModel rig. This will give us the ability for an avatar to "wear" non-rigid attachments, such as clothing. --- interface/src/avatar/SoftAttachmentModel.cpp | 83 ++++++++++++++++++++ interface/src/avatar/SoftAttachmentModel.h | 34 ++++++++ libraries/animation/src/Rig.cpp | 8 ++ libraries/animation/src/Rig.h | 1 + libraries/render-utils/src/Model.cpp | 3 + libraries/render-utils/src/Model.h | 5 +- 6 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 interface/src/avatar/SoftAttachmentModel.cpp create mode 100644 interface/src/avatar/SoftAttachmentModel.h diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp new file mode 100644 index 0000000000..a9f6b300ac --- /dev/null +++ b/interface/src/avatar/SoftAttachmentModel.cpp @@ -0,0 +1,83 @@ +// +// SoftAttachmentModel.cpp +// interface/src/avatar +// +// Created by Anthony J. Thibault on 12/17/15. +// Copyright 2013 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 "SoftAttachmentModel.h" + +SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) : + Model(rig, parent), + _rigOverride(rigOverride) { + assert(_rig); + assert(_rigOverride); +} + +SoftAttachmentModel::~SoftAttachmentModel() { +} + +// virtual +void SoftAttachmentModel::updateRig(float deltaTime, glm::mat4 parentTransform) { + _needsUpdateClusterMatrices = true; +} + +int SoftAttachmentModel::getJointIndexOverride(int i) const { + QString name = _rig->nameOfJoint(i); + if (name.isEmpty()) { + return -1; + } + return _rigOverride->indexOfJoint(name); +} + +// virtual +// use the _rigOverride matrices instead of the Model::_rig +void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { + if (!_needsUpdateClusterMatrices) { + return; + } + _needsUpdateClusterMatrices = false; + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + + glm::mat4 modelToWorld = glm::mat4_cast(modelOrientation); + for (int i = 0; i < _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + const FBXMesh& mesh = geometry.meshes.at(i); + + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + + // TODO: cache these look ups as an optimization + int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); + glm::mat4 jointMatrix(glm::mat4::_null); + if (jointIndexOverride >= 0 && jointIndexOverride < getJointStateCount()) { + jointMatrix = _rigOverride->getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig->getJointTransform(cluster.jointIndex); + } + state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix; + } + + // Once computed the cluster matrices, update the buffer(s) + if (mesh.clusters.size() > 1) { + if (!state.clusterBuffer) { + state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); + } else { + state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); + } + } + } + + // post the blender if we're not currently waiting for one to finish + if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + _blendedBlendshapeCoefficients = _blendshapeCoefficients; + DependencyManager::get()->noteRequiresBlend(this); + } +} diff --git a/interface/src/avatar/SoftAttachmentModel.h b/interface/src/avatar/SoftAttachmentModel.h new file mode 100644 index 0000000000..eda587af68 --- /dev/null +++ b/interface/src/avatar/SoftAttachmentModel.h @@ -0,0 +1,34 @@ +// +// SoftAttachmentModel.h +// interface/src/avatar +// +// Created by Anthony J. Thibault on 12/17/15. +// Copyright 2015 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 +// + +#ifndef hifi_SoftAttachmentModel_h +#define hifi_SoftAttachmentModel_h + +#include + +class SoftAttachmentModel : public Model { + Q_OBJECT + +public: + + SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride); + ~SoftAttachmentModel(); + + virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override; + virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) override; + +protected: + int getJointIndexOverride(int i) const; + + RigPointer _rigOverride; +}; + +#endif // hifi_SoftAttachmentModel_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 68f382d2d9..bfd4888961 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -245,6 +245,14 @@ int Rig::indexOfJoint(const QString& jointName) const { } } +QString Rig::nameOfJoint(int jointIndex) const { + if (_animSkeleton) { + return _animSkeleton->getJointName(jointIndex); + } else { + return ""; + } +} + void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { AnimPose newModelOffset = AnimPose(modelOffsetMat); if (!isEqual(_modelOffset.trans, newModelOffset.trans) || diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 9faf93e40b..669af2ea64 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -91,6 +91,7 @@ public: bool jointStatesEmpty(); int getJointStateCount() const; int indexOfJoint(const QString& jointName) const; + QString nameOfJoint(int jointIndex) const; void setModelOffset(const glm::mat4& modelOffsetMat); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index dbd3a6289f..efaa79ddbb 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -971,11 +971,14 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { _needsUpdateClusterMatrices = true; _rig->updateAnimations(deltaTime, parentTransform); } + void Model::simulateInternal(float deltaTime) { // update the world space transforms for all joints glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); updateRig(deltaTime, parentTransform); } + +// virtual void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { PerformanceTimer perfTimer("Model::updateClusterMatrices"); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index a93338e41a..3416a9b71e 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -112,7 +112,7 @@ public: bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); - void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); + virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -312,7 +312,7 @@ protected: // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; -private: +protected: void deleteGeometry(); void initJointTransforms(); @@ -370,7 +370,6 @@ private: bool _showCollisionHull = false; friend class ModelMeshPartPayload; -protected: RigPointer _rig; }; From 1618e0a92f1e72a937ab59c24472037dde6fe64d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Dec 2015 13:32:09 -0800 Subject: [PATCH 2/4] Network, Preferences and UI support for soft attachments Added an isSoft field to the AttachmentData which is edited by the Attachment Dialog Menu, sent over the network via AvatarData identity packets and saved in the Interface.ini preferences. AvatarData and AvatarBulkData version number has been bumped. Changed Avatar attachment collections to use smart pointers to models instead of raw ones. Removed _unusedAttachmentModels. I don't think the caching was worth the added code complexity. --- interface/src/Application.cpp | 3 +- interface/src/avatar/Avatar.cpp | 84 ++++++++++++++----- interface/src/avatar/Avatar.h | 8 +- interface/src/avatar/MyAvatar.cpp | 14 +++- interface/src/avatar/MyAvatar.h | 4 +- interface/src/avatar/SoftAttachmentModel.cpp | 3 +- interface/src/ui/AttachmentsDialog.cpp | 37 ++++---- interface/src/ui/AttachmentsDialog.h | 11 +-- libraries/avatars/src/AvatarData.cpp | 10 ++- libraries/avatars/src/AvatarData.h | 5 +- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 5 ++ 12 files changed, 132 insertions(+), 54 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fa5565417d..960e5adda1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3017,8 +3017,7 @@ void Application::update(float deltaTime) { getEntities()->update(); // update the models... } - myAvatar->harvestResultsFromPhysicsSimulation(); - myAvatar->simulateAttachments(deltaTime); + myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f8040754d7..0ab2425131 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -42,6 +42,7 @@ #include "Util.h" #include "world.h" #include "InterfaceLogging.h" +#include "SoftAttachmentModel.h" #include using namespace std; @@ -108,9 +109,6 @@ Avatar::Avatar(RigPointer rig) : Avatar::~Avatar() { assert(_motionState == nullptr); - for(auto attachment : _unusedAttachments) { - delete attachment; - } } const float BILLBOARD_LOD_DISTANCE = 40.0f; @@ -257,6 +255,8 @@ void Avatar::simulate(float deltaTime) { // until velocity is included in AvatarData update message. //_position += _velocity * deltaTime; measureMotionDerivatives(deltaTime); + + simulateAttachments(deltaTime); } bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { @@ -324,7 +324,7 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr _skeletonModel.addToScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges); - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, pendingChanges); } @@ -335,7 +335,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrgetFaceModel().removeFromScene(scene, pendingChanges); - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { attachmentModel->removeFromScene(scene, pendingChanges); } } @@ -565,15 +565,14 @@ void Avatar::fixupModelsInScene() { faceModel.removeFromScene(scene, pendingChanges); faceModel.addToScene(scene, pendingChanges); } - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges); } } - for (auto attachmentModelToRemove : _attachmentsToRemove) { + for (auto& attachmentModelToRemove : _attachmentsToRemove) { attachmentModelToRemove->removeFromScene(scene, pendingChanges); - _unusedAttachments << attachmentModelToRemove; } _attachmentsToRemove.clear(); scene->enqueuePendingChanges(pendingChanges); @@ -603,21 +602,29 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { return true; } +// virtual void Avatar::simulateAttachments(float deltaTime) { for (int i = 0; i < _attachmentModels.size(); i++) { const AttachmentData& attachment = _attachmentData.at(i); - Model* model = _attachmentModels.at(i); + auto& model = _attachmentModels.at(i); int jointIndex = getJointIndex(attachment.jointName); glm::vec3 jointPosition; glm::quat jointRotation; - if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && - _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { - model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); - model->setRotation(jointRotation * attachment.rotation); - model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale - model->setSnapModelToCenter(false); // hack to force resnap - model->setSnapModelToCenter(true); + if (attachment.isSoft) { + // soft attachments do not have transform offsets + model->setTranslation(getPosition()); + model->setRotation(getOrientation() * Quaternions::Y_180); model->simulate(deltaTime); + } else { + if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && + _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { + model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); + model->setRotation(jointRotation * attachment.rotation); + model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale + model->setSnapModelToCenter(false); // hack to force resnap + model->setSnapModelToCenter(true); + model->simulate(deltaTime); + } } } } @@ -940,13 +947,48 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModel.setURL(_skeletonModelURL); } +// create new model, can return an instance of a SoftAttachmentModel rather then Model +static std::shared_ptr allocateAttachmentModel(bool isSoft, RigPointer rigOverride) { + if (isSoft) { + // cast to std::shared_ptr + return std::dynamic_pointer_cast(std::make_shared(std::make_shared(), nullptr, rigOverride)); + } else { + return std::make_shared(std::make_shared()); + } +} + void Avatar::setAttachmentData(const QVector& attachmentData) { - AvatarData::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, Q_ARG(const QVector, attachmentData)); return; } + + auto oldAttachmentData = _attachmentData; + AvatarData::setAttachmentData(attachmentData); + + // if number of attachments has been reduced, remove excess models. + while (_attachmentModels.size() > attachmentData.size()) { + auto attachmentModel = _attachmentModels.back(); + _attachmentModels.pop_back(); + _attachmentsToRemove.push_back(attachmentModel); + } + + for (int i = 0; i < attachmentData.size(); i++) { + if (i == _attachmentModels.size()) { + // if number of attachments has been increased, we need to allocate a new model + _attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig())); + } + else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) { + // if the attachment has changed type, we need to re-allocate a new one. + _attachmentsToRemove.push_back(_attachmentModels[i]); + _attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig()); + } + _attachmentModels[i]->setURL(attachmentData[i].modelURL); + } + + // AJT: TODO REMOVE + /* // make sure we have as many models as attachments while (_attachmentModels.size() < attachmentData.size()) { Model* model = nullptr; @@ -959,16 +1001,20 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { _attachmentModels.append(model); } while (_attachmentModels.size() > attachmentData.size()) { - auto attachmentModel = _attachmentModels.takeLast(); - _attachmentsToRemove << attachmentModel; + auto attachmentModel = _attachmentModels.back(); + _attachmentModels.pop_back(); + _attachmentsToRemove.push_back(attachmentModel); } + */ + /* // update the urls for (int i = 0; i < attachmentData.size(); i++) { _attachmentModels[i]->setURL(attachmentData.at(i).modelURL); _attachmentModels[i]->setSnapModelToCenter(true); _attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale); } + */ } void Avatar::setBillboard(const QByteArray& billboard) { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 4926212b4d..6d4477e93f 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -68,7 +68,7 @@ public: void init(); void simulate(float deltaTime); - void simulateAttachments(float deltaTime); + virtual void simulateAttachments(float deltaTime); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition); @@ -177,9 +177,9 @@ protected: SkeletonModel _skeletonModel; glm::vec3 _skeletonOffset; - QVector _attachmentModels; - QVector _attachmentsToRemove; - QVector _unusedAttachments; + std::vector> _attachmentModels; + std::vector> _attachmentsToRemove; + float _bodyYawDelta; // degrees/sec // These position histories and derivatives are in the world-frame. diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5c8230bd88..1028f9aa64 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -201,6 +201,11 @@ MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); } +// virtual +void MyAvatar::simulateAttachments(float deltaTime) { + // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() +} + QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { CameraMode mode = qApp->getCamera()->getMode(); _globalPosition = getPosition(); @@ -621,6 +626,7 @@ void MyAvatar::saveData() { settings.setValue("rotation_y", eulers.y); settings.setValue("rotation_z", eulers.z); settings.setValue("scale", attachment.scale); + settings.setValue("isSoft", attachment.isSoft); } settings.endArray(); @@ -702,6 +708,7 @@ void MyAvatar::loadData() { eulers.z = loadSetting(settings, "rotation_z", 0.0f); attachment.rotation = glm::quat(eulers); attachment.scale = loadSetting(settings, "scale", 1.0f); + attachment.isSoft = settings.value("isSoft").toBool(); attachmentData.append(attachment); } settings.endArray(); @@ -1057,7 +1064,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setFollowVelocity(_followVelocity); } -void MyAvatar::harvestResultsFromPhysicsSimulation() { +void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaType) { glm::vec3 position = getPosition(); glm::quat orientation = getOrientation(); _characterController.getPositionAndOrientation(position, orientation); @@ -1068,6 +1075,9 @@ void MyAvatar::harvestResultsFromPhysicsSimulation() { } else { setVelocity(_characterController.getLinearVelocity()); } + + // now that physics has adjusted our position, we can update attachements. + Avatar::simulateAttachments(deltaType); } void MyAvatar::adjustSensorTransform() { @@ -1599,7 +1609,7 @@ void MyAvatar::maybeUpdateBillboard() { if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { return; } - foreach (Model* model, _attachmentModels) { + for (auto& model : _attachmentModels) { if (!model->isLoadedWithTextures()) { return; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 019ba0f992..1a5753d0a5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -83,6 +83,8 @@ public: MyAvatar(RigPointer rig); ~MyAvatar(); + virtual void simulateAttachments(float deltaTime) override; + AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; } AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } @@ -204,7 +206,7 @@ public: MyCharacterController* getCharacterController() { return &_characterController; } void prepareForPhysicsSimulation(); - void harvestResultsFromPhysicsSimulation(); + void harvestResultsFromPhysicsSimulation(float deltaTime); void adjustSensorTransform(); const QString& getCollisionSoundURL() { return _collisionSoundURL; } diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp index a9f6b300ac..92534f01ea 100644 --- a/interface/src/avatar/SoftAttachmentModel.cpp +++ b/interface/src/avatar/SoftAttachmentModel.cpp @@ -10,6 +10,7 @@ // #include "SoftAttachmentModel.h" +#include "InterfaceLogging.h" SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) : Model(rig, parent), @@ -55,7 +56,7 @@ void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::qu // TODO: cache these look ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); glm::mat4 jointMatrix(glm::mat4::_null); - if (jointIndexOverride >= 0 && jointIndexOverride < getJointStateCount()) { + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride->getJointStateCount()) { jointMatrix = _rigOverride->getJointTransform(jointIndexOverride); } else { jointMatrix = _rig->getJointTransform(cluster.jointIndex); diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 1d53eb3871..d718b52d6d 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -27,13 +28,13 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) : QDialog(parent) { - + setWindowTitle("Edit Attachments"); setAttribute(Qt::WA_DeleteOnClose); - + QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); - + QScrollArea* area = new QScrollArea(); layout->addWidget(area); area->setWidgetResizable(true); @@ -42,26 +43,26 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) : container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); area->setWidget(container); _attachments->addStretch(1); - + foreach (const AttachmentData& data, DependencyManager::get()->getMyAvatar()->getAttachmentData()) { addAttachment(data); } - + QPushButton* newAttachment = new QPushButton("New Attachment"); connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment())); layout->addWidget(newAttachment); - + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(buttons); connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); _ok = buttons->button(QDialogButtonBox::Ok); - + setMinimumSize(600, 600); } void AttachmentsDialog::setVisible(bool visible) { QDialog::setVisible(visible); - + // un-default the OK button if (visible) { _ok->setDefault(false); @@ -104,11 +105,11 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData _dialog(dialog), _applying(false) { setFrameStyle(QFrame::StyledPanel); - + QFormLayout* layout = new QFormLayout(); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); setLayout(layout); - + QHBoxLayout* urlBox = new QHBoxLayout(); layout->addRow("Model URL:", urlBox); urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); @@ -117,7 +118,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData QPushButton* chooseURL = new QPushButton("Choose"); urlBox->addWidget(chooseURL); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); - + layout->addRow("Joint:", _jointName = new QComboBox()); QSharedPointer geometry = DependencyManager::get()->getMyAvatar()->getSkeletonModel().getGeometry(); if (geometry && geometry->isLoaded()) { @@ -127,26 +128,30 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData } _jointName->setCurrentText(data.jointName); connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged())); - + QHBoxLayout* translationBox = new QHBoxLayout(); translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x)); translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y)); translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z)); layout->addRow("Translation:", translationBox); - + QHBoxLayout* rotationBox = new QHBoxLayout(); glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation)); rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x)); rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y)); rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z)); layout->addRow("Rotation:", rotationBox); - + layout->addRow("Scale:", _scale = new QDoubleSpinBox()); _scale->setSingleStep(0.01); _scale->setMaximum(FLT_MAX); _scale->setValue(data.scale); connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); - + + layout->addRow("Is Soft:", _isSoft = new QCheckBox()); + _isSoft->setChecked(data.isSoft); + connect(_isSoft, SIGNAL(stateChanged(int)), SLOT(updateAttachmentData())); + QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); @@ -160,6 +165,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const { data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value()); data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value()))); data.scale = _scale->value(); + data.isSoft = _isSoft->isChecked(); return data; } @@ -227,6 +233,7 @@ void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) { _rotationY->setValue(eulers.y); _rotationZ->setValue(eulers.z); _scale->setValue(attachment.scale); + _isSoft->setChecked(attachment.isSoft); _applying = false; _dialog->updateAttachmentData(); } diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index d3b9219a16..43ba5f8f3e 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -36,11 +36,11 @@ public slots: void updateAttachmentData(); private slots: - + void addAttachment(const AttachmentData& data = AttachmentData()); private: - + QVBoxLayout* _attachments; QPushButton* _ok; }; @@ -50,7 +50,7 @@ class AttachmentPanel : public QFrame { Q_OBJECT public: - + AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData()); AttachmentData getAttachmentData() const; @@ -64,9 +64,9 @@ private slots: void updateAttachmentData(); private: - + void applyAttachmentData(const AttachmentData& attachment); - + AttachmentsDialog* _dialog; QLineEdit* _modelURL; QComboBox* _jointName; @@ -77,6 +77,7 @@ private: QDoubleSpinBox* _rotationY; QDoubleSpinBox* _rotationZ; QDoubleSpinBox* _scale; + QCheckBox* _isSoft; bool _applying; }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2ed9a47e02..789f32d4c6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1260,6 +1260,7 @@ void AvatarData::updateJointMappings() { static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); +static const QString JSON_ATTACHMENT_IS_SOFT = QStringLiteral("isSoft"); QJsonObject AttachmentData::toJson() const { QJsonObject result; @@ -1278,6 +1279,7 @@ QJsonObject AttachmentData::toJson() const { if (!transform.isIdentity()) { result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform); } + result[JSON_ATTACHMENT_IS_SOFT] = isSoft; return result; } @@ -1302,6 +1304,10 @@ void AttachmentData::fromJson(const QJsonObject& json) { rotation = transform.getRotation(); scale = transform.getScale().x; } + + if (json.contains(JSON_ATTACHMENT_IS_SOFT)) { + isSoft = json[JSON_ATTACHMENT_IS_SOFT].toBool(); + } } bool AttachmentData::operator==(const AttachmentData& other) const { @@ -1311,12 +1317,12 @@ bool AttachmentData::operator==(const AttachmentData& other) const { QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) { return out << attachment.modelURL << attachment.jointName << - attachment.translation << attachment.rotation << attachment.scale; + attachment.translation << attachment.rotation << attachment.scale << attachment.isSoft; } QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { return in >> attachment.modelURL >> attachment.jointName >> - attachment.translation >> attachment.rotation >> attachment.scale; + attachment.translation >> attachment.rotation >> attachment.scale >> attachment.isSoft; } void AttachmentDataObject::setModelURL(const QString& modelURL) const { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 64c215cee7..bb35a6ea29 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -437,9 +437,10 @@ public: glm::vec3 translation; glm::quat rotation; float scale { 1.0f }; - + bool isSoft { false }; + bool isValid() const { return modelURL.isValid(); } - + bool operator==(const AttachmentData& other) const; QJsonObject toJson() const; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 4daf7f83b6..ca869ec29f 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -44,7 +44,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP; case PacketType::AvatarData: case PacketType::BulkAvatarData: - return 17; + return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); default: return 17; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5daa185af4..3b4b358daf 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -163,4 +163,9 @@ const PacketVersion VERSION_ENTITIES_POLYLINE_TEXTURE = 50; const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51; const PacketVersion VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52; +enum class AvatarMixerPacketVersion : PacketVersion { + TranslationSupport = 17, + SoftAttachmentSupport +}; + #endif // hifi_PacketHeaders_h From 098a455b67999b8082559cfa65219466f4a95bf2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Dec 2015 14:49:33 -0800 Subject: [PATCH 3/4] AttachmentData operator== bug fix This was causing some changes to not be reflected across the network. --- libraries/avatars/src/AvatarData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 789f32d4c6..d5e756d6db 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1312,7 +1312,7 @@ void AttachmentData::fromJson(const QJsonObject& json) { bool AttachmentData::operator==(const AttachmentData& other) const { return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation && - rotation == other.rotation && scale == other.scale; + rotation == other.rotation && scale == other.scale && isSoft == other.isSoft; } QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) { From f4f4bd44cb3f8cc3be377042b3ebdaac8f7640a2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 29 Dec 2015 13:28:04 -0800 Subject: [PATCH 4/4] SoftAttachmentModel: added a descriptive comment. --- interface/src/avatar/SoftAttachmentModel.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/interface/src/avatar/SoftAttachmentModel.h b/interface/src/avatar/SoftAttachmentModel.h index eda587af68..84a8b4cc66 100644 --- a/interface/src/avatar/SoftAttachmentModel.h +++ b/interface/src/avatar/SoftAttachmentModel.h @@ -14,6 +14,14 @@ #include +// A model that allows the creator to specify a secondary rig instance. +// When the cluster matrices are created for rendering, the +// cluster matrices will use the secondary rig for the joint poses +// instead of the primary rig. +// +// This is used by Avatar instances to wear clothing that follows the same +// animated pose as the SkeletonModel. + class SoftAttachmentModel : public Model { Q_OBJECT