From b6769f967860594b4bc39b42d4985a0e86409540 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Tue, 8 May 2018 21:43:07 +0300 Subject: [PATCH] avatar selection & wearables adjustments work-on-progress --- interface/resources/qml/hifi/AvatarApp.qml | 56 +++++++++----- .../qml/hifi/avatarapp/AdjustWearables.qml | 75 +++++++++++++++++-- .../qml/hifi/avatarapp/AvatarsModel.qml | 22 ++++-- .../resources/qml/hifi/avatarapp/Vector3.qml | 24 +++++- interface/src/AvatarBookmarks.cpp | 3 + interface/src/AvatarBookmarks.h | 3 + interface/src/avatar/MyAvatar.cpp | 6 +- scripts/system/avatarapp.js | 48 ++++++++++-- 8 files changed, 192 insertions(+), 45 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 8a0e053d2f..01726445e9 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -25,12 +25,34 @@ Rectangle { property var jointNames; + function setCurrentAvatar(currentAvatar) { + currentAvatarModel.clear(); + + root.currentAvatar = { + 'name' : '', + 'url' : allAvatars.makeThumbnailUrl(currentAvatar.avatarUrl), + 'wearables' : currentAvatar.avatarEntites ? currentAvatar.avatarEntites : [], + 'entry' : currentAvatar, + 'getMoreAvatars' : false + }; + + console.debug('AvatarApp.qml: currentAvatar: ', JSON.stringify(root.currentAvatar, null, '\t')) + currentAvatarModel.append(root.currentAvatar); + root.currentAvatar = currentAvatarModel.get(currentAvatarModel.count - 1); + } + function fromScript(message) { console.debug('AvatarApp.qml: fromScript: ', JSON.stringify(message, null, '\t')) if(message.method === 'initialize') { jointNames = message.reply.jointNames; emitSendToScript({'method' : getAvatarsMethod}); + } else if(message.method === 'bookmarkLoaded') { + setCurrentAvatar(message.reply.currentAvatar); + selectedAvatarId = message.reply.name; + var avatarIndex = allAvatars.findAvatarIndex(selectedAvatarId); + allAvatars.move(avatarIndex, 0, 1); + view.setPage(0); } else if(message.method === getAvatarsMethod) { var getAvatarsReply = message.reply; allAvatars.populate(getAvatarsReply.bookmarks); @@ -70,20 +92,10 @@ Rectangle { console.debug('selectedAvatarIndex = -1, avatar is not favorite') + setCurrentAvatar(currentAvatar); + if(selectedAvatarIndex === -1) { - - var currentAvatarEntry = { - 'name' : '', - 'url' : Qt.resolvedUrl(allAvatars.urls[i++ % allAvatars.urls.length]), - 'wearables' : currentAvatar.avatarEntites ? currentAvatar.avatarEntites : [], - 'entry' : currentAvatar, - 'getMoreAvatars' : false - }; - - currentAvatarModel.append(currentAvatarEntry); - currentAvatarEntry = allAvatars.get(allAvatars.count - 1); - - selectedAvatar = currentAvatarEntry; + selectedAvatar = root.currentAvatar; view.setPage(0); console.debug('selectedAvatar = ', JSON.stringify(selectedAvatar, null, '\t')) @@ -93,6 +105,8 @@ Rectangle { } } + property var currentAvatar; + property string selectedAvatarId: '' onSelectedAvatarIdChanged: { console.debug('selectedAvatarId: ', selectedAvatarId) @@ -174,6 +188,9 @@ Rectangle { anchors.top: header.bottom anchors.bottom: parent.bottom jointNames: root.jointNames + onWearableChanged: { + emitSendToScript({'method' : 'adjustWearable', 'id' : id, 'properties' : properties}) + } z: 3 } @@ -365,7 +382,7 @@ Rectangle { anchors.fill: parent onClicked: { console.debug('adjustWearables.open'); - adjustWearables.open(selectedAvatar); + adjustWearables.open(currentAvatar); } } } @@ -488,11 +505,7 @@ Rectangle { property int verticalSpacing: 36 function selectAvatar(avatar) { - AvatarBookmarks.loadBookmark(avatar.name); - selectedAvatarId = avatar.name; - var avatarIndex = allAvatars.findAvatarIndex(selectedAvatarId); - allAvatars.move(avatarIndex, 0, 1); - view.setPage(0); + emitSendToScript({'method' : 'selectAvatar', 'name' : avatar.name}) } AvatarsModel { @@ -630,7 +643,10 @@ Rectangle { imageUrl: url border.color: container.highlighted ? style.colors.blueHighlight : 'transparent' border.width: container.highlighted ? 2 : 0 - wearablesCount: !getMoreAvatars ? wearables.count : 0 + wearablesCount: { + console.debug('getMoreAvatars: ', getMoreAvatars, 'name: ', name); + return !getMoreAvatars ? wearables.count : 0 + } onWearablesCountChanged: { console.debug('delegate: AvatarThumbnail.wearablesCount: ', wearablesCount) } diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index abcd1e3353..653c3a75b0 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -11,6 +11,8 @@ Rectangle { height: 706 color: 'white' + signal wearableChanged(var id, var properties); + property bool modified: false; Component.onCompleted: { modified = false; @@ -24,11 +26,13 @@ Rectangle { property var onButton1Clicked; property var jointNames; + property var wearables: ({}) function open(avatar) { - console.debug('AdjustWearables.qml: open'); + console.debug('AdjustWearables.qml: open: ', JSON.stringify(avatar, null, '\t')); visible = true; wearablesCombobox.model.clear(); + wearables = {}; console.debug('AdjustWearables.qml: avatar.wearables.count: ', avatar.wearables.count); for(var i = 0; i < avatar.wearables.count; ++i) { @@ -38,6 +42,12 @@ Rectangle { for(var j = (wearable.modelURL.length - 1); j >= 0; --j) { if(wearable.modelURL[j] === '/') { wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]); + wearables[wearable.id] = { + position: wearable.localPosition, + rotation: wearable.localRotation, + dimensions: wearable.dimensions + }; + console.debug('wearable.text = ', wearable.text); break; } @@ -79,6 +89,25 @@ Rectangle { model: ListModel { } + + comboBox.onCurrentIndexChanged: { + console.debug('wearable index changed: ', currentIndex); + var currentWearable = wearablesCombobox.model.get(currentIndex) + + if(currentWearable) { + position.notify = false; + position.xvalue = currentWearable.localPosition.x + position.yvalue = currentWearable.localPosition.y + position.zvalue = currentWearable.localPosition.z + position.notify = true; + + rotation.notify = false; + rotation.xvalue = currentWearable.localRotationAngles.x + rotation.yvalue = currentWearable.localRotationAngles.y + rotation.zvalue = currentWearable.localRotationAngles.z + rotation.notify = true; + } + } } Column { @@ -101,9 +130,25 @@ Rectangle { id: position backgroundColor: "lightgray" - onXvalueChanged: modified = true; - onYvalueChanged: modified = true; - onZvalueChanged: modified = true; + function notifyPositionChanged() { + modified = true; + var properties = {}; + properties.localPosition = { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue } + + var currentWearable = wearablesCombobox.model.get(wearablesCombobox.currentIndex) + wearableChanged(currentWearable.id, properties); + } + + property bool notify: false; + + onXvalueChanged: if(notify) notifyPositionChanged(); + onYvalueChanged: if(notify) notifyPositionChanged(); + onZvalueChanged: if(notify) notifyPositionChanged(); + + decimals: 4 + realFrom: -100 + realTo: 100 + realStepSize: 0.0001 } } @@ -127,9 +172,25 @@ Rectangle { id: rotation backgroundColor: "lightgray" - onXvalueChanged: modified = true; - onYvalueChanged: modified = true; - onZvalueChanged: modified = true; + function notifyRotationChanged() { + modified = true; + var properties = {}; + properties.localRotationAngles = { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue } + + var currentWearable = wearablesCombobox.model.get(wearablesCombobox.currentIndex) + wearableChanged(currentWearable.id, properties); + } + + property bool notify: false; + + onXvalueChanged: if(notify) notifyRotationChanged(); + onYvalueChanged: if(notify) notifyRotationChanged(); + onZvalueChanged: if(notify) notifyRotationChanged(); + + decimals: 4 + realFrom: -100 + realTo: 100 + realStepSize: 0.0001 } } diff --git a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml index 2e7522183e..6a90b23c50 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml @@ -3,16 +3,22 @@ import QtQuick 2.9 ListModel { id: model + function makeThumbnailUrl(avatarUrl) { + var splittedUrl = avatarUrl.split('/'); + var marketId = splittedUrl[splittedUrl.length - 2]; + var indexOfVSuffix = marketId.indexOf('-v'); + if(indexOfVSuffix !== -1) { + marketId = marketId.substring(0, indexOfVSuffix); + } + var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/large/hifi-mp-%marketId%.jpg" + .split('%marketId%').join(marketId); + + return avatarThumbnailUrl; + } + function populate(bookmarks) { for(var avatarName in bookmarks) { - var splittedUrl = bookmarks[avatarName].avatarUrl.split('/'); - var marketId = splittedUrl[splittedUrl.length - 2]; - var indexOfVSuffix = marketId.indexOf('-v'); - if(indexOfVSuffix !== -1) { - marketId = marketId.substring(0, indexOfVSuffix); - } - var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/medium/hifi-mp-%marketId%.jpg" - .split('%marketId%').join(marketId); + var avatarThumbnailUrl = makeThumbnailUrl(bookmarks[avatarName].avatarUrl); var avatarEntry = { 'name' : avatarName, diff --git a/interface/resources/qml/hifi/avatarapp/Vector3.qml b/interface/resources/qml/hifi/avatarapp/Vector3.qml index 245f804e82..33e82fe0ee 100644 --- a/interface/resources/qml/hifi/avatarapp/Vector3.qml +++ b/interface/resources/qml/hifi/avatarapp/Vector3.qml @@ -5,6 +5,7 @@ import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls Row { + id: root width: parent.width height: xspinner.controlHeight @@ -12,11 +13,16 @@ Row { property int spinboxWidth: (parent.width - 2 * spinboxSpace) / 3 property color backgroundColor: "darkgray" + property int decimals: 4 + property real realFrom: 0 + property real realTo: 100 + property real realStepSize: 0.0001 + spacing: spinboxSpace - property real xvalue: xspinner.value - property real yvalue: yspinner.value - property real zvalue: zspinner.value + property alias xvalue: xspinner.realValue + property alias yvalue: yspinner.realValue + property alias zvalue: zspinner.realValue HifiControlsUit.SpinBox { id: xspinner @@ -25,6 +31,10 @@ Row { backgroundColor: parent.backgroundColor colorLabelInside: hifi.colors.redHighlight colorScheme: hifi.colorSchemes.light + decimals: root.decimals; + realFrom: root.realFrom + realTo: root.realTo + realStepSize: root.realStepSize } HifiControlsUit.SpinBox { @@ -34,6 +44,10 @@ Row { backgroundColor: parent.backgroundColor colorLabelInside: hifi.colors.greenHighlight colorScheme: hifi.colorSchemes.light + decimals: root.decimals; + realFrom: root.realFrom + realTo: root.realTo + realStepSize: root.realStepSize } HifiControlsUit.SpinBox { @@ -43,5 +57,9 @@ Row { backgroundColor: parent.backgroundColor colorLabelInside: hifi.colors.primaryHighlight colorScheme: hifi.colorSchemes.light + decimals: root.decimals; + realFrom: root.realFrom + realTo: root.realTo + realStepSize: root.realStepSize } } diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 7499cca57b..df3fbbfd24 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -162,6 +162,8 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); addAvatarEntities(avatarEntities); + + emit bookmarkLoaded(bookmarkName); } } } @@ -234,6 +236,7 @@ void AvatarBookmarks::changeToBookmarkedAvatar() { const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); addAvatarEntities(avatarEntities); + emit bookmarkLoaded(action->text()); } else { qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark"; } diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index c57b4760e7..d77e36d9ad 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -45,6 +45,9 @@ public slots: void removeBookmark(const QString& bookmarkName); QVariantMap getBookmarks() { return _bookmarks; } +signals: + void bookmarkLoaded(const QString& bookmarkName); + protected: void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override; void readFromFile() override; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b08496c2b8..77814dc461 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1596,7 +1596,11 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { auto modelEntity = std::dynamic_pointer_cast(child); if (modelEntity) { QVariantMap avatarEntityData; - EntityItemProperties entityProperties = modelEntity->getProperties(); + EncodeBitstreamParams params; + auto desiredProperties = modelEntity->getEntityProperties(params); + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_LOCAL_ROTATION; + EntityItemProperties entityProperties = modelEntity->getProperties(desiredProperties); QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); avatarEntityData["properties"] = scriptProperties.toVariant(); avatarEntitiesData.append(QVariant(avatarEntityData)); diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index ef7c3c1b20..e05f5b23d9 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -25,24 +25,51 @@ var ENTRY_AVATAR_ENTITIES = "avatarEntites"; var ENTRY_AVATAR_SCALE = "avatarScale"; var ENTRY_VERSION = "version"; +function getCurrentAvatar() { + var currentAvatar = {} + currentAvatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; + currentAvatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); + currentAvatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant(); + currentAvatar[ENTRY_AVATAR_ENTITIES] = MyAvatar.getAvatarEntitiesVariant(); + + for(var i = 0; i < currentAvatar[ENTRY_AVATAR_ENTITIES].length; ++i) { + var wearable = currentAvatar[ENTRY_AVATAR_ENTITIES][i]; + console.debug('updating localRotationAngles for wearable: ', JSON.stringify(wearable, null, '\t')); + var localRotation = wearable.properties.localRotation; + wearable.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) + } + + return currentAvatar; +} + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. console.debug('fromQml: message = ', JSON.stringify(message, null, '\t')) switch (message.method) { case 'getAvatars': - var currentAvatar = {} - currentAvatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; - currentAvatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); - currentAvatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant(); - currentAvatar[ENTRY_AVATAR_ENTITIES] = MyAvatar.getAvatarEntitiesVariant(); message.reply = { 'bookmarks' : AvatarBookmarks.getBookmarks(), - 'currentAvatar' : currentAvatar + 'currentAvatar' : getCurrentAvatar() }; + console.debug('avatarapp.js: currentAvatar: ', JSON.stringify(message.reply.currentAvatar, null, '\t')) sendToQml(message) break; + case 'adjustWearable': + if(message.properties.localRotationAngles) { + message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles) + delete message.properties.localRotationAngles; + } + + console.debug('Entities.editEntity(message.id, message.properties)'.replace('message.id', message.id).replace('message.properties', JSON.stringify(message.properties))); + Entities.editEntity(message.id, message.properties); + break; + case 'selectAvatar': + console.debug('avatarapp.js: selecting avatar: ', message.name); + AvatarBookmarks.loadBookmark(message.name); + break; + default: print('Unrecognized message from AvatarApp.qml:', JSON.stringify(message)); } @@ -52,6 +79,12 @@ function sendToQml(message) { tablet.sendToQml(message); } +function onBookmarkLoaded(bookmarkName) { + var currentAvatar = getCurrentAvatar(); + console.debug('avatarapp.js: onBookmarkLoaded: ', JSON.stringify(currentAvatar, 0, 4)); + sendToQml({'method' : 'bookmarkLoaded', 'reply' : {'name' : bookmarkName, 'currentAvatar' : getCurrentAvatar()} }); +} + // // Manage the connection between the button and the window. // @@ -69,6 +102,7 @@ function startup() { }); button.clicked.connect(onTabletButtonClicked); tablet.screenChanged.connect(onTabletScreenChanged); + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); // Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); // Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); // Users.avatarDisconnected.connect(avatarDisconnected); @@ -160,6 +194,8 @@ function shutdown() { button.clicked.disconnect(onTabletButtonClicked); tablet.removeButton(button); tablet.screenChanged.disconnect(onTabletScreenChanged); + AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); + // Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); // Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); // AvatarList.avatarAddedEvent.disconnect(avatarAdded);