mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-15 17:46:47 +02:00
Merge pull request #6702 from hyperlogic/tony/soft-attachments
Soft Attachment Support
This commit is contained in:
commit
02835fc960
17 changed files with 271 additions and 57 deletions
|
@ -2999,8 +2999,7 @@ void Application::update(float deltaTime) {
|
|||
getEntities()->update(); // update the models...
|
||||
}
|
||||
|
||||
myAvatar->harvestResultsFromPhysicsSimulation();
|
||||
myAvatar->simulateAttachments(deltaTime);
|
||||
myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "Util.h"
|
||||
#include "world.h"
|
||||
#include "InterfaceLogging.h"
|
||||
#include "SoftAttachmentModel.h"
|
||||
#include <Rig.h>
|
||||
|
||||
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<render::Scene>
|
|||
_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_ptr<render::S
|
|||
pendingChanges.removeItem(_renderItemID);
|
||||
_skeletonModel.removeFromScene(scene, pendingChanges);
|
||||
getHead()->getFaceModel().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<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) {
|
||||
AvatarData::setAttachmentData(attachmentData);
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection,
|
||||
Q_ARG(const QVector<AttachmentData>, 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>& 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) {
|
||||
|
|
|
@ -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<Model*> _attachmentModels;
|
||||
QVector<Model*> _attachmentsToRemove;
|
||||
QVector<Model*> _unusedAttachments;
|
||||
std::vector<std::shared_ptr<Model>> _attachmentModels;
|
||||
std::vector<std::shared_ptr<Model>> _attachmentsToRemove;
|
||||
|
||||
float _bodyYawDelta; // degrees/sec
|
||||
|
||||
// These position histories and derivatives are in the world-frame.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
84
interface/src/avatar/SoftAttachmentModel.cpp
Normal file
84
interface/src/avatar/SoftAttachmentModel.cpp
Normal 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);
|
||||
}
|
||||
}
|
42
interface/src/avatar/SoftAttachmentModel.h
Normal file
42
interface/src/avatar/SoftAttachmentModel.h
Normal 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
|
|
@ -17,6 +17,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCheckBox>
|
||||
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <avatar/MyAvatar.h>
|
||||
|
@ -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<AvatarManager>()->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<NetworkGeometry> geometry = DependencyManager::get<AvatarManager>()->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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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) ||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,21 +1304,25 @@ 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 {
|
||||
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) {
|
||||
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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
||||
default:
|
||||
return 17;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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<NetworkGeometry>& 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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue