Merge pull request #6702 from hyperlogic/tony/soft-attachments

Soft Attachment Support
This commit is contained in:
Brad Hefta-Gaub 2016-01-03 08:46:16 -10:00
commit 02835fc960
17 changed files with 271 additions and 57 deletions

View file

@ -2999,8 +2999,7 @@ void Application::update(float deltaTime) {
getEntities()->update(); // update the models... getEntities()->update(); // update the models...
} }
myAvatar->harvestResultsFromPhysicsSimulation(); myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
myAvatar->simulateAttachments(deltaTime);
} }
} }

View file

@ -42,6 +42,7 @@
#include "Util.h" #include "Util.h"
#include "world.h" #include "world.h"
#include "InterfaceLogging.h" #include "InterfaceLogging.h"
#include "SoftAttachmentModel.h"
#include <Rig.h> #include <Rig.h>
using namespace std; using namespace std;
@ -108,9 +109,6 @@ Avatar::Avatar(RigPointer rig) :
Avatar::~Avatar() { Avatar::~Avatar() {
assert(_motionState == nullptr); assert(_motionState == nullptr);
for(auto attachment : _unusedAttachments) {
delete attachment;
}
} }
const float BILLBOARD_LOD_DISTANCE = 40.0f; const float BILLBOARD_LOD_DISTANCE = 40.0f;
@ -257,6 +255,8 @@ void Avatar::simulate(float deltaTime) {
// until velocity is included in AvatarData update message. // until velocity is included in AvatarData update message.
//_position += _velocity * deltaTime; //_position += _velocity * deltaTime;
measureMotionDerivatives(deltaTime); measureMotionDerivatives(deltaTime);
simulateAttachments(deltaTime);
} }
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {
@ -324,7 +324,7 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene>
_skeletonModel.addToScene(scene, pendingChanges); _skeletonModel.addToScene(scene, pendingChanges);
getHead()->getFaceModel().addToScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges);
for (auto attachmentModel : _attachmentModels) { for (auto& attachmentModel : _attachmentModels) {
attachmentModel->addToScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges);
} }
@ -335,7 +335,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::S
pendingChanges.removeItem(_renderItemID); pendingChanges.removeItem(_renderItemID);
_skeletonModel.removeFromScene(scene, pendingChanges); _skeletonModel.removeFromScene(scene, pendingChanges);
getHead()->getFaceModel().removeFromScene(scene, pendingChanges); getHead()->getFaceModel().removeFromScene(scene, pendingChanges);
for (auto attachmentModel : _attachmentModels) { for (auto& attachmentModel : _attachmentModels) {
attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->removeFromScene(scene, pendingChanges);
} }
} }
@ -565,15 +565,14 @@ void Avatar::fixupModelsInScene() {
faceModel.removeFromScene(scene, pendingChanges); faceModel.removeFromScene(scene, pendingChanges);
faceModel.addToScene(scene, pendingChanges); faceModel.addToScene(scene, pendingChanges);
} }
for (auto attachmentModel : _attachmentModels) { for (auto& attachmentModel : _attachmentModels) {
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->removeFromScene(scene, pendingChanges);
attachmentModel->addToScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges);
} }
} }
for (auto attachmentModelToRemove : _attachmentsToRemove) { for (auto& attachmentModelToRemove : _attachmentsToRemove) {
attachmentModelToRemove->removeFromScene(scene, pendingChanges); attachmentModelToRemove->removeFromScene(scene, pendingChanges);
_unusedAttachments << attachmentModelToRemove;
} }
_attachmentsToRemove.clear(); _attachmentsToRemove.clear();
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);
@ -603,21 +602,29 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const {
return true; return true;
} }
// virtual
void Avatar::simulateAttachments(float deltaTime) { void Avatar::simulateAttachments(float deltaTime) {
for (int i = 0; i < _attachmentModels.size(); i++) { for (int i = 0; i < _attachmentModels.size(); i++) {
const AttachmentData& attachment = _attachmentData.at(i); const AttachmentData& attachment = _attachmentData.at(i);
Model* model = _attachmentModels.at(i); auto& model = _attachmentModels.at(i);
int jointIndex = getJointIndex(attachment.jointName); int jointIndex = getJointIndex(attachment.jointName);
glm::vec3 jointPosition; glm::vec3 jointPosition;
glm::quat jointRotation; glm::quat jointRotation;
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && if (attachment.isSoft) {
_skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { // soft attachments do not have transform offsets
model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); model->setTranslation(getPosition());
model->setRotation(jointRotation * attachment.rotation); model->setRotation(getOrientation() * Quaternions::Y_180);
model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale
model->setSnapModelToCenter(false); // hack to force resnap
model->setSnapModelToCenter(true);
model->simulate(deltaTime); 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); _skeletonModel.setURL(_skeletonModelURL);
} }
// create new model, can return an instance of a SoftAttachmentModel rather then Model
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
if (isSoft) {
// cast to std::shared_ptr<Model>
return std::dynamic_pointer_cast<Model>(std::make_shared<SoftAttachmentModel>(std::make_shared<Rig>(), nullptr, rigOverride));
} else {
return std::make_shared<Model>(std::make_shared<Rig>());
}
}
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) { void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
AvatarData::setAttachmentData(attachmentData);
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection,
Q_ARG(const QVector<AttachmentData>, attachmentData)); Q_ARG(const QVector<AttachmentData>, attachmentData));
return; 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 // make sure we have as many models as attachments
while (_attachmentModels.size() < attachmentData.size()) { while (_attachmentModels.size() < attachmentData.size()) {
Model* model = nullptr; Model* model = nullptr;
@ -959,16 +1001,20 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
_attachmentModels.append(model); _attachmentModels.append(model);
} }
while (_attachmentModels.size() > attachmentData.size()) { while (_attachmentModels.size() > attachmentData.size()) {
auto attachmentModel = _attachmentModels.takeLast(); auto attachmentModel = _attachmentModels.back();
_attachmentsToRemove << attachmentModel; _attachmentModels.pop_back();
_attachmentsToRemove.push_back(attachmentModel);
} }
*/
/*
// update the urls // update the urls
for (int i = 0; i < attachmentData.size(); i++) { for (int i = 0; i < attachmentData.size(); i++) {
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL); _attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
_attachmentModels[i]->setSnapModelToCenter(true); _attachmentModels[i]->setSnapModelToCenter(true);
_attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale); _attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale);
} }
*/
} }
void Avatar::setBillboard(const QByteArray& billboard) { void Avatar::setBillboard(const QByteArray& billboard) {

View file

@ -68,7 +68,7 @@ public:
void init(); void init();
void simulate(float deltaTime); void simulate(float deltaTime);
void simulateAttachments(float deltaTime); virtual void simulateAttachments(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition);
@ -177,9 +177,9 @@ protected:
SkeletonModel _skeletonModel; SkeletonModel _skeletonModel;
glm::vec3 _skeletonOffset; glm::vec3 _skeletonOffset;
QVector<Model*> _attachmentModels; std::vector<std::shared_ptr<Model>> _attachmentModels;
QVector<Model*> _attachmentsToRemove; std::vector<std::shared_ptr<Model>> _attachmentsToRemove;
QVector<Model*> _unusedAttachments;
float _bodyYawDelta; // degrees/sec float _bodyYawDelta; // degrees/sec
// These position histories and derivatives are in the world-frame. // These position histories and derivatives are in the world-frame.

View file

@ -201,6 +201,11 @@ MyAvatar::~MyAvatar() {
_lookAtTargetAvatar.reset(); _lookAtTargetAvatar.reset();
} }
// virtual
void MyAvatar::simulateAttachments(float deltaTime) {
// don't update attachments here, do it in harvestResultsFromPhysicsSimulation()
}
QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
CameraMode mode = qApp->getCamera()->getMode(); CameraMode mode = qApp->getCamera()->getMode();
_globalPosition = getPosition(); _globalPosition = getPosition();
@ -621,6 +626,7 @@ void MyAvatar::saveData() {
settings.setValue("rotation_y", eulers.y); settings.setValue("rotation_y", eulers.y);
settings.setValue("rotation_z", eulers.z); settings.setValue("rotation_z", eulers.z);
settings.setValue("scale", attachment.scale); settings.setValue("scale", attachment.scale);
settings.setValue("isSoft", attachment.isSoft);
} }
settings.endArray(); settings.endArray();
@ -702,6 +708,7 @@ void MyAvatar::loadData() {
eulers.z = loadSetting(settings, "rotation_z", 0.0f); eulers.z = loadSetting(settings, "rotation_z", 0.0f);
attachment.rotation = glm::quat(eulers); attachment.rotation = glm::quat(eulers);
attachment.scale = loadSetting(settings, "scale", 1.0f); attachment.scale = loadSetting(settings, "scale", 1.0f);
attachment.isSoft = settings.value("isSoft").toBool();
attachmentData.append(attachment); attachmentData.append(attachment);
} }
settings.endArray(); settings.endArray();
@ -1057,7 +1064,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
_characterController.setFollowVelocity(_followVelocity); _characterController.setFollowVelocity(_followVelocity);
} }
void MyAvatar::harvestResultsFromPhysicsSimulation() { void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaType) {
glm::vec3 position = getPosition(); glm::vec3 position = getPosition();
glm::quat orientation = getOrientation(); glm::quat orientation = getOrientation();
_characterController.getPositionAndOrientation(position, orientation); _characterController.getPositionAndOrientation(position, orientation);
@ -1068,6 +1075,9 @@ void MyAvatar::harvestResultsFromPhysicsSimulation() {
} else { } else {
setVelocity(_characterController.getLinearVelocity()); setVelocity(_characterController.getLinearVelocity());
} }
// now that physics has adjusted our position, we can update attachements.
Avatar::simulateAttachments(deltaType);
} }
void MyAvatar::adjustSensorTransform() { void MyAvatar::adjustSensorTransform() {
@ -1599,7 +1609,7 @@ void MyAvatar::maybeUpdateBillboard() {
if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) {
return; return;
} }
foreach (Model* model, _attachmentModels) { for (auto& model : _attachmentModels) {
if (!model->isLoadedWithTextures()) { if (!model->isLoadedWithTextures()) {
return; return;
} }

View file

@ -83,6 +83,8 @@ public:
MyAvatar(RigPointer rig); MyAvatar(RigPointer rig);
~MyAvatar(); ~MyAvatar();
virtual void simulateAttachments(float deltaTime) override;
AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; } AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; }
AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; }
AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; }
@ -204,7 +206,7 @@ public:
MyCharacterController* getCharacterController() { return &_characterController; } MyCharacterController* getCharacterController() { return &_characterController; }
void prepareForPhysicsSimulation(); void prepareForPhysicsSimulation();
void harvestResultsFromPhysicsSimulation(); void harvestResultsFromPhysicsSimulation(float deltaTime);
void adjustSensorTransform(); void adjustSensorTransform();
const QString& getCollisionSoundURL() { return _collisionSoundURL; } const QString& getCollisionSoundURL() { return _collisionSoundURL; }

