Merge branch 'master' into avatar-mixer-improvements

This commit is contained in:
Simon Walton 2018-10-24 10:39:23 -09:00 committed by GitHub
commit e22e0ecb04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 2317 additions and 620 deletions

View file

@ -222,7 +222,8 @@ void Agent::requestScript() {
return; return;
} }
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, scriptURL); auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
this, scriptURL, true, -1, "Agent::requestScript");
if (!request) { if (!request) {
qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString(); qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString();

View file

@ -21,7 +21,6 @@
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QUuid> #include <QUuid>
#include <ClientTraitsHandler.h>
#include <EntityEditPacketSender.h> #include <EntityEditPacketSender.h>
#include <EntityTree.h> #include <EntityTree.h>
#include <ScriptEngine.h> #include <ScriptEngine.h>

View file

@ -35,6 +35,7 @@
#include "AssignmentClientLogging.h" #include "AssignmentClientLogging.h"
#include "AssignmentFactory.h" #include "AssignmentFactory.h"
#include "ResourceRequestObserver.h"
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; 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<tracing::Tracer>();
DependencyManager::set<StatTracker>(); DependencyManager::set<StatTracker>();
DependencyManager::set<AccountManager>(); DependencyManager::set<AccountManager>();
DependencyManager::set<ResourceRequestObserver>();
auto addressManager = DependencyManager::set<AddressManager>(); auto addressManager = DependencyManager::set<AddressManager>();

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC73.zip URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC75.zip
URL_MD5 0c5edfb63cafb042311d3cf25261fbf2 URL_MD5 b4225d058952e17976ac228330ce8d51
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_COMMAND "" INSTALL_COMMAND ""

View 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": []
}
]
}
}

View file

@ -10,6 +10,7 @@ Item {
property int modality: Qt.NonModal property int modality: Qt.NonModal
implicitHeight: row.height implicitHeight: row.height
implicitWidth: row.width implicitWidth: row.width
visible: false
Component.onCompleted: { Component.onCompleted: {
stats.parentChanged.connect(fill); stats.parentChanged.connect(fill);

View file

@ -31,7 +31,7 @@ Rectangle {
scaleSlider.notify = false; scaleSlider.notify = false;
scaleSlider.value = Math.round(avatarScale * 10); scaleSlider.value = Math.round(avatarScale * 10);
scaleSlider.notify = true;; scaleSlider.notify = true;
if (settings.dominantHand === 'left') { if (settings.dominantHand === 'left') {
leftHandRadioButton.checked = true; leftHandRadioButton.checked = true;

View file

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

View file

@ -4,21 +4,19 @@
// //
// Load items not in the marketplace for testing purposes // 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. // Copyright 2018 High Fidelity, Inc.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
import QtQuick 2.5 import QtQuick 2.10
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.0
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3
import Hifi 1.0 as Hifi import Hifi 1.0 as Hifi
import "../../../styles-uit" as HifiStylesUit import "qrc:////qml//styles-uit" as HifiStylesUit
import "../../../controls-uit" as HifiControlsUit import "qrc:////qml//controls-uit" as HifiControlsUit
@ -27,33 +25,223 @@ Rectangle {
property string installedApps property string installedApps
property var nextResourceObjectId: 0 property var nextResourceObjectId: 0
signal sendToScript(var message)
HifiStylesUit.HifiConstants { id: hifi } HifiStylesUit.HifiConstants { id: hifi }
ListModel { id: resourceListModel } ListModel { id: resourceListModel }
color: hifi.colors.white color: hifi.colors.darkGray
AnimatedImage { //
id: spinner; // TITLE BAR START
source: "spinner.gif" //
width: 74; Item {
height: width; id: titleBarContainer
anchors.verticalCenter: parent.verticalCenter; // Size
anchors.horizontalCenter: parent.horizontalCenter; 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) { function fromScript(message) {
switch (message.method) { switch (message.method) {
case "newResourceObjectInTest": case "newResourceObjectInTest":
var resourceObject = message.resourceObject; var resourceObject = message.resourceObject;
resourceListModel.clear(); // REMOVE THIS once we support specific referrers
resourceListModel.append(resourceObject); resourceListModel.append(resourceObject);
spinner.visible = false; spinner.visible = false;
break; break;
case "nextObjectIdInTest": case "nextObjectIdInTest":
print("!!!! message from script! " + JSON.stringify(message));
nextResourceObjectId = message.id; nextResourceObjectId = message.id;
spinner.visible = false; spinner.visible = false;
break; 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\.gz$/) ? "content set" :
resource.match(/\.json$/) ? "entity or wearable" : resource.match(/\.json$/) ? "entity or wearable" :
"unknown"); "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, "resource": resource,
"assetType": assetType }; "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) { function toUrl(resource) {
var httpPattern = /^http/i; var httpPattern = /^http/i;
return httpPattern.test(resource) ? resource : "file:///" + resource; return httpPattern.test(resource) ? resource : "file:///" + resource;
} }
function rezEntity(resource, entityType) { function rezEntity(resource, entityType, resourceObjectId) {
print("!!!! tester_rezClicked");
sendToScript({ sendToScript({
method: 'tester_rezClicked', method: 'tester_rezClicked',
itemHref: toUrl(resource), itemHref: toUrl(resource),
itemType: entityType}); itemType: entityType,
itemId: resourceObjectId });
} }
ListView { signal sendToScript(var message)
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]()
}
}
}
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -226,6 +226,7 @@
#include "commerce/Ledger.h" #include "commerce/Ledger.h"
#include "commerce/Wallet.h" #include "commerce/Wallet.h"
#include "commerce/QmlCommerce.h" #include "commerce/QmlCommerce.h"
#include "ResourceRequestObserver.h"
#include "webbrowser/WebBrowserSuggestionsEngine.h" #include "webbrowser/WebBrowserSuggestionsEngine.h"
#include <DesktopPreviewProvider.h> #include <DesktopPreviewProvider.h>
@ -947,6 +948,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<WalletScriptingInterface>(); DependencyManager::set<WalletScriptingInterface>();
DependencyManager::set<FadeEffect>(); DependencyManager::set<FadeEffect>();
DependencyManager::set<ResourceRequestObserver>();
return previousSessionCrashed; return previousSessionCrashed;
} }
@ -3129,6 +3131,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data()); surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data()); surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance());
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get())); surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
@ -5022,12 +5025,12 @@ void Application::saveSettings() const {
PluginManager::getInstance()->saveSettings(); PluginManager::getInstance()->saveSettings();
} }
bool Application::importEntities(const QString& urlOrFilename) { bool Application::importEntities(const QString& urlOrFilename, const bool isObservable, const qint64 callerId) {
bool success = false; bool success = false;
_entityClipboard->withWriteLock([&] { _entityClipboard->withWriteLock([&] {
_entityClipboard->eraseAllOctreeElements(); _entityClipboard->eraseAllOctreeElements();
success = _entityClipboard->readFromURL(urlOrFilename); success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId);
if (success) { if (success) {
_entityClipboard->reaverageOctreeElements(); _entityClipboard->reaverageOctreeElements();
} }
@ -6811,6 +6814,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data()); scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get<AddressManager>().data()); scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get<AddressManager>().data());
scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance());
scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
@ -7197,7 +7201,8 @@ void Application::addAssetToWorldFromURL(QString url) {
addAssetToWorldInfo(filename, "Downloading model file " + filename + "."); 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); connect(request, &ResourceRequest::finished, this, &Application::addAssetToWorldFromURLRequestFinished);
request->send(); request->send();
} }

View file

@ -342,7 +342,7 @@ public slots:
QVector<EntityItemID> pasteEntities(float x, float y, float z); 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, const QVector<EntityItemID>& entityIDs, const glm::vec3* givenOffset = nullptr);
bool exportEntities(const QString& filename, float x, float y, float z, float scale); 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 updateThreadPoolCount() const;
void updateSystemTabletMode(); void updateSystemTabletMode();
void goToErrorDomainURL(QUrl errorDomainURL); void goToErrorDomainURL(QUrl errorDomainURL);

View file

@ -53,7 +53,8 @@ void ATPAssetMigrator::loadEntityServerFile() {
auto migrateResources = [=](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) { auto migrateResources = [=](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) {
auto request = auto request =
DependencyManager::get<ResourceManager>()->createResourceRequest(this, migrationURL); DependencyManager::get<ResourceManager>()->createResourceRequest(
this, migrationURL, true, -1, "ATPAssetMigrator::loadEntityServerFile");
if (request) { if (request) {
qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration"; qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";

View file

@ -135,7 +135,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
glm::vec3 palmPosition; glm::vec3 palmPosition;
glm::quat palmRotation; glm::quat palmRotation;
bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting(); bool isTransitingWithAvatar = holdingAvatar->getTransit()->isActive();
if (isTransitingWithAvatar != _isTransitingWithAvatar) { if (isTransitingWithAvatar != _isTransitingWithAvatar) {
_isTransitingWithAvatar = isTransitingWithAvatar; _isTransitingWithAvatar = isTransitingWithAvatar;
auto ownerEntity = _ownerEntity.lock(); auto ownerEntity = _ownerEntity.lock();
@ -424,7 +424,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
if (ownerEntity) { if (ownerEntity) {
ownerEntity->setDynamicDataDirty(true); ownerEntity->setDynamicDataDirty(true);
ownerEntity->setDynamicDataNeedsTransmit(true); ownerEntity->setDynamicDataNeedsTransmit(true);
ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting()); ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive());
} }
}); });
} }

View file

@ -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._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._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) { AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
@ -126,13 +124,39 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) {
_space = 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) { void AvatarManager::updateMyAvatar(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()");
AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig);
bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT); handleTransitAnimations(status);
bool blockTransitData = (status == AvatarTransit::Status::TRANSITING);
_myAvatar->update(deltaTime); _myAvatar->update(deltaTime);
render::Transaction transaction; render::Transaction transaction;
@ -142,18 +166,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
quint64 now = usecTimestampNow(); quint64 now = usecTimestampNow();
quint64 dt = now - _lastSendAvatarDataTime; quint64 dt = now - _lastSendAvatarDataTime;
if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) {
if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) {
// send head/hand data to the avatar mixer and voxel server // send head/hand data to the avatar mixer and voxel server
PerformanceTimer perfTimer("send"); PerformanceTimer perfTimer("send");
if (sendFirstTransitPackage) {
_myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition());
}
_myAvatar->sendAvatarDataPacket(); _myAvatar->sendAvatarDataPacket();
_lastSendAvatarDataTime = now; _lastSendAvatarDataTime = now;
_myAvatarSendRate.increment(); _myAvatarSendRate.increment();
} }
} }
@ -267,7 +286,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
if (inView && avatar->hasNewJointData()) { if (inView && avatar->hasNewJointData()) {
numAvatarsUpdated++; 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)) { if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) {
avatar->_transit.reset(); avatar->_transit.reset();
avatar->setIsNewAvatar(false); avatar->setIsNewAvatar(false);

View file

@ -221,6 +221,7 @@ private:
// frequently grabs a read lock on the hash to get a given avatar by ID // frequently grabs a read lock on the hash to get a given avatar by ID
void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar,
KillAvatarReason removalReason = KillAvatarReason::NoReason) override; KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
void handleTransitAnimations(AvatarTransit::Status status);
QVector<AvatarSharedPointer> _avatarsToFade; QVector<AvatarSharedPointer> _avatarsToFade;

View file

