diff --git a/examples/tests/avatarAttachmentTest.js b/examples/tests/avatarAttachmentTest.js new file mode 100644 index 0000000000..23b94ba8d5 --- /dev/null +++ b/examples/tests/avatarAttachmentTest.js @@ -0,0 +1,148 @@ +// +// avatarAttachmentTest.js +// examples/tests +// +// Created by Anthony Thibault on January 7, 2016 +// Copyright 2016 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 +// + +// Test for MyAvatar attachment API +// MyAvatar.setAttachmentData(); +// MyAvatar.getAttachmentData(); + +// Toggle button helper +function ToggleButtonBuddy(x, y, width, height, urls) { + this.up = Overlays.addOverlay("image", { + x: x, y: y, width: width, height: height, + subImage: { x: 0, y: height, width: width, height: height}, + imageURL: urls.up, + visible: true, + alpha: 1.0 + }); + this.down = Overlays.addOverlay("image", { + x: x, y: y, width: width, height: height, + subImage: { x: 0, y: height, width: width, height: height}, + imageURL: urls.down, + visible: false, + alpha: 1.0 + }); + this.callbacks = []; + + var self = this; + Controller.mousePressEvent.connect(function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + if (clickedOverlay === self.up) { + // flip visiblity + Overlays.editOverlay(self.up, {visible: false}); + Overlays.editOverlay(self.down, {visible: true}); + self.onToggle(true); + } else if (clickedOverlay === self.down) { + // flip visiblity + Overlays.editOverlay(self.up, {visible: true}); + Overlays.editOverlay(self.down, {visible: false}); + self.onToggle(false); + } + }); +} +ToggleButtonBuddy.prototype.destroy = function () { + Overlays.deleteOverlay(this.up); + Overlays.deleteOverlay(this.down); +}; +ToggleButtonBuddy.prototype.addToggleHandler = function (callback) { + this.callbacks.push(callback); + return callback; +}; +ToggleButtonBuddy.prototype.removeToggleHandler = function (callback) { + var index = this.callbacks.indexOf(callback); + if (index != -1) { + this.callbacks.splice(index, 1); + } +}; +ToggleButtonBuddy.prototype.onToggle = function (isDown) { + var i, l = this.callbacks.length; + for (i = 0; i < l; i++) { + this.callbacks[i](isDown); + } +}; + +var windowDimensions = Controller.getViewportDimensions(); +var BUTTON_WIDTH = 64; +var BUTTON_HEIGHT = 64; +var BUTTON_PADDING = 10; +var buttonPositionX = windowDimensions.x - BUTTON_PADDING - BUTTON_WIDTH; +var buttonPositionY = (windowDimensions.y - BUTTON_HEIGHT) / 2 - (BUTTON_HEIGHT + BUTTON_PADDING); + +var hatButton = new ToggleButtonBuddy(buttonPositionX, buttonPositionY, BUTTON_WIDTH, BUTTON_HEIGHT, { + up: "https://s3.amazonaws.com/hifi-public/tony/icons/hat-up.svg", + down: "https://s3.amazonaws.com/hifi-public/tony/icons/hat-down.svg" +}); + +buttonPositionY += BUTTON_HEIGHT + BUTTON_PADDING; +var coatButton = new ToggleButtonBuddy(buttonPositionX, buttonPositionY, BUTTON_WIDTH, BUTTON_HEIGHT, { + up: "https://s3.amazonaws.com/hifi-public/tony/icons/coat-up.svg", + down: "https://s3.amazonaws.com/hifi-public/tony/icons/coat-down.svg" +}); + +var HAT_ATTACHMENT = { + modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx", + jointName: "Head", + translation: {"x": 0, "y": 0.2, "z": 0}, + rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, + scale: 1, + isSoft: false +}; + +var COAT_ATTACHMENT = { + modelURL: "https://hifi-content.s3.amazonaws.com/ozan/dev/clothes/coat/coat_rig.fbx", + jointName: "Hips", + translation: {"x": 0, "y": 0, "z": 0}, + rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, + scale: 1, + isSoft: true +}; + +hatButton.addToggleHandler(function (isDown) { + if (isDown) { + wearAttachment(HAT_ATTACHMENT); + } else { + removeAttachment(HAT_ATTACHMENT); + } +}); + +coatButton.addToggleHandler(function (isDown) { + if (isDown) { + wearAttachment(COAT_ATTACHMENT); + } else { + removeAttachment(COAT_ATTACHMENT); + } +}); + + +function wearAttachment(attachment) { + MyAvatar.attach(attachment.modelURL, + attachment.jointName, + attachment.translation, + attachment.rotation, + attachment.scale, + attachment.isSoft); +} + +function removeAttachment(attachment) { + var attachments = MyAvatar.attachmentData; + var i, l = attachments.length; + for (i = 0; i < l; i++) { + if (attachments[i].modelURL === attachment.modelURL) { + attachments.splice(i, 1); + MyAvatar.attachmentData = attachments; + break; + } + } +} + +Script.scriptEnding.connect(function() { + hatButton.destroy(); + coatbutton.destroy(); +}); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index c1eb173629..4f19b33abf 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -944,7 +944,7 @@ static std::shared_ptr allocateAttachmentModel(bool isSoft, RigPointer ri void Avatar::setAttachmentData(const QVector& attachmentData) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, + QMetaObject::invokeMethod(this, "setAttachmentData", Qt::BlockingQueuedConnection, Q_ARG(const QVector, attachmentData)); return; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 98b09d6c59..a5303b401a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1022,12 +1022,12 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN } void MyAvatar::setAttachmentData(const QVector& attachmentData) { - Avatar::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, + QMetaObject::invokeMethod(this, "setAttachmentData", Qt::BlockingQueuedConnection, Q_ARG(const QVector, attachmentData)); return; } + Avatar::setAttachmentData(attachmentData); _billboardValid = false; } @@ -1165,21 +1165,25 @@ void MyAvatar::setCollisionSoundURL(const QString& url) { } } -void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, - const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { +void MyAvatar::attach(const QString& modelURL, const QString& jointName, + const glm::vec3& translation, const glm::quat& rotation, + float scale, bool isSoft, + bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { - Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); + Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); return; } if (useSaved) { AttachmentData attachment = loadAttachmentData(modelURL, jointName); if (attachment.isValid()) { - Avatar::attach(modelURL, attachment.jointName, attachment.translation, - attachment.rotation, attachment.scale, allowDuplicates, useSaved); + Avatar::attach(modelURL, attachment.jointName, + attachment.translation, attachment.rotation, + attachment.scale, attachment.isSoft, + allowDuplicates, useSaved); return; } } - Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); + Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); } void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 44823c9913..8c4bbcbba0 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -295,7 +295,8 @@ private: void setScriptedMotorTimescale(float timescale); void setScriptedMotorFrame(QString frame); virtual void attach(const QString& modelURL, const QString& jointName = QString(), - const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, + const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), + float scale = 1.0f, bool isSoft = false, bool allowDuplicates = false, bool useSaved = true) override; //void beginFollowingHMD(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 0015a064fb..af8e1c1cda 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1085,12 +1085,15 @@ void AvatarData::setAttachmentData(const QVector& attachmentData _attachmentData = attachmentData; } -void AvatarData::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation, - const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) { +void AvatarData::attach(const QString& modelURL, const QString& jointName, + const glm::vec3& translation, const glm::quat& rotation, + float scale, bool isSoft, + bool allowDuplicates, bool useSaved) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "attach", Q_ARG(const QString&, modelURL), Q_ARG(const QString&, jointName), - Q_ARG(const glm::vec3&, translation), Q_ARG(const glm::quat&, rotation), - Q_ARG(float, scale), Q_ARG(bool, allowDuplicates), Q_ARG(bool, useSaved)); + Q_ARG(const glm::vec3&, translation), Q_ARG(const glm::quat&, rotation), + Q_ARG(float, scale), Q_ARG(bool, isSoft), + Q_ARG(bool, allowDuplicates), Q_ARG(bool, useSaved)); return; } QVector attachmentData = getAttachmentData(); @@ -1107,6 +1110,7 @@ void AvatarData::attach(const QString& modelURL, const QString& jointName, const data.translation = translation; data.rotation = rotation; data.scale = scale; + data.isSoft = isSoft; attachmentData.append(data); setAttachmentData(attachmentData); } @@ -1334,7 +1338,7 @@ QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { attachment.translation >> attachment.rotation >> attachment.scale >> attachment.isSoft; } -void AttachmentDataObject::setModelURL(const QString& modelURL) const { +void AttachmentDataObject::setModelURL(const QString& modelURL) { AttachmentData data = qscriptvalue_cast(thisObject()); data.modelURL = modelURL; thisObject() = engine()->toScriptValue(data); @@ -1344,7 +1348,7 @@ QString AttachmentDataObject::getModelURL() const { return qscriptvalue_cast(thisObject()).modelURL.toString(); } -void AttachmentDataObject::setJointName(const QString& jointName) const { +void AttachmentDataObject::setJointName(const QString& jointName) { AttachmentData data = qscriptvalue_cast(thisObject()); data.jointName = jointName; thisObject() = engine()->toScriptValue(data); @@ -1354,7 +1358,7 @@ QString AttachmentDataObject::getJointName() const { return qscriptvalue_cast(thisObject()).jointName; } -void AttachmentDataObject::setTranslation(const glm::vec3& translation) const { +void AttachmentDataObject::setTranslation(const glm::vec3& translation) { AttachmentData data = qscriptvalue_cast(thisObject()); data.translation = translation; thisObject() = engine()->toScriptValue(data); @@ -1364,7 +1368,7 @@ glm::vec3 AttachmentDataObject::getTranslation() const { return qscriptvalue_cast(thisObject()).translation; } -void AttachmentDataObject::setRotation(const glm::quat& rotation) const { +void AttachmentDataObject::setRotation(const glm::quat& rotation) { AttachmentData data = qscriptvalue_cast(thisObject()); data.rotation = rotation; thisObject() = engine()->toScriptValue(data); @@ -1374,7 +1378,7 @@ glm::quat AttachmentDataObject::getRotation() const { return qscriptvalue_cast(thisObject()).rotation; } -void AttachmentDataObject::setScale(float scale) const { +void AttachmentDataObject::setScale(float scale) { AttachmentData data = qscriptvalue_cast(thisObject()); data.scale = scale; thisObject() = engine()->toScriptValue(data); @@ -1384,6 +1388,16 @@ float AttachmentDataObject::getScale() const { return qscriptvalue_cast(thisObject()).scale; } +void AttachmentDataObject::setIsSoft(bool isSoft) { + AttachmentData data = qscriptvalue_cast(thisObject()); + data.isSoft = isSoft; + thisObject() = engine()->toScriptValue(data); +} + +bool AttachmentDataObject::getIsSoft() const { + return qscriptvalue_cast(thisObject()).isSoft; +} + void registerAvatarTypes(QScriptEngine* engine) { qScriptRegisterSequenceMetaType >(engine); engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 542b0598ce..c443af7b70 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -304,8 +304,9 @@ public: Q_INVOKABLE virtual void setAttachmentData(const QVector& attachmentData); Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(), - const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, - bool allowDuplicates = false, bool useSaved = true); + const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), + float scale = 1.0f, bool isSoft = false, + bool allowDuplicates = false, bool useSaved = true); Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString()); Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString()); @@ -463,23 +464,27 @@ class AttachmentDataObject : public QObject, protected QScriptable { Q_PROPERTY(glm::vec3 translation READ getTranslation WRITE setTranslation) Q_PROPERTY(glm::quat rotation READ getRotation WRITE setRotation) Q_PROPERTY(float scale READ getScale WRITE setScale) + Q_PROPERTY(bool isSoft READ getIsSoft WRITE setIsSoft) public: - Q_INVOKABLE void setModelURL(const QString& modelURL) const; + Q_INVOKABLE void setModelURL(const QString& modelURL); Q_INVOKABLE QString getModelURL() const; - Q_INVOKABLE void setJointName(const QString& jointName) const; + Q_INVOKABLE void setJointName(const QString& jointName); Q_INVOKABLE QString getJointName() const; - Q_INVOKABLE void setTranslation(const glm::vec3& translation) const; + Q_INVOKABLE void setTranslation(const glm::vec3& translation); Q_INVOKABLE glm::vec3 getTranslation() const; - Q_INVOKABLE void setRotation(const glm::quat& rotation) const; + Q_INVOKABLE void setRotation(const glm::quat& rotation); Q_INVOKABLE glm::quat getRotation() const; - Q_INVOKABLE void setScale(float scale) const; + Q_INVOKABLE void setScale(float scale); Q_INVOKABLE float getScale() const; + + Q_INVOKABLE void setIsSoft(bool scale); + Q_INVOKABLE bool getIsSoft() const; }; void registerAvatarTypes(QScriptEngine* engine);