View file

@ -0,0 +1,84 @@
//
// 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"
#include "InterfaceLogging.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 < _rigOverride->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<gpu::Buffer>(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<ModelBlender>()->noteRequiresBlend(this);
}
}

View file

@ -0,0 +1,42 @@
//
// 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 <Model.h>
// 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
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

View file

@ -17,6 +17,7 @@
#include <QPushButton> #include <QPushButton>
#include <QScrollArea> #include <QScrollArea>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QCheckBox>
#include <avatar/AvatarManager.h> #include <avatar/AvatarManager.h>
#include <avatar/MyAvatar.h> #include <avatar/MyAvatar.h>
@ -27,13 +28,13 @@
AttachmentsDialog::AttachmentsDialog(QWidget* parent) : AttachmentsDialog::AttachmentsDialog(QWidget* parent) :
QDialog(parent) { QDialog(parent) {
setWindowTitle("Edit Attachments"); setWindowTitle("Edit Attachments");
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
QVBoxLayout* layout = new QVBoxLayout(); QVBoxLayout* layout = new QVBoxLayout();
setLayout(layout); setLayout(layout);
QScrollArea* area = new QScrollArea(); QScrollArea* area = new QScrollArea();
layout->addWidget(area); layout->addWidget(area);
area->setWidgetResizable(true); area->setWidgetResizable(true);
@ -42,26 +43,26 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) :
container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
area->setWidget(container); area->setWidget(container);
_attachments->addStretch(1); _attachments->addStretch(1);
foreach (const AttachmentData& data, DependencyManager::get<AvatarManager>()->getMyAvatar()->getAttachmentData()) { foreach (const AttachmentData& data, DependencyManager::get<AvatarManager>()->getMyAvatar()->getAttachmentData()) {
addAttachment(data); addAttachment(data);
} }
QPushButton* newAttachment = new QPushButton("New Attachment"); QPushButton* newAttachment = new QPushButton("New Attachment");
connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment())); connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment()));
layout->addWidget(newAttachment); layout->addWidget(newAttachment);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
layout->addWidget(buttons); layout->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); connect(buttons, SIGNAL(accepted()), SLOT(deleteLater()));
_ok = buttons->button(QDialogButtonBox::Ok); _ok = buttons->button(QDialogButtonBox::Ok);
setMinimumSize(600, 600); setMinimumSize(600, 600);
} }
void AttachmentsDialog::setVisible(bool visible) { void AttachmentsDialog::setVisible(bool visible) {
QDialog::setVisible(visible); QDialog::setVisible(visible);
// un-default the OK button // un-default the OK button
if (visible) { if (visible) {
_ok->setDefault(false); _ok->setDefault(false);
@ -104,11 +105,11 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
_dialog(dialog), _dialog(dialog),
_applying(false) { _applying(false) {
setFrameStyle(QFrame::StyledPanel); setFrameStyle(QFrame::StyledPanel);
QFormLayout* layout = new QFormLayout(); QFormLayout* layout = new QFormLayout();
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
setLayout(layout); setLayout(layout);
QHBoxLayout* urlBox = new QHBoxLayout(); QHBoxLayout* urlBox = new QHBoxLayout();
layout->addRow("Model URL:", urlBox); layout->addRow("Model URL:", urlBox);
urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1);
@ -117,7 +118,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
QPushButton* chooseURL = new QPushButton("Choose"); QPushButton* chooseURL = new QPushButton("Choose");
urlBox->addWidget(chooseURL); urlBox->addWidget(chooseURL);
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL()));
layout->addRow("Joint:", _jointName = new QComboBox()); layout->addRow("Joint:", _jointName = new QComboBox());
QSharedPointer<NetworkGeometry> geometry = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSkeletonModel().getGeometry(); QSharedPointer<NetworkGeometry> geometry = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSkeletonModel().getGeometry();
if (geometry && geometry->isLoaded()) { if (geometry && geometry->isLoaded()) {
@ -127,26 +128,30 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
} }
_jointName->setCurrentText(data.jointName); _jointName->setCurrentText(data.jointName);
connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged())); connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged()));
QHBoxLayout* translationBox = new QHBoxLayout(); QHBoxLayout* translationBox = new QHBoxLayout();
translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x)); translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x));
translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y)); translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y));
translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z)); translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z));
layout->addRow("Translation:", translationBox); layout->addRow("Translation:", translationBox);
QHBoxLayout* rotationBox = new QHBoxLayout(); QHBoxLayout* rotationBox = new QHBoxLayout();
glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation)); glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation));
rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x)); rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x));
rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y)); rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y));
rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z)); rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z));
layout->addRow("Rotation:", rotationBox); layout->addRow("Rotation:", rotationBox);
layout->addRow("Scale:", _scale = new QDoubleSpinBox()); layout->addRow("Scale:", _scale = new QDoubleSpinBox());
_scale->setSingleStep(0.01); _scale->setSingleStep(0.01);
_scale->setMaximum(FLT_MAX); _scale->setMaximum(FLT_MAX);
_scale->setValue(data.scale); _scale->setValue(data.scale);
connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); 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"); QPushButton* remove = new QPushButton("Delete");
layout->addRow(remove); layout->addRow(remove);
connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); 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.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value());
data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value()))); data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value())));
data.scale = _scale->value(); data.scale = _scale->value();
data.isSoft = _isSoft->isChecked();
return data; return data;
} }
@ -227,6 +233,7 @@ void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) {
_rotationY->setValue(eulers.y); _rotationY->setValue(eulers.y);
_rotationZ->setValue(eulers.z); _rotationZ->setValue(eulers.z);
_scale->setValue(attachment.scale); _scale->setValue(attachment.scale);
_isSoft->setChecked(attachment.isSoft);
_applying = false; _applying = false;
_dialog->updateAttachmentData(); _dialog->updateAttachmentData();
} }