@ -26,6 +26,7 @@
#include <AccountManager.h> #include <AccountManager.h>
#include <AddressManager.h> #include <AddressManager.h>
#include <AudioClient.h> #include <AudioClient.h>
#include <ClientTraitsHandler.h>
#include <display-plugins/DisplayPlugin.h> #include <display-plugins/DisplayPlugin.h>
#include <FSTReader.h> #include <FSTReader.h>
#include <GeometryUtil.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) { void MyAvatar::update(float deltaTime) {
// update moving average of HMD facing in xz plane. // update moving average of HMD facing in xz plane.
const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength();
const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders 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; float tau = deltaTime / HMD_FACING_TIMESCALE;
setHipToHandController(computeHandAzimuth()); setHipToHandController(computeHandAzimuth());
@ -493,11 +558,36 @@ void MyAvatar::update(float deltaTime) {
_smoothOrientationTimer += deltaTime; _smoothOrientationTimer += deltaTime;
} }
float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y; controller::Pose newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD);
int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER); if (newHeightReading.isValid()) {
_recentModeReadings.insert(newHeightReadingInCentimeters); int newHeightReadingInCentimeters = glm::floor(newHeightReading.getTranslation().y * CENTIMETERS_PER_METER);
setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); _averageUserHeightSensorSpace = lerp(_averageUserHeightSensorSpace, newHeightReading.getTranslation().y, HEIGHT_FILTER_COEFFICIENT);
setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); _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) { if (_drawAverageFacingEnabled) {
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
@ -968,7 +1058,6 @@ void MyAvatar::updateSensorToWorldMatrix() {
updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache);
if (hasSensorToWorldScaleChanged) { if (hasSensorToWorldScaleChanged) {
setTransitScale(sensorToWorldScale);
emit sensorToWorldScaleChanged(sensorToWorldScale); emit sensorToWorldScaleChanged(sensorToWorldScale);
} }
@ -3557,12 +3646,9 @@ glm::vec3 MyAvatar::computeCounterBalance() {
glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead; glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead;
// find the height of the hips // 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)); glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z));
float headMinusHipXz = glm::length(xzDiff); float headMinusHipXz = glm::length(xzDiff);
float headHipDefault = glm::length(tposeHead - tposeHips); float headHipDefault = glm::length(tposeHead - tposeHips);
float hipFootDefault = tposeHips.y - tposeRightFoot.y;
float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault);
float hipHeight = 0.0f; float hipHeight = 0.0f;
if (headHipDefault > headMinusHipXz) { if (headHipDefault > headMinusHipXz) {
hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz)); hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz));
@ -3574,10 +3660,6 @@ glm::vec3 MyAvatar::computeCounterBalance() {
if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { if (counterBalancedCg.y > (tposeHips.y + 0.05f)) {
// if the height is higher than default hips, clamp to default hips // if the height is higher than default hips, clamp to default hips
counterBalancedCg.y = tposeHips.y + 0.05f; counterBalancedCg.y = tposeHips.y + 0.05f;
} else if (counterBalancedCg.y < sitSquatThreshold) {
//do a height reset
setResetMode(true);
_follow.activate(FollowHelper::Vertical);
} }
return counterBalancedCg; return counterBalancedCg;
} }
@ -3818,6 +3900,18 @@ bool MyAvatar::getIsInWalkingState() const {
return _isInWalkingState; 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 { float MyAvatar::getWalkSpeed() const {
return _walkSpeed.get() * _walkSpeedScalar; return _walkSpeed.get() * _walkSpeedScalar;
} }
@ -3838,6 +3932,61 @@ void MyAvatar::setIsInWalkingState(bool isWalking) {
_isInWalkingState = 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) { void MyAvatar::setWalkSpeed(float value) {
_walkSpeed.set(value); _walkSpeed.set(value);
} }
@ -3854,6 +4003,14 @@ float MyAvatar::getSprintSpeed() const {
return _sprintSpeed.get(); return _sprintSpeed.get();
} }
void MyAvatar::setSitStandStateChange(bool stateChanged) {
_sitStandStateChange = stateChanged;
}
float MyAvatar::getSitStandStateChange() const {
return _sitStandStateChange;
}
QVector<QString> MyAvatar::getScriptUrls() { QVector<QString> MyAvatar::getScriptUrls() {
QVector<QString> scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector<QString>(); QVector<QString> scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector<QString>();
return scripts; return scripts;
@ -3997,6 +4154,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
// x axis of currentBodyMatrix in world space. // 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 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0]));
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
float forwardLeanAmount = glm::dot(forward, offset); float forwardLeanAmount = glm::dot(forward, offset);
float lateralLeanAmount = glm::dot(right, 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_FORWARD_LEAN = 0.15f;
const float MAX_BACKWARD_LEAN = 0.1f; const float MAX_BACKWARD_LEAN = 0.1f;
bool stepDetected = false;
if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { if (myAvatar.getIsInSittingState()) {
return true; if (!withinBaseOfSupport(currentHeadPose)) {
stepDetected = true;
}
} else if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
stepDetected = true;
} else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) {
return true; stepDetected = true;
} else {
stepDetected = fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
} }
return stepDetected;
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
} }
bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { 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 currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND);
controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD);
bool stepDetected = false; bool stepDetected = false;
float myScale = myAvatar.getAvatarScale(); float myScale = myAvatar.getAvatarScale();
@ -4030,7 +4194,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
} else { } else {
if (!withinBaseOfSupport(currentHeadPose) && if (!withinBaseOfSupport(currentHeadPose) &&
headAngularVelocityBelowThreshold(currentHeadPose) && headAngularVelocityBelowThreshold(currentHeadPose) &&
isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight(), myScale) && isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) &&
handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) &&
handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) &&
headVelocityGreaterThanThreshold(currentHeadPose) && headVelocityGreaterThanThreshold(currentHeadPose) &&
@ -4046,6 +4210,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); glm::vec3 currentHeadPosition = currentHeadPose.getTranslation();
float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition);
if (!isActive(Horizontal) && if (!isActive(Horizontal) &&
(!isActive(Vertical)) &&
(glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) {
myAvatar.setResetMode(true); myAvatar.setResetMode(true);
stepDetected = 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 { 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_TOP = 0.1f;
const float CYLINDER_BOTTOM = -1.5f; const float CYLINDER_BOTTOM = -1.5f;
const float SITTING_BOTTOM = -0.02f;
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); 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, void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix,
@ -4085,9 +4272,10 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
} }
} }
} else { } else {
// center of gravity model is not enabled
if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Horizontal); activate(Horizontal);
if (myAvatar.getEnableStepResetRotation()) { if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) {
activate(Rotation); activate(Rotation);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); 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)) { if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Vertical); activate(Vertical);
if (_squatDetected) {
_squatDetected = false;
}
} }
} else { } else {
if (!isActive(Rotation) && getForceActivateRotation()) { if (!isActive(Rotation) && getForceActivateRotation()) {
@ -4144,7 +4335,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining()); 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()) { if (isActive()) {
float dt = myAvatar.getCharacterController()->getFollowTime(); float dt = myAvatar.getCharacterController()->getFollowTime();
decrementTimeRemaining(dt); decrementTimeRemaining(dt);
@ -4161,6 +4352,11 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co
glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix),
sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); sensorLinearDisplacement + extractTranslation(currentBodyMatrix));
if (myAvatar.getSitStandStateChange()) {
myAvatar.setSitStandStateChange(false);
deactivate(Vertical);
setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor()));
}
return newBodyMat; return newBodyMat;
} else { } else {
return currentBodyMatrix; return currentBodyMatrix;

View file

@ -21,7 +21,6 @@
#include <AvatarConstants.h> #include <AvatarConstants.h>
#include <avatars-renderer/Avatar.h> #include <avatars-renderer/Avatar.h>
#include <avatars-renderer/ScriptAvatar.h> #include <avatars-renderer/ScriptAvatar.h>
#include <ClientTraitsHandler.h>
#include <controllers/Pose.h> #include <controllers/Pose.h>
#include <controllers/Actions.h> #include <controllers/Actions.h>
#include <EntityItem.h> #include <EntityItem.h>
@ -142,6 +141,8 @@ class MyAvatar : public Avatar {
* @property {number} walkSpeed * @property {number} walkSpeed
* @property {number} walkBackwardSpeed * @property {number} walkBackwardSpeed
* @property {number} sprintSpeed * @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 * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the
* registration point of the 3D model. * 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 walkSpeed READ getWalkSpeed WRITE setWalkSpeed);
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed); Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed);
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed); 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_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right"; const QString DOMINANT_RIGHT_HAND = "right";
@ -262,6 +266,15 @@ public:
}; };
Q_ENUM(DriveKeys) Q_ENUM(DriveKeys)
enum SitStandModelType {
ForceSit = 0,
ForceStand,
Auto,
DisableHMDLean,
NumSitStandTypes
};
Q_ENUM(SitStandModelType)
explicit MyAvatar(QThread* thread); explicit MyAvatar(QThread* thread);
virtual ~MyAvatar(); virtual ~MyAvatar();
@ -1121,12 +1134,21 @@ public:
void setIsInWalkingState(bool isWalking); void setIsInWalkingState(bool isWalking);
bool getIsInWalkingState() const; 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); void setWalkSpeed(float value);
float getWalkSpeed() const; float getWalkSpeed() const;
void setWalkBackwardSpeed(float value); void setWalkBackwardSpeed(float value);
float getWalkBackwardSpeed() const; float getWalkBackwardSpeed() const;
void setSprintSpeed(float value); void setSprintSpeed(float value);
float getSprintSpeed() const; float getSprintSpeed() const;
void setSitStandStateChange(bool stateChanged);
float getSitStandStateChange() const;
void updateSitStandState(float newHeightReading, float dt);
QVector<QString> getScriptUrls(); QVector<QString> getScriptUrls();
@ -1532,6 +1554,7 @@ signals:
*/ */
void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
private slots: private slots:
void leaveDomain(); void leaveDomain();
void updateCollisionCapsuleCache(); void updateCollisionCapsuleCache();
@ -1742,7 +1765,7 @@ private:
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); 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; bool getForceActivateRotation() const;
void setForceActivateRotation(bool val); void setForceActivateRotation(bool val);
bool getForceActivateVertical() const; bool getForceActivateVertical() const;
@ -1751,6 +1774,7 @@ private:
void setForceActivateHorizontal(bool val); void setForceActivateHorizontal(bool val);
bool getToggleHipsFollowing() const; bool getToggleHipsFollowing() const;
void setToggleHipsFollowing(bool followHead); void setToggleHipsFollowing(bool followHead);
bool _squatDetected { false };
std::atomic<bool> _forceActivateRotation { false }; std::atomic<bool> _forceActivateRotation { false };
std::atomic<bool> _forceActivateVertical { false }; std::atomic<bool> _forceActivateVertical { false };
std::atomic<bool> _forceActivateHorizontal { false }; std::atomic<bool> _forceActivateHorizontal { false };
@ -1820,10 +1844,13 @@ private:
std::mutex _pinnedJointsMutex; std::mutex _pinnedJointsMutex;
std::vector<int> _pinnedJoints; std::vector<int> _pinnedJoints;
void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize);
// height of user in sensor space, when standing erect. // height of user in sensor space, when standing erect.
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT }; ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
float _averageUserHeightSensorSpace { _userHeight.get() };
void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); bool _sitStandStateChange { false };
ThreadSafeValueCache<bool> _lockSitStandState { false };
// max unscaled forward movement speed // max unscaled forward movement speed
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
@ -1831,6 +1858,11 @@ private:
ThreadSafeValueCache<float> _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; ThreadSafeValueCache<float> _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR };
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
bool _isInWalkingState { false }; 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 // load avatar scripts once when rig is ready
bool _shouldLoadScripts { false }; bool _shouldLoadScripts { false };

