From 1618e0a92f1e72a937ab59c24472037dde6fe64d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 18 Dec 2015 13:32:09 -0800 Subject: [PATCH] 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