View file

@ -36,11 +36,11 @@ public slots:
void updateAttachmentData(); void updateAttachmentData();
private slots: private slots:
void addAttachment(const AttachmentData& data = AttachmentData()); void addAttachment(const AttachmentData& data = AttachmentData());
private: private:
QVBoxLayout* _attachments; QVBoxLayout* _attachments;
QPushButton* _ok; QPushButton* _ok;
}; };
@ -50,7 +50,7 @@ class AttachmentPanel : public QFrame {
Q_OBJECT Q_OBJECT
public: public:
AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData()); AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData());
AttachmentData getAttachmentData() const; AttachmentData getAttachmentData() const;
@ -64,9 +64,9 @@ private slots:
void updateAttachmentData(); void updateAttachmentData();
private: private:
void applyAttachmentData(const AttachmentData& attachment); void applyAttachmentData(const AttachmentData& attachment);
AttachmentsDialog* _dialog; AttachmentsDialog* _dialog;
QLineEdit* _modelURL; QLineEdit* _modelURL;
QComboBox* _jointName; QComboBox* _jointName;
@ -77,6 +77,7 @@ private:
QDoubleSpinBox* _rotationY; QDoubleSpinBox* _rotationY;
QDoubleSpinBox* _rotationZ; QDoubleSpinBox* _rotationZ;
QDoubleSpinBox* _scale; QDoubleSpinBox* _scale;
QCheckBox* _isSoft;
bool _applying; bool _applying;
}; };