View file

@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
} }
glm::mat4 hipsMat; 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 // then we use center of gravity model
hipsMat = myAvatar->deriveBodyUsingCgModel(); hipsMat = myAvatar->deriveBodyUsingCgModel();
} else { } else {
@ -250,6 +250,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose); bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose);
bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose); bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose);
if (spine2Exists && headExists && hipsExists) { if (spine2Exists && headExists && hipsExists) {
AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace());
glm::vec3 u, v, w; glm::vec3 u, v, w;
glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f);

View file

@ -315,7 +315,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) {
return installedAppsFromMarketplace; return installedAppsFromMarketplace;
} }
bool QmlCommerce::installApp(const QString& itemHref) { bool QmlCommerce::installApp(const QString& itemHref, const bool& alsoOpenImmediately) {
if (!QDir(_appsPath).exists()) { if (!QDir(_appsPath).exists()) {
if (!QDir().mkdir(_appsPath)) { if (!QDir().mkdir(_appsPath)) {
qCDebug(commerce) << "Couldn't make _appsPath directory."; qCDebug(commerce) << "Couldn't make _appsPath directory.";
@ -325,7 +325,8 @@ bool QmlCommerce::installApp(const QString& itemHref) {
QUrl appHref(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) { if (!request) {
qCDebug(commerce) << "Couldn't create resource request for app."; qCDebug(commerce) << "Couldn't create resource request for app.";
@ -357,13 +358,22 @@ bool QmlCommerce::installApp(const QString& itemHref) {
QJsonObject appFileJsonObject = appFileJsonDocument.object(); QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString scriptUrl = appFileJsonObject["scriptURL"].toString(); QString scriptUrl = appFileJsonObject["scriptURL"].toString();
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) { // Don't try to re-load (install) a script if it's already running
qCDebug(commerce) << "Couldn't load script."; QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts();
return false; 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; return true;
}); });
request->send(); request->send();

View file

@ -88,7 +88,7 @@ protected:
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = ""); 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 uninstallApp(const QString& appHref);
Q_INVOKABLE bool openApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref);

View file

@ -46,11 +46,17 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float
return retVal; return retVal;
} }
bool ClipboardScriptingInterface::importEntities(const QString& filename) { bool ClipboardScriptingInterface::importEntities(
const QString& filename,
const bool isObservable,
const qint64 callerId
) {
bool retVal; bool retVal;
BLOCKING_INVOKE_METHOD(qApp, "importEntities", BLOCKING_INVOKE_METHOD(qApp, "importEntities",
Q_RETURN_ARG(bool, retVal), 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; return retVal;
} }

View file

@ -50,9 +50,11 @@ public:
* You can generate a JSON file using {@link Clipboard.exportEntities}. * You can generate a JSON file using {@link Clipboard.exportEntities}.
* @function Clipboard.importEntities * @function Clipboard.importEntities
* @param {string} filename Path and name of file to import. * @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>. * @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 /**jsdoc
* Export the entities specified to a JSON file. * Export the entities specified to a JSON file.

View file

@ -259,6 +259,39 @@ void setupPreferences() {
auto preference = new CheckPreference(VR_MOVEMENT, "Show room boundaries while teleporting", getter, setter); auto preference = new CheckPreference(VR_MOVEMENT, "Show room boundaries while teleporting", getter, setter);
preferences->addPreference(preference); 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 getter = [=]()->float { return myAvatar->getUserHeight(); };
auto setter = [=](float value) { myAvatar->setUserHeight(value); }; auto setter = [=](float value) { myAvatar->setUserHeight(value); };

View file

@ -59,6 +59,7 @@
#include "raypick/PointerScriptingInterface.h" #include "raypick/PointerScriptingInterface.h"
#include <display-plugins/CompositorHelper.h> #include <display-plugins/CompositorHelper.h>
#include "AboutUtil.h" #include "AboutUtil.h"
#include "ResourceRequestObserver.h"
static int MAX_WINDOW_SIZE = 4096; static int MAX_WINDOW_SIZE = 4096;
static const float METERS_TO_INCHES = 39.3701f; 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("Window", DependencyManager::get<WindowScriptingInterface>().data());
_webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); _webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
_webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
// Override min fps for tablet UI, for silky smooth scrolling // Override min fps for tablet UI, for silky smooth scrolling
setMaxFPS(90); setMaxFPS(90);

View file

@ -51,6 +51,8 @@ public:
bool getMirrorFlag() const { return _mirrorFlag; } bool getMirrorFlag() const { return _mirrorFlag; }
void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; } void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; }
float getFrame() const { return _frame; }
void loadURL(const QString& url); void loadURL(const QString& url);
protected: protected:

View file

@ -31,6 +31,7 @@
#include "AnimSkeleton.h" #include "AnimSkeleton.h"
#include "AnimUtil.h" #include "AnimUtil.h"
#include "IKTarget.h" #include "IKTarget.h"
#include "PathUtils.h"
static int nextRigId = 1; 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); _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() { void Rig::restoreAnimation() {
if (_userAnimState.clipNodeEnum != UserAnimState::None) { if (_userAnimState.clipNodeEnum != UserAnimState::None) {
_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 { QStringList Rig::getAnimationRoles() const {
if (_animNode) { if (_animNode) {
QStringList list; QStringList list;
@ -208,11 +243,17 @@ void Rig::restoreRoleAnimation(const QString& role) {
void Rig::destroyAnimGraph() { void Rig::destroyAnimGraph() {
_animSkeleton.reset(); _animSkeleton.reset();
_animLoader.reset(); _animLoader.reset();
_networkLoader.reset();
_animNode.reset(); _animNode.reset();
_internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses.clear();
_internalPoseSet._absolutePoses.clear(); _internalPoseSet._absolutePoses.clear();
_internalPoseSet._overridePoses.clear(); _internalPoseSet._overridePoses.clear();
_internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.clear();
_networkNode.reset();
_networkPoseSet._relativePoses.clear();
_networkPoseSet._absolutePoses.clear();
_networkPoseSet._overridePoses.clear();
_networkPoseSet._overrideFlags.clear();
_numOverrides = 0; _numOverrides = 0;
_leftEyeJointChildren.clear(); _leftEyeJointChildren.clear();
_rightEyeJointChildren.clear(); _rightEyeJointChildren.clear();
@ -229,14 +270,24 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff
_internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses.clear();
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
_networkPoseSet._relativePoses.clear();
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
_internalPoseSet._overridePoses.clear(); _internalPoseSet._overridePoses.clear();
_internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
_internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.clear();
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
_networkPoseSet._overridePoses.clear();
_networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
_networkPoseSet._overrideFlags.clear();
_networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
_numOverrides = 0; _numOverrides = 0;
buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
@ -270,6 +321,18 @@ void Rig::reset(const FBXGeometry& geometry) {
_internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.clear();
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); _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; _numOverrides = 0;
buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
@ -1049,26 +1112,56 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
updateAnimationStateHandlers(); updateAnimationStateHandlers();
_animVars.setRigToGeometryTransform(_rigToGeometryTransform); _animVars.setRigToGeometryTransform(_rigToGeometryTransform);
if (_networkNode) {
_networkVars.setRigToGeometryTransform(_rigToGeometryTransform);
}
AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains, AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains,
getGeometryToRigTransform(), rigToWorldTransform); getGeometryToRigTransform(), rigToWorldTransform);
// evaluate the animation // evaluate the animation
AnimVariantMap triggersOut; AnimVariantMap triggersOut;
AnimVariantMap networkTriggersOut;
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); _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()) { if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
// animations haven't fully loaded yet. // animations haven't fully loaded yet.
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
} }
if ((int)_networkPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
// animations haven't fully loaded yet.
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
}
_lastAnimVars = _animVars; _lastAnimVars = _animVars;
_animVars.clearTriggers(); _animVars.clearTriggers();
_animVars = triggersOut; _animVars = triggersOut;
_networkVars.clearTriggers();
_networkVars = networkTriggersOut;
_lastContext = context; _lastContext = context;
} }
applyOverridePoses(); applyOverridePoses();
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
// copy internal poses to external poses // copy internal poses to external poses
{ {
QWriteLocker writeLock(&_externalPoseSetLock); QWriteLocker writeLock(&_externalPoseSetLock);
@ -1672,11 +1765,14 @@ void Rig::initAnimGraph(const QUrl& url) {
_animGraphURL = url; _animGraphURL = url;
_animNode.reset(); _animNode.reset();
_networkNode.reset();
// load the anim graph // load the anim graph
_animLoader.reset(new AnimNodeLoader(url)); _animLoader.reset(new AnimNodeLoader(url));
auto networkUrl = PathUtils::resourcesUrl("avatar/network-animation.json");
_networkLoader.reset(new AnimNodeLoader(networkUrl));
std::weak_ptr<AnimSkeleton> weakSkeletonPtr = _animSkeleton; 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; _animNode = nodeIn;
// abort load if the previous skeleton was deleted. // abort load if the previous skeleton was deleted.
@ -1703,7 +1799,33 @@ void Rig::initAnimGraph(const QUrl& url) {
emit onLoadComplete(); emit onLoadComplete();
}); });
connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { 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)) { if (isIndexValid(i)) {
// rotations are in absolute rig frame. // rotations are in absolute rig frame.
glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot(); 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); data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot);
// translations are in relative frame but scaled so that they are in meters, // translations are in relative frame but scaled so that they are in meters,
// instead of geometry units. // instead of geometry units.
glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans(); 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); data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans);
} else { } else {
data.translationIsDefaultPose = true; data.translationIsDefaultPose = true;

View file

@ -113,7 +113,10 @@ public:
void destroyAnimGraph(); void destroyAnimGraph();
void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
void triggerNetworkAnimation(const QString& animName);
void restoreAnimation(); void restoreAnimation();
void restoreNetworkAnimation();
QStringList getAnimationRoles() const; QStringList getAnimationRoles() const;
void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
void restoreRoleAnimation(const QString& role); void restoreRoleAnimation(const QString& role);
@ -269,6 +272,7 @@ protected:
// Only accessed by the main thread // Only accessed by the main thread
PoseSet _internalPoseSet; PoseSet _internalPoseSet;
PoseSet _networkPoseSet;
// Copy of the _poseSet for external threads. // Copy of the _poseSet for external threads.
PoseSet _externalPoseSet; PoseSet _externalPoseSet;
@ -300,9 +304,12 @@ protected:
QUrl _animGraphURL; QUrl _animGraphURL;
std::shared_ptr<AnimNode> _animNode; std::shared_ptr<AnimNode> _animNode;
std::shared_ptr<AnimNode> _networkNode;
std::shared_ptr<AnimSkeleton> _animSkeleton; std::shared_ptr<AnimSkeleton> _animSkeleton;
std::unique_ptr<AnimNodeLoader> _animLoader; std::unique_ptr<AnimNodeLoader> _animLoader;
std::unique_ptr<AnimNodeLoader> _networkLoader;
AnimVariantMap _animVars; AnimVariantMap _animVars;
AnimVariantMap _networkVars;
enum class RigRole { enum class RigRole {
Idle = 0, Idle = 0,
@ -315,6 +322,25 @@ protected:
RigRole _state { RigRole::Idle }; RigRole _state { RigRole::Idle };
RigRole _desiredState { RigRole::Idle }; RigRole _desiredState { RigRole::Idle };
float _desiredStateAge { 0.0f }; 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 { struct UserAnimState {
enum ClipNodeEnum { enum ClipNodeEnum {
@ -349,6 +375,7 @@ protected:
}; };
UserAnimState _userAnimState; UserAnimState _userAnimState;
NetworkAnimState _networkAnimState;
std::map<QString, RoleAnimState> _roleAnimStates; std::map<QString, RoleAnimState> _roleAnimStates;
float _leftHandOverlayAlpha { 0.0f }; float _leftHandOverlayAlpha { 0.0f };
@ -382,6 +409,7 @@ protected:
int _rigId; int _rigId;
bool _headEnabled { false }; bool _headEnabled { false };
bool _sendNetworkNode { false };
AnimContext _lastContext; AnimContext _lastContext;
AnimVariantMap _lastAnimVars; AnimVariantMap _lastAnimVars;

View file

@ -114,27 +114,29 @@ void Avatar::setShowNamesAboveHeads(bool show) {
} }
AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition; float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition);
float oneFrameDistance = glm::length(currentPosition - _lastPosition); if (oneFrameDistance > (config._minTriggerDistance * _scale)) {
const float MAX_TRANSIT_DISTANCE = 30.0f; if (oneFrameDistance < (config._maxTriggerDistance * _scale)) {
float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale; start(deltaTime, _lastPosition, avatarPosition, config);
if (oneFrameDistance > config._triggerDistance && !_isTransiting) {
if (oneFrameDistance < scaledMaxTransitDistance) {
start(deltaTime, _lastPosition, currentPosition, config);
} else { } else {
_lastPosition = currentPosition; _lastPosition = avatarPosition;
return Status::ABORT_TRANSIT; _status = Status::ABORT_TRANSIT;
} }
} }
_lastPosition = currentPosition; _lastPosition = avatarPosition;
_status = updatePosition(deltaTime); _status = updatePosition(deltaTime);
if (_isActive && oneFrameDistance > (config._abortDistance * _scale) && _status == Status::POST_TRANSIT) {
reset();
_status = Status::ENDED;
}
return _status; return _status;
} }
void AvatarTransit::reset() { void AvatarTransit::reset() {
_lastPosition = _endPosition; _lastPosition = _endPosition;
_currentPosition = _endPosition; _currentPosition = _endPosition;
_isTransiting = false; _isActive = false;
} }
void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) { void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) {
_startPosition = startPosition; _startPosition = startPosition;
@ -143,12 +145,14 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const
_transitLine = endPosition - startPosition; _transitLine = endPosition - startPosition;
_totalDistance = glm::length(_transitLine); _totalDistance = glm::length(_transitLine);
_easeType = config._easeType; _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; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance;
_totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; _transitTime = (float)transitFrames / AVATAR_TRANSIT_FRAMES_PER_SECOND;
_currentTime = 0.0f; _totalTime = _transitTime + _preTransitTime + _postTransitTime;
_isTransiting = true; _currentTime = _isActive ? _preTransitTime : 0.0f;
_isActive = true;
} }
float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { 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) { AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) {
Status status = Status::IDLE; Status status = Status::IDLE;
if (_isTransiting) { if (_isActive) {
float nextTime = _currentTime + deltaTime; float nextTime = _currentTime + deltaTime;
if (nextTime >= _totalTime) { if (nextTime < _preTransitTime) {
_currentPosition = _endPosition; _currentPosition = _startPosition;
_isTransiting = false; status = Status::PRE_TRANSIT;
status = Status::END_TRANSIT;
} else {
if (_currentTime == 0) { if (_currentTime == 0) {
status = Status::STARTED;
}
} else if (nextTime < _totalTime - _postTransitTime){
status = Status::TRANSITING;
if (_currentTime <= _preTransitTime) {
status = Status::START_TRANSIT; status = Status::START_TRANSIT;
} else { } 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; _currentTime = nextTime;
} }
return status; return status;
} }
bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) {
nextPosition = _currentPosition;
return _isTransiting;
}
Avatar::Avatar(QThread* thread) : Avatar::Avatar(QThread* thread) :
_voiceSphereID(GeometryCache::UNKNOWN_ID) _voiceSphereID(GeometryCache::UNKNOWN_ID)
{ {
@ -536,16 +546,10 @@ void Avatar::relayJointDataToChildren() {
void Avatar::simulate(float deltaTime, bool inView) { void Avatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate"); PROFILE_RANGE(simulation, "simulate");
if (_transit.isTransiting()) { _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition;
glm::vec3 nextPosition; if (!hasParent()) {
if (_transit.getNextPosition(nextPosition)) { setLocalPosition(_globalPosition);
_globalPosition = nextPosition;
_globalPositionChanged = usecTimestampNow();
if (!hasParent()) {
setLocalPosition(nextPosition);
}
}
} }
_simulationRate.increment(); _simulationRate.increment();
@ -558,7 +562,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "updateJoints"); PROFILE_RANGE(simulation, "updateJoints");
if (inView) { if (inView) {
Head* head = getHead(); Head* head = getHead();
if (_hasNewJointData || _transit.isTransiting()) { if (_hasNewJointData || _transit.isActive()) {
_skeletonModel->getRig().copyJointsFromJointData(_jointData); _skeletonModel->getRig().copyJointsFromJointData(_jointData);
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
_skeletonModel->getRig().computeExternalPoses(rootTransform); _skeletonModel->getRig().computeExternalPoses(rootTransform);
@ -1731,6 +1735,7 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
glm::vec3 Avatar::getWorldFeetPosition() { glm::vec3 Avatar::getWorldFeetPosition() {
ShapeInfo shapeInfo; ShapeInfo shapeInfo;
computeShapeInfo(shapeInfo); computeShapeInfo(shapeInfo);
glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight
glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y - halfExtents.x, 0.0f); 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); std::lock_guard<std::mutex> lock(_transitLock);
_transit.setScale(avatarScale);
return _transit.update(deltaTime, avatarPosition, config); 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) { void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
std::lock_guard<std::mutex> lock(_materialsLock); std::lock_guard<std::mutex> lock(_materialsLock);
_materials[parentMaterialName].push(material); _materials[parentMaterialName].push(material);

View file

@ -56,9 +56,13 @@ class AvatarTransit {
public: public:
enum Status { enum Status {
IDLE = 0, IDLE = 0,
STARTED,
PRE_TRANSIT,
START_TRANSIT, START_TRANSIT,
TRANSITING, TRANSITING,
END_TRANSIT, END_TRANSIT,
POST_TRANSIT,
ENDED,
ABORT_TRANSIT ABORT_TRANSIT
}; };
@ -72,20 +76,20 @@ public:
struct TransitConfig { struct TransitConfig {
TransitConfig() {}; TransitConfig() {};
int _totalFrames { 0 }; int _totalFrames { 0 };
int _framesPerMeter { 0 }; float _framesPerMeter { 0.0f };
bool _isDistanceBased { false }; bool _isDistanceBased { false };
float _triggerDistance { 0 }; float _minTriggerDistance { 0.0f };
float _maxTriggerDistance { 0.0f };
float _abortDistance{ 0.0f };
EaseType _easeType { EaseType::EASE_OUT }; EaseType _easeType { EaseType::EASE_OUT };
}; };
AvatarTransit() {}; AvatarTransit() {};
Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config); Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config);
Status getStatus() { return _status; } Status getStatus() { return _status; }
bool isTransiting() { return _isTransiting; } bool isActive() { return _isActive; }
glm::vec3 getCurrentPosition() { return _currentPosition; } glm::vec3 getCurrentPosition() { return _currentPosition; }
bool getNextPosition(glm::vec3& nextPosition);
glm::vec3 getEndPosition() { return _endPosition; } glm::vec3 getEndPosition() { return _endPosition; }
float getTransitTime() { return _totalTime; }
void setScale(float scale) { _scale = scale; } void setScale(float scale) { _scale = scale; }
void reset(); void reset();
@ -93,7 +97,7 @@ private:
Status updatePosition(float deltaTime); Status updatePosition(float deltaTime);
void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config); void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config);
float getEaseValue(AvatarTransit::EaseType type, float value); float getEaseValue(AvatarTransit::EaseType type, float value);
bool _isTransiting { false }; bool _isActive { false };
glm::vec3 _startPosition; glm::vec3 _startPosition;
glm::vec3 _endPosition; glm::vec3 _endPosition;
@ -103,7 +107,10 @@ private:
glm::vec3 _transitLine; glm::vec3 _transitLine;
float _totalDistance { 0.0f }; float _totalDistance { 0.0f };
float _preTransitTime { 0.0f };
float _totalTime { 0.0f }; float _totalTime { 0.0f };
float _transitTime { 0.0f };
float _postTransitTime { 0.0f };
float _currentTime { 0.0f }; float _currentTime { 0.0f };
EaseType _easeType { EaseType::EASE_OUT }; EaseType _easeType { EaseType::EASE_OUT };
Status _status { Status::IDLE }; Status _status { Status::IDLE };
@ -431,11 +438,7 @@ public:
virtual scriptable::ScriptableModelBase getScriptableModel() override; virtual scriptable::ScriptableModelBase getScriptableModel() override;
std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); }; std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); };
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config);
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config);
void setTransitScale(float scale);
void overrideNextPackagePositionData(const glm::vec3& position);
signals: signals:
void targetScaleChanged(float targetScale); void targetScaleChanged(float targetScale);

View file

@ -44,6 +44,7 @@
#include "AvatarLogging.h" #include "AvatarLogging.h"
#include "AvatarTraits.h" #include "AvatarTraits.h"
#include "ClientTraitsHandler.h" #include "ClientTraitsHandler.h"
#include "ResourceRequestObserver.h"
//#define WANT_DEBUG //#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); 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; _serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset;
if (_globalPosition != newValue) { auto oneStepDistance = glm::length(_globalPosition - _serverPosition);
_globalPosition = newValue; 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; _globalPositionChanged = now;
} }
sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
int numBytesRead = sourceBuffer - startSection; int numBytesRead = sourceBuffer - startSection;
_globalPositionRate.increment(numBytesRead); _globalPositionRate.increment(numBytesRead);
_globalPositionUpdateRate.increment(); _globalPositionUpdateRate.increment();
// if we don't have a parent, make sure to also set our local position
if (!hasParent()) {
setLocalPosition(newValue);
}
} }
if (hasAvatarBoundingBox) { if (hasAvatarBoundingBox) {
@ -2167,10 +2170,6 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) {
} }
} }
if (_overrideGlobalPosition) {
_overrideGlobalPosition = false;
}
doneEncoding(cullSmallData); doneEncoding(cullSmallData);
static AvatarDataSequenceNumber sequenceNumber = 0; static AvatarDataSequenceNumber sequenceNumber = 0;
@ -2214,11 +2213,21 @@ void AvatarData::updateJointMappings() {
} }
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { 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(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
DependencyManager::get<ResourceRequestObserver>()->update(
_skeletonModelURL, -1, "AvatarData::updateJointMappings");
QNetworkReply* networkReply = networkAccessManager.get(networkRequest); QNetworkReply* networkReply = networkAccessManager.get(networkRequest);
//
////
connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply);
} }
} }

View file

@ -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). // This is the start location in the Sandbox (xyz: 6270, 211, 6000).
const glm::vec3 START_LOCATION(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 { enum KeyState {
NO_KEY_DOWN = 0, NO_KEY_DOWN = 0,
INSERT_KEY_DOWN, 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 // where Entities are located. This is currently only used by the mixer to decide how often to send
// updates about one avatar to another. // updates about one avatar to another.
glm::vec3 _globalPosition { 0, 0, 0 }; glm::vec3 _globalPosition { 0, 0, 0 };
glm::vec3 _globalPositionOverride { 0, 0, 0 }; glm::vec3 _serverPosition { 0, 0, 0 };
bool _overrideGlobalPosition { false };
quint64 _globalPositionChanged { 0 }; quint64 _globalPositionChanged { 0 };
quint64 _avatarBoundingBoxChanged { 0 }; quint64 _avatarBoundingBoxChanged { 0 };

View file

@ -31,7 +31,27 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) :
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride"); 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() { void ClientTraitsHandler::resetForNewMixer() {
Lock lock(_traitLock);
// re-set the current version to 0 // re-set the current version to 0
_currentTraitVersion = AvatarTraits::DEFAULT_TRAIT_VERSION; _currentTraitVersion = AvatarTraits::DEFAULT_TRAIT_VERSION;
@ -46,6 +66,8 @@ void ClientTraitsHandler::resetForNewMixer() {
} }
void ClientTraitsHandler::sendChangedTraitsToMixer() { void ClientTraitsHandler::sendChangedTraitsToMixer() {
Lock lock(_traitLock);
if (hasChangedTraits() || _shouldPerformInitialSend) { if (hasChangedTraits() || _shouldPerformInitialSend) {
// we have at least one changed trait to send // we have at least one changed trait to send
@ -113,6 +135,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) { void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
if (sendingNode->getType() == NodeType::AvatarMixer) { if (sendingNode->getType() == NodeType::AvatarMixer) {
Lock lock(_traitLock);
while (message->getBytesLeftToRead()) { while (message->getBytesLeftToRead()) {
AvatarTraits::TraitType traitType; AvatarTraits::TraitType traitType;
message->readPrimitive(&traitType); message->readPrimitive(&traitType);

View file

@ -26,14 +26,11 @@ public:
void sendChangedTraitsToMixer(); void sendChangedTraitsToMixer();
bool hasChangedTraits() { return _hasChangedTraits; } bool hasChangedTraits() const { return _hasChangedTraits; }
void markTraitUpdated(AvatarTraits::TraitType updatedTrait) void markTraitUpdated(AvatarTraits::TraitType updatedTrait);
{ _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; } void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID);
void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID);
{ _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; }
void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID)
{ _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; }
void resetForNewMixer(); void resetForNewMixer();
@ -41,17 +38,21 @@ public slots:
void processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private: private:
using Mutex = std::recursive_mutex;
using Lock = std::lock_guard<Mutex>;
enum ClientTraitStatus { enum ClientTraitStatus {
Unchanged, Unchanged,
Updated, Updated,
Deleted Deleted
}; };
AvatarData* _owningAvatar; AvatarData* const _owningAvatar;
Mutex _traitLock;
AvatarTraits::AssociatedTraitValues<ClientTraitStatus, Unchanged> _traitStatuses; 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 }; AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION };
bool _shouldPerformInitialSend { false }; bool _shouldPerformInitialSend { false };

View file

@ -200,7 +200,8 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) {
_filterDataMap.insert(entityID, filterData); _filterDataMap.insert(entityID, filterData);
_lock.unlock(); _lock.unlock();
auto scriptRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, scriptURL); auto scriptRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(
this, scriptURL, true, -1, "EntityEditFilters::addFilter");
if (!scriptRequest) { if (!scriptRequest) {
qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString(); qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString();
scriptRequestFinished(entityID); scriptRequestFinished(entityID);

View file

@ -953,7 +953,8 @@ bool GLTFReader::doesResourceExist(const QString& url) {
} }
std::tuple<bool, QByteArray> GLTFReader::requestData(QUrl& 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) { if (!request) {
return std::make_tuple(false, QByteArray()); return std::make_tuple(false, QByteArray());

View file

@ -443,7 +443,8 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file
} }
std::tuple<bool, QByteArray> requestData(QUrl& url) { 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) { if (!request) {
return std::make_tuple(false, QByteArray()); return std::make_tuple(false, QByteArray());

View file

@ -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 // 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 // The actual requested url is _activeUrl and will not contain the fragment
_url.setFragment("head"); _url.setFragment("head");
_ktxHeaderRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(this, _activeUrl); _ktxHeaderRequest = DependencyManager::get<ResourceManager>()->createResourceRequest(
this, _activeUrl, true, -1, "NetworkTexture::makeRequest");
if (!_ktxHeaderRequest) { if (!_ktxHeaderRequest) {
qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); 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; 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) { if (!_ktxMipRequest) {
qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString();

View file

@ -24,8 +24,12 @@
static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5; static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5;
AssetResourceRequest::AssetResourceRequest(const QUrl& url) : AssetResourceRequest::AssetResourceRequest(
ResourceRequest(url) 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); _lastProgressDebug = p_high_resolution_clock::now() - std::chrono::seconds(DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS);
} }

View file

@ -22,7 +22,11 @@
class AssetResourceRequest : public ResourceRequest { class AssetResourceRequest : public ResourceRequest {
Q_OBJECT Q_OBJECT
public: public:
AssetResourceRequest(const QUrl& url); AssetResourceRequest(
const QUrl& url,
const bool isObservable = true,
const qint64 callerId = -1,
const QString& extra = "");
virtual ~AssetResourceRequest() override; virtual ~AssetResourceRequest() override;
protected: protected:

View file

@ -14,7 +14,8 @@
#include "ResourceManager.h" #include "ResourceManager.h"
AtpReply::AtpReply(const QUrl& url, QObject* parent) : 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); setOperation(QNetworkAccessManager::GetOperation);
connect(_resourceRequest, &AssetResourceRequest::progress, this, &AtpReply::downloadProgress); connect(_resourceRequest, &AssetResourceRequest::progress, this, &AtpReply::downloadProgress);

View file

@ -19,7 +19,12 @@
class FileResourceRequest : public ResourceRequest { class FileResourceRequest : public ResourceRequest {
Q_OBJECT Q_OBJECT
public: 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: protected:
virtual void doSend() override; virtual void doSend() override;

View file

@ -21,7 +21,12 @@
class HTTPResourceRequest : public ResourceRequest { class HTTPResourceRequest : public ResourceRequest {
Q_OBJECT Q_OBJECT
public: 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(); ~HTTPResourceRequest();
protected: protected:

View file

@ -10,6 +10,7 @@
// //
#include "ResourceCache.h" #include "ResourceCache.h"
#include "ResourceRequestObserver.h"
#include <cfloat> #include <cfloat>
#include <cmath> #include <cmath>
@ -252,7 +253,9 @@ ResourceCache::ResourceCache(QObject* parent) : QObject(parent) {
} }
} }
ResourceCache::~ResourceCache() {} ResourceCache::~ResourceCache() {
clearUnusedResources();
}
void ResourceCache::clearATPAssets() { void ResourceCache::clearATPAssets() {
{ {
@ -338,28 +341,31 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
} }
if (resource) { if (resource) {
removeUnusedResource(resource); removeUnusedResource(resource);
return resource;
} }
if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { if (!resource && !url.isValid() && !url.isEmpty() && fallback.isValid()) {
return getResource(fallback, QUrl()); resource = getResource(fallback, QUrl());
} }
resource = createResource( if (!resource) {
url, resource = createResource(
fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer<Resource>(), url,
extra); fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer<Resource>(),
resource->setSelf(resource); extra);
resource->setCache(this); resource->setSelf(resource);
resource->moveToThread(qApp->thread()); resource->setCache(this);
connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); resource->moveToThread(qApp->thread());
{ connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);
QWriteLocker locker(&_resourcesLock); {
_resources.insert(url, resource); 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; return resource;
} }
@ -682,7 +688,8 @@ void Resource::makeRequest() {
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } }); 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) { if (!_request) {
qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();

View file

@ -112,22 +112,28 @@ void ResourceManager::cleanup() {
_thread.wait(); _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 normalizedURL = normalizeURL(url);
auto scheme = normalizedURL.scheme(); auto scheme = normalizedURL.scheme();
ResourceRequest* request = nullptr; ResourceRequest* request = nullptr;
if (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) { 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) { } 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) { } else if (scheme == URL_SCHEME_ATP) {
if (!_atpSupportEnabled) { if (!_atpSupportEnabled) {
qCDebug(networking) << "ATP support not enabled, unable to create request for URL: " << url.url(); qCDebug(networking) << "ATP support not enabled, unable to create request for URL: " << url.url();
return nullptr; return nullptr;
} }
request = new AssetResourceRequest(normalizedURL); request = new AssetResourceRequest(normalizedURL, isObservable, callerId, extra);
} else { } else {
qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url(); qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url();
return nullptr; return nullptr;
@ -163,7 +169,7 @@ bool ResourceManager::resourceExists(const QUrl& url) {
return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200; return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200;
} else if (scheme == URL_SCHEME_ATP && _atpSupportEnabled) { } else if (scheme == URL_SCHEME_ATP && _atpSupportEnabled) {
auto request = new AssetResourceRequest(url); auto request = new AssetResourceRequest(url, ResourceRequest::IS_NOT_OBSERVABLE);
ByteRange range; ByteRange range;
range.fromInclusive = 1; range.fromInclusive = 1;
range.toExclusive = 1; range.toExclusive = 1;

View file

@ -34,7 +34,12 @@ public:
QString normalizeURL(const QString& urlString); QString normalizeURL(const QString& urlString);
QUrl normalizeURL(const QUrl& url); 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 init();
void cleanup(); void cleanup();

View file

@ -10,13 +10,13 @@
// //
#include "ResourceRequest.h" #include "ResourceRequest.h"
#include "ResourceRequestObserver.h"
#include <DependencyManager.h> #include <DependencyManager.h>
#include <StatTracker.h> #include <StatTracker.h>
#include <QtCore/QThread> #include <QtCore/QThread>
ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { }
void ResourceRequest::send() { void ResourceRequest::send() {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
@ -24,6 +24,10 @@ void ResourceRequest::send() {
return; return;
} }
if (_isObservable) {
DependencyManager::get<ResourceRequestObserver>()->update(_url, _callerId, _extra + " => ResourceRequest::send");
}
Q_ASSERT(_state == NotStarted); Q_ASSERT(_state == NotStarted);
_state = InProgress; _state = InProgress;

View file

@ -40,7 +40,20 @@ const QString STAT_FILE_RESOURCE_TOTAL_BYTES = "FILEBytesDownloaded";
class ResourceRequest : public QObject { class ResourceRequest : public QObject {
Q_OBJECT Q_OBJECT
public: 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; virtual ~ResourceRequest() = default;
enum State { enum State {
@ -99,6 +112,9 @@ protected:
bool _rangeRequestSuccessful { false }; bool _rangeRequestSuccessful { false };
uint64_t _totalSizeOfResource { 0 }; uint64_t _totalSizeOfResource { 0 };
int64_t _lastRecordedBytesDownloaded { 0 }; int64_t _lastRecordedBytesDownloaded { 0 };
bool _isObservable;
qint64 _callerId;
QString _extra;
}; };
#endif #endif

View file

@ -734,11 +734,17 @@ QString getMarketplaceID(const QString& urlString) {
return QString(); 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 trimmedUrl = urlString.trimmed();
QString marketplaceID = getMarketplaceID(trimmedUrl); QString marketplaceID = getMarketplaceID(trimmedUrl);
auto request = qDebug() << "!!!!! going to createResourceRequest " << callerId;
std::unique_ptr<ResourceRequest>(DependencyManager::get<ResourceManager>()->createResourceRequest(this, trimmedUrl)); auto request = std::unique_ptr<ResourceRequest>(
DependencyManager::get<ResourceManager>()->createResourceRequest(
this, trimmedUrl, isObservable, callerId, "Octree::readFromURL"));
if (!request) { if (!request) {
return false; 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 // decide if this is binary SVO or JSON-formatted SVO
QIODevice *device = inputStream.device(); QIODevice *device = inputStream.device();
char firstChar; char firstChar;
@ -809,7 +819,11 @@ QJsonDocument addMarketplaceIDToDocumentEntities(QJsonDocument& doc, const QStri
const int READ_JSON_BUFFER_SIZE = 2048; 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 // 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. // we get an eof. Leave streamLength parameter for consistency.

View file

@ -210,7 +210,7 @@ public:
// Octree importers // Octree importers
bool readFromFile(const char* filename); 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 readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="");
bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream); bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream);
bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="");

View file

@ -138,7 +138,8 @@ QString FileScriptingInterface::convertUrlToPath(QUrl url) {
// this function is not in use // this function is not in use
void FileScriptingInterface::downloadZip(QString path, const QString link) { void FileScriptingInterface::downloadZip(QString path, const QString link) {
QUrl url = QUrl(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]{ connect(request, &ResourceRequest::finished, this, [this, path]{
unzipFile(path, ""); // so intellisense isn't mad unzipFile(path, ""); // so intellisense isn't mad
}); });

View file

@ -109,7 +109,8 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
#ifdef THREAD_DEBUGGING #ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif #endif
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(nullptr, url); auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
nullptr, url, true, -1, "ScriptCache::getScriptContents");
Q_ASSERT(request); Q_ASSERT(request);
request->setCacheEnabled(!forceDownload); request->setCacheEnabled(!forceDownload);
connect(request, &ResourceRequest::finished, this, [=]{ scriptContentAvailable(maxRetries); }); 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") qCDebug(scriptengine) << QString("Retrying script request [%1 / %2]: %3")
.arg(attempt).arg(maxRetries).arg(url.toString()); .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); Q_ASSERT(request);
// We've already made a request, so the cache must be disabled or it wasn't there, so enabling // We've already made a request, so the cache must be disabled or it wasn't there, so enabling

View file

@ -21,6 +21,7 @@
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h> #include <NetworkingConstants.h>
#include "ResourceRequestObserver.h"
#include "ScriptEngine.h" #include "ScriptEngine.h"
const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/"; const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/";
@ -189,7 +190,7 @@ void XMLHttpRequestClass::send(const QScriptValue& data) {
} }
void XMLHttpRequestClass::doSend() { void XMLHttpRequestClass::doSend() {
DependencyManager::get<ResourceRequestObserver>()->update(_url, -1, "XMLHttpRequestClass::doSend");
_reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData); _reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData);
connectToReply(_reply); connectToReply(_reply);

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

View 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);
};

View file

@ -85,6 +85,11 @@ private:
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) { if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError(); qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError();
return; 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();
}
} }
} }

View file

@ -463,7 +463,7 @@ bool OpenVrDisplayPlugin::internalActivate() {
auto chaperone = vr::VRChaperone(); auto chaperone = vr::VRChaperone();
if (chaperone) { if (chaperone) {
float const UI_RADIUS = 1.0f; 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 const UI_Z_OFFSET = 0.5;
float xSize, zSize; float xSize, zSize;

View file

@ -42,6 +42,9 @@ var TITLE_OFFSET = 60;
var CREATE_TOOLS_WIDTH = 490; var CREATE_TOOLS_WIDTH = 490;
var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; 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( var createToolsWindow = new CreateWindow(
Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml", Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml",
'Create Tools', '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 toolBar = (function () {
var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts
var that = {}, var that = {},
@ -303,11 +502,29 @@ var toolBar = (function () {
dialogWindow = null, dialogWindow = null,
tablet = null; tablet = null;
function applyProperties(originalProperties, newProperties) {
for (var key in newProperties) {
originalProperties[key] = newProperties[key];
}
}
function createNewEntity(properties) { function createNewEntity(properties) {
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
var position = getPositionToCreateEntity(); var position = getPositionToCreateEntity();
var entityID = null; 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) { if (position !== null && position !== undefined) {
var direction; var direction;
if (Camera.mode === "entity" || Camera.mode === "independent") { if (Camera.mode === "entity" || Camera.mode === "independent") {
@ -385,7 +602,7 @@ var toolBar = (function () {
Entities.editEntity(entityID, { Entities.editEntity(entityID, {
position: position position: position
}); });
selectionManager._update(); selectionManager._update(false, this);
} else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) {
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
} }
@ -397,9 +614,9 @@ var toolBar = (function () {
properties.type + " would be out of bounds."); properties.type + " would be out of bounds.");
} }
selectionManager.clearSelections(); selectionManager.clearSelections(this);
entityListTool.sendUpdate(); entityListTool.sendUpdate();
selectionManager.setSelections([entityID]); selectionManager.setSelections([entityID], this);
Window.setFocus(); Window.setFocus();
@ -550,7 +767,7 @@ var toolBar = (function () {
} }
deletedEntityTimer = Script.setTimeout(function () { deletedEntityTimer = Script.setTimeout(function () {
if (entitiesToDelete.length > 0) { if (entitiesToDelete.length > 0) {
selectionManager.removeEntities(entitiesToDelete); selectionManager.removeEntities(entitiesToDelete, this);
} }
entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); entityListTool.removeEntities(entitiesToDelete, selectionManager.selections);
entitiesToDelete = []; entitiesToDelete = [];
@ -679,14 +896,12 @@ var toolBar = (function () {
addButton("newLightButton", function () { addButton("newLightButton", function () {
createNewEntity({ createNewEntity({
type: "Light", type: "Light",
dimensions: DEFAULT_LIGHT_DIMENSIONS,
isSpotlight: false, isSpotlight: false,
color: { color: {
red: 150, red: 150,
green: 150, green: 150,
blue: 150 blue: 150
}, },
constantAttenuation: 1, constantAttenuation: 1,
linearAttenuation: 0, linearAttenuation: 0,
quadraticAttenuation: 0, quadraticAttenuation: 0,
@ -698,116 +913,30 @@ var toolBar = (function () {
addButton("newTextButton", function () { addButton("newTextButton", function () {
createNewEntity({ createNewEntity({
type: "Text", 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 () { 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({ createNewEntity({
type: "Model", type: "Image",
dimensions: {
x: 0.5385,
y: 0.2819,
z: 0.0092
},
shapeType: "box",
collisionless: true,
modelURL: IMAGE_MODEL,
textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE })
}); });
}); });
addButton("newWebButton", function () { addButton("newWebButton", function () {
createNewEntity({ createNewEntity({
type: "Web", type: "Web",
dimensions: {
x: 1.6,
y: 0.9,
z: 0.01
},
sourceUrl: "https://highfidelity.com/"
}); });
}); });
addButton("newZoneButton", function () { addButton("newZoneButton", function () {
createNewEntity({ createNewEntity({
type: "Zone", type: "Zone",
dimensions: {
x: 10,
y: 10,
z: 10
}
}); });
}); });
addButton("newParticleButton", function () { addButton("newParticleButton", function () {
createNewEntity({ createNewEntity({
type: "ParticleEffect", 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); gridTool.setVisible(false);
grid.setEnabled(false); grid.setEnabled(false);
propertiesTool.setVisible(false); propertiesTool.setVisible(false);
selectionManager.clearSelections(); selectionManager.clearSelections(this);
cameraManager.disable(); cameraManager.disable();
selectionDisplay.disableTriggerMapping(); selectionDisplay.disableTriggerMapping();
tablet.landscape = false; tablet.landscape = false;
@ -994,7 +1123,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) {
var entity = entityIconOverlayManager.findEntity(data.overlayID); var entity = entityIconOverlayManager.findEntity(data.overlayID);
if (entity !== null) { if (entity !== null) {
selectionManager.setSelections([entity]); selectionManager.setSelections([entity], this);
} }
} }
} }
@ -1141,7 +1270,7 @@ function mouseClickEvent(event) {
if (result === null || result === undefined) { if (result === null || result === undefined) {
if (!event.isShifted) { if (!event.isShifted) {
selectionManager.clearSelections(); selectionManager.clearSelections(this);
} }
return; return;
} }
@ -1193,9 +1322,9 @@ function mouseClickEvent(event) {
} }
if (!event.isShifted) { if (!event.isShifted) {
selectionManager.setSelections([foundEntity]); selectionManager.setSelections([foundEntity], this);
} else { } else {
selectionManager.addEntity(foundEntity, true); selectionManager.addEntity(foundEntity, true, this);
} }
if (wantDebug) { 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) { if (savedProperties.length > 0) {
SelectionManager.clearSelections(); SelectionManager.clearSelections(this);
pushCommandForSelections([], savedProperties); pushCommandForSelections([], savedProperties);
entityListTool.deleteEntities(deletedIDs); entityListTool.deleteEntities(deletedIDs);
} }
@ -1650,7 +1779,7 @@ function toggleSelectedEntitiesLocked() {
}); });
} }
entityListTool.sendUpdate(); entityListTool.sendUpdate();
selectionManager._update(); selectionManager._update(false, this);
} }
} }
@ -1664,7 +1793,7 @@ function toggleSelectedEntitiesVisible() {
}); });
} }
entityListTool.sendUpdate(); entityListTool.sendUpdate();
selectionManager._update(); selectionManager._update(false, this);
} }
} }
@ -1861,7 +1990,7 @@ function importSVO(importURL) {
} }
if (isActive) { if (isActive) {
selectionManager.setSelections(pastedEntityIDs); selectionManager.setSelections(pastedEntityIDs, this);
} }
} else { } else {
Window.notifyEditError("Can't import entities: entities would be out of bounds."); Window.notifyEditError("Can't import entities: entities would be out of bounds.");
@ -1909,7 +2038,7 @@ function deleteKey(value) {
} }
function deselectKey(value) { function deselectKey(value) {
if (value === 0) { // on release if (value === 0) { // on release
selectionManager.clearSelections(); selectionManager.clearSelections(this);
} }
} }
function toggleKey(value) { 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 // 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. // our selections, causing the edit widgets to display.
if (isActive) { if (isActive) {
selectionManager.setSelections(selectedEntityIDs); selectionManager.setSelections(selectedEntityIDs, this);
} }
} }
@ -2272,7 +2401,7 @@ var PropertiesTool = function (opts) {
} }
} }
pushCommandForSelections(); pushCommandForSelections();
selectionManager._update(); selectionManager._update(false, this);
} else if (data.type === 'parent') { } else if (data.type === 'parent') {
parentSelectedEntities(); parentSelectedEntities();
} else if (data.type === 'unparent') { } else if (data.type === 'unparent') {
@ -2301,7 +2430,7 @@ var PropertiesTool = function (opts) {
}); });
} }
pushCommandForSelections(); pushCommandForSelections();
selectionManager._update(); selectionManager._update(false, this);
} }
} else if (data.action === "moveAllToGrid") { } else if (data.action === "moveAllToGrid") {
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {
@ -2321,7 +2450,7 @@ var PropertiesTool = function (opts) {
}); });
} }
pushCommandForSelections(); pushCommandForSelections();
selectionManager._update(); selectionManager._update(false, this);
} }
} else if (data.action === "resetToNaturalDimensions") { } else if (data.action === "resetToNaturalDimensions") {
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {
@ -2342,7 +2471,7 @@ var PropertiesTool = function (opts) {
} }
} }
pushCommandForSelections(); pushCommandForSelections();
selectionManager._update(); selectionManager._update(false, this);
} }
} else if (data.action === "previewCamera") { } else if (data.action === "previewCamera") {
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {
@ -2360,7 +2489,7 @@ var PropertiesTool = function (opts) {
}); });
} }
pushCommandForSelections(); pushCommandForSelections();
selectionManager._update(); selectionManager._update(false, this);
} }
} else if (data.action === "reloadClientScripts") { } else if (data.action === "reloadClientScripts") {
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {

View file

@ -1801,3 +1801,43 @@ input[type=button]#export {
body#entity-list-body { body#entity-list-body {
padding-bottom: 0; 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;
}

View file

@ -16,6 +16,7 @@
<script type="text/javascript" src="js/eventBridgeLoader.js"></script> <script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.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/listView.js"></script>
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
<script type="text/javascript" src="js/entityList.js"></script> <script type="text/javascript" src="js/entityList.js"></script>
</head> </head>
<body onload='loaded();' id="entity-list-body"> <body onload='loaded();' id="entity-list-body">

View file

@ -13,7 +13,7 @@ const DESCENDING_STRING = '&#x25BE;';
const LOCKED_GLYPH = "&#xe006;"; const LOCKED_GLYPH = "&#xe006;";
const VISIBLE_GLYPH = "&#xe007;"; const VISIBLE_GLYPH = "&#xe007;";
const TRANSPARENCY_GLYPH = "&#xe00b;"; const TRANSPARENCY_GLYPH = "&#xe00b;";
const BAKED_GLYPH = "&#xe01a;" const BAKED_GLYPH = "&#xe01a;";
const SCRIPT_GLYPH = "k"; const SCRIPT_GLYPH = "k";
const BYTES_PER_MEGABYTE = 1024 * 1024; const BYTES_PER_MEGABYTE = 1024 * 1024;
const IMAGE_MODEL_NAME = 'default-image-model.fbx'; const IMAGE_MODEL_NAME = 'default-image-model.fbx';
@ -54,10 +54,10 @@ const COMPARE_ASCENDING = function(a, b) {
} }
return 1; return 1;
} };
const COMPARE_DESCENDING = function(a, b) { const COMPARE_DESCENDING = function(a, b) {
return COMPARE_ASCENDING(b, a); return COMPARE_ASCENDING(b, a);
} };
// List of all entities // List of all entities
var entities = []; var entities = [];
@ -70,6 +70,11 @@ var selectedEntities = [];
var entityList = null; // The ListView var entityList = null; // The ListView
/**
* @type EntityListContextMenu
*/
var entityListContextMenu = null;
var currentSortColumn = 'type'; var currentSortColumn = 'type';
var currentSortOrder = ASCENDING_SORT; var currentSortOrder = ASCENDING_SORT;
var isFilterInView = false; var isFilterInView = false;
@ -156,22 +161,22 @@ function loaded() {
}; };
elRefresh.onclick = function() { elRefresh.onclick = function() {
refreshEntities(); refreshEntities();
} };
elToggleLocked.onclick = function() { elToggleLocked.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' }));
} };
elToggleVisible.onclick = function() { elToggleVisible.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' }));
} };
elExport.onclick = function() { elExport.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); EventBridge.emitWebEvent(JSON.stringify({ type: 'export'}));
} };
elPal.onclick = function() { elPal.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' }));
} };
elDelete.onclick = function() { elDelete.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
} };
elFilter.onkeyup = refreshEntityList; elFilter.onkeyup = refreshEntityList;
elFilter.onpaste = refreshEntityList; elFilter.onpaste = refreshEntityList;
elFilter.onchange = onFilterChange; elFilter.onchange = onFilterChange;
@ -184,17 +189,105 @@ function loaded() {
entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow,
createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); 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) { function onRowClicked(clickEvent) {
let entityID = this.dataset.entityID; let entityID = this.dataset.entityID;
let selection = [entityID]; let selection = [entityID];
if (clickEvent.ctrlKey) { if (clickEvent.ctrlKey) {
let selectedIndex = selectedEntities.indexOf(entityID); let selectedIndex = selectedEntities.indexOf(entityID);
if (selectedIndex >= 0) { if (selectedIndex >= 0) {
selection = []; selection = [];
selection = selection.concat(selectedEntities); selection = selection.concat(selectedEntities);
selection.splice(selectedIndex, 1) selection.splice(selectedIndex, 1);
} else { } else {
selection = selection.concat(selectedEntities); selection = selection.concat(selectedEntities);
} }
@ -221,28 +314,29 @@ function loaded() {
} }
} }
} else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { } 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) { if (selectedEntities[0] === entityID) {
selection = []; startRenamingEntity(entityID);
} }
} }
updateSelectedEntities(selection); updateSelectedEntities(selection, false);
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate", type: "selectionUpdate",
focus: false, focus: false,
entityIds: selection, entityIds: selection,
})); }));
refreshFooter();
} }
function onRowDoubleClicked() { function onRowDoubleClicked() {
let selection = [this.dataset.entityID];
updateSelectedEntities(selection, false);
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate", type: "selectionUpdate",
focus: true, focus: true,
entityIds: [this.dataset.entityID], entityIds: selection,
})); }));
} }
@ -289,7 +383,7 @@ function loaded() {
hasScript: entity.hasScript, hasScript: entity.hasScript,
elRow: null, // if this entity has a visible row element assigned to it 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 selected: false // if this entity is selected for edit regardless of having a visible row
} };
entities.push(entityData); entities.push(entityData);
entitiesByID[entityData.id] = entityData; entitiesByID[entityData.id] = entityData;
@ -418,7 +512,7 @@ function loaded() {
isBaked: document.querySelector('#entity-isBaked .sort-order'), isBaked: document.querySelector('#entity-isBaked .sort-order'),
drawCalls: document.querySelector('#entity-drawCalls .sort-order'), drawCalls: document.querySelector('#entity-drawCalls .sort-order'),
hasScript: document.querySelector('#entity-hasScript .sort-order'), hasScript: document.querySelector('#entity-hasScript .sort-order'),
} };
function setSortColumn(column) { function setSortColumn(column) {
PROFILE("set-sort-column", function() { PROFILE("set-sort-column", function() {
if (currentSortColumn === column) { if (currentSortColumn === column) {
@ -453,7 +547,7 @@ function loaded() {
} }
} }
function updateSelectedEntities(selectedIDs) { function updateSelectedEntities(selectedIDs, autoScroll) {
let notFound = false; let notFound = false;
// reset all currently selected entities and their rows first // 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(); refreshFooter();
return notFound; return notFound;
@ -502,6 +616,7 @@ function loaded() {
} }
row.appendChild(column); row.appendChild(column);
} }
row.oncontextmenu = onRowContextMenu;
row.onclick = onRowClicked; row.onclick = onRowClicked;
row.ondblclick = onRowDoubleClicked; row.ondblclick = onRowDoubleClicked;
return row; return row;
@ -641,7 +756,7 @@ function loaded() {
if (data.type === "clearEntityList") { if (data.type === "clearEntityList") {
clearEntities(); clearEntities();
} else if (data.type === "selectionUpdate") { } else if (data.type === "selectionUpdate") {
let notFound = updateSelectedEntities(data.selectedIDs); let notFound = updateSelectedEntities(data.selectedIDs, true);
if (notFound) { if (notFound) {
refreshEntities(); refreshEntities();
} }
@ -653,13 +768,13 @@ function loaded() {
clearEntities(); clearEntities();
} else { } else {
updateEntityData(newEntities); updateEntityData(newEntities);
updateSelectedEntities(data.selectedIDs); updateSelectedEntities(data.selectedIDs, true);
} }
} }
}); });
} else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) {
removeEntities(data.deletedIDs); removeEntities(data.deletedIDs);
updateSelectedEntities(data.selectedIDs); updateSelectedEntities(data.selectedIDs, true);
} else if (data.type === "deleted" && data.ids) { } else if (data.type === "deleted" && data.ids) {
removeEntities(data.ids); removeEntities(data.ids);
} }
@ -672,8 +787,15 @@ function loaded() {
augmentSpinButtons(); 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) { 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(); event.preventDefault();
}, false); }, false);
// close context menu when switching focus to another window
$(window).blur(function() {
entityListContextMenu.close();
});
} }

View 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));
}
};

View file

@ -38,7 +38,7 @@ function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunctio
this.lastRowShiftScrollTop = 0; this.lastRowShiftScrollTop = 0;
this.initialize(); this.initialize();
}; }
ListView.prototype = { ListView.prototype = {
getNumRows: function() { getNumRows: function() {
@ -152,6 +152,30 @@ ListView.prototype = {
this.refresh(); 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() { refresh: function() {
// block refreshing before rows are initialized // block refreshing before rows are initialized

View file

@ -15,7 +15,7 @@ var PROFILING_ENABLED = false;
var profileIndent = ''; var profileIndent = '';
const PROFILE_NOOP = function(_name, fn, args) { const PROFILE_NOOP = function(_name, fn, args) {
fn.apply(this, args); fn.apply(this, args);
} ; };
PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
var previousIndent = profileIndent; var previousIndent = profileIndent;
@ -98,7 +98,11 @@ EntityListTool = function(shouldUseEditTabletApp) {
that.setVisible(!visible); 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 = []; var selectedIDs = [];
for (var i = 0; i < selectionManager.selections.length; i++) { for (var i = 0; i < selectionManager.selections.length; i++) {
@ -224,7 +228,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
for (var i = 0; i < ids.length; i++) { for (var i = 0; i < ids.length; i++) {
entityIDs.push(ids[i]); entityIDs.push(ids[i]);
} }
selectionManager.setSelections(entityIDs); selectionManager.setSelections(entityIDs, that);
if (data.focus) { if (data.focus) {
cameraManager.enable(); cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition, cameraManager.focus(selectionManager.worldPosition,
@ -245,7 +249,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
Window.saveAsync("Select Where to Save", "", "*.json"); Window.saveAsync("Select Where to Save", "", "*.json");
} }
} else if (data.type === "pal") { } 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) { selectionManager.selections.forEach(function (id) {
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
if (lastEditedBy) { if (lastEditedBy) {
@ -271,6 +275,19 @@ EntityListTool = function(shouldUseEditTabletApp) {
filterInView = data.filterInView === true; filterInView = data.filterInView === true;
} else if (data.type === "radius") { } else if (data.type === "radius") {
searchRadius = data.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();
} }
}; };

View file

@ -40,7 +40,7 @@ SelectionManager = (function() {
Messages.messageReceived.connect(handleEntitySelectionToolUpdates); Messages.messageReceived.connect(handleEntitySelectionToolUpdates);
} }
// FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES
function handleEntitySelectionToolUpdates(channel, message, sender) { function handleEntitySelectionToolUpdates(channel, message, sender) {
if (channel !== 'entityToolUpdates') { if (channel !== 'entityToolUpdates') {
return; return;
@ -63,7 +63,7 @@ SelectionManager = (function() {
if (wantDebug) { if (wantDebug) {
print("setting selection to " + messageParsed.entityID); print("setting selection to " + messageParsed.entityID);
} }
that.setSelections([messageParsed.entityID]); that.setSelections([messageParsed.entityID], that);
} }
} else if (messageParsed.method === "clearSelection") { } else if (messageParsed.method === "clearSelection") {
if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {
@ -136,7 +136,7 @@ SelectionManager = (function() {
return that.selections.length > 0; return that.selections.length > 0;
}; };
that.setSelections = function(entityIDs) { that.setSelections = function(entityIDs, caller) {
that.selections = []; that.selections = [];
for (var i = 0; i < entityIDs.length; i++) { for (var i = 0; i < entityIDs.length; i++) {
var entityID = entityIDs[i]; var entityID = entityIDs[i];
@ -144,10 +144,10 @@ SelectionManager = (function() {
Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); 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) { if (entityID) {
var idx = -1; var idx = -1;
for (var i = 0; i < that.selections.length; i++) { 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) { function removeEntityByID(entityID) {
@ -176,21 +176,21 @@ SelectionManager = (function() {
} }
} }
that.removeEntity = function (entityID) { that.removeEntity = function (entityID, caller) {
removeEntityByID(entityID); 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++) { for (var i = 0, length = entityIDs.length; i < length; i++) {
removeEntityByID(entityIDs[i]); removeEntityByID(entityIDs[i]);
} }
that._update(true); that._update(true, caller);
}; };
that.clearSelections = function() { that.clearSelections = function(caller) {
that.selections = []; that.selections = [];
that._update(true); that._update(true, caller);
}; };
that.addChildrenEntities = function(parentEntityID, entityList) { that.addChildrenEntities = function(parentEntityID, entityList) {
@ -353,12 +353,12 @@ SelectionManager = (function() {
} }
return createdEntityIDs; return createdEntityIDs;
} };
that.cutSelectedEntities = function() { that.cutSelectedEntities = function() {
copySelectedEntities(); that.copySelectedEntities();
deleteSelectedEntities(); deleteSelectedEntities();
} };
that.copySelectedEntities = function() { that.copySelectedEntities = function() {
var entityProperties = Entities.getMultipleEntityProperties(that.selections); var entityProperties = Entities.getMultipleEntityProperties(that.selections);
@ -434,7 +434,7 @@ SelectionManager = (function() {
z: brn.z + entityClipboard.dimensions.z / 2 z: brn.z + entityClipboard.dimensions.z / 2
}; };
} }
} };
that.pasteEntities = function() { that.pasteEntities = function() {
var dimensions = entityClipboard.dimensions; var dimensions = entityClipboard.dimensions;
@ -442,7 +442,7 @@ SelectionManager = (function() {
var pastePosition = getPositionToCreateEntity(maxDimension); var pastePosition = getPositionToCreateEntity(maxDimension);
var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position);
var copiedProperties = [] var copiedProperties = [];
var ids = []; var ids = [];
entityClipboard.entities.forEach(function(originalProperties) { entityClipboard.entities.forEach(function(originalProperties) {
var properties = deepCopy(originalProperties); var properties = deepCopy(originalProperties);
@ -475,9 +475,9 @@ SelectionManager = (function() {
redo(copiedProperties); redo(copiedProperties);
undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties);
} };
that._update = function(selectionUpdated) { that._update = function(selectionUpdated, caller) {
var properties = null; var properties = null;
if (that.selections.length === 0) { if (that.selections.length === 0) {
that.localDimensions = null; that.localDimensions = null;
@ -542,7 +542,7 @@ SelectionManager = (function() {
for (var j = 0; j < listeners.length; j++) { for (var j = 0; j < listeners.length; j++) {
try { try {
listeners[j](selectionUpdated === true); listeners[j](selectionUpdated === true, caller);
} catch (e) { } catch (e) {
print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e));
} }
@ -985,7 +985,7 @@ SelectionDisplay = (function() {
that.pressedHand = NO_HAND; that.pressedHand = NO_HAND;
that.triggered = function() { that.triggered = function() {
return that.triggeredHand !== NO_HAND; return that.triggeredHand !== NO_HAND;
} };
function pointingAtDesktopWindowOrTablet(hand) { function pointingAtDesktopWindowOrTablet(hand) {
var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand &&
SelectionManager.pointingAtDesktopWindowRight) || SelectionManager.pointingAtDesktopWindowRight) ||
@ -1032,7 +1032,7 @@ SelectionDisplay = (function() {
that.disableTriggerMapping = function() { that.disableTriggerMapping = function() {
that.triggerClickMapping.disable(); that.triggerClickMapping.disable();
that.triggerPressMapping.disable(); that.triggerPressMapping.disable();
} };
Script.scriptEnding.connect(that.disableTriggerMapping); Script.scriptEnding.connect(that.disableTriggerMapping);
// FUNCTION DEF(s): Intersection Check Helpers // FUNCTION DEF(s): Intersection Check Helpers
@ -1234,7 +1234,7 @@ SelectionDisplay = (function() {
if (wantDebug) { if (wantDebug) {
print(" Trigger SelectionManager::update"); print(" Trigger SelectionManager::update");
} }
SelectionManager._update(); SelectionManager._update(false, that);
if (wantDebug) { if (wantDebug) {
print("=============== eST::MouseMoveEvent END ======================="); print("=============== eST::MouseMoveEvent END =======================");
@ -1299,7 +1299,7 @@ SelectionDisplay = (function() {
lastMouseEvent.isControl = event.isControl; lastMouseEvent.isControl = event.isControl;
lastMouseEvent.isAlt = event.isAlt; lastMouseEvent.isAlt = event.isAlt;
activeTool.onMove(lastMouseEvent); activeTool.onMove(lastMouseEvent);
SelectionManager._update(); SelectionManager._update(false, this);
} }
}; };
@ -1315,7 +1315,7 @@ SelectionDisplay = (function() {
lastMouseEvent.isControl = event.isControl; lastMouseEvent.isControl = event.isControl;
lastMouseEvent.isAlt = event.isAlt; lastMouseEvent.isAlt = event.isAlt;
activeTool.onMove(lastMouseEvent); 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; previousPickRay = pickRay;
SelectionManager._update(); SelectionManager._update(false, this);
} }
}); });
} }
@ -2488,7 +2488,7 @@ SelectionDisplay = (function() {
previousPickRay = pickRay; previousPickRay = pickRay;
SelectionManager._update(); SelectionManager._update(false, this);
} }
}); });
} }
@ -2599,7 +2599,7 @@ SelectionDisplay = (function() {
previousPickRay = pickRay; previousPickRay = pickRay;
SelectionManager._update(); SelectionManager._update(false, this);
} }
}); });
} }

View file

@ -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 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) { function onMessageBoxClosed(id, button) {
if (id === messageBox && button === CANCEL_BUTTON) { if (id === messageBox && button === CANCEL_BUTTON) {
isDownloadBeingCancelled = true; isDownloadBeingCancelled = true;
@ -522,13 +558,18 @@ function getPositionToCreateEntity(extra) {
return position; 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 isWearable = itemType === "wearable";
var success = Clipboard.importEntities(itemHref); var success = Clipboard.importEntities(itemHref, true, marketplaceItemTesterId);
var wearableLocalPosition = null; var wearableLocalPosition = null;
var wearableLocalRotation = null; var wearableLocalRotation = null;
var wearableLocalDimensions = null; var wearableLocalDimensions = null;
var wearableDimensions = null; var wearableDimensions = null;
marketplaceItemTesterId = defaultFor(marketplaceItemTesterId, -1);
if (itemType === "contentSet") { if (itemType === "contentSet") {
console.log("Item is a content set; codepath shouldn't go here."); console.log("Item is a content set; codepath shouldn't go here.");
@ -816,7 +857,8 @@ var resourceObjectsInTest = [];
function signalNewResourceObjectInTest(resourceObject) { function signalNewResourceObjectInTest(resourceObject) {
ui.tablet.sendToQml({ ui.tablet.sendToQml({
method: "newResourceObjectInTest", method: "newResourceObjectInTest",
resourceObject: resourceObject }); resourceObject: resourceObject
});
} }
var onQmlMessageReceived = function onQmlMessageReceived(message) { var onQmlMessageReceived = function onQmlMessageReceived(message) {
@ -877,11 +919,15 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
case 'checkout_rezClicked': case 'checkout_rezClicked':
case 'purchases_rezClicked': case 'purchases_rezClicked':
case 'tester_rezClicked': case 'tester_rezClicked':
rezEntity(message.itemHref, message.itemType); rezEntity(message.itemHref, message.itemType, message.itemId);
break; break;
case 'tester_newResourceObject': case 'tester_newResourceObject':
var resourceObject = message.resourceObject; 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); signalNewResourceObjectInTest(resourceObject);
break; break;
case 'tester_updateResourceObjectAssetType': case 'tester_updateResourceObjectAssetType':
@ -890,6 +936,13 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
case 'tester_deleteResourceObject': case 'tester_deleteResourceObject':
delete resourceObjectsInTest[message.objectId]; delete resourceObjectsInTest[message.objectId];
break; 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 'header_marketplaceImageClicked':
case 'purchases_backClicked': case 'purchases_backClicked':
openMarketplace(message.referrerURL); openMarketplace(message.referrerURL);
@ -1029,16 +1082,22 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
}; };
function pushResourceObjectsInTest() { function pushResourceObjectsInTest() {
var maxObjectId = -1; var maxResourceObjectId = -1;
for (var objectId in resourceObjectsInTest) { var length = resourceObjectsInTest.length;
signalNewResourceObjectInTest(resourceObjectsInTest[objectId]); for (var i = 0; i < length; i++) {
maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId; 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 // N.B. Thinking about removing the following sendToQml? Be sure
// that the marketplace item tester QML has heard from us, at least // 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 // so that it can indicate to the user that all of the resoruce
// objects in test have been transmitted to it. // 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() // Function Name: onTabletScreenChanged()
@ -1193,6 +1252,7 @@ function startup() {
ui.tablet.webEventReceived.connect(onWebEventReceived); ui.tablet.webEventReceived.connect(onWebEventReceived);
Wallet.walletStatusChanged.connect(sendCommerceSettings); Wallet.walletStatusChanged.connect(sendCommerceSettings);
Window.messageBoxClosed.connect(onMessageBoxClosed); Window.messageBoxClosed.connect(onMessageBoxClosed);
ResourceRequestObserver.resourceRequestEvent.connect(onResourceRequestEvent);
Wallet.refreshWalletStatus(); Wallet.refreshWalletStatus();
} }
@ -1226,6 +1286,7 @@ function shutdown() {
GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML); ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML);
ResourceRequestObserver.resourceRequestEvent.disconnect(onResourceRequestEvent);
off(); off();
} }

View file

@ -874,10 +874,6 @@ function onContentLoaded() {
hasShownUpdateNotification = true; 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); deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX);

View file

@ -5,6 +5,8 @@ const process = require('process');
const hfApp = require('./hf-app'); const hfApp = require('./hf-app');
const path = require('path'); const path = require('path');
const AccountInfo = require('./hf-acctinfo').AccountInfo; const AccountInfo = require('./hf-acctinfo').AccountInfo;
const url = require('url');
const shell = require('electron').shell;
const GetBuildInfo = hfApp.getBuildInfo; const GetBuildInfo = hfApp.getBuildInfo;
const buildInfo = GetBuildInfo(); const buildInfo = GetBuildInfo();
const osType = os.type(); const osType = os.type();
@ -154,8 +156,13 @@ function HifiNotifications(config, menuNotificationCallback) {
var _menuNotificationCallback = menuNotificationCallback; var _menuNotificationCallback = menuNotificationCallback;
notifier.on('click', function (notifierObject, options) { notifier.on('click', function (notifierObject, options) {
StartInterface(options.url); const optUrl = url.parse(options.url);
_menuNotificationCallback(options.notificationType, false); if ((optUrl.protocol === "hifi:") || (optUrl.protocol === "hifiapp:")) {
StartInterface(options.url);
_menuNotificationCallback(options.notificationType, false);
} else {
shell.openExternal(options.url);
}
}); });
} }