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.
This commit is contained in:
Anthony J. Thibault 2015-12-18 13:32:09 -08:00
parent 6b5b272cd7
commit 1618e0a92f
12 changed files with 132 additions and 54 deletions

View file

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

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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

View file

@ -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();
}

View file

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

View file

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

View file

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

View file

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

View file

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