View file

@ -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) { void Rig::setModelOffset(const glm::mat4& modelOffsetMat) {
AnimPose newModelOffset = AnimPose(modelOffsetMat); AnimPose newModelOffset = AnimPose(modelOffsetMat);
if (!isEqual(_modelOffset.trans, newModelOffset.trans) || if (!isEqual(_modelOffset.trans, newModelOffset.trans) ||

View file

@ -91,6 +91,7 @@ public:
bool jointStatesEmpty(); bool jointStatesEmpty();
int getJointStateCount() const; int getJointStateCount() const;
int indexOfJoint(const QString& jointName) const; int indexOfJoint(const QString& jointName) const;
QString nameOfJoint(int jointIndex) const;
void setModelOffset(const glm::mat4& modelOffsetMat); void setModelOffset(const glm::mat4& modelOffsetMat);

View file

@ -1260,6 +1260,7 @@ void AvatarData::updateJointMappings() {
static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl");
static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName");
static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform");
static const QString JSON_ATTACHMENT_IS_SOFT = QStringLiteral("isSoft");
QJsonObject AttachmentData::toJson() const { QJsonObject AttachmentData::toJson() const {
QJsonObject result; QJsonObject result;
@ -1278,6 +1279,7 @@ QJsonObject AttachmentData::toJson() const {
if (!transform.isIdentity()) { if (!transform.isIdentity()) {
result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform); result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform);
} }
result[JSON_ATTACHMENT_IS_SOFT] = isSoft;
return result; return result;
} }
@ -1302,21 +1304,25 @@ void AttachmentData::fromJson(const QJsonObject& json) {
rotation = transform.getRotation(); rotation = transform.getRotation();
scale = transform.getScale().x; 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 { bool AttachmentData::operator==(const AttachmentData& other) const {
return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation && 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) { QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) {
return out << attachment.modelURL << attachment.jointName << 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) { QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) {
return in >> attachment.modelURL >> attachment.jointName >> 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 { void AttachmentDataObject::setModelURL(const QString& modelURL) const {

View file

@ -437,9 +437,10 @@ public:
glm::vec3 translation; glm::vec3 translation;
glm::quat rotation; glm::quat rotation;
float scale { 1.0f }; float scale { 1.0f };
bool isSoft { false };
bool isValid() const { return modelURL.isValid(); } bool isValid() const { return modelURL.isValid(); }
bool operator==(const AttachmentData& other) const; bool operator==(const AttachmentData& other) const;
QJsonObject toJson() const; QJsonObject toJson() const;

View file

@ -44,7 +44,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
return VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP; return VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP;
case PacketType::AvatarData: case PacketType::AvatarData:
case PacketType::BulkAvatarData: case PacketType::BulkAvatarData:
return 17; return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
default: default:
return 17; return 17;
} }

View file

@ -163,4 +163,9 @@ const PacketVersion VERSION_ENTITIES_POLYLINE_TEXTURE = 50;
const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51; const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51;
const PacketVersion VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52; const PacketVersion VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,
SoftAttachmentSupport
};
#endif // hifi_PacketHeaders_h #endif // hifi_PacketHeaders_h

View file

@ -971,11 +971,14 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
_needsUpdateClusterMatrices = true; _needsUpdateClusterMatrices = true;
_rig->updateAnimations(deltaTime, parentTransform); _rig->updateAnimations(deltaTime, parentTransform);
} }
void Model::simulateInternal(float deltaTime) { void Model::simulateInternal(float deltaTime) {
// update the world space transforms for all joints // update the world space transforms for all joints
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
updateRig(deltaTime, parentTransform); updateRig(deltaTime, parentTransform);
} }
// virtual
void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) {
PerformanceTimer perfTimer("Model::updateClusterMatrices"); PerformanceTimer perfTimer("Model::updateClusterMatrices");

View file

@ -112,7 +112,7 @@ public:
bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
virtual void simulate(float deltaTime, bool fullUpdate = true); 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. /// Returns a reference to the shared geometry.
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; } const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
@ -312,7 +312,7 @@ protected:
// hook for derived classes to be notified when setUrl invalidates the current model. // hook for derived classes to be notified when setUrl invalidates the current model.
virtual void onInvalidate() {}; virtual void onInvalidate() {};
private: protected:
void deleteGeometry(); void deleteGeometry();
void initJointTransforms(); void initJointTransforms();
@ -370,7 +370,6 @@ private:
bool _showCollisionHull = false; bool _showCollisionHull = false;
friend class ModelMeshPartPayload; friend class ModelMeshPartPayload;
protected:
RigPointer _rig; RigPointer _rig;
}; };