mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-09 21:32:12 +02:00
Merge branch 'master' into avatar-mixer-improvements
This commit is contained in:
commit
e22e0ecb04
68 changed files with 2317 additions and 620 deletions
|
@ -222,7 +222,8 @@ void Agent::requestScript() {
|
|||
return;
|
||||
}
|
||||
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, scriptURL);
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
this, scriptURL, true, -1, "Agent::requestScript");
|
||||
|
||||
if (!request) {
|
||||
qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString();
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <QtCore/QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <ClientTraitsHandler.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <EntityTree.h>
|
||||
#include <ScriptEngine.h>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include "AssignmentClientLogging.h"
|
||||
#include "AssignmentFactory.h"
|
||||
#include "ResourceRequestObserver.h"
|
||||
|
||||
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
|
||||
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
|
||||
|
@ -49,6 +50,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
DependencyManager::set<tracing::Tracer>();
|
||||
DependencyManager::set<StatTracker>();
|
||||
DependencyManager::set<AccountManager>();
|
||||
DependencyManager::set<ResourceRequestObserver>();
|
||||
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC73.zip
|
||||
URL_MD5 0c5edfb63cafb042311d3cf25261fbf2
|
||||
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC75.zip
|
||||
URL_MD5 b4225d058952e17976ac228330ce8d51
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
140
interface/resources/avatar/network-animation.json
Normal file
140
interface/resources/avatar/network-animation.json
Normal file
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"version": "1.1",
|
||||
"root": {
|
||||
"id": "userAnimStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "idleAnim",
|
||||
"states": [
|
||||
{
|
||||
"id": "idleAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "postTransitAnim", "state": "postTransitAnim" },
|
||||
{ "var": "preTransitAnim", "state": "preTransitAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "preTransitAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "idleAnim", "state": "idleAnim" },
|
||||
{ "var": "transitAnim", "state": "transitAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "transitAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "preTransitAnim", "state": "preTransitAnim" },
|
||||
{ "var": "postTransitAnim", "state": "postTransitAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "postTransitAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "transitAnim", "state": "transitAnim" },
|
||||
{ "var": "idleAnim", "state": "idleAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "userAnimA",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "idleAnim", "state": "idleAnim" },
|
||||
{ "var": "userAnimB", "state": "userAnimB" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "userAnimB",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "idleAnim", "state": "idleAnim" },
|
||||
{ "var": "userAnimA", "state": "userAnimA" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "idleAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "preTransitAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 10.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "transitAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx",
|
||||
"startFrame": 11.0,
|
||||
"endFrame": 11.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "postTransitAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx",
|
||||
"startFrame": 22.0,
|
||||
"endFrame": 49.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "userAnimA",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "userAnimB",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ Item {
|
|||
property int modality: Qt.NonModal
|
||||
implicitHeight: row.height
|
||||
implicitWidth: row.width
|
||||
visible: false
|
||||
|
||||
Component.onCompleted: {
|
||||
stats.parentChanged.connect(fill);
|
||||
|
|
|
@ -31,7 +31,7 @@ Rectangle {
|
|||
|
||||
scaleSlider.notify = false;
|
||||
scaleSlider.value = Math.round(avatarScale * 10);
|
||||
scaleSlider.notify = true;;
|
||||
scaleSlider.notify = true;
|
||||
|
||||
if (settings.dominantHand === 'left') {
|
||||
leftHandRadioButton.checked = true;
|
||||
|
|
|
@ -0,0 +1,364 @@
|
|||
//
|
||||
// ItemUnderTest
|
||||
// qml/hifi/commerce/marketplaceItemTester
|
||||
//
|
||||
// Load items not in the marketplace for testing purposes
|
||||
//
|
||||
// Created by Kerry Ivan Kurian on 2018-10-18
|
||||
// Copyright 2018 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
|
||||
//
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import Hifi 1.0 as Hifi
|
||||
import "qrc:////qml//styles-uit" as HifiStylesUit
|
||||
import "qrc:////qml//controls-uit" as HifiControlsUit
|
||||
|
||||
Rectangle {
|
||||
id: root;
|
||||
color: hifi.colors.baseGray
|
||||
width: parent.width - 16
|
||||
height: childrenRect.height + itemHeaderContainer.anchors.topMargin + detailsContainer.anchors.topMargin
|
||||
|
||||
property var detailsExpanded: false
|
||||
|
||||
property var actions: {
|
||||
"forward": function(resource, assetType, resourceObjectId){
|
||||
switch(assetType) {
|
||||
case "application":
|
||||
Commerce.installApp(resource, true);
|
||||
break;
|
||||
case "avatar":
|
||||
MyAvatar.useFullAvatarURL(resource);
|
||||
break;
|
||||
case "content set":
|
||||
urlHandler.handleUrl("hifi://localhost/0,0,0");
|
||||
Commerce.replaceContentSet(toUrl(resource), "");
|
||||
break;
|
||||
case "entity":
|
||||
case "wearable":
|
||||
rezEntity(resource, assetType, resourceObjectId);
|
||||
break;
|
||||
default:
|
||||
print("Marketplace item tester unsupported assetType " + assetType);
|
||||
}
|
||||
},
|
||||
"trash": function(resource, assetType){
|
||||
if ("application" === assetType) {
|
||||
Commerce.uninstallApp(resource);
|
||||
}
|
||||
sendToScript({
|
||||
method: "tester_deleteResourceObject",
|
||||
objectId: resourceListModel.get(index).resourceObjectId});
|
||||
resourceListModel.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: itemHeaderContainer
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 8
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
width: parent.width - 16
|
||||
height: childrenRect.height
|
||||
|
||||
Item {
|
||||
id: itemNameContainer
|
||||
width: parent.width * 0.5
|
||||
height: childrenRect.height
|
||||
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
id: resourceName
|
||||
height: paintedHeight
|
||||
width: parent.width
|
||||
text: {
|
||||
var match = resource.match(/\/([^/]*)$/);
|
||||
return match ? match[1] : resource;
|
||||
}
|
||||
size: 14
|
||||
color: hifi.colors.white
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
id: resourceUrl
|
||||
anchors.top: resourceName.bottom;
|
||||
anchors.topMargin: 4;
|
||||
height: paintedHeight
|
||||
width: parent.width
|
||||
text: resource
|
||||
size: 12
|
||||
color: hifi.colors.faintGray;
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.ComboBox {
|
||||
id: comboBox
|
||||
anchors.left: itemNameContainer.right
|
||||
anchors.leftMargin: 4
|
||||
anchors.verticalCenter: itemNameContainer.verticalCenter
|
||||
height: 30
|
||||
width: parent.width * 0.3 - anchors.leftMargin
|
||||
|
||||
model: [
|
||||
"application",
|
||||
"avatar",
|
||||
"content set",
|
||||
"entity",
|
||||
"wearable",
|
||||
"unknown"
|
||||
]
|
||||
|
||||
currentIndex: (("entity or wearable" === assetType) ?
|
||||
model.indexOf("unknown") : model.indexOf(assetType))
|
||||
|
||||
Component.onCompleted: {
|
||||
onCurrentIndexChanged.connect(function() {
|
||||
assetType = model[currentIndex];
|
||||
sendToScript({
|
||||
method: "tester_updateResourceObjectAssetType",
|
||||
objectId: resourceListModel.get(index)["resourceObjectId"],
|
||||
assetType: assetType });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: actionButton
|
||||
property var glyphs: {
|
||||
"application": hifi.glyphs.install,
|
||||
"avatar": hifi.glyphs.avatar,
|
||||
"content set": hifi.glyphs.globe,
|
||||
"entity": hifi.glyphs.wand,
|
||||
"trash": hifi.glyphs.trash,
|
||||
"unknown": hifi.glyphs.circleSlash,
|
||||
"wearable": hifi.glyphs.hat
|
||||
}
|
||||
property int color: hifi.buttons.blue;
|
||||
property int colorScheme: hifi.colorSchemes.dark;
|
||||
anchors.left: comboBox.right
|
||||
anchors.leftMargin: 4
|
||||
anchors.verticalCenter: itemNameContainer.verticalCenter
|
||||
width: parent.width * 0.10 - anchors.leftMargin
|
||||
height: width
|
||||
enabled: comboBox.model[comboBox.currentIndex] !== "unknown"
|
||||
|
||||
onClicked: {
|
||||
if (model.currentlyRecordingResources) {
|
||||
model.currentlyRecordingResources = false;
|
||||
} else {
|
||||
model.resourceAccessEventText = "";
|
||||
model.currentlyRecordingResources = true;
|
||||
root.actions["forward"](resource, comboBox.currentText, resourceObjectId);
|
||||
}
|
||||
sendToScript({
|
||||
method: "tester_updateResourceRecordingStatus",
|
||||
objectId: resourceListModel.get(index).resourceObjectId,
|
||||
status: model.currentlyRecordingResources
|
||||
});
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: 4;
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
color: {
|
||||
if (!actionButton.enabled) {
|
||||
hifi.buttons.disabledColorStart[actionButton.colorScheme]
|
||||
} else if (actionButton.pressed) {
|
||||
hifi.buttons.pressedColor[actionButton.color]
|
||||
} else if (actionButton.hovered) {
|
||||
hifi.buttons.hoveredColor[actionButton.color]
|
||||
} else {
|
||||
hifi.buttons.colorStart[actionButton.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: {
|
||||
if (!actionButton.enabled) {
|
||||
hifi.buttons.disabledColorFinish[actionButton.colorScheme]
|
||||
} else if (actionButton.pressed) {
|
||||
hifi.buttons.pressedColor[actionButton.color]
|
||||
} else if (actionButton.hovered) {
|
||||
hifi.buttons.hoveredColor[actionButton.color]
|
||||
} else {
|
||||
hifi.buttons.colorFinish[actionButton.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
HifiStylesUit.HiFiGlyphs {
|
||||
id: rezIcon;
|
||||
text: model.currentlyRecordingResources ? hifi.glyphs.scriptStop : actionButton.glyphs[comboBox.model[comboBox.currentIndex]];
|
||||
anchors.fill: parent
|
||||
size: 30;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
color: enabled ? hifi.buttons.textColor[actionButton.color]
|
||||
: hifi.buttons.disabledTextColor[actionButton.colorScheme]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: trashButton
|
||||
property int color: hifi.buttons.red;
|
||||
property int colorScheme: hifi.colorSchemes.dark;
|
||||
anchors.left: actionButton.right
|
||||
anchors.verticalCenter: itemNameContainer.verticalCenter
|
||||
anchors.leftMargin: 4
|
||||
width: parent.width * 0.10 - anchors.leftMargin
|
||||
height: width
|
||||
|
||||
onClicked: {
|
||||
root.actions["trash"](resource, comboBox.currentText, resourceObjectId);
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: 4;
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
color: {
|
||||
if (!trashButton.enabled) {
|
||||
hifi.buttons.disabledColorStart[trashButton.colorScheme]
|
||||
} else if (trashButton.pressed) {
|
||||
hifi.buttons.pressedColor[trashButton.color]
|
||||
} else if (trashButton.hovered) {
|
||||
hifi.buttons.hoveredColor[trashButton.color]
|
||||
} else {
|
||||
hifi.buttons.colorStart[trashButton.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: {
|
||||
if (!trashButton.enabled) {
|
||||
hifi.buttons.disabledColorFinish[trashButton.colorScheme]
|
||||
} else if (trashButton.pressed) {
|
||||
hifi.buttons.pressedColor[trashButton.color]
|
||||
} else if (trashButton.hovered) {
|
||||
hifi.buttons.hoveredColor[trashButton.color]
|
||||
} else {
|
||||
hifi.buttons.colorFinish[trashButton.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
HifiStylesUit.HiFiGlyphs {
|
||||
id: trashIcon;
|
||||
text: hifi.glyphs.trash
|
||||
anchors.fill: parent
|
||||
size: 22;
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: enabled ? hifi.buttons.textColor[trashButton.color]
|
||||
: hifi.buttons.disabledTextColor[trashButton.colorScheme]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: detailsContainer
|
||||
|
||||
width: parent.width - 16
|
||||
height: root.detailsExpanded ? 300 : 26
|
||||
anchors.top: itemHeaderContainer.bottom
|
||||
anchors.topMargin: 12
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
|
||||
HifiStylesUit.HiFiGlyphs {
|
||||
id: detailsToggle
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -4
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -2
|
||||
width: 22
|
||||
text: root.detailsExpanded ? hifi.glyphs.minimize : hifi.glyphs.maximize
|
||||
color: hifi.colors.white
|
||||
size: 22
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.detailsExpanded = !root.detailsExpanded
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: detailsTextContainer
|
||||
anchors.top: parent.top
|
||||
anchors.left: detailsToggle.right
|
||||
anchors.leftMargin: 4
|
||||
anchors.right: parent.right
|
||||
height: detailsContainer.height - (root.detailsExpanded ? (copyToClipboardButton.height + copyToClipboardButton.anchors.topMargin) : 0)
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: detailsText
|
||||
readOnly: true
|
||||
color: hifi.colors.white
|
||||
text: {
|
||||
var numUniqueResources = (model.resourceAccessEventText.split("\n").length - 1);
|
||||
if (root.detailsExpanded && numUniqueResources > 0) {
|
||||
return model.resourceAccessEventText
|
||||
} else {
|
||||
return numUniqueResources.toString() + " unique source/resource url pair" + (numUniqueResources === 1 ? "" : "s") + " recorded"
|
||||
}
|
||||
}
|
||||
font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal })
|
||||
wrapMode: TextEdit.NoWrap
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent;
|
||||
color: hifi.colors.baseGrayShadow;
|
||||
border.width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (root.detailsExpanded) {
|
||||
detailsText.selectAll();
|
||||
} else {
|
||||
root.detailsExpanded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
id: copyToClipboardButton;
|
||||
visible: root.detailsExpanded
|
||||
color: hifi.buttons.noneBorderlessWhite
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
anchors.top: detailsTextContainer.bottom
|
||||
anchors.topMargin: 8
|
||||
anchors.right: parent.right
|
||||
width: 160
|
||||
height: 30
|
||||
text: "Copy to Clipboard"
|
||||
|
||||
onClicked: {
|
||||
Window.copyToClipboard(detailsText.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,21 +4,19 @@
|
|||
//
|
||||
// Load items not in the marketplace for testing purposes
|
||||
//
|
||||
// Created by Zach Fox on 2018-09-05
|
||||
// Created by Kerry Ivan Kurian on 2018-09-05
|
||||
// Copyright 2018 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
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtQuick.Dialogs 1.0
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Controls 2.3
|
||||
import Hifi 1.0 as Hifi
|
||||
import "../../../styles-uit" as HifiStylesUit
|
||||
import "../../../controls-uit" as HifiControlsUit
|
||||
import "qrc:////qml//styles-uit" as HifiStylesUit
|
||||
import "qrc:////qml//controls-uit" as HifiControlsUit
|
||||
|
||||
|
||||
|
||||
|
@ -27,33 +25,223 @@ Rectangle {
|
|||
|
||||
property string installedApps
|
||||
property var nextResourceObjectId: 0
|
||||
signal sendToScript(var message)
|
||||
|
||||
HifiStylesUit.HifiConstants { id: hifi }
|
||||
ListModel { id: resourceListModel }
|
||||
|
||||
color: hifi.colors.white
|
||||
color: hifi.colors.darkGray
|
||||
|
||||
AnimatedImage {
|
||||
id: spinner;
|
||||
source: "spinner.gif"
|
||||
width: 74;
|
||||
height: width;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
//
|
||||
// TITLE BAR START
|
||||
//
|
||||
Item {
|
||||
id: titleBarContainer
|
||||
// Size
|
||||
width: root.width
|
||||
height: 50
|
||||
// Anchors
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
// Title bar text
|
||||
HifiStylesUit.RalewaySemiBold {
|
||||
id: titleBarText
|
||||
text: "Marketplace Item Tester"
|
||||
// Text size
|
||||
size: 24
|
||||
// Anchors
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
width: paintedWidth
|
||||
// Style
|
||||
color: hifi.colors.lightGrayText
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignHLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
// Separator
|
||||
HifiControlsUit.Separator {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 1
|
||||
}
|
||||
}
|
||||
//
|
||||
// TITLE BAR END
|
||||
//
|
||||
|
||||
Rectangle {
|
||||
id: spinner
|
||||
z: 999
|
||||
anchors.top: titleBarContainer.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: buttonContainer.top
|
||||
color: hifi.colors.darkGray
|
||||
|
||||
AnimatedImage {
|
||||
source: "spinner.gif"
|
||||
width: 74
|
||||
height: width
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: instructionsContainer
|
||||
z: 998
|
||||
color: hifi.colors.darkGray
|
||||
visible: resourceListModel.count === 0 && !spinner.visible
|
||||
anchors.top: titleBarContainer.bottom
|
||||
anchors.topMargin: 20
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 20
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
anchors.bottom: buttonContainer.top
|
||||
anchors.bottomMargin: 20
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
text: "Use Marketplace Item Tester to test out your items before submitting them to the Marketplace." +
|
||||
"\n\nUse one of the buttons below to load your item."
|
||||
// Text size
|
||||
size: 20
|
||||
// Anchors
|
||||
anchors.fill: parent
|
||||
// Style
|
||||
color: hifi.colors.lightGrayText
|
||||
wrapMode: Text.Wrap
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: itemList
|
||||
visible: !instructionsContainer.visible
|
||||
anchors.top: titleBarContainer.bottom
|
||||
anchors.topMargin: 20
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: buttonContainer.top
|
||||
anchors.bottomMargin: 20
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
visible: !instructionsContainer.visible
|
||||
policy: ScrollBar.AlwaysOn
|
||||
parent: itemList.parent
|
||||
anchors.top: itemList.top
|
||||
anchors.right: itemList.right
|
||||
anchors.bottom: itemList.bottom
|
||||
width: 16
|
||||
}
|
||||
clip: true
|
||||
model: resourceListModel
|
||||
spacing: 8
|
||||
|
||||
delegate: ItemUnderTest { }
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttonContainer
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 12
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 12
|
||||
height: 40
|
||||
|
||||
property string currentAction
|
||||
property var actions: {
|
||||
"Load File": function() {
|
||||
buttonContainer.currentAction = "load file";
|
||||
Window.browseChanged.connect(onResourceSelected);
|
||||
Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)");
|
||||
},
|
||||
"Load URL": function() {
|
||||
buttonContainer.currentAction = "load url";
|
||||
Window.promptTextChanged.connect(onResourceSelected);
|
||||
Window.promptAsync("Please enter a URL", "");
|
||||
}
|
||||
}
|
||||
|
||||
function onResourceSelected(resource) {
|
||||
// It is possible that we received the present signal
|
||||
// from something other than our browserAsync window.
|
||||
// Alas, there is nothing we can do about that so charge
|
||||
// ahead as though we are sure the present signal is one
|
||||
// we expect.
|
||||
print("!!!! resource selected");
|
||||
switch(currentAction) {
|
||||
case "load file":
|
||||
Window.browseChanged.disconnect(onResourceSelected);
|
||||
break
|
||||
case "load url":
|
||||
Window.promptTextChanged.disconnect(onResourceSelected);
|
||||
break;
|
||||
}
|
||||
if (resource) {
|
||||
print("!!!! building resource object");
|
||||
var resourceObj = buildResourceObj(resource);
|
||||
print("!!!! notifying script of resource object");
|
||||
sendToScript({
|
||||
method: 'tester_newResourceObject',
|
||||
resourceObject: resourceObj
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
enabled: !spinner.visible
|
||||
anchors.right: parent.horizontalCenter
|
||||
anchors.rightMargin: width/4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: hifi.buttons.blue
|
||||
fontSize: 20
|
||||
text: "Load File"
|
||||
width: parent.width / 3
|
||||
height: parent.height
|
||||
onClicked: buttonContainer.actions[text]()
|
||||
}
|
||||
|
||||
HifiControlsUit.Button {
|
||||
enabled: !spinner.visible
|
||||
anchors.left: parent.horizontalCenter
|
||||
anchors.leftMargin: width/4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: hifi.buttons.blue
|
||||
fontSize: 20
|
||||
text: "Load URL"
|
||||
width: parent.width / 3
|
||||
height: parent.height
|
||||
onClicked: buttonContainer.actions[text]()
|
||||
}
|
||||
}
|
||||
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case "newResourceObjectInTest":
|
||||
var resourceObject = message.resourceObject;
|
||||
resourceListModel.clear(); // REMOVE THIS once we support specific referrers
|
||||
resourceListModel.append(resourceObject);
|
||||
spinner.visible = false;
|
||||
break;
|
||||
case "nextObjectIdInTest":
|
||||
print("!!!! message from script! " + JSON.stringify(message));
|
||||
nextResourceObjectId = message.id;
|
||||
spinner.visible = false;
|
||||
break;
|
||||
case "resourceRequestEvent":
|
||||
// When we support multiple items under test simultaneously,
|
||||
// we'll have to replace "0" with the correct index.
|
||||
resourceListModel.setProperty(0, "resourceAccessEventText", message.resourceAccessEventText);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,227 +252,26 @@ Rectangle {
|
|||
resource.match(/\.json\.gz$/) ? "content set" :
|
||||
resource.match(/\.json$/) ? "entity or wearable" :
|
||||
"unknown");
|
||||
return { "id": nextResourceObjectId++,
|
||||
// Uncomment this once we support more than one item in test at the same time
|
||||
//nextResourceObjectId++;
|
||||
return { "resourceObjectId": nextResourceObjectId,
|
||||
"resource": resource,
|
||||
"assetType": assetType };
|
||||
}
|
||||
|
||||
function installResourceObj(resourceObj) {
|
||||
if ("application" === resourceObj.assetType) {
|
||||
Commerce.installApp(resourceObj.resource);
|
||||
}
|
||||
}
|
||||
|
||||
function addAllInstalledAppsToList() {
|
||||
var i, apps = Commerce.getInstalledApps().split(","), len = apps.length;
|
||||
for(i = 0; i < len - 1; ++i) {
|
||||
if (i in apps) {
|
||||
resourceListModel.append(buildResourceObj(apps[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toUrl(resource) {
|
||||
var httpPattern = /^http/i;
|
||||
return httpPattern.test(resource) ? resource : "file:///" + resource;
|
||||
}
|
||||
|
||||
function rezEntity(resource, entityType) {
|
||||
function rezEntity(resource, entityType, resourceObjectId) {
|
||||
print("!!!! tester_rezClicked");
|
||||
sendToScript({
|
||||
method: 'tester_rezClicked',
|
||||
itemHref: toUrl(resource),
|
||||
itemType: entityType});
|
||||
itemType: entityType,
|
||||
itemId: resourceObjectId });
|
||||
}
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 12
|
||||
anchors.bottomMargin: 40
|
||||
anchors.rightMargin: 12
|
||||
model: resourceListModel
|
||||
spacing: 5
|
||||
interactive: false
|
||||
|
||||
delegate: RowLayout {
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
spacing: 5
|
||||
|
||||
property var actions: {
|
||||
"forward": function(resource, assetType){
|
||||
switch(assetType) {
|
||||
case "application":
|
||||
Commerce.openApp(resource);
|
||||
break;
|
||||
case "avatar":
|
||||
MyAvatar.useFullAvatarURL(resource);
|
||||
break;
|
||||
case "content set":
|
||||
urlHandler.handleUrl("hifi://localhost/0,0,0");
|
||||
Commerce.replaceContentSet(toUrl(resource), "");
|
||||
break;
|
||||
case "entity":
|
||||
case "wearable":
|
||||
rezEntity(resource, assetType);
|
||||
break;
|
||||
default:
|
||||
print("Marketplace item tester unsupported assetType " + assetType);
|
||||
}
|
||||
},
|
||||
"trash": function(resource, assetType){
|
||||
if ("application" === assetType) {
|
||||
Commerce.uninstallApp(resource);
|
||||
}
|
||||
sendToScript({
|
||||
method: "tester_deleteResourceObject",
|
||||
objectId: resourceListModel.get(index).id});
|
||||
resourceListModel.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Layout.preferredWidth: root.width * .6
|
||||
spacing: 5
|
||||
Text {
|
||||
text: {
|
||||
var match = resource.match(/\/([^/]*)$/);
|
||||
return match ? match[1] : resource;
|
||||
}
|
||||
font.pointSize: 12
|
||||
horizontalAlignment: Text.AlignBottom
|
||||
}
|
||||
Text {
|
||||
text: resource
|
||||
font.pointSize: 8
|
||||
width: root.width * .6
|
||||
horizontalAlignment: Text.AlignBottom
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBox
|
||||
|
||||
Layout.preferredWidth: root.width * .2
|
||||
|
||||
model: [
|
||||
"application",
|
||||
"avatar",
|
||||
"content set",
|
||||
"entity",
|
||||
"wearable",
|
||||
"unknown"
|
||||
]
|
||||
|
||||
currentIndex: (("entity or wearable" === assetType) ?
|
||||
model.indexOf("unknown") : model.indexOf(assetType))
|
||||
|
||||
Component.onCompleted: {
|
||||
onCurrentIndexChanged.connect(function() {
|
||||
assetType = model[currentIndex];
|
||||
sendToScript({
|
||||
method: "tester_updateResourceObjectAssetType",
|
||||
objectId: resourceListModel.get(index)["id"],
|
||||
assetType: assetType });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: [ "forward", "trash" ]
|
||||
|
||||
HifiStylesUit.HiFiGlyphs {
|
||||
property var glyphs: {
|
||||
"application": hifi.glyphs.install,
|
||||
"avatar": hifi.glyphs.avatar,
|
||||
"content set": hifi.glyphs.globe,
|
||||
"entity": hifi.glyphs.wand,
|
||||
"trash": hifi.glyphs.trash,
|
||||
"unknown": hifi.glyphs.circleSlash,
|
||||
"wearable": hifi.glyphs.hat,
|
||||
}
|
||||
text: (("trash" === modelData) ?
|
||||
glyphs.trash :
|
||||
glyphs[comboBox.model[comboBox.currentIndex]])
|
||||
size: ("trash" === modelData) ? 22 : 30
|
||||
color: hifi.colors.black
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
actions[modelData](resource, comboBox.currentText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
header: HifiStylesUit.RalewayRegular {
|
||||
id: rootHeader
|
||||
text: "Marketplace Item Tester"
|
||||
height: 80
|
||||
width: paintedWidth
|
||||
size: 22
|
||||
color: hifi.colors.black
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
}
|
||||
|
||||
footerPositioning: ListView.OverlayFooter
|
||||
footer: Row {
|
||||
id: rootActions
|
||||
spacing: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
property string currentAction
|
||||
property var actions: {
|
||||
"Load File": function(){
|
||||
rootActions.currentAction = "load file";
|
||||
Window.browseChanged.connect(onResourceSelected);
|
||||
Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)");
|
||||
},
|
||||
"Load URL": function(){
|
||||
rootActions.currentAction = "load url";
|
||||
Window.promptTextChanged.connect(onResourceSelected);
|
||||
Window.promptAsync("Please enter a URL", "");
|
||||
}
|
||||
}
|
||||
|
||||
function onResourceSelected(resource) {
|
||||
// It is possible that we received the present signal
|
||||
// from something other than our browserAsync window.
|
||||
// Alas, there is nothing we can do about that so charge
|
||||
// ahead as though we are sure the present signal is one
|
||||
// we expect.
|
||||
switch(currentAction) {
|
||||
case "load file":
|
||||
Window.browseChanged.disconnect(onResourceSelected);
|
||||
break
|
||||
case "load url":
|
||||
Window.promptTextChanged.disconnect(onResourceSelected);
|
||||
break;
|
||||
}
|
||||
if (resource) {
|
||||
var resourceObj = buildResourceObj(resource);
|
||||
installResourceObj(resourceObj);
|
||||
sendToScript({
|
||||
method: 'tester_newResourceObject',
|
||||
resourceObject: resourceObj });
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: [ "Load File", "Load URL" ]
|
||||
HifiControlsUit.Button {
|
||||
color: hifi.buttons.blue
|
||||
fontSize: 20
|
||||
text: modelData
|
||||
width: root.width / 3
|
||||
height: 40
|
||||
onClicked: actions[text]()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
signal sendToScript(var message)
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 58 KiB |
|
@ -226,6 +226,7 @@
|
|||
#include "commerce/Ledger.h"
|
||||
#include "commerce/Wallet.h"
|
||||
#include "commerce/QmlCommerce.h"
|
||||
#include "ResourceRequestObserver.h"
|
||||
|
||||
#include "webbrowser/WebBrowserSuggestionsEngine.h"
|
||||
#include <DesktopPreviewProvider.h>
|
||||
|
@ -947,6 +948,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<WalletScriptingInterface>();
|
||||
|
||||
DependencyManager::set<FadeEffect>();
|
||||
DependencyManager::set<ResourceRequestObserver>();
|
||||
|
||||
return previousSessionCrashed;
|
||||
}
|
||||
|
@ -3129,6 +3131,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
|
||||
surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance());
|
||||
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
|
||||
|
||||
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
|
||||
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
|
||||
|
@ -5022,12 +5025,12 @@ void Application::saveSettings() const {
|
|||
PluginManager::getInstance()->saveSettings();
|
||||
}
|
||||
|
||||
bool Application::importEntities(const QString& urlOrFilename) {
|
||||
bool Application::importEntities(const QString& urlOrFilename, const bool isObservable, const qint64 callerId) {
|
||||
bool success = false;
|
||||
_entityClipboard->withWriteLock([&] {
|
||||
_entityClipboard->eraseAllOctreeElements();
|
||||
|
||||
success = _entityClipboard->readFromURL(urlOrFilename);
|
||||
success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId);
|
||||
if (success) {
|
||||
_entityClipboard->reaverageOctreeElements();
|
||||
}
|
||||
|
@ -6811,6 +6814,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get<AddressManager>().data());
|
||||
scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance());
|
||||
scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
|
||||
|
||||
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
|
||||
|
||||
|
@ -7197,7 +7201,8 @@ void Application::addAssetToWorldFromURL(QString url) {
|
|||
|
||||
addAssetToWorldInfo(filename, "Downloading model file " + filename + ".");
|
||||
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, QUrl(url));
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, QUrl(url), true, -1, "Application::addAssetToWorldFromURL");
|
||||
connect(request, &ResourceRequest::finished, this, &Application::addAssetToWorldFromURLRequestFinished);
|
||||
request->send();
|
||||
}
|
||||
|
|
|
@ -342,7 +342,7 @@ public slots:
|
|||
QVector<EntityItemID> pasteEntities(float x, float y, float z);
|
||||
bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs, const glm::vec3* givenOffset = nullptr);
|
||||
bool exportEntities(const QString& filename, float x, float y, float z, float scale);
|
||||
bool importEntities(const QString& url);
|
||||
bool importEntities(const QString& url, const bool isObservable = true, const qint64 callerId = -1);
|
||||
void updateThreadPoolCount() const;
|
||||
void updateSystemTabletMode();
|
||||
void goToErrorDomainURL(QUrl errorDomainURL);
|
||||
|
|
|
@ -53,7 +53,8 @@ void ATPAssetMigrator::loadEntityServerFile() {
|
|||
|
||||
auto migrateResources = [=](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) {
|
||||
auto request =
|
||||
DependencyManager::get<ResourceManager>()->createResourceRequest(this, migrationURL);
|
||||
DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
this, migrationURL, true, -1, "ATPAssetMigrator::loadEntityServerFile");
|
||||
|
||||
if (request) {
|
||||
qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";
|
||||
|
|
|
@ -135,7 +135,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
|
|||
glm::vec3 palmPosition;
|
||||
glm::quat palmRotation;
|
||||
|
||||
bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting();
|
||||
bool isTransitingWithAvatar = holdingAvatar->getTransit()->isActive();
|
||||
if (isTransitingWithAvatar != _isTransitingWithAvatar) {
|
||||
_isTransitingWithAvatar = isTransitingWithAvatar;
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
|
@ -424,7 +424,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
if (ownerEntity) {
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting());
|
||||
ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -71,14 +71,12 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
}
|
||||
});
|
||||
|
||||
const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f;
|
||||
const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing
|
||||
const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing
|
||||
|
||||
_transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT;
|
||||
_transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE;
|
||||
_transitConfig._minTriggerDistance = AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE;
|
||||
_transitConfig._maxTriggerDistance = AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE;
|
||||
_transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER;
|
||||
_transitConfig._isDistanceBased = true;
|
||||
_transitConfig._isDistanceBased = AVATAR_TRANSIT_DISTANCE_BASED;
|
||||
_transitConfig._abortDistance = AVATAR_TRANSIT_ABORT_DISTANCE;
|
||||
}
|
||||
|
||||
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||
|
@ -126,13 +124,39 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) {
|
|||
_space = space;
|
||||
}
|
||||
|
||||
void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) {
|
||||
switch (status) {
|
||||
case AvatarTransit::Status::STARTED:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::START_TRANSIT:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::END_TRANSIT:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::ENDED:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::PRE_TRANSIT:
|
||||
break;
|
||||
case AvatarTransit::Status::POST_TRANSIT:
|
||||
break;
|
||||
case AvatarTransit::Status::IDLE:
|
||||
break;
|
||||
case AvatarTransit::Status::TRANSITING:
|
||||
break;
|
||||
case AvatarTransit::Status::ABORT_TRANSIT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::updateMyAvatar(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()");
|
||||
|
||||
AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig);
|
||||
bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT);
|
||||
bool blockTransitData = (status == AvatarTransit::Status::TRANSITING);
|
||||
AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig);
|
||||
handleTransitAnimations(status);
|
||||
|
||||
_myAvatar->update(deltaTime);
|
||||
render::Transaction transaction;
|
||||
|
@ -142,18 +166,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
|
|||
quint64 now = usecTimestampNow();
|
||||
quint64 dt = now - _lastSendAvatarDataTime;
|
||||
|
||||
|
||||
if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) {
|
||||
if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) {
|
||||
// send head/hand data to the avatar mixer and voxel server
|
||||
PerformanceTimer perfTimer("send");
|
||||
if (sendFirstTransitPackage) {
|
||||
_myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition());
|
||||
}
|
||||
PerformanceTimer perfTimer("send");
|
||||
_myAvatar->sendAvatarDataPacket();
|
||||
_lastSendAvatarDataTime = now;
|
||||
_myAvatarSendRate.increment();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -267,7 +286,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
if (inView && avatar->hasNewJointData()) {
|
||||
numAvatarsUpdated++;
|
||||
}
|
||||
auto transitStatus = avatar->_transit.update(deltaTime, avatar->_globalPosition, _transitConfig);
|
||||
auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig);
|
||||
if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) {
|
||||
avatar->_transit.reset();
|
||||
avatar->setIsNewAvatar(false);
|
||||
|
|
|
@ -221,6 +221,7 @@ private:
|
|||
// frequently grabs a read lock on the hash to get a given avatar by ID
|
||||
void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar,
|
||||
KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
|
||||
void handleTransitAnimations(AvatarTransit::Status status);
|
||||
|
||||
QVector<AvatarSharedPointer> _avatarsToFade;
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <AudioClient.h>
|
||||
#include <ClientTraitsHandler.h>
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <FSTReader.h>
|
||||
#include <GeometryUtil.h>
|
||||
|
@ -463,10 +464,74 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updateSitStandState(float newHeightReading, float dt) {
|
||||
const float STANDING_HEIGHT_MULTIPLE = 1.2f;
|
||||
const float SITTING_HEIGHT_MULTIPLE = 0.833f;
|
||||
const float SITTING_TIMEOUT = 4.0f; // 4 seconds
|
||||
const float STANDING_TIMEOUT = 0.3333f; // 1/3 second
|
||||
const float SITTING_UPPER_BOUND = 1.52f;
|
||||
if (!getIsSitStandStateLocked()) {
|
||||
if (!getIsAway() && qApp->isHMDMode()) {
|
||||
if (getIsInSittingState()) {
|
||||
if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) {
|
||||
// if we recenter upwards then no longer in sitting state
|
||||
_sitStandStateTimer += dt;
|
||||
if (_sitStandStateTimer > STANDING_TIMEOUT) {
|
||||
_averageUserHeightSensorSpace = newHeightReading;
|
||||
_tippingPoint = newHeightReading;
|
||||
setIsInSittingState(false);
|
||||
}
|
||||
} else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) {
|
||||
// if we are mis labelled as sitting but we are standing in the real world this will
|
||||
// make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state
|
||||
_sitStandStateTimer += dt;
|
||||
if (_sitStandStateTimer > SITTING_TIMEOUT) {
|
||||
_averageUserHeightSensorSpace = newHeightReading;
|
||||
_tippingPoint = newHeightReading;
|
||||
// here we stay in sit state but reset the average height
|
||||
setIsInSittingState(true);
|
||||
}
|
||||
} else {
|
||||
// sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please)
|
||||
if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) {
|
||||
setIsInSittingState(false);
|
||||
} else {
|
||||
// tipping point is average height when sitting.
|
||||
_tippingPoint = _averageUserHeightSensorSpace;
|
||||
_sitStandStateTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in the standing state
|
||||
if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) {
|
||||
_sitStandStateTimer += dt;
|
||||
if (_sitStandStateTimer > SITTING_TIMEOUT) {
|
||||
_averageUserHeightSensorSpace = newHeightReading;
|
||||
_tippingPoint = newHeightReading;
|
||||
setIsInSittingState(true);
|
||||
}
|
||||
} else {
|
||||
// use the mode height for the tipping point when we are standing.
|
||||
_tippingPoint = getCurrentStandingHeight();
|
||||
_sitStandStateTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//if you are away then reset the average and set state to standing.
|
||||
_averageUserHeightSensorSpace = _userHeight.get();
|
||||
_tippingPoint = _userHeight.get();
|
||||
setIsInSittingState(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::update(float deltaTime) {
|
||||
// update moving average of HMD facing in xz plane.
|
||||
const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength();
|
||||
const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders
|
||||
const float COSINE_THIRTY_DEGREES = 0.866f;
|
||||
const float SQUATTY_TIMEOUT = 30.0f; // 30 seconds
|
||||
const float HEIGHT_FILTER_COEFFICIENT = 0.01f;
|
||||
|
||||
float tau = deltaTime / HMD_FACING_TIMESCALE;
|
||||
setHipToHandController(computeHandAzimuth());
|
||||
|
@ -493,11 +558,36 @@ void MyAvatar::update(float deltaTime) {
|
|||
_smoothOrientationTimer += deltaTime;
|
||||
}
|
||||
|
||||
float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y;
|
||||
int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER);
|
||||
_recentModeReadings.insert(newHeightReadingInCentimeters);
|
||||
setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
|
||||
setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
|
||||
controller::Pose newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD);
|
||||
if (newHeightReading.isValid()) {
|
||||
int newHeightReadingInCentimeters = glm::floor(newHeightReading.getTranslation().y * CENTIMETERS_PER_METER);
|
||||
_averageUserHeightSensorSpace = lerp(_averageUserHeightSensorSpace, newHeightReading.getTranslation().y, HEIGHT_FILTER_COEFFICIENT);
|
||||
_recentModeReadings.insert(newHeightReadingInCentimeters);
|
||||
setCurrentStandingHeight(computeStandingHeightMode(newHeightReading));
|
||||
setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
|
||||
}
|
||||
|
||||
// if the spine is straight and the head is below the default position by 5 cm then increment squatty count.
|
||||
const float SQUAT_THRESHOLD = 0.05f;
|
||||
glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head"));
|
||||
glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2"));
|
||||
glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
if (glm::length(upSpine2) > 0.0f) {
|
||||
upSpine2 = glm::normalize(upSpine2);
|
||||
}
|
||||
float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_THIRTY_DEGREES)) {
|
||||
_squatTimer += deltaTime;
|
||||
if (_squatTimer > SQUATTY_TIMEOUT) {
|
||||
_squatTimer = 0.0f;
|
||||
_follow._squatDetected = true;
|
||||
}
|
||||
} else {
|
||||
_squatTimer = 0.0f;
|
||||
}
|
||||
|
||||
// put update sit stand state counts here
|
||||
updateSitStandState(newHeightReading.getTranslation().y, deltaTime);
|
||||
|
||||
if (_drawAverageFacingEnabled) {
|
||||
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
|
||||
|
@ -968,7 +1058,6 @@ void MyAvatar::updateSensorToWorldMatrix() {
|
|||
updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache);
|
||||
|
||||
if (hasSensorToWorldScaleChanged) {
|
||||
setTransitScale(sensorToWorldScale);
|
||||
emit sensorToWorldScaleChanged(sensorToWorldScale);
|
||||
}
|
||||
|
||||
|
@ -3557,12 +3646,9 @@ glm::vec3 MyAvatar::computeCounterBalance() {
|
|||
glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead;
|
||||
|
||||
// find the height of the hips
|
||||
const float UPPER_LEG_FRACTION = 0.3333f;
|
||||
glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z));
|
||||
float headMinusHipXz = glm::length(xzDiff);
|
||||
float headHipDefault = glm::length(tposeHead - tposeHips);
|
||||
float hipFootDefault = tposeHips.y - tposeRightFoot.y;
|
||||
float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault);
|
||||
float hipHeight = 0.0f;
|
||||
if (headHipDefault > headMinusHipXz) {
|
||||
hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz));
|
||||
|
@ -3574,10 +3660,6 @@ glm::vec3 MyAvatar::computeCounterBalance() {
|
|||
if (counterBalancedCg.y > (tposeHips.y + 0.05f)) {
|
||||
// if the height is higher than default hips, clamp to default hips
|
||||
counterBalancedCg.y = tposeHips.y + 0.05f;
|
||||
} else if (counterBalancedCg.y < sitSquatThreshold) {
|
||||
//do a height reset
|
||||
setResetMode(true);
|
||||
_follow.activate(FollowHelper::Vertical);
|
||||
}
|
||||
return counterBalancedCg;
|
||||
}
|
||||
|
@ -3818,6 +3900,18 @@ bool MyAvatar::getIsInWalkingState() const {
|
|||
return _isInWalkingState;
|
||||
}
|
||||
|
||||
bool MyAvatar::getIsInSittingState() const {
|
||||
return _isInSittingState.get();
|
||||
}
|
||||
|
||||
MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const {
|
||||
return _userRecenterModel.get();
|
||||
}
|
||||
|
||||
bool MyAvatar::getIsSitStandStateLocked() const {
|
||||
return _lockSitStandState.get();
|
||||
}
|
||||
|
||||
float MyAvatar::getWalkSpeed() const {
|
||||
return _walkSpeed.get() * _walkSpeedScalar;
|
||||
}
|
||||
|
@ -3838,6 +3932,61 @@ void MyAvatar::setIsInWalkingState(bool isWalking) {
|
|||
_isInWalkingState = isWalking;
|
||||
}
|
||||
|
||||
void MyAvatar::setIsInSittingState(bool isSitting) {
|
||||
_sitStandStateTimer = 0.0f;
|
||||
_squatTimer = 0.0f;
|
||||
// on reset height we need the count to be more than one in case the user sits and stands up quickly.
|
||||
_isInSittingState.set(isSitting);
|
||||
setResetMode(true);
|
||||
if (isSitting) {
|
||||
setCenterOfGravityModelEnabled(false);
|
||||
} else {
|
||||
setCenterOfGravityModelEnabled(true);
|
||||
}
|
||||
setSitStandStateChange(true);
|
||||
}
|
||||
|
||||
void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) {
|
||||
|
||||
_userRecenterModel.set(modelName);
|
||||
|
||||
switch (modelName) {
|
||||
case MyAvatar::SitStandModelType::ForceSit:
|
||||
setHMDLeanRecenterEnabled(true);
|
||||
setIsInSittingState(true);
|
||||
setIsSitStandStateLocked(true);
|
||||
break;
|
||||
case MyAvatar::SitStandModelType::ForceStand:
|
||||
setHMDLeanRecenterEnabled(true);
|
||||
setIsInSittingState(false);
|
||||
setIsSitStandStateLocked(true);
|
||||
break;
|
||||
case MyAvatar::SitStandModelType::Auto:
|
||||
default:
|
||||
setHMDLeanRecenterEnabled(true);
|
||||
setIsInSittingState(false);
|
||||
setIsSitStandStateLocked(false);
|
||||
break;
|
||||
case MyAvatar::SitStandModelType::DisableHMDLean:
|
||||
setHMDLeanRecenterEnabled(false);
|
||||
setIsInSittingState(false);
|
||||
setIsSitStandStateLocked(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setIsSitStandStateLocked(bool isLocked) {
|
||||
_lockSitStandState.set(isLocked);
|
||||
_sitStandStateTimer = 0.0f;
|
||||
_squatTimer = 0.0f;
|
||||
_averageUserHeightSensorSpace = _userHeight.get();
|
||||
_tippingPoint = _userHeight.get();
|
||||
if (!isLocked) {
|
||||
// always start the auto transition mode in standing state.
|
||||
setIsInSittingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setWalkSpeed(float value) {
|
||||
_walkSpeed.set(value);
|
||||
}
|
||||
|
@ -3854,6 +4003,14 @@ float MyAvatar::getSprintSpeed() const {
|
|||
return _sprintSpeed.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setSitStandStateChange(bool stateChanged) {
|
||||
_sitStandStateChange = stateChanged;
|
||||
}
|
||||
|
||||
float MyAvatar::getSitStandStateChange() const {
|
||||
return _sitStandStateChange;
|
||||
}
|
||||
|
||||
QVector<QString> MyAvatar::getScriptUrls() {
|
||||
QVector<QString> scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector<QString>();
|
||||
return scripts;
|
||||
|
@ -3997,6 +4154,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
|
|||
// x axis of currentBodyMatrix in world space.
|
||||
glm::vec3 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0]));
|
||||
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
|
||||
controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
|
||||
|
||||
float forwardLeanAmount = glm::dot(forward, offset);
|
||||
float lateralLeanAmount = glm::dot(right, offset);
|
||||
|
@ -4005,14 +4163,19 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
|
|||
const float MAX_FORWARD_LEAN = 0.15f;
|
||||
const float MAX_BACKWARD_LEAN = 0.1f;
|
||||
|
||||
|
||||
if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
|
||||
return true;
|
||||
bool stepDetected = false;
|
||||
if (myAvatar.getIsInSittingState()) {
|
||||
if (!withinBaseOfSupport(currentHeadPose)) {
|
||||
stepDetected = true;
|
||||
}
|
||||
} else if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
|
||||
stepDetected = true;
|
||||
} else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) {
|
||||
return true;
|
||||
stepDetected = true;
|
||||
} else {
|
||||
stepDetected = fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
|
||||
}
|
||||
|
||||
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
|
||||
return stepDetected;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const {
|
||||
|
@ -4021,6 +4184,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
|
|||
controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
|
||||
controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
|
||||
controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND);
|
||||
controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD);
|
||||
|
||||
bool stepDetected = false;
|
||||
float myScale = myAvatar.getAvatarScale();
|
||||
|
@ -4030,7 +4194,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
|
|||
} else {
|
||||
if (!withinBaseOfSupport(currentHeadPose) &&
|
||||
headAngularVelocityBelowThreshold(currentHeadPose) &&
|
||||
isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight(), myScale) &&
|
||||
isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) &&
|
||||
handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) &&
|
||||
handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) &&
|
||||
headVelocityGreaterThanThreshold(currentHeadPose) &&
|
||||
|
@ -4046,6 +4210,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
|
|||
glm::vec3 currentHeadPosition = currentHeadPose.getTranslation();
|
||||
float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition);
|
||||
if (!isActive(Horizontal) &&
|
||||
(!isActive(Vertical)) &&
|
||||
(glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) {
|
||||
myAvatar.setResetMode(true);
|
||||
stepDetected = true;
|
||||
|
@ -4061,10 +4226,32 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
|
|||
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
const float CYLINDER_TOP = 0.1f;
|
||||
const float CYLINDER_BOTTOM = -1.5f;
|
||||
const float SITTING_BOTTOM = -0.02f;
|
||||
|
||||
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
|
||||
bool returnValue = false;
|
||||
|
||||
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
|
||||
if (myAvatar.getSitStandStateChange()) {
|
||||
returnValue = true;
|
||||
} else {
|
||||
if (myAvatar.getIsInSittingState()) {
|
||||
if (myAvatar.getIsSitStandStateLocked()) {
|
||||
returnValue = (offset.y > CYLINDER_TOP);
|
||||
}
|
||||
if (offset.y < SITTING_BOTTOM) {
|
||||
// we recenter more easily when in sitting state.
|
||||
returnValue = true;
|
||||
}
|
||||
} else {
|
||||
// in the standing state
|
||||
returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
|
||||
// finally check for squats in standing
|
||||
if (_squatDetected) {
|
||||
returnValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix,
|
||||
|
@ -4085,9 +4272,10 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// center of gravity model is not enabled
|
||||
if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Horizontal);
|
||||
if (myAvatar.getEnableStepResetRotation()) {
|
||||
if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) {
|
||||
activate(Rotation);
|
||||
myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing());
|
||||
}
|
||||
|
@ -4095,6 +4283,9 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
|
|||
}
|
||||
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Vertical);
|
||||
if (_squatDetected) {
|
||||
_squatDetected = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!isActive(Rotation) && getForceActivateRotation()) {
|
||||
|
@ -4144,7 +4335,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
|
|||
myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining());
|
||||
}
|
||||
|
||||
glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) {
|
||||
glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) {
|
||||
if (isActive()) {
|
||||
float dt = myAvatar.getCharacterController()->getFollowTime();
|
||||
decrementTimeRemaining(dt);
|
||||
|
@ -4161,6 +4352,11 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co
|
|||
|
||||
glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix),
|
||||
sensorLinearDisplacement + extractTranslation(currentBodyMatrix));
|
||||
if (myAvatar.getSitStandStateChange()) {
|
||||
myAvatar.setSitStandStateChange(false);
|
||||
deactivate(Vertical);
|
||||
setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor()));
|
||||
}
|
||||
return newBodyMat;
|
||||
} else {
|
||||
return currentBodyMatrix;
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <AvatarConstants.h>
|
||||
#include <avatars-renderer/Avatar.h>
|
||||
#include <avatars-renderer/ScriptAvatar.h>
|
||||
#include <ClientTraitsHandler.h>
|
||||
#include <controllers/Pose.h>
|
||||
#include <controllers/Actions.h>
|
||||
#include <EntityItem.h>
|
||||
|
@ -142,6 +141,8 @@ class MyAvatar : public Avatar {
|
|||
* @property {number} walkSpeed
|
||||
* @property {number} walkBackwardSpeed
|
||||
* @property {number} sprintSpeed
|
||||
* @property {number} isInSittingState
|
||||
* @property {number} userRecenterModel
|
||||
*
|
||||
* @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the
|
||||
* registration point of the 3D model.
|
||||
|
@ -242,6 +243,9 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed);
|
||||
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed);
|
||||
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed);
|
||||
Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState);
|
||||
Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel);
|
||||
Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked);
|
||||
|
||||
const QString DOMINANT_LEFT_HAND = "left";
|
||||
const QString DOMINANT_RIGHT_HAND = "right";
|
||||
|
@ -262,6 +266,15 @@ public:
|
|||
};
|
||||
Q_ENUM(DriveKeys)
|
||||
|
||||
enum SitStandModelType {
|
||||
ForceSit = 0,
|
||||
ForceStand,
|
||||
Auto,
|
||||
DisableHMDLean,
|
||||
NumSitStandTypes
|
||||
};
|
||||
Q_ENUM(SitStandModelType)
|
||||
|
||||
explicit MyAvatar(QThread* thread);
|
||||
virtual ~MyAvatar();
|
||||
|
||||
|
@ -1121,12 +1134,21 @@ public:
|
|||
|
||||
void setIsInWalkingState(bool isWalking);
|
||||
bool getIsInWalkingState() const;
|
||||
void setIsInSittingState(bool isSitting);
|
||||
bool getIsInSittingState() const;
|
||||
void setUserRecenterModel(MyAvatar::SitStandModelType modelName);
|
||||
MyAvatar::SitStandModelType getUserRecenterModel() const;
|
||||
void setIsSitStandStateLocked(bool isLocked);
|
||||
bool getIsSitStandStateLocked() const;
|
||||
void setWalkSpeed(float value);
|
||||
float getWalkSpeed() const;
|
||||
void setWalkBackwardSpeed(float value);
|
||||
float getWalkBackwardSpeed() const;
|
||||
void setSprintSpeed(float value);
|
||||
float getSprintSpeed() const;
|
||||
void setSitStandStateChange(bool stateChanged);
|
||||
float getSitStandStateChange() const;
|
||||
void updateSitStandState(float newHeightReading, float dt);
|
||||
|
||||
QVector<QString> getScriptUrls();
|
||||
|
||||
|
@ -1532,6 +1554,7 @@ signals:
|
|||
*/
|
||||
void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
|
||||
|
||||
|
||||
private slots:
|
||||
void leaveDomain();
|
||||
void updateCollisionCapsuleCache();
|
||||
|
@ -1742,7 +1765,7 @@ private:
|
|||
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
|
||||
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
|
||||
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
|
||||
glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
|
||||
bool getForceActivateRotation() const;
|
||||
void setForceActivateRotation(bool val);
|
||||
bool getForceActivateVertical() const;
|
||||
|
@ -1751,6 +1774,7 @@ private:
|
|||
void setForceActivateHorizontal(bool val);
|
||||
bool getToggleHipsFollowing() const;
|
||||
void setToggleHipsFollowing(bool followHead);
|
||||
bool _squatDetected { false };
|
||||
std::atomic<bool> _forceActivateRotation { false };
|
||||
std::atomic<bool> _forceActivateVertical { false };
|
||||
std::atomic<bool> _forceActivateHorizontal { false };
|
||||
|
@ -1820,10 +1844,13 @@ private:
|
|||
std::mutex _pinnedJointsMutex;
|
||||
std::vector<int> _pinnedJoints;
|
||||
|
||||
void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize);
|
||||
|
||||
// height of user in sensor space, when standing erect.
|
||||
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
|
||||
|
||||
void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize);
|
||||
float _averageUserHeightSensorSpace { _userHeight.get() };
|
||||
bool _sitStandStateChange { false };
|
||||
ThreadSafeValueCache<bool> _lockSitStandState { false };
|
||||
|
||||
// max unscaled forward movement speed
|
||||
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||
|
@ -1831,6 +1858,11 @@ private:
|
|||
ThreadSafeValueCache<float> _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR };
|
||||
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
|
||||
bool _isInWalkingState { false };
|
||||
ThreadSafeValueCache<bool> _isInSittingState { false };
|
||||
ThreadSafeValueCache<MyAvatar::SitStandModelType> _userRecenterModel { MyAvatar::SitStandModelType::Auto };
|
||||
float _sitStandStateTimer { 0.0f };
|
||||
float _squatTimer { 0.0f };
|
||||
float _tippingPoint { _userHeight.get() };
|
||||
|
||||
// load avatar scripts once when rig is ready
|
||||
bool _shouldLoadScripts { false };
|
||||
|
|
|
@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
}
|
||||
|
||||
glm::mat4 hipsMat;
|
||||
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && myAvatar->getHMDLeanRecenterEnabled()) {
|
||||
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) {
|
||||
// then we use center of gravity model
|
||||
hipsMat = myAvatar->deriveBodyUsingCgModel();
|
||||
} else {
|
||||
|
@ -250,6 +250,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose);
|
||||
bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose);
|
||||
if (spine2Exists && headExists && hipsExists) {
|
||||
|
||||
AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace());
|
||||
glm::vec3 u, v, w;
|
||||
glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
|
|
|
@ -315,7 +315,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) {
|
|||
return installedAppsFromMarketplace;
|
||||
}
|
||||
|
||||
bool QmlCommerce::installApp(const QString& itemHref) {
|
||||
bool QmlCommerce::installApp(const QString& itemHref, const bool& alsoOpenImmediately) {
|
||||
if (!QDir(_appsPath).exists()) {
|
||||
if (!QDir().mkdir(_appsPath)) {
|
||||
qCDebug(commerce) << "Couldn't make _appsPath directory.";
|
||||
|
@ -325,7 +325,8 @@ bool QmlCommerce::installApp(const QString& itemHref) {
|
|||
|
||||
QUrl appHref(itemHref);
|
||||
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, appHref);
|
||||
auto request =
|
||||
DependencyManager::get<ResourceManager>()->createResourceRequest(this, appHref, true, -1, "QmlCommerce::installApp");
|
||||
|
||||
if (!request) {
|
||||
qCDebug(commerce) << "Couldn't create resource request for app.";
|
||||
|
@ -357,13 +358,22 @@ bool QmlCommerce::installApp(const QString& itemHref) {
|
|||
QJsonObject appFileJsonObject = appFileJsonDocument.object();
|
||||
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
|
||||
|
||||
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) {
|
||||
qCDebug(commerce) << "Couldn't load script.";
|
||||
return false;
|
||||
// Don't try to re-load (install) a script if it's already running
|
||||
QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts();
|
||||
if (!runningScripts.contains(scriptUrl)) {
|
||||
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) {
|
||||
qCDebug(commerce) << "Couldn't load script.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo appFileInfo(appFile);
|
||||
emit appInstalled(appFileInfo.baseName());
|
||||
}
|
||||
|
||||
if (alsoOpenImmediately) {
|
||||
QmlCommerce::openApp(itemHref);
|
||||
}
|
||||
|
||||
QFileInfo appFileInfo(appFile);
|
||||
emit appInstalled(appFileInfo.baseName());
|
||||
return true;
|
||||
});
|
||||
request->send();
|
||||
|
|
|
@ -88,7 +88,7 @@ protected:
|
|||
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
|
||||
|
||||
Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = "");
|
||||
Q_INVOKABLE bool installApp(const QString& appHref);
|
||||
Q_INVOKABLE bool installApp(const QString& appHref, const bool& alsoOpenImmediately = false);
|
||||
Q_INVOKABLE bool uninstallApp(const QString& appHref);
|
||||
Q_INVOKABLE bool openApp(const QString& appHref);
|
||||
|
||||
|
|
|
@ -46,11 +46,17 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float
|
|||
return retVal;
|
||||
}
|
||||
|
||||
bool ClipboardScriptingInterface::importEntities(const QString& filename) {
|
||||
bool ClipboardScriptingInterface::importEntities(
|
||||
const QString& filename,
|
||||
const bool isObservable,
|
||||
const qint64 callerId
|
||||
) {
|
||||
bool retVal;
|
||||
BLOCKING_INVOKE_METHOD(qApp, "importEntities",
|
||||
Q_RETURN_ARG(bool, retVal),
|
||||
Q_ARG(const QString&, filename));
|
||||
Q_ARG(const QString&, filename),
|
||||
Q_ARG(const bool, isObservable),
|
||||
Q_ARG(const qint64, callerId));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,9 +50,11 @@ public:
|
|||
* You can generate a JSON file using {@link Clipboard.exportEntities}.
|
||||
* @function Clipboard.importEntities
|
||||
* @param {string} filename Path and name of file to import.
|
||||
* @param {boolean} does the ResourceRequestObserver observe this request?
|
||||
* @param {number} optional internal id of object causing this import.
|
||||
* @returns {boolean} <code>true</code> if the import was successful, otherwise <code>false</code>.
|
||||
*/
|
||||
Q_INVOKABLE bool importEntities(const QString& filename);
|
||||
Q_INVOKABLE bool importEntities(const QString& filename, const bool isObservable = true, const qint64 callerId = -1);
|
||||
|
||||
/**jsdoc
|
||||
* Export the entities specified to a JSON file.
|
||||
|
|
|
@ -259,6 +259,39 @@ void setupPreferences() {
|
|||
auto preference = new CheckPreference(VR_MOVEMENT, "Show room boundaries while teleporting", getter, setter);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->int {
|
||||
switch (myAvatar->getUserRecenterModel()) {
|
||||
case MyAvatar::SitStandModelType::Auto:
|
||||
default:
|
||||
return 0;
|
||||
case MyAvatar::SitStandModelType::ForceSit:
|
||||
return 1;
|
||||
case MyAvatar::SitStandModelType::DisableHMDLean:
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
auto setter = [myAvatar](int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
default:
|
||||
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto);
|
||||
break;
|
||||
case 1:
|
||||
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit);
|
||||
break;
|
||||
case 2:
|
||||
myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean);
|
||||
break;
|
||||
}
|
||||
};
|
||||
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Disable Recenter", getter, setter);
|
||||
QStringList items;
|
||||
items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]";
|
||||
preference->setHeading("Avatar leaning behavior");
|
||||
preference->setItems(items);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->float { return myAvatar->getUserHeight(); };
|
||||
auto setter = [=](float value) { myAvatar->setUserHeight(value); };
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
#include "raypick/PointerScriptingInterface.h"
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
#include "AboutUtil.h"
|
||||
#include "ResourceRequestObserver.h"
|
||||
|
||||
static int MAX_WINDOW_SIZE = 4096;
|
||||
static const float METERS_TO_INCHES = 39.3701f;
|
||||
|
@ -269,6 +270,7 @@ void Web3DOverlay::setupQmlSurface(bool isTablet) {
|
|||
_webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
|
||||
|
||||
// Override min fps for tablet UI, for silky smooth scrolling
|
||||
setMaxFPS(90);
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
bool getMirrorFlag() const { return _mirrorFlag; }
|
||||
void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; }
|
||||
|
||||
float getFrame() const { return _frame; }
|
||||
|
||||
void loadURL(const QString& url);
|
||||
protected:
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "AnimSkeleton.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "IKTarget.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
|
||||
static int nextRigId = 1;
|
||||
|
@ -133,6 +134,30 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs
|
|||
_animVars.set("userAnimB", clipNodeEnum == UserAnimState::B);
|
||||
}
|
||||
|
||||
void Rig::triggerNetworkAnimation(const QString& animName) {
|
||||
_networkVars.set("idleAnim", false);
|
||||
_networkVars.set("preTransitAnim", false);
|
||||
_networkVars.set("transitAnim", false);
|
||||
_networkVars.set("postTransitAnim", false);
|
||||
_sendNetworkNode = true;
|
||||
|
||||
if (animName == "idleAnim") {
|
||||
_networkVars.set("idleAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::Idle;
|
||||
_sendNetworkNode = false;
|
||||
} else if (animName == "preTransitAnim") {
|
||||
_networkVars.set("preTransitAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::PreTransit;
|
||||
} else if (animName == "transitAnim") {
|
||||
_networkVars.set("transitAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::Transit;
|
||||
} else if (animName == "postTransitAnim") {
|
||||
_networkVars.set("postTransitAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::PostTransit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Rig::restoreAnimation() {
|
||||
if (_userAnimState.clipNodeEnum != UserAnimState::None) {
|
||||
_userAnimState.clipNodeEnum = UserAnimState::None;
|
||||
|
@ -144,6 +169,16 @@ void Rig::restoreAnimation() {
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::restoreNetworkAnimation() {
|
||||
if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) {
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::Idle;
|
||||
_networkVars.set("idleAnim", true);
|
||||
_networkVars.set("preTransitAnim", false);
|
||||
_networkVars.set("transitAnim", false);
|
||||
_networkVars.set("postTransitAnim", false);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList Rig::getAnimationRoles() const {
|
||||
if (_animNode) {
|
||||
QStringList list;
|
||||
|
@ -208,11 +243,17 @@ void Rig::restoreRoleAnimation(const QString& role) {
|
|||
void Rig::destroyAnimGraph() {
|
||||
_animSkeleton.reset();
|
||||
_animLoader.reset();
|
||||
_networkLoader.reset();
|
||||
_animNode.reset();
|
||||
_internalPoseSet._relativePoses.clear();
|
||||
_internalPoseSet._absolutePoses.clear();
|
||||
_internalPoseSet._overridePoses.clear();
|
||||
_internalPoseSet._overrideFlags.clear();
|
||||
_networkNode.reset();
|
||||
_networkPoseSet._relativePoses.clear();
|
||||
_networkPoseSet._absolutePoses.clear();
|
||||
_networkPoseSet._overridePoses.clear();
|
||||
_networkPoseSet._overrideFlags.clear();
|
||||
_numOverrides = 0;
|
||||
_leftEyeJointChildren.clear();
|
||||
_rightEyeJointChildren.clear();
|
||||
|
@ -229,14 +270,24 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff
|
|||
|
||||
_internalPoseSet._relativePoses.clear();
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
_networkPoseSet._relativePoses.clear();
|
||||
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
|
||||
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
|
||||
|
||||
_internalPoseSet._overridePoses.clear();
|
||||
_internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
_internalPoseSet._overrideFlags.clear();
|
||||
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_networkPoseSet._overridePoses.clear();
|
||||
_networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
_networkPoseSet._overrideFlags.clear();
|
||||
_networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_numOverrides = 0;
|
||||
|
||||
buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
|
||||
|
@ -270,6 +321,18 @@ void Rig::reset(const FBXGeometry& geometry) {
|
|||
|
||||
_internalPoseSet._overrideFlags.clear();
|
||||
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_networkPoseSet._relativePoses.clear();
|
||||
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
|
||||
|
||||
_networkPoseSet._overridePoses.clear();
|
||||
_networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
_networkPoseSet._overrideFlags.clear();
|
||||
_networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_numOverrides = 0;
|
||||
|
||||
buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
|
||||
|
@ -1049,26 +1112,56 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
|
|||
|
||||
updateAnimationStateHandlers();
|
||||
_animVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
|
||||
if (_networkNode) {
|
||||
_networkVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
}
|
||||
AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains,
|
||||
getGeometryToRigTransform(), rigToWorldTransform);
|
||||
|
||||
// evaluate the animation
|
||||
AnimVariantMap triggersOut;
|
||||
|
||||
AnimVariantMap networkTriggersOut;
|
||||
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut);
|
||||
if (_networkNode) {
|
||||
_networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut);
|
||||
const float NETWORK_ANIMATION_BLEND_FRAMES = 6.0f;
|
||||
float alpha = 1.0f;
|
||||
std::shared_ptr<AnimClip> clip;
|
||||
if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("preTransitAnim"));
|
||||
if (clip) {
|
||||
alpha = (clip->getFrame() - clip->getStartFrame()) / NETWORK_ANIMATION_BLEND_FRAMES;
|
||||
}
|
||||
} else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("postTransitAnim"));
|
||||
if (clip) {
|
||||
alpha = (clip->getEndFrame() - clip->getFrame()) / NETWORK_ANIMATION_BLEND_FRAMES;
|
||||
}
|
||||
}
|
||||
if (_sendNetworkNode) {
|
||||
for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) {
|
||||
_networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
|
||||
// animations haven't fully loaded yet.
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
}
|
||||
if ((int)_networkPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
|
||||
// animations haven't fully loaded yet.
|
||||
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
}
|
||||
_lastAnimVars = _animVars;
|
||||
_animVars.clearTriggers();
|
||||
_animVars = triggersOut;
|
||||
_networkVars.clearTriggers();
|
||||
_networkVars = networkTriggersOut;
|
||||
_lastContext = context;
|
||||
}
|
||||
applyOverridePoses();
|
||||
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
|
||||
|
||||
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
|
||||
// copy internal poses to external poses
|
||||
{
|
||||
QWriteLocker writeLock(&_externalPoseSetLock);
|
||||
|
@ -1672,11 +1765,14 @@ void Rig::initAnimGraph(const QUrl& url) {
|
|||
_animGraphURL = url;
|
||||
|
||||
_animNode.reset();
|
||||
_networkNode.reset();
|
||||
|
||||
// load the anim graph
|
||||
_animLoader.reset(new AnimNodeLoader(url));
|
||||
auto networkUrl = PathUtils::resourcesUrl("avatar/network-animation.json");
|
||||
_networkLoader.reset(new AnimNodeLoader(networkUrl));
|
||||
std::weak_ptr<AnimSkeleton> weakSkeletonPtr = _animSkeleton;
|
||||
connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) {
|
||||
connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, url](AnimNode::Pointer nodeIn) {
|
||||
_animNode = nodeIn;
|
||||
|
||||
// abort load if the previous skeleton was deleted.
|
||||
|
@ -1703,7 +1799,33 @@ void Rig::initAnimGraph(const QUrl& url) {
|
|||
emit onLoadComplete();
|
||||
});
|
||||
connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) {
|
||||
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
|
||||
qCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
|
||||
});
|
||||
|
||||
connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, networkUrl](AnimNode::Pointer nodeIn) {
|
||||
_networkNode = nodeIn;
|
||||
// abort load if the previous skeleton was deleted.
|
||||
auto sharedSkeletonPtr = weakSkeletonPtr.lock();
|
||||
if (!sharedSkeletonPtr) {
|
||||
return;
|
||||
}
|
||||
_networkNode->setSkeleton(sharedSkeletonPtr);
|
||||
if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) {
|
||||
// restore the user animation we had before reset.
|
||||
NetworkAnimState origState = _networkAnimState;
|
||||
_networkAnimState = { NetworkAnimState::Idle, "", 30.0f, false, 0.0f, 0.0f };
|
||||
if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) {
|
||||
triggerNetworkAnimation("preTransitAnim");
|
||||
} else if (_networkAnimState.clipNodeEnum == NetworkAnimState::Transit) {
|
||||
triggerNetworkAnimation("transitAnim");
|
||||
} else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) {
|
||||
triggerNetworkAnimation("postTransitAnim");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) {
|
||||
qCritical(animation) << "Error loading" << networkUrl.toDisplayString() << "code = " << error << "str =" << str;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1782,13 +1904,13 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
|
|||
if (isIndexValid(i)) {
|
||||
// rotations are in absolute rig frame.
|
||||
glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot();
|
||||
data.rotation = _internalPoseSet._absolutePoses[i].rot();
|
||||
data.rotation = !_sendNetworkNode ? _internalPoseSet._absolutePoses[i].rot() : _networkPoseSet._absolutePoses[i].rot();
|
||||
data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot);
|
||||
|
||||
// translations are in relative frame but scaled so that they are in meters,
|
||||
// instead of geometry units.
|
||||
glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans();
|
||||
data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans();
|
||||
data.translation = _geometryOffset.scale() * (!_sendNetworkNode ? _internalPoseSet._relativePoses[i].trans() : _networkPoseSet._relativePoses[i].trans());
|
||||
data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans);
|
||||
} else {
|
||||
data.translationIsDefaultPose = true;
|
||||
|
|
|
@ -113,7 +113,10 @@ public:
|
|||
void destroyAnimGraph();
|
||||
|
||||
void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
void triggerNetworkAnimation(const QString& animName);
|
||||
void restoreAnimation();
|
||||
void restoreNetworkAnimation();
|
||||
|
||||
QStringList getAnimationRoles() const;
|
||||
void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
void restoreRoleAnimation(const QString& role);
|
||||
|
@ -269,6 +272,7 @@ protected:
|
|||
|
||||
// Only accessed by the main thread
|
||||
PoseSet _internalPoseSet;
|
||||
PoseSet _networkPoseSet;
|
||||
|
||||
// Copy of the _poseSet for external threads.
|
||||
PoseSet _externalPoseSet;
|
||||
|
@ -300,9 +304,12 @@ protected:
|
|||
|
||||
QUrl _animGraphURL;
|
||||
std::shared_ptr<AnimNode> _animNode;
|
||||
std::shared_ptr<AnimNode> _networkNode;
|
||||
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
||||
std::unique_ptr<AnimNodeLoader> _animLoader;
|
||||
std::unique_ptr<AnimNodeLoader> _networkLoader;
|
||||
AnimVariantMap _animVars;
|
||||
AnimVariantMap _networkVars;
|
||||
|
||||
enum class RigRole {
|
||||
Idle = 0,
|
||||
|
@ -315,6 +322,25 @@ protected:
|
|||
RigRole _state { RigRole::Idle };
|
||||
RigRole _desiredState { RigRole::Idle };
|
||||
float _desiredStateAge { 0.0f };
|
||||
|
||||
struct NetworkAnimState {
|
||||
enum ClipNodeEnum {
|
||||
Idle = 0,
|
||||
PreTransit,
|
||||
Transit,
|
||||
PostTransit
|
||||
};
|
||||
NetworkAnimState() : clipNodeEnum(NetworkAnimState::Idle) {}
|
||||
NetworkAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
|
||||
clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {}
|
||||
|
||||
ClipNodeEnum clipNodeEnum;
|
||||
QString url;
|
||||
float fps;
|
||||
bool loop;
|
||||
float firstFrame;
|
||||
float lastFrame;
|
||||
};
|
||||
|
||||
struct UserAnimState {
|
||||
enum ClipNodeEnum {
|
||||
|
@ -349,6 +375,7 @@ protected:
|
|||
};
|
||||
|
||||
UserAnimState _userAnimState;
|
||||
NetworkAnimState _networkAnimState;
|
||||
std::map<QString, RoleAnimState> _roleAnimStates;
|
||||
|
||||
float _leftHandOverlayAlpha { 0.0f };
|
||||
|
@ -382,6 +409,7 @@ protected:
|
|||
|
||||
int _rigId;
|
||||
bool _headEnabled { false };
|
||||
bool _sendNetworkNode { false };
|
||||
|
||||
AnimContext _lastContext;
|
||||
AnimVariantMap _lastAnimVars;
|
||||
|
|
|
@ -114,27 +114,29 @@ void Avatar::setShowNamesAboveHeads(bool show) {
|
|||
}
|
||||
|
||||
AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
|
||||
glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition;
|
||||
float oneFrameDistance = glm::length(currentPosition - _lastPosition);
|
||||
const float MAX_TRANSIT_DISTANCE = 30.0f;
|
||||
float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale;
|
||||
if (oneFrameDistance > config._triggerDistance && !_isTransiting) {
|
||||
if (oneFrameDistance < scaledMaxTransitDistance) {
|
||||
start(deltaTime, _lastPosition, currentPosition, config);
|
||||
float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition);
|
||||
if (oneFrameDistance > (config._minTriggerDistance * _scale)) {
|
||||
if (oneFrameDistance < (config._maxTriggerDistance * _scale)) {
|
||||
start(deltaTime, _lastPosition, avatarPosition, config);
|
||||
} else {
|
||||
_lastPosition = currentPosition;
|
||||
return Status::ABORT_TRANSIT;
|
||||
_lastPosition = avatarPosition;
|
||||
_status = Status::ABORT_TRANSIT;
|
||||
}
|
||||
}
|
||||
_lastPosition = currentPosition;
|
||||
_lastPosition = avatarPosition;
|
||||
_status = updatePosition(deltaTime);
|
||||
|
||||
if (_isActive && oneFrameDistance > (config._abortDistance * _scale) && _status == Status::POST_TRANSIT) {
|
||||
reset();
|
||||
_status = Status::ENDED;
|
||||
}
|
||||
return _status;
|
||||
}
|
||||
|
||||
void AvatarTransit::reset() {
|
||||
_lastPosition = _endPosition;
|
||||
_currentPosition = _endPosition;
|
||||
_isTransiting = false;
|
||||
_isActive = false;
|
||||
}
|
||||
void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) {
|
||||
_startPosition = startPosition;
|
||||
|
@ -143,12 +145,14 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const
|
|||
_transitLine = endPosition - startPosition;
|
||||
_totalDistance = glm::length(_transitLine);
|
||||
_easeType = config._easeType;
|
||||
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
||||
|
||||
|
||||
_preTransitTime = AVATAR_PRE_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND;
|
||||
_postTransitTime = AVATAR_POST_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND;
|
||||
int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance;
|
||||
_totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND;
|
||||
_currentTime = 0.0f;
|
||||
_isTransiting = true;
|
||||
_transitTime = (float)transitFrames / AVATAR_TRANSIT_FRAMES_PER_SECOND;
|
||||
_totalTime = _transitTime + _preTransitTime + _postTransitTime;
|
||||
_currentTime = _isActive ? _preTransitTime : 0.0f;
|
||||
_isActive = true;
|
||||
}
|
||||
|
||||
float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) {
|
||||
|
@ -171,31 +175,37 @@ float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) {
|
|||
|
||||
AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) {
|
||||
Status status = Status::IDLE;
|
||||
if (_isTransiting) {
|
||||
if (_isActive) {
|
||||
float nextTime = _currentTime + deltaTime;
|
||||
if (nextTime >= _totalTime) {
|
||||
_currentPosition = _endPosition;
|
||||
_isTransiting = false;
|
||||
status = Status::END_TRANSIT;
|
||||
} else {
|
||||
if (nextTime < _preTransitTime) {
|
||||
_currentPosition = _startPosition;
|
||||
status = Status::PRE_TRANSIT;
|
||||
if (_currentTime == 0) {
|
||||
status = Status::STARTED;
|
||||
}
|
||||
} else if (nextTime < _totalTime - _postTransitTime){
|
||||
status = Status::TRANSITING;
|
||||
if (_currentTime <= _preTransitTime) {
|
||||
status = Status::START_TRANSIT;
|
||||
} else {
|
||||
status = Status::TRANSITING;
|
||||
float percentageIntoTransit = (nextTime - _preTransitTime) / _transitTime;
|
||||
_currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine;
|
||||
}
|
||||
} else {
|
||||
status = Status::POST_TRANSIT;
|
||||
_currentPosition = _endPosition;
|
||||
if (nextTime >= _totalTime) {
|
||||
_isActive = false;
|
||||
status = Status::ENDED;
|
||||
} else if (_currentTime < _totalTime - _postTransitTime) {
|
||||
status = Status::END_TRANSIT;
|
||||
}
|
||||
float percentageIntoTransit = nextTime / _totalTime;
|
||||
_currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine;
|
||||
}
|
||||
_currentTime = nextTime;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) {
|
||||
nextPosition = _currentPosition;
|
||||
return _isTransiting;
|
||||
}
|
||||
|
||||
Avatar::Avatar(QThread* thread) :
|
||||
_voiceSphereID(GeometryCache::UNKNOWN_ID)
|
||||
{
|
||||
|
@ -536,16 +546,10 @@ void Avatar::relayJointDataToChildren() {
|
|||
|
||||
void Avatar::simulate(float deltaTime, bool inView) {
|
||||
PROFILE_RANGE(simulation, "simulate");
|
||||
|
||||
if (_transit.isTransiting()) {
|
||||
glm::vec3 nextPosition;
|
||||
if (_transit.getNextPosition(nextPosition)) {
|
||||
_globalPosition = nextPosition;
|
||||
_globalPositionChanged = usecTimestampNow();
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(nextPosition);
|
||||
}
|
||||
}
|
||||
|
||||
_globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition;
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(_globalPosition);
|
||||
}
|
||||
|
||||
_simulationRate.increment();
|
||||
|
@ -558,7 +562,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
|||
PROFILE_RANGE(simulation, "updateJoints");
|
||||
if (inView) {
|
||||
Head* head = getHead();
|
||||
if (_hasNewJointData || _transit.isTransiting()) {
|
||||
if (_hasNewJointData || _transit.isActive()) {
|
||||
_skeletonModel->getRig().copyJointsFromJointData(_jointData);
|
||||
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
|
||||
_skeletonModel->getRig().computeExternalPoses(rootTransform);
|
||||
|
@ -1731,6 +1735,7 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
|
|||
|
||||
glm::vec3 Avatar::getWorldFeetPosition() {
|
||||
ShapeInfo shapeInfo;
|
||||
|
||||
computeShapeInfo(shapeInfo);
|
||||
glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight
|
||||
glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y - halfExtents.x, 0.0f);
|
||||
|
@ -1994,22 +1999,12 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const {
|
|||
}
|
||||
}
|
||||
|
||||
AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
|
||||
AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config) {
|
||||
std::lock_guard<std::mutex> lock(_transitLock);
|
||||
_transit.setScale(avatarScale);
|
||||
return _transit.update(deltaTime, avatarPosition, config);
|
||||
}
|
||||
|
||||
void Avatar::setTransitScale(float scale) {
|
||||
std::lock_guard<std::mutex> lock(_transitLock);
|
||||
return _transit.setScale(scale);
|
||||
}
|
||||
|
||||
void Avatar::overrideNextPackagePositionData(const glm::vec3& position) {
|
||||
std::lock_guard<std::mutex> lock(_transitLock);
|
||||
_overrideGlobalPosition = true;
|
||||
_globalPositionOverride = position;
|
||||
}
|
||||
|
||||
void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].push(material);
|
||||
|
|
|
@ -56,9 +56,13 @@ class AvatarTransit {
|
|||
public:
|
||||
enum Status {
|
||||
IDLE = 0,
|
||||
STARTED,
|
||||
PRE_TRANSIT,
|
||||
START_TRANSIT,
|
||||
TRANSITING,
|
||||
END_TRANSIT,
|
||||
POST_TRANSIT,
|
||||
ENDED,
|
||||
ABORT_TRANSIT
|
||||
};
|
||||
|
||||
|
@ -72,20 +76,20 @@ public:
|
|||
struct TransitConfig {
|
||||
TransitConfig() {};
|
||||
int _totalFrames { 0 };
|
||||
int _framesPerMeter { 0 };
|
||||
float _framesPerMeter { 0.0f };
|
||||
bool _isDistanceBased { false };
|
||||
float _triggerDistance { 0 };
|
||||
float _minTriggerDistance { 0.0f };
|
||||
float _maxTriggerDistance { 0.0f };
|
||||
float _abortDistance{ 0.0f };
|
||||
EaseType _easeType { EaseType::EASE_OUT };
|
||||
};
|
||||
|
||||
AvatarTransit() {};
|
||||
Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config);
|
||||
Status getStatus() { return _status; }
|
||||
bool isTransiting() { return _isTransiting; }
|
||||
bool isActive() { return _isActive; }
|
||||
glm::vec3 getCurrentPosition() { return _currentPosition; }
|
||||
bool getNextPosition(glm::vec3& nextPosition);
|
||||
glm::vec3 getEndPosition() { return _endPosition; }
|
||||
float getTransitTime() { return _totalTime; }
|
||||
void setScale(float scale) { _scale = scale; }
|
||||
void reset();
|
||||
|
||||
|
@ -93,7 +97,7 @@ private:
|
|||
Status updatePosition(float deltaTime);
|
||||
void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config);
|
||||
float getEaseValue(AvatarTransit::EaseType type, float value);
|
||||
bool _isTransiting { false };
|
||||
bool _isActive { false };
|
||||
|
||||
glm::vec3 _startPosition;
|
||||
glm::vec3 _endPosition;
|
||||
|
@ -103,7 +107,10 @@ private:
|
|||
|
||||
glm::vec3 _transitLine;
|
||||
float _totalDistance { 0.0f };
|
||||
float _preTransitTime { 0.0f };
|
||||
float _totalTime { 0.0f };
|
||||
float _transitTime { 0.0f };
|
||||
float _postTransitTime { 0.0f };
|
||||
float _currentTime { 0.0f };
|
||||
EaseType _easeType { EaseType::EASE_OUT };
|
||||
Status _status { Status::IDLE };
|
||||
|
@ -431,11 +438,7 @@ public:
|
|||
virtual scriptable::ScriptableModelBase getScriptableModel() override;
|
||||
|
||||
std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); };
|
||||
|
||||
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config);
|
||||
void setTransitScale(float scale);
|
||||
|
||||
void overrideNextPackagePositionData(const glm::vec3& position);
|
||||
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config);
|
||||
|
||||
signals:
|
||||
void targetScaleChanged(float targetScale);
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "AvatarLogging.h"
|
||||
#include "AvatarTraits.h"
|
||||
#include "ClientTraitsHandler.h"
|
||||
#include "ResourceRequestObserver.h"
|
||||
|
||||
//#define WANT_DEBUG
|
||||
|
||||
|
@ -938,20 +939,22 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS);
|
||||
}
|
||||
|
||||
auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset;
|
||||
if (_globalPosition != newValue) {
|
||||
_globalPosition = newValue;
|
||||
_serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset;
|
||||
auto oneStepDistance = glm::length(_globalPosition - _serverPosition);
|
||||
if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) {
|
||||
_globalPosition = _serverPosition;
|
||||
// if we don't have a parent, make sure to also set our local position
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(_serverPosition);
|
||||
}
|
||||
}
|
||||
if (_globalPosition != _serverPosition) {
|
||||
_globalPositionChanged = now;
|
||||
}
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_globalPositionRate.increment(numBytesRead);
|
||||
_globalPositionUpdateRate.increment();
|
||||
|
||||
// if we don't have a parent, make sure to also set our local position
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAvatarBoundingBox) {
|
||||
|
@ -2167,10 +2170,6 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) {
|
|||
}
|
||||
}
|
||||
|
||||
if (_overrideGlobalPosition) {
|
||||
_overrideGlobalPosition = false;
|
||||
}
|
||||
|
||||
doneEncoding(cullSmallData);
|
||||
|
||||
static AvatarDataSequenceNumber sequenceNumber = 0;
|
||||
|
@ -2214,11 +2213,21 @@ void AvatarData::updateJointMappings() {
|
|||
}
|
||||
|
||||
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) {
|
||||
////
|
||||
// TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead?
|
||||
// HTTPResourceRequest::doSend() covers all of the following and
|
||||
// then some. It doesn't cover the connect() call, so we may
|
||||
// want to add a HTTPResourceRequest::doSend() method that does
|
||||
// connects.
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
DependencyManager::get<ResourceRequestObserver>()->update(
|
||||
_skeletonModelURL, -1, "AvatarData::updateJointMappings");
|
||||
QNetworkReply* networkReply = networkAccessManager.get(networkRequest);
|
||||
//
|
||||
////
|
||||
connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -338,6 +338,17 @@ const float AVATAR_DISTANCE_LEVEL_5 = 200.0f; // meters
|
|||
// This is the start location in the Sandbox (xyz: 6270, 211, 6000).
|
||||
const glm::vec3 START_LOCATION(6270, 211, 6000);
|
||||
|
||||
// Avatar Transit Constants
|
||||
const float AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE = 1.0f;
|
||||
const float AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE = 30.0f;
|
||||
const int AVATAR_TRANSIT_FRAME_COUNT = 11;
|
||||
const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f;
|
||||
const float AVATAR_TRANSIT_ABORT_DISTANCE = 0.1f;
|
||||
const bool AVATAR_TRANSIT_DISTANCE_BASED = true;
|
||||
const float AVATAR_TRANSIT_FRAMES_PER_SECOND = 30.0f;
|
||||
const float AVATAR_PRE_TRANSIT_FRAME_COUNT = 10.0f;
|
||||
const float AVATAR_POST_TRANSIT_FRAME_COUNT = 27.0f;
|
||||
|
||||
enum KeyState {
|
||||
NO_KEY_DOWN = 0,
|
||||
INSERT_KEY_DOWN,
|
||||
|
@ -1393,8 +1404,7 @@ protected:
|
|||
// where Entities are located. This is currently only used by the mixer to decide how often to send
|
||||
// updates about one avatar to another.
|
||||
glm::vec3 _globalPosition { 0, 0, 0 };
|
||||
glm::vec3 _globalPositionOverride { 0, 0, 0 };
|
||||
bool _overrideGlobalPosition { false };
|
||||
glm::vec3 _serverPosition { 0, 0, 0 };
|
||||
|
||||
quint64 _globalPositionChanged { 0 };
|
||||
quint64 _avatarBoundingBoxChanged { 0 };
|
||||
|
|
|
@ -31,7 +31,27 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) :
|
|||
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride");
|
||||
}
|
||||
|
||||
void ClientTraitsHandler::markTraitUpdated(AvatarTraits::TraitType updatedTrait) {
|
||||
Lock lock(_traitLock);
|
||||
_traitStatuses[updatedTrait] = Updated;
|
||||
_hasChangedTraits = true;
|
||||
}
|
||||
|
||||
void ClientTraitsHandler::markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) {
|
||||
Lock lock(_traitLock);
|
||||
_traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated);
|
||||
_hasChangedTraits = true;
|
||||
}
|
||||
|
||||
void ClientTraitsHandler::markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID) {
|
||||
Lock lock(_traitLock);
|
||||
_traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted);
|
||||
_hasChangedTraits = true;
|
||||
}
|
||||
|
||||
void ClientTraitsHandler::resetForNewMixer() {
|
||||
Lock lock(_traitLock);
|
||||
|
||||
// re-set the current version to 0
|
||||
_currentTraitVersion = AvatarTraits::DEFAULT_TRAIT_VERSION;
|
||||
|
||||
|
@ -46,6 +66,8 @@ void ClientTraitsHandler::resetForNewMixer() {
|
|||
}
|
||||
|
||||
void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
||||
Lock lock(_traitLock);
|
||||
|
||||
if (hasChangedTraits() || _shouldPerformInitialSend) {
|
||||
// we have at least one changed trait to send
|
||||
|
||||
|
@ -113,6 +135,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
|
||||
void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
if (sendingNode->getType() == NodeType::AvatarMixer) {
|
||||
Lock lock(_traitLock);
|
||||
while (message->getBytesLeftToRead()) {
|
||||
AvatarTraits::TraitType traitType;
|
||||
message->readPrimitive(&traitType);
|
||||
|
|
|
@ -26,14 +26,11 @@ public:
|
|||
|
||||
void sendChangedTraitsToMixer();
|
||||
|
||||
bool hasChangedTraits() { return _hasChangedTraits; }
|
||||
bool hasChangedTraits() const { return _hasChangedTraits; }
|
||||
|
||||
void markTraitUpdated(AvatarTraits::TraitType updatedTrait)
|
||||
{ _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; }
|
||||
void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID)
|
||||
{ _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; }
|
||||
void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID)
|
||||
{ _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; }
|
||||
void markTraitUpdated(AvatarTraits::TraitType updatedTrait);
|
||||
void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID);
|
||||
void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID);
|
||||
|
||||
void resetForNewMixer();
|
||||
|
||||
|
@ -41,17 +38,21 @@ public slots:
|
|||
void processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
using Mutex = std::recursive_mutex;
|
||||
using Lock = std::lock_guard<Mutex>;
|
||||
|
||||
enum ClientTraitStatus {
|
||||
Unchanged,
|
||||
Updated,
|
||||
Deleted
|
||||
};
|
||||
|
||||
AvatarData* _owningAvatar;
|
||||
AvatarData* const _owningAvatar;
|
||||
|
||||
Mutex _traitLock;
|
||||
AvatarTraits::AssociatedTraitValues<ClientTraitStatus, Unchanged> _traitStatuses;
|
||||
AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION };
|
||||
|
||||
AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION };
|
||||
AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION };
|
||||
|
||||
bool _shouldPerformInitialSend { false };
|
||||
|
|
|
@ -200,7 +200,8 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) {
|
|||
_filterDataMap.insert(entityID, filterData);
|
||||
_lock.unlock();
|
||||
|
||||
auto scriptRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, scriptURL);
|
||||
auto scriptRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
this, scriptURL, true, -1, "EntityEditFilters::addFilter");
|
||||
if (!scriptRequest) {
|
||||
qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString();
|
||||
scriptRequestFinished(entityID);
|
||||
|
|
|
@ -953,7 +953,8 @@ bool GLTFReader::doesResourceExist(const QString& url) {
|
|||
}
|
||||
|
||||
std::tuple<bool, QByteArray> GLTFReader::requestData(QUrl& url) {
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url);
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, url, true, -1, "GLTFReader::requestData");
|
||||
|
||||
if (!request) {
|
||||
return std::make_tuple(false, QByteArray());
|
||||
|
|
|
@ -443,7 +443,8 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file
|
|||
}
|
||||
|
||||
std::tuple<bool, QByteArray> requestData(QUrl& url) {
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url);
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, url, true, -1, "(OBJReader) requestData");
|
||||
|
||||
if (!request) {
|
||||
return std::make_tuple(false, QByteArray());
|
||||
|
|
|
@ -456,7 +456,8 @@ void NetworkTexture::makeRequest() {
|
|||
// Add a fragment to the base url so we can identify the section of the ktx being requested when debugging
|
||||
// The actual requested url is _activeUrl and will not contain the fragment
|
||||
_url.setFragment("head");
|
||||
_ktxHeaderRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl);
|
||||
_ktxHeaderRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
this, _activeUrl, true, -1, "NetworkTexture::makeRequest");
|
||||
|
||||
if (!_ktxHeaderRequest) {
|
||||
qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
||||
|
@ -617,7 +618,8 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
|
|||
|
||||
bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL;
|
||||
|
||||
_ktxMipRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl);
|
||||
_ktxMipRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
this, _activeUrl, true, -1, "NetworkTexture::startMipRangeRequest");
|
||||
|
||||
if (!_ktxMipRequest) {
|
||||
qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
||||
|
|
|
@ -24,8 +24,12 @@
|
|||
|
||||
static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5;
|
||||
|
||||
AssetResourceRequest::AssetResourceRequest(const QUrl& url) :
|
||||
ResourceRequest(url)
|
||||
AssetResourceRequest::AssetResourceRequest(
|
||||
const QUrl& url,
|
||||
const bool isObservable,
|
||||
const qint64 callerId,
|
||||
const QString& extra) :
|
||||
ResourceRequest(url, isObservable, callerId, extra)
|
||||
{
|
||||
_lastProgressDebug = p_high_resolution_clock::now() - std::chrono::seconds(DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,11 @@
|
|||
class AssetResourceRequest : public ResourceRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetResourceRequest(const QUrl& url);
|
||||
AssetResourceRequest(
|
||||
const QUrl& url,
|
||||
const bool isObservable = true,
|
||||
const qint64 callerId = -1,
|
||||
const QString& extra = "");
|
||||
virtual ~AssetResourceRequest() override;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
#include "ResourceManager.h"
|
||||
|
||||
AtpReply::AtpReply(const QUrl& url, QObject* parent) :
|
||||
_resourceRequest(DependencyManager::get<ResourceManager>()->createResourceRequest(parent, url)) {
|
||||
_resourceRequest(DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
parent, url, true, -1, "AtpReply::AtpReply")) {
|
||||
setOperation(QNetworkAccessManager::GetOperation);
|
||||
|
||||
connect(_resourceRequest, &AssetResourceRequest::progress, this, &AtpReply::downloadProgress);
|
||||
|
|
|
@ -19,7 +19,12 @@
|
|||
class FileResourceRequest : public ResourceRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
FileResourceRequest(const QUrl& url) : ResourceRequest(url) { }
|
||||
FileResourceRequest(
|
||||
const QUrl& url,
|
||||
const bool isObservable = true,
|
||||
const qint64 callerId = -1,
|
||||
const QString& extra = ""
|
||||
) : ResourceRequest(url, isObservable, callerId, extra) { }
|
||||
|
||||
protected:
|
||||
virtual void doSend() override;
|
||||
|
|
|
@ -21,7 +21,12 @@
|
|||
class HTTPResourceRequest : public ResourceRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
HTTPResourceRequest(const QUrl& url) : ResourceRequest(url) { }
|
||||
HTTPResourceRequest(
|
||||
const QUrl& url,
|
||||
const bool isObservable = true,
|
||||
const qint64 callerId = -1,
|
||||
const QString& = ""
|
||||
) : ResourceRequest(url, isObservable, callerId) { }
|
||||
~HTTPResourceRequest();
|
||||
|
||||
protected:
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include "ResourceCache.h"
|
||||
#include "ResourceRequestObserver.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
|
@ -252,7 +253,9 @@ ResourceCache::ResourceCache(QObject* parent) : QObject(parent) {
|
|||
}
|
||||
}
|
||||
|
||||
ResourceCache::~ResourceCache() {}
|
||||
ResourceCache::~ResourceCache() {
|
||||
clearUnusedResources();
|
||||
}
|
||||
|
||||
void ResourceCache::clearATPAssets() {
|
||||
{
|
||||
|
@ -338,28 +341,31 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
|
|||
}
|
||||
if (resource) {
|
||||
removeUnusedResource(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) {
|
||||
return getResource(fallback, QUrl());
|
||||
if (!resource && !url.isValid() && !url.isEmpty() && fallback.isValid()) {
|
||||
resource = getResource(fallback, QUrl());
|
||||
}
|
||||
|
||||
resource = createResource(
|
||||
url,
|
||||
fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer<Resource>(),
|
||||
extra);
|
||||
resource->setSelf(resource);
|
||||
resource->setCache(this);
|
||||
resource->moveToThread(qApp->thread());
|
||||
connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);
|
||||
{
|
||||
QWriteLocker locker(&_resourcesLock);
|
||||
_resources.insert(url, resource);
|
||||
if (!resource) {
|
||||
resource = createResource(
|
||||
url,
|
||||
fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer<Resource>(),
|
||||
extra);
|
||||
resource->setSelf(resource);
|
||||
resource->setCache(this);
|
||||
resource->moveToThread(qApp->thread());
|
||||
connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);
|
||||
{
|
||||
QWriteLocker locker(&_resourcesLock);
|
||||
_resources.insert(url, resource);
|
||||
}
|
||||
removeUnusedResource(resource);
|
||||
resource->ensureLoading();
|
||||
}
|
||||
removeUnusedResource(resource);
|
||||
resource->ensureLoading();
|
||||
|
||||
DependencyManager::get<ResourceRequestObserver>()->update(
|
||||
resource->getURL(), -1, "ResourceCache::getResource");
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
@ -682,7 +688,8 @@ void Resource::makeRequest() {
|
|||
|
||||
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
|
||||
|
||||
_request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl);
|
||||
_request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
this, _activeUrl, true, -1, "Resource::makeRequest");
|
||||
|
||||
if (!_request) {
|
||||
qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
||||
|
|
|
@ -112,22 +112,28 @@ void ResourceManager::cleanup() {
|
|||
_thread.wait();
|
||||
}
|
||||
|
||||
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
|
||||
ResourceRequest* ResourceManager::createResourceRequest(
|
||||
QObject* parent,
|
||||
const QUrl& url,
|
||||
const bool isObservable,
|
||||
const qint64 callerId,
|
||||
const QString& extra
|
||||
) {
|
||||
auto normalizedURL = normalizeURL(url);
|
||||
auto scheme = normalizedURL.scheme();
|
||||
|
||||
ResourceRequest* request = nullptr;
|
||||
|
||||
if (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) {
|
||||
request = new FileResourceRequest(normalizedURL);
|
||||
request = new FileResourceRequest(normalizedURL, isObservable, callerId, extra);
|
||||
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
|
||||
request = new HTTPResourceRequest(normalizedURL);
|
||||
request = new HTTPResourceRequest(normalizedURL, isObservable, callerId, extra);
|
||||
} else if (scheme == URL_SCHEME_ATP) {
|
||||
if (!_atpSupportEnabled) {
|
||||
qCDebug(networking) << "ATP support not enabled, unable to create request for URL: " << url.url();
|
||||
return nullptr;
|
||||
}
|
||||
request = new AssetResourceRequest(normalizedURL);
|
||||
request = new AssetResourceRequest(normalizedURL, isObservable, callerId, extra);
|
||||
} else {
|
||||
qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url();
|
||||
return nullptr;
|
||||
|
@ -163,7 +169,7 @@ bool ResourceManager::resourceExists(const QUrl& url) {
|
|||
|
||||
return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200;
|
||||
} else if (scheme == URL_SCHEME_ATP && _atpSupportEnabled) {
|
||||
auto request = new AssetResourceRequest(url);
|
||||
auto request = new AssetResourceRequest(url, ResourceRequest::IS_NOT_OBSERVABLE);
|
||||
ByteRange range;
|
||||
range.fromInclusive = 1;
|
||||
range.toExclusive = 1;
|
||||
|
|
|
@ -34,7 +34,12 @@ public:
|
|||
QString normalizeURL(const QString& urlString);
|
||||
QUrl normalizeURL(const QUrl& url);
|
||||
|
||||
ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url);
|
||||
ResourceRequest* createResourceRequest(
|
||||
QObject* parent,
|
||||
const QUrl& url,
|
||||
const bool isObservable = true,
|
||||
const qint64 callerId = -1,
|
||||
const QString& extra = "");
|
||||
|
||||
void init();
|
||||
void cleanup();
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
//
|
||||
|
||||
#include "ResourceRequest.h"
|
||||
#include "ResourceRequestObserver.h"
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <StatTracker.h>
|
||||
|
||||
#include <QtCore/QThread>
|
||||
|
||||
ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { }
|
||||
|
||||
void ResourceRequest::send() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
|
@ -24,6 +24,10 @@ void ResourceRequest::send() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (_isObservable) {
|
||||
DependencyManager::get<ResourceRequestObserver>()->update(_url, _callerId, _extra + " => ResourceRequest::send");
|
||||
}
|
||||
|
||||
Q_ASSERT(_state == NotStarted);
|
||||
|
||||
_state = InProgress;
|
||||
|
|
|
@ -40,7 +40,20 @@ const QString STAT_FILE_RESOURCE_TOTAL_BYTES = "FILEBytesDownloaded";
|
|||
class ResourceRequest : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResourceRequest(const QUrl& url);
|
||||
static const bool IS_OBSERVABLE = true;
|
||||
static const bool IS_NOT_OBSERVABLE = false;
|
||||
|
||||
ResourceRequest(
|
||||
const QUrl& url,
|
||||
const bool isObservable = IS_OBSERVABLE,
|
||||
const qint64 callerId = -1,
|
||||
const QString& extra = ""
|
||||
) : _url(url),
|
||||
_isObservable(isObservable),
|
||||
_callerId(callerId),
|
||||
_extra(extra)
|
||||
{ }
|
||||
|
||||
virtual ~ResourceRequest() = default;
|
||||
|
||||
enum State {
|
||||
|
@ -99,6 +112,9 @@ protected:
|
|||
bool _rangeRequestSuccessful { false };
|
||||
uint64_t _totalSizeOfResource { 0 };
|
||||
int64_t _lastRecordedBytesDownloaded { 0 };
|
||||
bool _isObservable;
|
||||
qint64 _callerId;
|
||||
QString _extra;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -734,11 +734,17 @@ QString getMarketplaceID(const QString& urlString) {
|
|||
return QString();
|
||||
}
|
||||
|
||||
bool Octree::readFromURL(const QString& urlString) {
|
||||
bool Octree::readFromURL(
|
||||
const QString& urlString,
|
||||
const bool isObservable,
|
||||
const qint64 callerId
|
||||
) {
|
||||
QString trimmedUrl = urlString.trimmed();
|
||||
QString marketplaceID = getMarketplaceID(trimmedUrl);
|
||||
auto request =
|
||||
std::unique_ptr<ResourceRequest>(DependencyManager::get<ResourceManager>()->createResourceRequest(this, trimmedUrl));
|
||||
qDebug() << "!!!!! going to createResourceRequest " << callerId;
|
||||
auto request = std::unique_ptr<ResourceRequest>(
|
||||
DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
this, trimmedUrl, isObservable, callerId, "Octree::readFromURL"));
|
||||
|
||||
if (!request) {
|
||||
return false;
|
||||
|
@ -768,7 +774,11 @@ bool Octree::readFromURL(const QString& urlString) {
|
|||
}
|
||||
|
||||
|
||||
bool Octree::readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID) {
|
||||
bool Octree::readFromStream(
|
||||
uint64_t streamLength,
|
||||
QDataStream& inputStream,
|
||||
const QString& marketplaceID
|
||||
) {
|
||||
// decide if this is binary SVO or JSON-formatted SVO
|
||||
QIODevice *device = inputStream.device();
|
||||
char firstChar;
|
||||
|
@ -809,7 +819,11 @@ QJsonDocument addMarketplaceIDToDocumentEntities(QJsonDocument& doc, const QStri
|
|||
|
||||
const int READ_JSON_BUFFER_SIZE = 2048;
|
||||
|
||||
bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID /*=""*/) {
|
||||
bool Octree::readJSONFromStream(
|
||||
uint64_t streamLength,
|
||||
QDataStream& inputStream,
|
||||
const QString& marketplaceID /*=""*/
|
||||
) {
|
||||
// if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until
|
||||
// we get an eof. Leave streamLength parameter for consistency.
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ public:
|
|||
|
||||
// Octree importers
|
||||
bool readFromFile(const char* filename);
|
||||
bool readFromURL(const QString& url); // will support file urls as well...
|
||||
bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1); // will support file urls as well...
|
||||
bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="");
|
||||
bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream);
|
||||
bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="");
|
||||
|
|
|
@ -138,7 +138,8 @@ QString FileScriptingInterface::convertUrlToPath(QUrl url) {
|
|||
// this function is not in use
|
||||
void FileScriptingInterface::downloadZip(QString path, const QString link) {
|
||||
QUrl url = QUrl(link);
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url);
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, url, true, -1, "FileScriptingInterface::downloadZip");
|
||||
connect(request, &ResourceRequest::finished, this, [this, path]{
|
||||
unzipFile(path, ""); // so intellisense isn't mad
|
||||
});
|
||||
|
|
|
@ -109,7 +109,8 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
|
|||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
|
||||
#endif
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url);
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, url, true, -1, "ScriptCache::getScriptContents");
|
||||
Q_ASSERT(request);
|
||||
request->setCacheEnabled(!forceDownload);
|
||||
connect(request, &ResourceRequest::finished, this, [=]{ scriptContentAvailable(maxRetries); });
|
||||
|
@ -166,7 +167,8 @@ void ScriptCache::scriptContentAvailable(int maxRetries) {
|
|||
qCDebug(scriptengine) << QString("Retrying script request [%1 / %2]: %3")
|
||||
.arg(attempt).arg(maxRetries).arg(url.toString());
|
||||
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url);
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, url, true, -1, "ScriptCache::scriptContentAvailable");
|
||||
Q_ASSERT(request);
|
||||
|
||||
// We've already made a request, so the cache must be disabled or it wasn't there, so enabling
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <NetworkAccessManager.h>
|
||||
#include <NetworkingConstants.h>
|
||||
|
||||
#include "ResourceRequestObserver.h"
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/";
|
||||
|
@ -189,7 +190,7 @@ void XMLHttpRequestClass::send(const QScriptValue& data) {
|
|||
}
|
||||
|
||||
void XMLHttpRequestClass::doSend() {
|
||||
|
||||
DependencyManager::get<ResourceRequestObserver>()->update(_url, -1, "XMLHttpRequestClass::doSend");
|
||||
_reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData);
|
||||
connectToReply(_reply);
|
||||
|
||||
|
|
28
libraries/shared/src/ResourceRequestObserver.cpp
Normal file
28
libraries/shared/src/ResourceRequestObserver.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// ResourceAccessMonitor.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Kerry Ivan Kurian on 9/27/18.
|
||||
// Copyright 2018 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 <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include "ResourceRequestObserver.h"
|
||||
|
||||
void ResourceRequestObserver::update(const QUrl& requestUrl,
|
||||
const qint64 callerId,
|
||||
const QString& extra) {
|
||||
QJsonArray array;
|
||||
QJsonObject data { { "url", requestUrl.toString() },
|
||||
{ "callerId", callerId },
|
||||
{ "extra", extra }
|
||||
};
|
||||
emit resourceRequestEvent(data.toVariantMap());
|
||||
}
|
29
libraries/shared/src/ResourceRequestObserver.h
Normal file
29
libraries/shared/src/ResourceRequestObserver.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// ResourceRequestObserver.h
|
||||
// libraries/commerce/src
|
||||
//
|
||||
// Created by Kerry Ivan Kurian on 9/27/18.
|
||||
// Copyright 2018 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 <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "DependencyManager.h"
|
||||
|
||||
|
||||
class ResourceRequestObserver : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
void update(const QUrl& requestUrl, const qint64 callerId = -1, const QString& extra = "");
|
||||
|
||||
signals:
|
||||
void resourceRequestEvent(QVariantMap result);
|
||||
};
|
|
@ -85,6 +85,11 @@ private:
|
|||
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
|
||||
qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError();
|
||||
return;
|
||||
} else {
|
||||
ovrResult setFloorLevelOrigin = ovr_SetTrackingOriginType(session, ovrTrackingOrigin::ovrTrackingOrigin_FloorLevel);
|
||||
if (!OVR_SUCCESS(setFloorLevelOrigin)) {
|
||||
qCWarning(oculusLog) << "Failed to set the Oculus tracking origin to floor level" << ovr::getError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -463,7 +463,7 @@ bool OpenVrDisplayPlugin::internalActivate() {
|
|||
auto chaperone = vr::VRChaperone();
|
||||
if (chaperone) {
|
||||
float const UI_RADIUS = 1.0f;
|
||||
float const UI_HEIGHT = 1.6f;
|
||||
float const UI_HEIGHT = 0.0f;
|
||||
float const UI_Z_OFFSET = 0.5;
|
||||
|
||||
float xSize, zSize;
|
||||
|
|
|
@ -42,6 +42,9 @@ var TITLE_OFFSET = 60;
|
|||
var CREATE_TOOLS_WIDTH = 490;
|
||||
var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942;
|
||||
|
||||
var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
|
||||
var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg";
|
||||
|
||||
var createToolsWindow = new CreateWindow(
|
||||
Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml",
|
||||
'Create Tools',
|
||||
|
@ -294,6 +297,202 @@ function checkEditPermissionsAndUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
const DEFAULT_ENTITY_PROPERTIES = {
|
||||
All: {
|
||||
description: "",
|
||||
rotation: { x: 0, y: 0, z: 0, w: 1 },
|
||||
collidesWith: "static,dynamic,kinematic,otherAvatar",
|
||||
collisionSoundURL: "",
|
||||
cloneable: false,
|
||||
ignoreIK: true,
|
||||
canCastShadow: true,
|
||||
href: "",
|
||||
script: "",
|
||||
serverScripts:"",
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
damping: 0,
|
||||
angularVelocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
angularDamping: 0,
|
||||
restitution: 0.5,
|
||||
friction: 0.5,
|
||||
density: 1000,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
acceleration: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
dynamic: false,
|
||||
},
|
||||
Shape: {
|
||||
shape: "Box",
|
||||
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
|
||||
color: { red: 0, green: 180, blue: 239 },
|
||||
},
|
||||
Text: {
|
||||
text: "Text",
|
||||
dimensions: {
|
||||
x: 0.65,
|
||||
y: 0.3,
|
||||
z: 0.01
|
||||
},
|
||||
textColor: { red: 255, green: 255, blue: 255 },
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
lineHeight: 0.06,
|
||||
faceCamera: false,
|
||||
},
|
||||
Zone: {
|
||||
dimensions: {
|
||||
x: 10,
|
||||
y: 10,
|
||||
z: 10
|
||||
},
|
||||
flyingAllowed: true,
|
||||
ghostingAllowed: true,
|
||||
filter: "",
|
||||
keyLightMode: "inherit",
|
||||
keyLightColor: { red: 255, green: 255, blue: 255 },
|
||||
keyLight: {
|
||||
intensity: 1.0,
|
||||
direction: {
|
||||
x: 0.0,
|
||||
y: -0.707106769084930, // 45 degrees
|
||||
z: 0.7071067690849304
|
||||
},
|
||||
castShadows: true
|
||||
},
|
||||
ambientLightMode: "inherit",
|
||||
ambientLight: {
|
||||
ambientIntensity: 0.5,
|
||||
ambientURL: ""
|
||||
},
|
||||
hazeMode: "inherit",
|
||||
haze: {
|
||||
hazeRange: 1000,
|
||||
hazeAltitudeEffect: false,
|
||||
hazeBaseRef: 0,
|
||||
hazeColor: {
|
||||
red: 128,
|
||||
green: 154,
|
||||
blue: 179
|
||||
},
|
||||
hazeBackgroundBlend: 0,
|
||||
hazeEnableGlare: false,
|
||||
hazeGlareColor: {
|
||||
red: 255,
|
||||
green: 229,
|
||||
blue: 179
|
||||
},
|
||||
},
|
||||
bloomMode: "inherit"
|
||||
},
|
||||
Model: {
|
||||
collisionShape: "none",
|
||||
compoundShapeURL: "",
|
||||
animation: {
|
||||
url: "",
|
||||
running: false,
|
||||
allowTranslation: false,
|
||||
loop: true,
|
||||
hold: false,
|
||||
currentFrame: 0,
|
||||
firstFrame: 0,
|
||||
lastFrame: 100000,
|
||||
fps: 30.0,
|
||||
}
|
||||
},
|
||||
Image: {
|
||||
dimensions: {
|
||||
x: 0.5385,
|
||||
y: 0.2819,
|
||||
z: 0.0092
|
||||
},
|
||||
shapeType: "box",
|
||||
collisionless: true,
|
||||
modelURL: IMAGE_MODEL,
|
||||
textures: JSON.stringify({ "tex.picture": "" })
|
||||
},
|
||||
Web: {
|
||||
dimensions: {
|
||||
x: 1.6,
|
||||
y: 0.9,
|
||||
z: 0.01
|
||||
},
|
||||
sourceUrl: "https://highfidelity.com/",
|
||||
dpi: 30,
|
||||
},
|
||||
ParticleEffect: {
|
||||
lifespan: 1.5,
|
||||
maxParticles: 10,
|
||||
textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png",
|
||||
emitRate: 5.5,
|
||||
emitSpeed: 0,
|
||||
speedSpread: 0,
|
||||
emitDimensions: { x: 0, y: 0, z: 0 },
|
||||
emitOrientation: { x: 0, y: 0, z: 0, w: 1 },
|
||||
emitterShouldTrail: true,
|
||||
particleRadius: 0.25,
|
||||
radiusStart: 0,
|
||||
radiusFinish: 0.1,
|
||||
radiusSpread: 0,
|
||||
particleColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
colorSpread: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
alpha: 0,
|
||||
alphaStart: 1,
|
||||
alphaFinish: 0,
|
||||
alphaSpread: 0,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 2.5,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
particleSpin: 0,
|
||||
spinStart: 0,
|
||||
spinFinish: 0,
|
||||
spinSpread: 0,
|
||||
rotateWithEntity: false,
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
azimuthStart: -Math.PI,
|
||||
azimuthFinish: Math.PI
|
||||
},
|
||||
Light: {
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
intensity: 5.0,
|
||||
dimensions: DEFAULT_LIGHT_DIMENSIONS,
|
||||
falloffRadius: 1.0,
|
||||
isSpotlight: false,
|
||||
exponent: 1.0,
|
||||
cutoff: 75.0,
|
||||
dimensions: { x: 20, y: 20, z: 20 },
|
||||
},
|
||||
};
|
||||
|
||||
var toolBar = (function () {
|
||||
var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts
|
||||
var that = {},
|
||||
|
@ -303,11 +502,29 @@ var toolBar = (function () {
|
|||
dialogWindow = null,
|
||||
tablet = null;
|
||||
|
||||
function applyProperties(originalProperties, newProperties) {
|
||||
for (var key in newProperties) {
|
||||
originalProperties[key] = newProperties[key];
|
||||
}
|
||||
}
|
||||
function createNewEntity(properties) {
|
||||
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
|
||||
var position = getPositionToCreateEntity();
|
||||
var entityID = null;
|
||||
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All);
|
||||
|
||||
var type = properties.type;
|
||||
if (type == "Box" || type == "Sphere") {
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape);
|
||||
} else if (type == "Image") {
|
||||
properties.type = "Model";
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Image);
|
||||
} else {
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]);
|
||||
}
|
||||
|
||||
|
||||
if (position !== null && position !== undefined) {
|
||||
var direction;
|
||||
if (Camera.mode === "entity" || Camera.mode === "independent") {
|
||||
|
@ -385,7 +602,7 @@ var toolBar = (function () {
|
|||
Entities.editEntity(entityID, {
|
||||
position: position
|
||||
});
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
} else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) {
|
||||
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
|
||||
}
|
||||
|
@ -397,9 +614,9 @@ var toolBar = (function () {
|
|||
properties.type + " would be out of bounds.");
|
||||
}
|
||||
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
entityListTool.sendUpdate();
|
||||
selectionManager.setSelections([entityID]);
|
||||
selectionManager.setSelections([entityID], this);
|
||||
|
||||
Window.setFocus();
|
||||
|
||||
|
@ -550,7 +767,7 @@ var toolBar = (function () {
|
|||
}
|
||||
deletedEntityTimer = Script.setTimeout(function () {
|
||||
if (entitiesToDelete.length > 0) {
|
||||
selectionManager.removeEntities(entitiesToDelete);
|
||||
selectionManager.removeEntities(entitiesToDelete, this);
|
||||
}
|
||||
entityListTool.removeEntities(entitiesToDelete, selectionManager.selections);
|
||||
entitiesToDelete = [];
|
||||
|
@ -679,14 +896,12 @@ var toolBar = (function () {
|
|||
addButton("newLightButton", function () {
|
||||
createNewEntity({
|
||||
type: "Light",
|
||||
dimensions: DEFAULT_LIGHT_DIMENSIONS,
|
||||
isSpotlight: false,
|
||||
color: {
|
||||
red: 150,
|
||||
green: 150,
|
||||
blue: 150
|
||||
},
|
||||
|
||||
constantAttenuation: 1,
|
||||
linearAttenuation: 0,
|
||||
quadraticAttenuation: 0,
|
||||
|
@ -698,116 +913,30 @@ var toolBar = (function () {
|
|||
addButton("newTextButton", function () {
|
||||
createNewEntity({
|
||||
type: "Text",
|
||||
dimensions: {
|
||||
x: 0.65,
|
||||
y: 0.3,
|
||||
z: 0.01
|
||||
},
|
||||
backgroundColor: {
|
||||
red: 64,
|
||||
green: 64,
|
||||
blue: 64
|
||||
},
|
||||
textColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
text: "some text",
|
||||
lineHeight: 0.06
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newImageButton", function () {
|
||||
var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
|
||||
var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg";
|
||||
createNewEntity({
|
||||
type: "Model",
|
||||
dimensions: {
|
||||
x: 0.5385,
|
||||
y: 0.2819,
|
||||
z: 0.0092
|
||||
},
|
||||
shapeType: "box",
|
||||
collisionless: true,
|
||||
modelURL: IMAGE_MODEL,
|
||||
textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE })
|
||||
type: "Image",
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newWebButton", function () {
|
||||
createNewEntity({
|
||||
type: "Web",
|
||||
dimensions: {
|
||||
x: 1.6,
|
||||
y: 0.9,
|
||||
z: 0.01
|
||||
},
|
||||
sourceUrl: "https://highfidelity.com/"
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newZoneButton", function () {
|
||||
createNewEntity({
|
||||
type: "Zone",
|
||||
dimensions: {
|
||||
x: 10,
|
||||
y: 10,
|
||||
z: 10
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newParticleButton", function () {
|
||||
createNewEntity({
|
||||
type: "ParticleEffect",
|
||||
isEmitting: true,
|
||||
emitterShouldTrail: true,
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
},
|
||||
colorSpread: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
colorStart: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
},
|
||||
colorFinish: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
emitAcceleration: {
|
||||
x: -0.5,
|
||||
y: 2.5,
|
||||
z: -0.5
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0.5,
|
||||
y: 1,
|
||||
z: 0.5
|
||||
},
|
||||
emitRate: 5.5,
|
||||
emitSpeed: 0,
|
||||
speedSpread: 0,
|
||||
lifespan: 1.5,
|
||||
maxParticles: 10,
|
||||
particleRadius: 0.25,
|
||||
radiusStart: 0,
|
||||
radiusFinish: 0.1,
|
||||
radiusSpread: 0,
|
||||
alpha: 0,
|
||||
alphaStart: 1,
|
||||
alphaFinish: 0,
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png"
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -866,7 +995,7 @@ var toolBar = (function () {
|
|||
gridTool.setVisible(false);
|
||||
grid.setEnabled(false);
|
||||
propertiesTool.setVisible(false);
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
cameraManager.disable();
|
||||
selectionDisplay.disableTriggerMapping();
|
||||
tablet.landscape = false;
|
||||
|
@ -994,7 +1123,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) {
|
|||
var entity = entityIconOverlayManager.findEntity(data.overlayID);
|
||||
|
||||
if (entity !== null) {
|
||||
selectionManager.setSelections([entity]);
|
||||
selectionManager.setSelections([entity], this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1141,7 +1270,7 @@ function mouseClickEvent(event) {
|
|||
|
||||
if (result === null || result === undefined) {
|
||||
if (!event.isShifted) {
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1193,9 +1322,9 @@ function mouseClickEvent(event) {
|
|||
}
|
||||
|
||||
if (!event.isShifted) {
|
||||
selectionManager.setSelections([foundEntity]);
|
||||
selectionManager.setSelections([foundEntity], this);
|
||||
} else {
|
||||
selectionManager.addEntity(foundEntity, true);
|
||||
selectionManager.addEntity(foundEntity, true, this);
|
||||
}
|
||||
|
||||
if (wantDebug) {
|
||||
|
@ -1493,7 +1622,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) {
|
|||
}
|
||||
}
|
||||
}
|
||||
selectionManager.setSelections(entities);
|
||||
selectionManager.setSelections(entities, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1633,7 +1762,7 @@ function deleteSelectedEntities() {
|
|||
}
|
||||
|
||||
if (savedProperties.length > 0) {
|
||||
SelectionManager.clearSelections();
|
||||
SelectionManager.clearSelections(this);
|
||||
pushCommandForSelections([], savedProperties);
|
||||
entityListTool.deleteEntities(deletedIDs);
|
||||
}
|
||||
|
@ -1650,7 +1779,7 @@ function toggleSelectedEntitiesLocked() {
|
|||
});
|
||||
}
|
||||
entityListTool.sendUpdate();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1664,7 +1793,7 @@ function toggleSelectedEntitiesVisible() {
|
|||
});
|
||||
}
|
||||
entityListTool.sendUpdate();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1861,7 +1990,7 @@ function importSVO(importURL) {
|
|||
}
|
||||
|
||||
if (isActive) {
|
||||
selectionManager.setSelections(pastedEntityIDs);
|
||||
selectionManager.setSelections(pastedEntityIDs, this);
|
||||
}
|
||||
} else {
|
||||
Window.notifyEditError("Can't import entities: entities would be out of bounds.");
|
||||
|
@ -1909,7 +2038,7 @@ function deleteKey(value) {
|
|||
}
|
||||
function deselectKey(value) {
|
||||
if (value === 0) { // on release
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
}
|
||||
}
|
||||
function toggleKey(value) {
|
||||
|
@ -2069,7 +2198,7 @@ function applyEntityProperties(data) {
|
|||
// We might be getting an undo while edit.js is disabled. If that is the case, don't set
|
||||
// our selections, causing the edit widgets to display.
|
||||
if (isActive) {
|
||||
selectionManager.setSelections(selectedEntityIDs);
|
||||
selectionManager.setSelections(selectedEntityIDs, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2272,7 +2401,7 @@ var PropertiesTool = function (opts) {
|
|||
}
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
} else if (data.type === 'parent') {
|
||||
parentSelectedEntities();
|
||||
} else if (data.type === 'unparent') {
|
||||
|
@ -2301,7 +2430,7 @@ var PropertiesTool = function (opts) {
|
|||
});
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "moveAllToGrid") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -2321,7 +2450,7 @@ var PropertiesTool = function (opts) {
|
|||
});
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "resetToNaturalDimensions") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -2342,7 +2471,7 @@ var PropertiesTool = function (opts) {
|
|||
}
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "previewCamera") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -2360,7 +2489,7 @@ var PropertiesTool = function (opts) {
|
|||
});
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "reloadClientScripts") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
|
|
@ -1801,3 +1801,43 @@ input[type=button]#export {
|
|||
body#entity-list-body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
display: none;
|
||||
position: fixed;
|
||||
color: #000000;
|
||||
background-color: #afafaf;
|
||||
padding: 5px 0 5px 0;
|
||||
cursor: default;
|
||||
}
|
||||
.context-menu li {
|
||||
list-style-type: none;
|
||||
padding: 4px 18px 4px 18px;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.context-menu li:hover {
|
||||
background-color: #e3e3e3;
|
||||
}
|
||||
.context-menu li.separator {
|
||||
border-top: 1px solid #333333;
|
||||
margin: 5px 5px;
|
||||
padding: 0 0;
|
||||
}
|
||||
.context-menu li.disabled {
|
||||
color: #333333;
|
||||
}
|
||||
.context-menu li.separator:hover, .context-menu li.disabled:hover {
|
||||
background-color: #afafaf;
|
||||
}
|
||||
|
||||
input.rename-entity {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: none;
|
||||
font-family: FiraSans-SemiBold;
|
||||
font-size: 15px;
|
||||
/* need this to show the text cursor when the input field is empty */
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/listView.js"></script>
|
||||
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
|
||||
<script type="text/javascript" src="js/entityList.js"></script>
|
||||
</head>
|
||||
<body onload='loaded();' id="entity-list-body">
|
||||
|
|
|
@ -13,7 +13,7 @@ const DESCENDING_STRING = '▾';
|
|||
const LOCKED_GLYPH = "";
|
||||
const VISIBLE_GLYPH = "";
|
||||
const TRANSPARENCY_GLYPH = "";
|
||||
const BAKED_GLYPH = ""
|
||||
const BAKED_GLYPH = "";
|
||||
const SCRIPT_GLYPH = "k";
|
||||
const BYTES_PER_MEGABYTE = 1024 * 1024;
|
||||
const IMAGE_MODEL_NAME = 'default-image-model.fbx';
|
||||
|
@ -54,10 +54,10 @@ const COMPARE_ASCENDING = function(a, b) {
|
|||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
const COMPARE_DESCENDING = function(a, b) {
|
||||
return COMPARE_ASCENDING(b, a);
|
||||
}
|
||||
};
|
||||
|
||||
// List of all entities
|
||||
var entities = [];
|
||||
|
@ -70,6 +70,11 @@ var selectedEntities = [];
|
|||
|
||||
var entityList = null; // The ListView
|
||||
|
||||
/**
|
||||
* @type EntityListContextMenu
|
||||
*/
|
||||
var entityListContextMenu = null;
|
||||
|
||||
var currentSortColumn = 'type';
|
||||
var currentSortOrder = ASCENDING_SORT;
|
||||
var isFilterInView = false;
|
||||
|
@ -156,22 +161,22 @@ function loaded() {
|
|||
};
|
||||
elRefresh.onclick = function() {
|
||||
refreshEntities();
|
||||
}
|
||||
};
|
||||
elToggleLocked.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' }));
|
||||
}
|
||||
};
|
||||
elToggleVisible.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' }));
|
||||
}
|
||||
};
|
||||
elExport.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'export'}));
|
||||
}
|
||||
};
|
||||
elPal.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' }));
|
||||
}
|
||||
};
|
||||
elDelete.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
}
|
||||
};
|
||||
elFilter.onkeyup = refreshEntityList;
|
||||
elFilter.onpaste = refreshEntityList;
|
||||
elFilter.onchange = onFilterChange;
|
||||
|
@ -184,17 +189,105 @@ function loaded() {
|
|||
|
||||
entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow,
|
||||
createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT);
|
||||
|
||||
|
||||
entityListContextMenu = new EntityListContextMenu();
|
||||
|
||||
|
||||
function startRenamingEntity(entityID) {
|
||||
let entity = entitiesByID[entityID];
|
||||
if (!entity || entity.locked || !entity.elRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
let elCell = entity.elRow.childNodes[COLUMN_INDEX.NAME];
|
||||
let elRenameInput = document.createElement("input");
|
||||
elRenameInput.setAttribute('class', 'rename-entity');
|
||||
elRenameInput.value = entity.name;
|
||||
let ignoreClicks = function(event) {
|
||||
event.stopPropagation();
|
||||
};
|
||||
elRenameInput.onclick = ignoreClicks;
|
||||
elRenameInput.ondblclick = ignoreClicks;
|
||||
elRenameInput.onkeyup = function(keyEvent) {
|
||||
if (keyEvent.key === "Enter") {
|
||||
elRenameInput.blur();
|
||||
}
|
||||
};
|
||||
|
||||
elRenameInput.onblur = function(event) {
|
||||
let value = elRenameInput.value;
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: 'rename',
|
||||
entityID: entityID,
|
||||
name: value
|
||||
}));
|
||||
entity.name = value;
|
||||
elCell.innerText = value;
|
||||
};
|
||||
|
||||
elCell.innerHTML = "";
|
||||
elCell.appendChild(elRenameInput);
|
||||
|
||||
elRenameInput.select();
|
||||
}
|
||||
|
||||
entityListContextMenu.setOnSelectedCallback(function(optionName, selectedEntityID) {
|
||||
switch (optionName) {
|
||||
case "Cut":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' }));
|
||||
break;
|
||||
case "Copy":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' }));
|
||||
break;
|
||||
case "Paste":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' }));
|
||||
break;
|
||||
case "Rename":
|
||||
startRenamingEntity(selectedEntityID);
|
||||
break;
|
||||
case "Duplicate":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' }));
|
||||
break;
|
||||
case "Delete":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function onRowContextMenu(clickEvent) {
|
||||
let entityID = this.dataset.entityID;
|
||||
|
||||
if (!selectedEntities.includes(entityID)) {
|
||||
let selection = [entityID];
|
||||
updateSelectedEntities(selection);
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: false,
|
||||
entityIds: selection,
|
||||
}));
|
||||
}
|
||||
|
||||
let enabledContextMenuItems = ['Copy', 'Paste', 'Duplicate'];
|
||||
if (entitiesByID[entityID] && !entitiesByID[entityID].locked) {
|
||||
enabledContextMenuItems.push('Cut');
|
||||
enabledContextMenuItems.push('Rename');
|
||||
enabledContextMenuItems.push('Delete');
|
||||
}
|
||||
|
||||
entityListContextMenu.open(clickEvent, entityID, enabledContextMenuItems);
|
||||
}
|
||||
|
||||
function onRowClicked(clickEvent) {
|
||||
let entityID = this.dataset.entityID;
|
||||
let selection = [entityID];
|
||||
|
||||
|
||||
if (clickEvent.ctrlKey) {
|
||||
let selectedIndex = selectedEntities.indexOf(entityID);
|
||||
if (selectedIndex >= 0) {
|
||||
selection = [];
|
||||
selection = selection.concat(selectedEntities);
|
||||
selection.splice(selectedIndex, 1)
|
||||
selection.splice(selectedIndex, 1);
|
||||
} else {
|
||||
selection = selection.concat(selectedEntities);
|
||||
}
|
||||
|
@ -221,28 +314,29 @@ function loaded() {
|
|||
}
|
||||
}
|
||||
} else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) {
|
||||
// if reselecting the same entity then deselect it
|
||||
// if reselecting the same entity then start renaming it
|
||||
if (selectedEntities[0] === entityID) {
|
||||
selection = [];
|
||||
startRenamingEntity(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
updateSelectedEntities(selection);
|
||||
updateSelectedEntities(selection, false);
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: false,
|
||||
entityIds: selection,
|
||||
}));
|
||||
|
||||
refreshFooter();
|
||||
}
|
||||
|
||||
function onRowDoubleClicked() {
|
||||
let selection = [this.dataset.entityID];
|
||||
updateSelectedEntities(selection, false);
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: true,
|
||||
entityIds: [this.dataset.entityID],
|
||||
entityIds: selection,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -289,7 +383,7 @@ function loaded() {
|
|||
hasScript: entity.hasScript,
|
||||
elRow: null, // if this entity has a visible row element assigned to it
|
||||
selected: false // if this entity is selected for edit regardless of having a visible row
|
||||
}
|
||||
};
|
||||
|
||||
entities.push(entityData);
|
||||
entitiesByID[entityData.id] = entityData;
|
||||
|
@ -418,7 +512,7 @@ function loaded() {
|
|||
isBaked: document.querySelector('#entity-isBaked .sort-order'),
|
||||
drawCalls: document.querySelector('#entity-drawCalls .sort-order'),
|
||||
hasScript: document.querySelector('#entity-hasScript .sort-order'),
|
||||
}
|
||||
};
|
||||
function setSortColumn(column) {
|
||||
PROFILE("set-sort-column", function() {
|
||||
if (currentSortColumn === column) {
|
||||
|
@ -453,7 +547,7 @@ function loaded() {
|
|||
}
|
||||
}
|
||||
|
||||
function updateSelectedEntities(selectedIDs) {
|
||||
function updateSelectedEntities(selectedIDs, autoScroll) {
|
||||
let notFound = false;
|
||||
|
||||
// reset all currently selected entities and their rows first
|
||||
|
@ -482,6 +576,26 @@ function loaded() {
|
|||
}
|
||||
});
|
||||
|
||||
if (autoScroll && selectedIDs.length > 0) {
|
||||
let firstItem = Number.MAX_VALUE;
|
||||
let lastItem = -1;
|
||||
let itemFound = false;
|
||||
visibleEntities.forEach(function(entity, index) {
|
||||
if (selectedIDs.indexOf(entity.id) !== -1) {
|
||||
if (firstItem > index) {
|
||||
firstItem = index;
|
||||
}
|
||||
if (lastItem < index) {
|
||||
lastItem = index;
|
||||
}
|
||||
itemFound = true;
|
||||
}
|
||||
});
|
||||
if (itemFound) {
|
||||
entityList.scrollToRow(firstItem, lastItem);
|
||||
}
|
||||
}
|
||||
|
||||
refreshFooter();
|
||||
|
||||
return notFound;
|
||||
|
@ -502,6 +616,7 @@ function loaded() {
|
|||
}
|
||||
row.appendChild(column);
|
||||
}
|
||||
row.oncontextmenu = onRowContextMenu;
|
||||
row.onclick = onRowClicked;
|
||||
row.ondblclick = onRowDoubleClicked;
|
||||
return row;
|
||||
|
@ -641,7 +756,7 @@ function loaded() {
|
|||
if (data.type === "clearEntityList") {
|
||||
clearEntities();
|
||||
} else if (data.type === "selectionUpdate") {
|
||||
let notFound = updateSelectedEntities(data.selectedIDs);
|
||||
let notFound = updateSelectedEntities(data.selectedIDs, true);
|
||||
if (notFound) {
|
||||
refreshEntities();
|
||||
}
|
||||
|
@ -653,13 +768,13 @@ function loaded() {
|
|||
clearEntities();
|
||||
} else {
|
||||
updateEntityData(newEntities);
|
||||
updateSelectedEntities(data.selectedIDs);
|
||||
updateSelectedEntities(data.selectedIDs, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) {
|
||||
removeEntities(data.deletedIDs);
|
||||
updateSelectedEntities(data.selectedIDs);
|
||||
updateSelectedEntities(data.selectedIDs, true);
|
||||
} else if (data.type === "deleted" && data.ids) {
|
||||
removeEntities(data.ids);
|
||||
}
|
||||
|
@ -672,8 +787,15 @@ function loaded() {
|
|||
|
||||
augmentSpinButtons();
|
||||
|
||||
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
document.addEventListener("contextmenu", function (event) {
|
||||
entityListContextMenu.close();
|
||||
|
||||
// Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
// close context menu when switching focus to another window
|
||||
$(window).blur(function() {
|
||||
entityListContextMenu.close();
|
||||
});
|
||||
}
|
||||
|
|
163
scripts/system/html/js/entityListContextMenu.js
Normal file
163
scripts/system/html/js/entityListContextMenu.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// entityListContextMenu.js
|
||||
//
|
||||
// exampleContextMenus.js was originally created by David Rowe on 22 Aug 2018.
|
||||
// Modified to entityListContextMenu.js by Thijs Wenker on 10 Oct 2018
|
||||
// Copyright 2018 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
|
||||
//
|
||||
|
||||
/* eslint-env browser */
|
||||
const CONTEXT_MENU_CLASS = "context-menu";
|
||||
|
||||
/**
|
||||
* ContextMenu class for EntityList
|
||||
* @constructor
|
||||
*/
|
||||
function EntityListContextMenu() {
|
||||
this._elContextMenu = null;
|
||||
this._onSelectedCallback = null;
|
||||
this._listItems = [];
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
EntityListContextMenu.prototype = {
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_elContextMenu: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onSelectedCallback: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_selectedEntityID: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_listItems: null,
|
||||
|
||||
/**
|
||||
* Close the context menu
|
||||
*/
|
||||
close: function() {
|
||||
if (this.isContextMenuOpen()) {
|
||||
this._elContextMenu.style.display = "none";
|
||||
}
|
||||
},
|
||||
|
||||
isContextMenuOpen: function() {
|
||||
return this._elContextMenu.style.display === "block";
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the context menu
|
||||
* @param clickEvent
|
||||
* @param selectedEntityID
|
||||
* @param enabledOptions
|
||||
*/
|
||||
open: function(clickEvent, selectedEntityID, enabledOptions) {
|
||||
this._selectedEntityID = selectedEntityID;
|
||||
|
||||
this._listItems.forEach(function(listItem) {
|
||||
let enabled = enabledOptions.includes(listItem.label);
|
||||
listItem.enabled = enabled;
|
||||
listItem.element.setAttribute('class', enabled ? '' : 'disabled');
|
||||
});
|
||||
|
||||
this._elContextMenu.style.display = "block";
|
||||
this._elContextMenu.style.left
|
||||
= Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px";
|
||||
this._elContextMenu.style.top
|
||||
= Math.min(clickEvent.pageY, document.body.clientHeight - this._elContextMenu.offsetHeight).toString() + "px";
|
||||
clickEvent.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the callback for when a menu item is selected
|
||||
* @param onSelectedCallback
|
||||
*/
|
||||
setOnSelectedCallback: function(onSelectedCallback) {
|
||||
this._onSelectedCallback = onSelectedCallback;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a labeled item to the context menu
|
||||
* @param itemLabel
|
||||
* @private
|
||||
*/
|
||||
_addListItem: function(itemLabel) {
|
||||
let elListItem = document.createElement("li");
|
||||
elListItem.innerText = itemLabel;
|
||||
|
||||
let listItem = {
|
||||
label: itemLabel,
|
||||
element: elListItem,
|
||||
enabled: false
|
||||
};
|
||||
|
||||
elListItem.addEventListener("click", function () {
|
||||
if (listItem.enabled && this._onSelectedCallback) {
|
||||
this._onSelectedCallback.call(this, itemLabel, this._selectedEntityID);
|
||||
}
|
||||
}.bind(this), false);
|
||||
|
||||
elListItem.setAttribute('class', 'disabled');
|
||||
|
||||
this._listItems.push(listItem);
|
||||
this._elContextMenu.appendChild(elListItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a separator item to the context menu
|
||||
* @private
|
||||
*/
|
||||
_addListSeparator: function() {
|
||||
let elListItem = document.createElement("li");
|
||||
elListItem.setAttribute('class', 'separator');
|
||||
this._elContextMenu.appendChild(elListItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the context menu.
|
||||
* @private
|
||||
*/
|
||||
_initialize: function() {
|
||||
this._elContextMenu = document.createElement("ul");
|
||||
this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS);
|
||||
document.body.appendChild(this._elContextMenu);
|
||||
|
||||
this._addListItem("Cut");
|
||||
this._addListItem("Copy");
|
||||
this._addListItem("Paste");
|
||||
this._addListSeparator();
|
||||
this._addListItem("Rename");
|
||||
this._addListItem("Duplicate");
|
||||
this._addListItem("Delete");
|
||||
|
||||
// Ignore clicks on context menu background or separator.
|
||||
this._elContextMenu.addEventListener("click", function(event) {
|
||||
// Sink clicks on context menu background or separator but let context menu item clicks through.
|
||||
if (event.target.classList.contains(CONTEXT_MENU_CLASS)) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Provide means to close context menu without clicking menu item.
|
||||
document.body.addEventListener("click", this.close.bind(this));
|
||||
document.body.addEventListener("keydown", function(event) {
|
||||
// Close context menu with Esc key.
|
||||
if (this.isContextMenuOpen() && event.key === "Escape") {
|
||||
this.close();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
|
@ -38,7 +38,7 @@ function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunctio
|
|||
this.lastRowShiftScrollTop = 0;
|
||||
|
||||
this.initialize();
|
||||
};
|
||||
}
|
||||
|
||||
ListView.prototype = {
|
||||
getNumRows: function() {
|
||||
|
@ -152,6 +152,30 @@ ListView.prototype = {
|
|||
this.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Scrolls firstRowIndex with least effort, also tries to make the window include the other selections in case lastRowIndex is set.
|
||||
* In the case that firstRowIndex and lastRowIndex are already within the visible bounds then nothing will happen.
|
||||
* @param {number} firstRowIndex - The row that will be scrolled to.
|
||||
* @param {number} lastRowIndex - The last index of the bound.
|
||||
*/
|
||||
scrollToRow: function (firstRowIndex, lastRowIndex) {
|
||||
lastRowIndex = lastRowIndex ? lastRowIndex : firstRowIndex;
|
||||
let boundingTop = firstRowIndex * this.rowHeight;
|
||||
let boundingBottom = (lastRowIndex * this.rowHeight) + this.rowHeight;
|
||||
if ((boundingBottom - boundingTop) > this.elTableScroll.clientHeight) {
|
||||
boundingBottom = boundingTop + this.elTableScroll.clientHeight;
|
||||
}
|
||||
|
||||
let currentVisibleAreaTop = this.elTableScroll.scrollTop;
|
||||
let currentVisibleAreaBottom = currentVisibleAreaTop + this.elTableScroll.clientHeight;
|
||||
|
||||
if (boundingTop < currentVisibleAreaTop) {
|
||||
this.elTableScroll.scrollTop = boundingTop;
|
||||
} else if (boundingBottom > currentVisibleAreaBottom) {
|
||||
this.elTableScroll.scrollTop = boundingBottom - (this.elTableScroll.clientHeight);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
// block refreshing before rows are initialized
|
||||
|
|
|
@ -15,7 +15,7 @@ var PROFILING_ENABLED = false;
|
|||
var profileIndent = '';
|
||||
const PROFILE_NOOP = function(_name, fn, args) {
|
||||
fn.apply(this, args);
|
||||
} ;
|
||||
};
|
||||
PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
|
||||
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
|
||||
var previousIndent = profileIndent;
|
||||
|
@ -98,7 +98,11 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
that.setVisible(!visible);
|
||||
};
|
||||
|
||||
selectionManager.addEventListener(function() {
|
||||
selectionManager.addEventListener(function(isSelectionUpdate, caller) {
|
||||
if (caller === that) {
|
||||
// ignore events that we emitted from the entity list itself
|
||||
return;
|
||||
}
|
||||
var selectedIDs = [];
|
||||
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
|
@ -224,7 +228,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
for (var i = 0; i < ids.length; i++) {
|
||||
entityIDs.push(ids[i]);
|
||||
}
|
||||
selectionManager.setSelections(entityIDs);
|
||||
selectionManager.setSelections(entityIDs, that);
|
||||
if (data.focus) {
|
||||
cameraManager.enable();
|
||||
cameraManager.focus(selectionManager.worldPosition,
|
||||
|
@ -245,7 +249,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
Window.saveAsync("Select Where to Save", "", "*.json");
|
||||
}
|
||||
} else if (data.type === "pal") {
|
||||
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
|
||||
var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates.
|
||||
selectionManager.selections.forEach(function (id) {
|
||||
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
|
||||
if (lastEditedBy) {
|
||||
|
@ -271,6 +275,19 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
filterInView = data.filterInView === true;
|
||||
} else if (data.type === "radius") {
|
||||
searchRadius = data.radius;
|
||||
} else if (data.type === "cut") {
|
||||
SelectionManager.cutSelectedEntities();
|
||||
} else if (data.type === "copy") {
|
||||
SelectionManager.copySelectedEntities();
|
||||
} else if (data.type === "paste") {
|
||||
SelectionManager.pasteEntities();
|
||||
} else if (data.type === "duplicate") {
|
||||
SelectionManager.duplicateSelection();
|
||||
that.sendUpdate();
|
||||
} else if (data.type === "rename") {
|
||||
Entities.editEntity(data.entityID, {name: data.name});
|
||||
// make sure that the name also gets updated in the properties window
|
||||
SelectionManager._update();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ SelectionManager = (function() {
|
|||
Messages.messageReceived.connect(handleEntitySelectionToolUpdates);
|
||||
}
|
||||
|
||||
// FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES
|
||||
// FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES
|
||||
function handleEntitySelectionToolUpdates(channel, message, sender) {
|
||||
if (channel !== 'entityToolUpdates') {
|
||||
return;
|
||||
|
@ -63,7 +63,7 @@ SelectionManager = (function() {
|
|||
if (wantDebug) {
|
||||
print("setting selection to " + messageParsed.entityID);
|
||||
}
|
||||
that.setSelections([messageParsed.entityID]);
|
||||
that.setSelections([messageParsed.entityID], that);
|
||||
}
|
||||
} else if (messageParsed.method === "clearSelection") {
|
||||
if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {
|
||||
|
@ -136,7 +136,7 @@ SelectionManager = (function() {
|
|||
return that.selections.length > 0;
|
||||
};
|
||||
|
||||
that.setSelections = function(entityIDs) {
|
||||
that.setSelections = function(entityIDs, caller) {
|
||||
that.selections = [];
|
||||
for (var i = 0; i < entityIDs.length; i++) {
|
||||
var entityID = entityIDs[i];
|
||||
|
@ -144,10 +144,10 @@ SelectionManager = (function() {
|
|||
Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);
|
||||
}
|
||||
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.addEntity = function(entityID, toggleSelection) {
|
||||
that.addEntity = function(entityID, toggleSelection, caller) {
|
||||
if (entityID) {
|
||||
var idx = -1;
|
||||
for (var i = 0; i < that.selections.length; i++) {
|
||||
|
@ -165,7 +165,7 @@ SelectionManager = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
function removeEntityByID(entityID) {
|
||||
|
@ -176,21 +176,21 @@ SelectionManager = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
that.removeEntity = function (entityID) {
|
||||
that.removeEntity = function (entityID, caller) {
|
||||
removeEntityByID(entityID);
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.removeEntities = function(entityIDs) {
|
||||
that.removeEntities = function(entityIDs, caller) {
|
||||
for (var i = 0, length = entityIDs.length; i < length; i++) {
|
||||
removeEntityByID(entityIDs[i]);
|
||||
}
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.clearSelections = function() {
|
||||
that.clearSelections = function(caller) {
|
||||
that.selections = [];
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.addChildrenEntities = function(parentEntityID, entityList) {
|
||||
|
@ -353,12 +353,12 @@ SelectionManager = (function() {
|
|||
}
|
||||
|
||||
return createdEntityIDs;
|
||||
}
|
||||
};
|
||||
|
||||
that.cutSelectedEntities = function() {
|
||||
copySelectedEntities();
|
||||
that.copySelectedEntities();
|
||||
deleteSelectedEntities();
|
||||
}
|
||||
};
|
||||
|
||||
that.copySelectedEntities = function() {
|
||||
var entityProperties = Entities.getMultipleEntityProperties(that.selections);
|
||||
|
@ -434,7 +434,7 @@ SelectionManager = (function() {
|
|||
z: brn.z + entityClipboard.dimensions.z / 2
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
that.pasteEntities = function() {
|
||||
var dimensions = entityClipboard.dimensions;
|
||||
|
@ -442,7 +442,7 @@ SelectionManager = (function() {
|
|||
var pastePosition = getPositionToCreateEntity(maxDimension);
|
||||
var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position);
|
||||
|
||||
var copiedProperties = []
|
||||
var copiedProperties = [];
|
||||
var ids = [];
|
||||
entityClipboard.entities.forEach(function(originalProperties) {
|
||||
var properties = deepCopy(originalProperties);
|
||||
|
@ -475,9 +475,9 @@ SelectionManager = (function() {
|
|||
|
||||
redo(copiedProperties);
|
||||
undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties);
|
||||
}
|
||||
};
|
||||
|
||||
that._update = function(selectionUpdated) {
|
||||
that._update = function(selectionUpdated, caller) {
|
||||
var properties = null;
|
||||
if (that.selections.length === 0) {
|
||||
that.localDimensions = null;
|
||||
|
@ -542,7 +542,7 @@ SelectionManager = (function() {
|
|||
|
||||
for (var j = 0; j < listeners.length; j++) {
|
||||
try {
|
||||
listeners[j](selectionUpdated === true);
|
||||
listeners[j](selectionUpdated === true, caller);
|
||||
} catch (e) {
|
||||
print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e));
|
||||
}
|
||||
|
@ -985,7 +985,7 @@ SelectionDisplay = (function() {
|
|||
that.pressedHand = NO_HAND;
|
||||
that.triggered = function() {
|
||||
return that.triggeredHand !== NO_HAND;
|
||||
}
|
||||
};
|
||||
function pointingAtDesktopWindowOrTablet(hand) {
|
||||
var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand &&
|
||||
SelectionManager.pointingAtDesktopWindowRight) ||
|
||||
|
@ -1032,7 +1032,7 @@ SelectionDisplay = (function() {
|
|||
that.disableTriggerMapping = function() {
|
||||
that.triggerClickMapping.disable();
|
||||
that.triggerPressMapping.disable();
|
||||
}
|
||||
};
|
||||
Script.scriptEnding.connect(that.disableTriggerMapping);
|
||||
|
||||
// FUNCTION DEF(s): Intersection Check Helpers
|
||||
|
@ -1234,7 +1234,7 @@ SelectionDisplay = (function() {
|
|||
if (wantDebug) {
|
||||
print(" Trigger SelectionManager::update");
|
||||
}
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, that);
|
||||
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent END =======================");
|
||||
|
@ -1299,7 +1299,7 @@ SelectionDisplay = (function() {
|
|||
lastMouseEvent.isControl = event.isControl;
|
||||
lastMouseEvent.isAlt = event.isAlt;
|
||||
activeTool.onMove(lastMouseEvent);
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1315,7 +1315,7 @@ SelectionDisplay = (function() {
|
|||
lastMouseEvent.isControl = event.isControl;
|
||||
lastMouseEvent.isAlt = event.isAlt;
|
||||
activeTool.onMove(lastMouseEvent);
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2179,7 +2179,7 @@ SelectionDisplay = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2301,7 +2301,7 @@ SelectionDisplay = (function() {
|
|||
|
||||
previousPickRay = pickRay;
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2488,7 +2488,7 @@ SelectionDisplay = (function() {
|
|||
|
||||
previousPickRay = pickRay;
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2599,7 +2599,7 @@ SelectionDisplay = (function() {
|
|||
|
||||
previousPickRay = pickRay;
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -49,6 +49,42 @@ var NO_BUTTON = 0; // QMessageBox::NoButton
|
|||
|
||||
var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
|
||||
|
||||
|
||||
var resourceRequestEvents = [];
|
||||
function signalResourceRequestEvent(data) {
|
||||
// Once we can tie resource request events to specific resources,
|
||||
// we will have to update the "0" in here.
|
||||
var resourceData = "from: " + data.extra + ": " + data.url.toString().replace("__NONE__,", "");
|
||||
|
||||
if (resourceObjectsInTest[0].resourceDataArray.indexOf(resourceData) === -1) {
|
||||
resourceObjectsInTest[0].resourceDataArray.push(resourceData);
|
||||
|
||||
resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " +
|
||||
resourceData + "\n";
|
||||
|
||||
ui.tablet.sendToQml({
|
||||
method: "resourceRequestEvent",
|
||||
data: data,
|
||||
resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onResourceRequestEvent(data) {
|
||||
// Once we can tie resource request events to specific resources,
|
||||
// we will have to update the "0" in here.
|
||||
if (resourceObjectsInTest[0] && resourceObjectsInTest[0].currentlyRecordingResources) {
|
||||
var resourceRequestEvent = {
|
||||
"date": new Date(),
|
||||
"url": data.url,
|
||||
"callerId": data.callerId,
|
||||
"extra": data.extra
|
||||
};
|
||||
resourceRequestEvents.push(resourceRequestEvent);
|
||||
signalResourceRequestEvent(resourceRequestEvent);
|
||||
}
|
||||
}
|
||||
|
||||
function onMessageBoxClosed(id, button) {
|
||||
if (id === messageBox && button === CANCEL_BUTTON) {
|
||||
isDownloadBeingCancelled = true;
|
||||
|
@ -522,13 +558,18 @@ function getPositionToCreateEntity(extra) {
|
|||
return position;
|
||||
}
|
||||
|
||||
function rezEntity(itemHref, itemType) {
|
||||
function defaultFor(arg, val) {
|
||||
return typeof arg !== 'undefined' ? arg : val;
|
||||
}
|
||||
|
||||
function rezEntity(itemHref, itemType, marketplaceItemTesterId) {
|
||||
var isWearable = itemType === "wearable";
|
||||
var success = Clipboard.importEntities(itemHref);
|
||||
var success = Clipboard.importEntities(itemHref, true, marketplaceItemTesterId);
|
||||
var wearableLocalPosition = null;
|
||||
var wearableLocalRotation = null;
|
||||
var wearableLocalDimensions = null;
|
||||
var wearableDimensions = null;
|
||||
marketplaceItemTesterId = defaultFor(marketplaceItemTesterId, -1);
|
||||
|
||||
if (itemType === "contentSet") {
|
||||
console.log("Item is a content set; codepath shouldn't go here.");
|
||||
|
@ -816,7 +857,8 @@ var resourceObjectsInTest = [];
|
|||
function signalNewResourceObjectInTest(resourceObject) {
|
||||
ui.tablet.sendToQml({
|
||||
method: "newResourceObjectInTest",
|
||||
resourceObject: resourceObject });
|
||||
resourceObject: resourceObject
|
||||
});
|
||||
}
|
||||
|
||||
var onQmlMessageReceived = function onQmlMessageReceived(message) {
|
||||
|
@ -877,11 +919,15 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
|
|||
case 'checkout_rezClicked':
|
||||
case 'purchases_rezClicked':
|
||||
case 'tester_rezClicked':
|
||||
rezEntity(message.itemHref, message.itemType);
|
||||
rezEntity(message.itemHref, message.itemType, message.itemId);
|
||||
break;
|
||||
case 'tester_newResourceObject':
|
||||
var resourceObject = message.resourceObject;
|
||||
resourceObjectsInTest[resourceObject.id] = resourceObject;
|
||||
resourceObjectsInTest = []; // REMOVE THIS once we support specific referrers
|
||||
resourceObject.currentlyRecordingResources = false;
|
||||
resourceObject.resourceAccessEventText = "";
|
||||
resourceObjectsInTest[resourceObject.resourceObjectId] = resourceObject;
|
||||
resourceObjectsInTest[resourceObject.resourceObjectId].resourceDataArray = [];
|
||||
signalNewResourceObjectInTest(resourceObject);
|
||||
break;
|
||||
case 'tester_updateResourceObjectAssetType':
|
||||
|
@ -890,6 +936,13 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
|
|||
case 'tester_deleteResourceObject':
|
||||
delete resourceObjectsInTest[message.objectId];
|
||||
break;
|
||||
case 'tester_updateResourceRecordingStatus':
|
||||
resourceObjectsInTest[message.objectId].currentlyRecordingResources = message.status;
|
||||
if (message.status) {
|
||||
resourceObjectsInTest[message.objectId].resourceDataArray = [];
|
||||
resourceObjectsInTest[message.objectId].resourceAccessEventText = "";
|
||||
}
|
||||
break;
|
||||
case 'header_marketplaceImageClicked':
|
||||
case 'purchases_backClicked':
|
||||
openMarketplace(message.referrerURL);
|
||||
|
@ -1029,16 +1082,22 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
|
|||
};
|
||||
|
||||
function pushResourceObjectsInTest() {
|
||||
var maxObjectId = -1;
|
||||
for (var objectId in resourceObjectsInTest) {
|
||||
signalNewResourceObjectInTest(resourceObjectsInTest[objectId]);
|
||||
maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId;
|
||||
var maxResourceObjectId = -1;
|
||||
var length = resourceObjectsInTest.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (i in resourceObjectsInTest) {
|
||||
signalNewResourceObjectInTest(resourceObjectsInTest[i]);
|
||||
var resourceObjectId = resourceObjectsInTest[i].resourceObjectId;
|
||||
maxResourceObjectId = (maxResourceObjectId < resourceObjectId) ? parseInt(resourceObjectId) : maxResourceObjectId;
|
||||
}
|
||||
}
|
||||
// N.B. Thinking about removing the following sendToQml? Be sure
|
||||
// that the marketplace item tester QML has heard from us, at least
|
||||
// so that it can indicate to the user that all of the resoruce
|
||||
// objects in test have been transmitted to it.
|
||||
ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxObjectId + 1 });
|
||||
//ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxResourceObjectId + 1 });
|
||||
// Since, for now, we only support 1 object in test, always send id: 0
|
||||
ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: 0 });
|
||||
}
|
||||
|
||||
// Function Name: onTabletScreenChanged()
|
||||
|
@ -1193,6 +1252,7 @@ function startup() {
|
|||
ui.tablet.webEventReceived.connect(onWebEventReceived);
|
||||
Wallet.walletStatusChanged.connect(sendCommerceSettings);
|
||||
Window.messageBoxClosed.connect(onMessageBoxClosed);
|
||||
ResourceRequestObserver.resourceRequestEvent.connect(onResourceRequestEvent);
|
||||
|
||||
Wallet.refreshWalletStatus();
|
||||
}
|
||||
|
@ -1226,6 +1286,7 @@ function shutdown() {
|
|||
GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
|
||||
Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
|
||||
ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML);
|
||||
ResourceRequestObserver.resourceRequestEvent.disconnect(onResourceRequestEvent);
|
||||
|
||||
off();
|
||||
}
|
||||
|
|
|
@ -874,10 +874,6 @@ function onContentLoaded() {
|
|||
hasShownUpdateNotification = true;
|
||||
}
|
||||
});
|
||||
notifier.on('click', function(notifierObject, options) {
|
||||
log.debug("Got click", options.url);
|
||||
shell.openExternal(options.url);
|
||||
});
|
||||
}
|
||||
|
||||
deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX);
|
||||
|
|
|
@ -5,6 +5,8 @@ const process = require('process');
|
|||
const hfApp = require('./hf-app');
|
||||
const path = require('path');
|
||||
const AccountInfo = require('./hf-acctinfo').AccountInfo;
|
||||
const url = require('url');
|
||||
const shell = require('electron').shell;
|
||||
const GetBuildInfo = hfApp.getBuildInfo;
|
||||
const buildInfo = GetBuildInfo();
|
||||
const osType = os.type();
|
||||
|
@ -154,8 +156,13 @@ function HifiNotifications(config, menuNotificationCallback) {
|
|||
|
||||
var _menuNotificationCallback = menuNotificationCallback;
|
||||
notifier.on('click', function (notifierObject, options) {
|
||||
StartInterface(options.url);
|
||||
_menuNotificationCallback(options.notificationType, false);
|
||||
const optUrl = url.parse(options.url);
|
||||
if ((optUrl.protocol === "hifi:") || (optUrl.protocol === "hifiapp:")) {
|
||||
StartInterface(options.url);
|
||||
_menuNotificationCallback(options.notificationType, false);
|
||||
} else {
|
||||
shell.openExternal(options.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue