Merge branch 'master' of github.com:highfidelity/hifi into near-grab-via-parenting

This commit is contained in:
Seth Alves 2016-01-04 19:35:36 -08:00
commit ce1b8ae6f0
76 changed files with 1851 additions and 715 deletions

View file

@ -0,0 +1 @@
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)

View file

@ -30,6 +30,7 @@
#include <SharedUtil.h> #include <SharedUtil.h>
#include <ShutdownEventListener.h> #include <ShutdownEventListener.h>
#include <SoundCache.h> #include <SoundCache.h>
#include <ResourceScriptingInterface.h>
#include "AssignmentFactory.h" #include "AssignmentFactory.h"
#include "AssignmentActionFactory.h" #include "AssignmentActionFactory.h"
@ -61,6 +62,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
DependencyManager::registerInheritance<EntityActionFactoryInterface, AssignmentActionFactory>(); DependencyManager::registerInheritance<EntityActionFactoryInterface, AssignmentActionFactory>();
auto actionFactory = DependencyManager::set<AssignmentActionFactory>(); auto actionFactory = DependencyManager::set<AssignmentActionFactory>();
DependencyManager::set<ResourceScriptingInterface>();
// setup a thread for the NodeList and its PacketReceiver // setup a thread for the NodeList and its PacketReceiver
QThread* nodeThread = new QThread(this); QThread* nodeThread = new QThread(this);

View file

@ -430,7 +430,12 @@ function MyController(hand) {
} }
this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); this.searchSphere = Overlays.addOverlay("sphere", sphereProperties);
} else { } else {
Overlays.editOverlay(this.searchSphere, { position: location, size: size, color: color, visible: true }); Overlays.editOverlay(this.searchSphere, {
position: location,
size: size,
color: color,
visible: true
});
} }
} }
@ -795,6 +800,12 @@ function MyController(hand) {
length: PICK_MAX_DISTANCE length: PICK_MAX_DISTANCE
}; };
var searchVisualizationPickRay = {
origin: handPosition,
direction: Quat.getUp(this.getHandRotation()),
length: PICK_MAX_DISTANCE
};
// Pick at some maximum rate, not always // Pick at some maximum rate, not always
var pickRays = []; var pickRays = [];
var now = Date.now(); var now = Date.now();
@ -1015,6 +1026,11 @@ function MyController(hand) {
if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) {
this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR);
} }
if (USE_OVERLAY_LINES_FOR_SEARCHING === true) {
this.overlayLineOn(searchVisualizationPickRay.origin, Vec3.sum(searchVisualizationPickRay.origin, Vec3.multiply(searchVisualizationPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR);
}
if (this.intersectionDistance > 0) { if (this.intersectionDistance > 0) {
var SPHERE_INTERSECTION_SIZE = 0.011; var SPHERE_INTERSECTION_SIZE = 0.011;
var SEARCH_SPHERE_FOLLOW_RATE = 0.50; var SEARCH_SPHERE_FOLLOW_RATE = 0.50;

View file

@ -62,8 +62,13 @@ var directory = (function () {
function setUp() { function setUp() {
viewport = Controller.getViewportDimensions(); viewport = Controller.getViewportDimensions();
directoryWindow = new OverlayWebWindow('Directory', DIRECTORY_URL, 900, 700, false); directoryWindow = new OverlayWebWindow({
directoryWindow.setVisible(false); title: 'Directory',
source: DIRECTORY_URL,
width: 900,
height: 700,
visible: false
});
directoryButton = Overlays.addOverlay("image", { directoryButton = Overlays.addOverlay("image", {
imageURL: DIRECTORY_BUTTON_URL, imageURL: DIRECTORY_BUTTON_URL,

View file

@ -140,8 +140,13 @@ var importingSVOTextOverlay = Overlays.addOverlay("text", {
}); });
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
var marketplaceWindow = new OverlayWebWindow('Marketplace', "about:blank", 900, 700, false); var marketplaceWindow = new OverlayWebWindow({
marketplaceWindow.setVisible(false); title: 'Marketplace',
source: "about:blank",
width: 900,
height: 700,
visible: false
});
function showMarketplace(marketplaceID) { function showMarketplace(marketplaceID) {
var url = MARKETPLACE_URL; var url = MARKETPLACE_URL;

View file

@ -24,6 +24,9 @@ RaveStick = function(spawnPosition) {
green: 10, green: 10,
blue: 40 blue: 40
}]; }];
var stick = Entities.addEntity({ var stick = Entities.addEntity({
type: "Model", type: "Model",
name: "raveStick", name: "raveStick",
@ -57,6 +60,8 @@ RaveStick = function(spawnPosition) {
}) })
}); });
var glowEmitter = createGlowEmitter();
var light = Entities.addEntity({ var light = Entities.addEntity({
type: 'Light', type: 'Light',
name: "raveLight", name: "raveLight",
@ -85,11 +90,73 @@ RaveStick = function(spawnPosition) {
function cleanup() { function cleanup() {
Entities.deleteEntity(stick); Entities.deleteEntity(stick);
Entities.deleteEntity(light); Entities.deleteEntity(light);
Entities.deleteEntity(glowEmitter);
} }
this.cleanup = cleanup; this.cleanup = cleanup;
function createGlowEmitter() {
var props = Entities.getEntityProperties(stick, ["position", "rotation"]);
var forwardVec = Quat.getFront(props.rotation);
var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec);
var position = props.position;
var color = {
red: 150,
green: 20,
blue: 100
}
var props = {
type: "ParticleEffect",
name: "Rave Stick Glow Emitter",
position: position,
parentID: stick,
isEmitting: true,
colorStart: color,
color: {
red: 200,
green: 200,
blue: 255
},
colorFinish: color,
maxParticles: 100000,
lifespan: 0.8,
emitRate: 1000,
emitOrientation: forwardQuat,
emitSpeed: 0.2,
speedSpread: 0.0,
emitDimensions: {
x: 0,
y: 0,
z: 0
},
polarStart: 0,
polarFinish: 0,
azimuthStart: 0.1,
azimuthFinish: 0.01,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: 0.00,
y: 0.00,
z: 0.00
},
radiusStart: 0.01,
radiusFinish: 0.005,
alpha: 0.7,
alphaSpread: 0.1,
alphaStart: 0.1,
alphaFinish: 0.1,
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png",
emitterShouldTrail: false
}
var glowEmitter = Entities.addEntity(props);
return glowEmitter;
}
} }

40
examples/tests/qmlTest.js Normal file
View file

@ -0,0 +1,40 @@
print("Launching web window");
qmlWindow = new OverlayWindow({
title: 'Test Qml',
source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml",
height: 240,
width: 320,
toolWindow: false,
visible: true
});
//qmlWindow.eventBridge.webEventReceived.connect(function(data) {
// print("JS Side event received: " + data);
//});
//
//var titles = ["A", "B", "C"];
//var titleIndex = 0;
//
//Script.setInterval(function() {
// qmlWindow.eventBridge.emitScriptEvent("JS Event sent");
// var size = qmlWindow.size;
// var position = qmlWindow.position;
// print("Window visible: " + qmlWindow.visible)
// if (qmlWindow.visible) {
// print("Window size: " + size.x + "x" + size.y)
// print("Window pos: " + position.x + "x" + position.y)
// qmlWindow.setVisible(false);
// } else {
// qmlWindow.setVisible(true);
// qmlWindow.setTitle(titles[titleIndex]);
// qmlWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
// titleIndex += 1;
// titleIndex %= titles.length;
// }
//}, 2 * 1000);
//
//Script.setTimeout(function() {
// print("Closing script");
// qmlWindow.close();
// Script.stop();
//}, 15 * 1000)

View file

@ -1,6 +1,7 @@
print("Launching web window"); print("Launching web window");
webWindow = new OverlayWebWindow('Test Event Bridge', "file:///C:/Users/bdavis/Git/hifi/examples/html/qmlWebTest.html", 320, 240, false); var htmlUrl = Script.resolvePath("..//html/qmlWebTest.html")
webWindow = new OverlayWebWindow('Test Event Bridge', htmlUrl, 320, 240, false);
print("JS Side window: " + webWindow); print("JS Side window: " + webWindow);
print("JS Side bridge: " + webWindow.eventBridge); print("JS Side bridge: " + webWindow.eventBridge);
webWindow.eventBridge.webEventReceived.connect(function(data) { webWindow.eventBridge.webEventReceived.connect(function(data) {

View file

@ -37,6 +37,13 @@
this.bulletForce = 10; this.bulletForce = 10;
this.showLaser = false; this.showLaser = false;
this.laserOffsets = {
y: 0.095
};
this.firingOffsets = {
z: 0.16
}
}; };
Pistol.prototype = { Pistol.prototype = {
@ -272,46 +279,12 @@
}); });
}, 100); }, 100);
Entities.editEntity(this.flash, { var flash = Entities.addEntity({
isEmitting: true
});
Script.setTimeout(function() {
Entities.editEntity(_this.flash, {
isEmitting: false
});
}, 100)
},
preload: function(entityID) {
this.entityID = entityID;
this.laser = Overlays.addOverlay("line3d", {
start: ZERO_VECTOR,
end: ZERO_VECTOR,
color: COLORS.RED,
alpha: 1,
visible: true,
lineWidth: 2
});
this.laserOffsets = {
y: 0.095
};
this.firingOffsets = {
z: 0.16
}
var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
var position = gunProps.position;
var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0);
this.firingDirection = Quat.getFront(rotation);
var upVec = Quat.getUp(rotation);
this.barrelPoint = Vec3.sum(position, Vec3.multiply(upVec, this.laserOffsets.y));
this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z))
this.flash = Entities.addEntity({
type: "ParticleEffect", type: "ParticleEffect",
position: this.barrelPoint, position: this.barrelPoint,
"name": "Muzzle Flash", "name": "Muzzle Flash",
isEmitting: false, lifetime: 4,
parentID: this.entityID,
"color": { "color": {
red: 228, red: 228,
green: 128, green: 128,
@ -363,12 +336,25 @@
"additiveBlending": true, "additiveBlending": true,
"textures": "http://ericrius1.github.io/PartiArt/assets/star.png" "textures": "http://ericrius1.github.io/PartiArt/assets/star.png"
}); });
Script.setTimeout(function() { Script.setTimeout(function() {
Entities.editEntity(_this.flash, {parentID: _this.entityID}); Entities.editEntity(flash, {
}, 500) isEmitting: false
});
}, 100)
}, },
preload: function(entityID) {
this.entityID = entityID;
this.laser = Overlays.addOverlay("line3d", {
start: ZERO_VECTOR,
end: ZERO_VECTOR,
color: COLORS.RED,
alpha: 1,
visible: true,
lineWidth: 2
});
},
}; };
// entity scripts always need to return a newly constructed object of our type // entity scripts always need to return a newly constructed object of our type

View file

@ -0,0 +1,61 @@
{
"name": "Standard to Action",
"when": "Application.NavigationFocused",
"channels": [
{ "disabled_from": { "makeAxis" : [ "Standard.DD", "Standard.DU" ] }, "to": "Actions.UiNavVertical" },
{ "disabled_from": { "makeAxis" : [ "Standard.DL", "Standard.DR" ] }, "to": "Actions.UiNavLateral" },
{ "disabled_from": { "makeAxis" : [ "Standard.LB", "Standard.RB" ] }, "to": "Actions.UiNavGroup" },
{ "from": "Standard.DU", "to": "Actions.UiNavVertical" },
{ "from": "Standard.DD", "to": "Actions.UiNavVertical", "filters": "invert" },
{ "from": "Standard.DL", "to": "Actions.UiNavLateral", "filters": "invert" },
{ "from": "Standard.DR", "to": "Actions.UiNavLateral" },
{ "from": "Standard.LB", "to": "Actions.UiNavGroup","filters": "invert" },
{ "from": "Standard.RB", "to": "Actions.UiNavGroup" },
{ "from": [ "Standard.A", "Standard.X", "Standard.RT", "Standard.LT" ], "to": "Actions.UiNavSelect" },
{ "from": [ "Standard.B", "Standard.Y", "Standard.RightPrimaryThumb", "Standard.LeftPrimaryThumb" ], "to": "Actions.UiNavBack" },
{
"from": [ "Standard.RT", "Standard.LT" ],
"to": "Actions.UiNavSelect",
"filters": [
{ "type": "deadZone", "min": 0.5 },
"constrainToInteger"
]
},
{
"from": "Standard.LX", "to": "Actions.UiNavLateral",
"filters": [
{ "type": "deadZone", "min": 0.95 },
"constrainToInteger",
{ "type": "pulse", "interval": 0.4 }
]
},
{
"from": "Standard.LY", "to": "Actions.UiNavVertical",
"filters": [
"invert",
{ "type": "deadZone", "min": 0.95 },
"constrainToInteger",
{ "type": "pulse", "interval": 0.4 }
]
},
{
"from": "Standard.RX", "to": "Actions.UiNavLateral",
"filters": [
{ "type": "deadZone", "min": 0.95 },
"constrainToInteger",
{ "type": "pulse", "interval": 0.4 }
]
},
{
"from": "Standard.RY", "to": "Actions.UiNavVertical",
"filters": [
"invert",
{ "type": "deadZone", "min": 0.95 },
"constrainToInteger",
{ "type": "pulse", "interval": 0.4 }
]
}
]
}

View file

@ -84,6 +84,7 @@ Hifi.AvatarInputs {
Item { Item {
width: root.mirrorWidth width: root.mirrorWidth
height: 44 height: 44
visible: !root.isHMD
x: root.mirrorLeftPad x: root.mirrorLeftPad
y: root.mirrorVisible ? root.mirrorTopPad + root.mirrorHeight : 5 y: root.mirrorVisible ? root.mirrorTopPad + root.mirrorHeight : 5

View file

@ -1,9 +1,6 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebEngine 1.1 import QtWebEngine 1.1
import QtWebChannel 1.0
import QtWebSockets 1.0
import "controls" import "controls"
import "styles" import "styles"
@ -13,6 +10,8 @@ VrDialog {
HifiConstants { id: hifi } HifiConstants { id: hifi }
title: "WebWindow" title: "WebWindow"
resizable: true resizable: true
enabled: false
visible: false
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
contentImplicitWidth: clientArea.implicitWidth contentImplicitWidth: clientArea.implicitWidth
@ -25,22 +24,12 @@ VrDialog {
webview.stop(); webview.stop();
} }
Component.onCompleted: { Component.onCompleted: {
enabled = true // Ensure the JS from the web-engine makes it to our logging
console.log("Web Window Created " + root);
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
}); });
webview.loadingChanged.connect(handleWebviewLoading)
}
function handleWebviewLoading(loadRequest) {
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var newUrl = loadRequest.url.toString();
root.navigating(newUrl)
}
} }
Item { Item {
@ -56,13 +45,28 @@ VrDialog {
id: webview id: webview
url: root.source url: root.source
anchors.fill: parent anchors.fill: parent
focus: true
onUrlChanged: { onUrlChanged: {
var currentUrl = url.toString(); var currentUrl = url.toString();
var newUrl = urlFixer.fixupUrl(currentUrl); var newUrl = urlHandler.fixupUrl(currentUrl).toString();
if (newUrl != currentUrl) { if (newUrl != currentUrl) {
url = newUrl; url = newUrl;
} }
} }
onLoadingChanged: {
// Required to support clicking on "hifi://" links
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var url = loadRequest.url.toString();
if (urlHandler.canHandleUrl(url)) {
if (urlHandler.handleUrl(url)) {
webview.stop();
}
}
}
}
profile: WebEngineProfile { profile: WebEngineProfile {
id: webviewProfile id: webviewProfile
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)" httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"

View file

@ -0,0 +1,55 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebChannel 1.0
import QtWebSockets 1.0
import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel
import "controls"
import "styles"
VrDialog {
id: root
objectName: "topLevelWindow"
HifiConstants { id: hifi }
title: "QmlWindow"
resizable: true
enabled: false
visible: false
focus: true
property var channel;
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
contentImplicitWidth: clientArea.implicitWidth
contentImplicitHeight: clientArea.implicitHeight
property alias source: pageLoader.source
Item {
id: clientArea
implicitHeight: 600
implicitWidth: 800
x: root.clientX
y: root.clientY
width: root.clientWidth
height: root.clientHeight
focus: true
clip: true
Loader {
id: pageLoader
objectName: "Loader"
anchors.fill: parent
focus: true
property var dialog: root
onLoaded: {
forceActiveFocus()
}
Keys.onPressed: {
console.log("QmlWindow pageLoader keypress")
}
}
} // item
} // dialog

View file

@ -258,14 +258,14 @@ Item {
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: root.showAcuity
text: "LOD: " + root.lodStatus; text: "LOD: " + root.lodStatus;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: root.expanded
text: "Renderable avatars: " + root.avatarRenderableCount + " w/in " + root.avatarRenderDistance + "m"; text: root.lodStatsRenderText;
} }
} }
} }

View file

@ -1,7 +1,9 @@
import Hifi 1.0 as Hifi import Hifi 1.0 as Hifi
import QtQuick 2.4 import QtQuick 2.4
import QtQuick.Controls 1.3 import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3 import QtQuick.Controls.Styles 1.3
import "controls" import "controls"
import "styles" import "styles"
@ -21,15 +23,12 @@ Hifi.VrMenu {
property var models: [] property var models: []
property var columns: [] property var columns: []
onEnabledChanged: { onEnabledChanged: {
if (enabled && columns.length == 0) { if (enabled && columns.length == 0) {
pushColumn(rootMenu.items); pushColumn(rootMenu.items);
} }
opacity = enabled ? 1.0 : 0.0 opacity = enabled ? 1.0 : 0.0
if (enabled) { offscreenFlags.navigationFocused = enabled;
forceActiveFocus()
}
} }
// The actual animator // The actual animator
@ -49,13 +48,12 @@ Hifi.VrMenu {
} }
property var menuBuilder: Component { property var menuBuilder: Component {
Border { VrMenuView {
HifiConstants { id: hifi } property int menuDepth: root.models.length - 1
property int menuDepth model: root.models[menuDepth]
Component.onCompleted: { Component.onCompleted: {
menuDepth = root.models.length - 1 if (menuDepth === 0) {
if (menuDepth == 0) {
x = lastMousePosition.x - 20 x = lastMousePosition.x - 20
y = lastMousePosition.y - 20 y = lastMousePosition.y - 20
} else { } else {
@ -65,48 +63,8 @@ Hifi.VrMenu {
} }
} }
border.color: hifi.colors.hifiBlue onSelected: {
color: hifi.colors.window root.selectItem(menuDepth, item)
implicitHeight: listView.implicitHeight + 16
implicitWidth: listView.implicitWidth + 16
Column {
id: listView
property real minWidth: 0
anchors {
top: parent.top
topMargin: 8
left: parent.left
leftMargin: 8
right: parent.right
rightMargin: 8
}
Repeater {
model: root.models[menuDepth]
delegate: Loader {
id: loader
source: "VrMenuItem.qml"
Binding {
target: loader.item
property: "menuContainer"
value: root
when: loader.status == Loader.Ready
}
Binding {
target: loader.item
property: "source"
value: modelData
when: loader.status == Loader.Ready
}
Binding {
target: loader.item
property: "listView"
value: listView
when: loader.status == Loader.Ready
}
}
}
} }
} }
} }
@ -116,14 +74,14 @@ Hifi.VrMenu {
} }
function pushColumn(items) { function pushColumn(items) {
models.push(items) models.push(itemsToModel(items))
if (columns.length) { if (columns.length) {
var oldColumn = lastColumn(); var oldColumn = lastColumn();
//oldColumn.enabled = false //oldColumn.enabled = false
} }
var newColumn = menuBuilder.createObject(root); var newColumn = menuBuilder.createObject(root);
columns.push(newColumn); columns.push(newColumn);
newColumn.forceActiveFocus(); forceActiveFocus();
} }
function popColumn() { function popColumn() {
@ -145,13 +103,39 @@ Hifi.VrMenu {
curColumn.forceActiveFocus(); curColumn.forceActiveFocus();
} }
function selectItem(source) { function itemsToModel(items) {
var newListModel = Qt.createQmlObject('import QtQuick 2.2; ListModel {}', root);
for (var i = 0; i < items.length; ++i) {
var item = items[i];
switch (item.type) {
case 2:
newListModel.append({"type":item.type, "name": item.title, "item": item})
break;
case 1:
newListModel.append({"type":item.type, "name": item.text, "item": item})
break;
case 0:
newListModel.append({"type":item.type, "name": "-----", "item": item})
break;
}
}
return newListModel;
}
function selectItem(depth, source) {
var popped = false;
while (depth + 1 < columns.length) {
popColumn()
popped = true
}
switch (source.type) { switch (source.type) {
case 2: case 2:
lastColumn().enabled = false
pushColumn(source.items) pushColumn(source.items)
break; break;
case 1: case 1:
source.trigger() if (!popped) source.trigger()
enabled = false enabled = false
break; break;
case 0: case 0:
@ -165,14 +149,6 @@ Hifi.VrMenu {
} }
} }
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Escape:
root.popColumn()
event.accepted = true;
}
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
id: mouseArea id: mouseArea
@ -206,4 +182,36 @@ Hifi.VrMenu {
function removeItem(menu, menuItem) { function removeItem(menu, menuItem) {
menu.removeItem(menuItem); menu.removeItem(menuItem);
} }
function previousItem() {
if (columns.length) {
lastColumn().incrementCurrentIndex()
}
}
function nextItem() {
if (columns.length) {
lastColumn().decrementCurrentIndex()
}
}
function selectCurrentItem() {
if (columns.length) {
var depth = columns.length - 1;
var index = lastColumn().currentIndex;
if (index >= 0) {
var model = models[depth];
var item = model.get(index).item;
selectItem(depth, item);
}
}
}
Keys.onDownPressed: previousItem();
Keys.onUpPressed: nextItem();
Keys.onSpacePressed: selectCurrentItem();
Keys.onReturnPressed: selectCurrentItem();
Keys.onRightPressed: selectCurrentItem();
Keys.onLeftPressed: popColumn();
Keys.onEscapePressed: popColumn();
} }

View file

@ -10,53 +10,14 @@ Item {
id: hifi id: hifi
} }
// The model object
property alias text: label.text
property var source property var source
property var menuContainer
property var listView
MouseArea {
anchors.left: parent.left
anchors.right: tag.right
anchors.rightMargin: -4
anchors.top: parent.top
anchors.bottom: parent.bottom
acceptedButtons: Qt.LeftButton
hoverEnabled: true
Rectangle {
id: highlight
visible: false
anchors.fill: parent
color: "#7f0e7077"
}
onEntered: {
//if (source.type == 2 && enabled) {
// timer.start()
//}
highlight.visible = source.enabled
}
onExited: {
timer.stop()
highlight.visible = false
}
onClicked: {
select()
}
}
implicitHeight: source.visible ? label.implicitHeight * 1.5 : 0 implicitHeight: source.visible ? label.implicitHeight * 1.5 : 0
implicitWidth: label.implicitWidth + label.height * 2.5 implicitWidth: label.width + label.height * 2.5
visible: source.visible visible: source.visible
width: parent.width
Timer {
id: timer
interval: 1000
onTriggered: parent.select()
}
FontAwesome { FontAwesome {
clip: true clip: true
@ -84,32 +45,18 @@ Item {
Text { Text {
id: label id: label
text: typedText()
anchors.left: check.right anchors.left: check.right
anchors.leftMargin: 4 anchors.leftMargin: 4
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: source.enabled ? hifi.colors.text : hifi.colors.disabledText color: source.enabled ? hifi.colors.text : hifi.colors.disabledText
enabled: source.enabled && source.visible enabled: source.visible && (source.type !== 0 ? source.enabled : false)
visible: source.visible visible: source.visible
function typedText() {
if (source) {
switch (source.type) {
case 2:
return source.title
case 1:
return source.text
case 0:
return "-----"
}
}
return ""
}
} }
FontAwesome { FontAwesome {
id: tag id: tag
x: listView.width - width - 4 x: root.parent.width - width
size: label.height size: label.height
width: implicitWidth width: implicitWidth
visible: source.visible && (source.type == 2) visible: source.visible && (source.type == 2)
@ -117,17 +64,4 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: label.color color: label.color
} }
function select() {
//timer.stop();
var popped = false
while (columns.length - 1 > listView.parent.menuDepth) {
popColumn()
popped = true
}
if (!popped || source.type != 1) {
root.menuContainer.selectItem(source)
}
}
} }

View file

@ -0,0 +1,77 @@
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import "styles"
ListView {
id: root
HifiConstants { id: hifi }
width: 128
height: count * 32
onEnabledChanged: recalcSize();
onVisibleChanged: recalcSize();
onCountChanged: recalcSize();
signal selected(var item)
highlight: Rectangle {
width: root.currentItem ? root.currentItem.width : 0
height: root.currentItem ? root.currentItem.height : 0
color: "lightsteelblue"; radius: 3
}
delegate: VrMenuItem {
text: name
source: item
onImplicitHeightChanged: root.recalcSize()
onImplicitWidthChanged: root.recalcSize()
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: root.currentIndex = index
onClicked: root.selected(item)
}
}
function recalcSize() {
if (model.count !== count || !visible) {
return;
}
var originalIndex = currentIndex;
var maxWidth = width;
var newHeight = 0;
for (var i = 0; i < count; ++i) {
currentIndex = i;
if (!currentItem) {
continue;
}
if (currentItem && currentItem.implicitWidth > maxWidth) {
maxWidth = currentItem.implicitWidth
}
if (currentItem.visible) {
newHeight += currentItem.implicitHeight
}
}
if (maxWidth > width) {
width = maxWidth;
}
if (newHeight > height) {
height = newHeight
}
currentIndex = originalIndex;
}
Border {
id: border
anchors.fill: parent
anchors.margins: -8
z: parent.z - 1
border.color: hifi.colors.hifiBlue
color: hifi.colors.window
}
}

View file

@ -41,6 +41,11 @@ DialogBase {
// modify the visibility // modify the visibility
onEnabledChanged: { onEnabledChanged: {
opacity = enabled ? 1.0 : 0.0 opacity = enabled ? 1.0 : 0.0
// If the dialog is initially invisible, setting opacity doesn't
// trigger making it visible.
if (enabled) {
visible = true;
}
} }
// The actual animator // The actual animator

View file

@ -11,6 +11,8 @@
#include "Application.h" #include "Application.h"
#include <gl/Config.h>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtx/component_wise.hpp> #include <glm/gtx/component_wise.hpp>
#include <glm/gtx/quaternion.hpp> #include <glm/gtx/quaternion.hpp>
@ -28,11 +30,14 @@
#include <QtGui/QImage> #include <QtGui/QImage>
#include <QtGui/QWheelEvent> #include <QtGui/QWheelEvent>
#include <QtGui/QWindow> #include <QtGui/QWindow>
#include <QtQml/QQmlContext>
#include <QtGui/QKeyEvent> #include <QtGui/QKeyEvent>
#include <QtGui/QMouseEvent> #include <QtGui/QMouseEvent>
#include <QtGui/QDesktopServices> #include <QtGui/QDesktopServices>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlEngine>
#include <QtQuick/QQuickWindow>
#include <QtWidgets/QActionGroup> #include <QtWidgets/QActionGroup>
#include <QtWidgets/QDesktopWidget> #include <QtWidgets/QDesktopWidget>
#include <QtWidgets/QFileDialog> #include <QtWidgets/QFileDialog>
@ -47,6 +52,7 @@
#include <gl/Config.h> #include <gl/Config.h>
#include <gl/QOpenGLContextWrapper.h> #include <gl/QOpenGLContextWrapper.h>
#include <ResourceScriptingInterface.h>
#include <AccountManager.h> #include <AccountManager.h>
#include <AddressManager.h> #include <AddressManager.h>
#include <ApplicationVersion.h> #include <ApplicationVersion.h>
@ -337,6 +343,8 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<RecordingScriptingInterface>(); DependencyManager::set<RecordingScriptingInterface>();
DependencyManager::set<WindowScriptingInterface>(); DependencyManager::set<WindowScriptingInterface>();
DependencyManager::set<HMDScriptingInterface>(); DependencyManager::set<HMDScriptingInterface>();
DependencyManager::set<ResourceScriptingInterface>();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
DependencyManager::set<SpeechRecognizer>(); DependencyManager::set<SpeechRecognizer>();
@ -682,6 +690,74 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// Setup the userInputMapper with the actions // Setup the userInputMapper with the actions
auto userInputMapper = DependencyManager::get<UserInputMapper>(); auto userInputMapper = DependencyManager::get<UserInputMapper>();
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
using namespace controller;
static auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (offscreenUi->navigationFocused()) {
auto actionEnum = static_cast<Action>(action);
int key = Qt::Key_unknown;
static int lastKey = Qt::Key_unknown;
bool navAxis = false;
switch (actionEnum) {
case Action::UI_NAV_VERTICAL:
navAxis = true;
if (state > 0.0f) {
key = Qt::Key_Up;
} else if (state < 0.0f) {
key = Qt::Key_Down;
}
break;
case Action::UI_NAV_LATERAL:
navAxis = true;
if (state > 0.0f) {
key = Qt::Key_Right;
} else if (state < 0.0f) {
key = Qt::Key_Left;
}
break;
case Action::UI_NAV_GROUP:
navAxis = true;
if (state > 0.0f) {
key = Qt::Key_Tab;
} else if (state < 0.0f) {
key = Qt::Key_Backtab;
}
break;
case Action::UI_NAV_BACK:
key = Qt::Key_Escape;
break;
case Action::UI_NAV_SELECT:
key = Qt::Key_Return;
break;
}
if (navAxis) {
if (lastKey != Qt::Key_unknown) {
QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier);
sendEvent(offscreenUi->getWindow(), &event);
lastKey = Qt::Key_unknown;
}
if (key != Qt::Key_unknown) {
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
sendEvent(offscreenUi->getWindow(), &event);
lastKey = key;
}
} else if (key != Qt::Key_unknown) {
if (state) {
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
sendEvent(offscreenUi->getWindow(), &event);
} else {
QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier);
sendEvent(offscreenUi->getWindow(), &event);
}
return;
}
}
if (action == controller::toInt(controller::Action::RETICLE_CLICK)) { if (action == controller::toInt(controller::Action::RETICLE_CLICK)) {
auto globalPos = QCursor::pos(); auto globalPos = QCursor::pos();
auto localPos = _glWidget->mapFromGlobal(globalPos); auto localPos = _glWidget->mapFromGlobal(globalPos);
@ -752,6 +828,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float { _applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float {
return (float)qApp->getMyAvatar()->getCharacterController()->onGround(); return (float)qApp->getMyAvatar()->getCharacterController()->onGround();
})); }));
_applicationStateDevice->addInputVariant(QString("NavigationFocused"), controller::StateController::ReadLambda([]() -> float {
static auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->navigationFocused() ? 1.0 : 0.0;
}));
userInputMapper->registerDevice(_applicationStateDevice); userInputMapper->registerDevice(_applicationStateDevice);
@ -1094,9 +1174,59 @@ void Application::initializeUi() {
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
offscreenUi->load("Root.qml"); offscreenUi->load("Root.qml");
offscreenUi->load("RootMenu.qml"); offscreenUi->load("RootMenu.qml");
auto scriptingInterface = DependencyManager::get<controller::ScriptingInterface>(); // FIXME either expose so that dialogs can set this themselves or
offscreenUi->getRootContext()->setContextProperty("Controller", scriptingInterface.data()); // do better detection in the offscreen UI of what has focus
offscreenUi->getRootContext()->setContextProperty("MyAvatar", getMyAvatar()); offscreenUi->setNavigationFocused(false);
auto rootContext = offscreenUi->getRootContext();
auto engine = rootContext->engine();
connect(engine, &QQmlEngine::quit, [] {
qApp->quit();
});
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
rootContext->setContextProperty("MyAvatar", getMyAvatar());
rootContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
rootContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
rootContext->setContextProperty("TREE_SCALE", TREE_SCALE);
rootContext->setContextProperty("Quat", new Quat());
rootContext->setContextProperty("Vec3", new Vec3());
rootContext->setContextProperty("Uuid", new ScriptUUID());
rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
rootContext->setContextProperty("Camera", &_myCamera);
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
rootContext->setContextProperty("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data());
#endif
rootContext->setContextProperty("Overlays", &_overlays);
rootContext->setContextProperty("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
rootContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance());
rootContext->setContextProperty("Stats", Stats::getInstance());
rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
rootContext->setContextProperty("AudioDevice", AudioDeviceScriptingInterface::getInstance());
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
rootContext->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
rootContext->setContextProperty("Account", AccountScriptingInterface::getInstance());
rootContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
rootContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance());
rootContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
rootContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
rootContext->setContextProperty("UndoStack", &_undoStackScriptingInterface);
rootContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data());
rootContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
rootContext->setContextProperty("Render", DependencyManager::get<RenderScriptingInterface>().data());
rootContext->setContextProperty("ScriptDiscoveryService", this->getRunningScriptsWidget());
_glWidget->installEventFilter(offscreenUi.data()); _glWidget->installEventFilter(offscreenUi.data());
VrMenu::load(); VrMenu::load();
VrMenu::executeQueuedLambdas(); VrMenu::executeQueuedLambdas();
@ -2860,7 +2990,11 @@ void Application::update(float deltaTime) {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::update()"); PerformanceWarning warn(showWarnings, "Application::update()");
if (DependencyManager::get<LODManager>()->getUseAcuity()) {
updateLOD(); updateLOD();
} else {
DependencyManager::get<LODManager>()->updatePIDRenderDistance(getTargetFrameRate(), getLastInstanteousFps(), deltaTime, isThrottleRendering());
}
{ {
PerformanceTimer perfTimer("devices"); PerformanceTimer perfTimer("devices");
@ -2999,8 +3133,7 @@ void Application::update(float deltaTime) {
getEntities()->update(); // update the models... getEntities()->update(); // update the models...
} }
myAvatar->harvestResultsFromPhysicsSimulation(); myAvatar->harvestResultsFromPhysicsSimulation(deltaTime);
myAvatar->simulateAttachments(deltaTime);
} }
} }
@ -4111,6 +4244,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance());

View file

@ -9,6 +9,7 @@
// 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
// //
#include <avatar/AvatarManager.h>
#include <SettingHandle.h> #include <SettingHandle.h>
#include <Util.h> #include <Util.h>
@ -20,9 +21,30 @@
Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS);
Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS);
// There are two different systems in use, based on lodPreference:
// pid: renderDistance is adjusted by a PID such that frame rate targets are met.
// acuity: a pseudo-acuity target is held, or adjusted to match minimum frame rates (and a PID controlls avatar rendering distance)
// If unspecified, acuity is used only if user has specified non-default minumum frame rates.
Setting::Handle<int> lodPreference("lodPreference", (int)LODManager::LODPreference::unspecified);
const float SMALLEST_REASONABLE_HORIZON = 50.0f; // meters
Setting::Handle<float> renderDistanceInverseHighLimit("renderDistanceInverseHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON);
void LODManager::setRenderDistanceInverseHighLimit(float newValue) {
renderDistanceInverseHighLimit.set(newValue); // persist it, and tell all the controllers that use it
_renderDistanceController.setControlledValueHighLimit(newValue);
}
LODManager::LODManager() { LODManager::LODManager() {
calculateAvatarLODDistanceMultiplier(); calculateAvatarLODDistanceMultiplier();
setRenderDistanceInverseHighLimit(renderDistanceInverseHighLimit.get());
setRenderDistanceInverseLowLimit(1.0f / (float)TREE_SCALE);
// Advice for tuning parameters:
// See PIDController.h. There's a section on tuning in the reference.
// Turn on logging with the following (or from js with LODManager.setRenderDistanceControllerHistory("render pid", 240))
//setRenderDistanceControllerHistory("render pid", 60 * 4);
// Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on.
setRenderDistanceKP(0.000012f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0.
setRenderDistanceKI(0.00002f); // Big enough to bring us to target with the above KP.
} }
float LODManager::getLODDecreaseFPS() { float LODManager::getLODDecreaseFPS() {
@ -39,7 +61,6 @@ float LODManager::getLODIncreaseFPS() {
return getDesktopLODIncreaseFPS(); return getDesktopLODIncreaseFPS();
} }
void LODManager::autoAdjustLOD(float currentFPS) { void LODManager::autoAdjustLOD(float currentFPS) {
// NOTE: our first ~100 samples at app startup are completely all over the place, and we don't // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't
@ -217,14 +238,57 @@ QString LODManager::getLODFeedbackText() {
return result; return result;
} }
static float renderDistance = (float)TREE_SCALE;
static int renderedCount = 0;
static int lastRenderedCount = 0;
bool LODManager::getUseAcuity() { return lodPreference.get() == (int)LODManager::LODPreference::acuity; }
void LODManager::setUseAcuity(bool newValue) { lodPreference.set(newValue ? (int)LODManager::LODPreference::acuity : (int)LODManager::LODPreference::pid); }
float LODManager::getRenderDistance() {
return renderDistance;
}
int LODManager::getRenderedCount() {
return lastRenderedCount;
}
QString LODManager::getLODStatsRenderText() {
QString label = getUseAcuity() ? "Renderable avatars: " : "Rendered objects: ";
return label + QString::number(getRenderedCount()) + " w/in " + QString::number((int)getRenderDistance()) + "m";
}
// compare audoAdjustLOD()
void LODManager::updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled) {
float distance;
if (!isThrottled) {
_renderDistanceController.setMeasuredValueSetpoint(targetFps / 2.0f); // No problem updating in flight.
// The PID controller raises the controlled value when the measured value goes up.
// The measured value is frame rate. When the controlled value (1 / render cutoff distance)
// goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate
// goes up.
distance = 1.0f / _renderDistanceController.update(measuredFps, deltaTime);
} else {
// Here we choose to just use the maximum render cutoff distance if throttled.
distance = 1.0f / _renderDistanceController.getControlledValueLowLimit();
}
_renderDistanceAverage.updateAverage(distance);
renderDistance = _renderDistanceAverage.getAverage(); // average only once per cycle
lastRenderedCount = renderedCount;
renderedCount = 0;
}
bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) {
float distanceToCamera = glm::length(bounds.calcCenter() - args->_viewFrustum->getPosition());
float largestDimension = bounds.getLargestDimension();
if (!getUseAcuity()) {
const float scenerySize = 300; // meters
bool isRendered = (largestDimension > scenerySize) || // render scenery regardless of distance
(fabsf(distanceToCamera - largestDimension) < renderDistance);
renderedCount += isRendered ? 1 : 0;
return isRendered;
}
const float maxScale = (float)TREE_SCALE; const float maxScale = (float)TREE_SCALE;
const float octreeToMeshRatio = 4.0f; // must be this many times closer to a mesh than a voxel to see it. const float octreeToMeshRatio = 4.0f; // must be this many times closer to a mesh than a voxel to see it.
float octreeSizeScale = args->_sizeScale; float octreeSizeScale = args->_sizeScale;
int boundaryLevelAdjust = args->_boundaryLevelAdjust; int boundaryLevelAdjust = args->_boundaryLevelAdjust;
float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / octreeToMeshRatio; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / octreeToMeshRatio;
float distanceToCamera = glm::length(bounds.calcCenter() - args->_viewFrustum->getPosition());
float largestDimension = bounds.getLargestDimension();
static bool shouldRenderTableNeedsBuilding = true; static bool shouldRenderTableNeedsBuilding = true;
static QMap<float, float> shouldRenderTable; static QMap<float, float> shouldRenderTable;
@ -315,6 +379,12 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
void LODManager::loadSettings() { void LODManager::loadSettings() {
setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get());
setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get());
if (lodPreference.get() == (int)LODManager::LODPreference::unspecified) {
setUseAcuity((getDesktopLODDecreaseFPS() != DEFAULT_DESKTOP_LOD_DOWN_FPS) || (getHMDLODDecreaseFPS() != DEFAULT_HMD_LOD_DOWN_FPS));
}
Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(getUseAcuity());
Menu::getInstance()->getSubMenuFromName(MenuOption::RenderResolution, Menu::getInstance()->getSubMenuFromName("Render", Menu::getInstance()->getMenu("Developer")))->setEnabled(getUseAcuity());
} }
void LODManager::saveSettings() { void LODManager::saveSettings() {

View file

@ -15,6 +15,7 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <OctreeConstants.h> #include <OctreeConstants.h>
#include <PIDController.h>
#include <SimpleMovingAverage.h> #include <SimpleMovingAverage.h>
const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 15.0; const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 15.0;
@ -81,6 +82,27 @@ public:
Q_INVOKABLE float getLODDecreaseFPS(); Q_INVOKABLE float getLODDecreaseFPS();
Q_INVOKABLE float getLODIncreaseFPS(); Q_INVOKABLE float getLODIncreaseFPS();
enum class LODPreference {
pid = 0,
acuity,
unspecified
};
static bool getUseAcuity();
static void setUseAcuity(bool newValue);
Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); }
Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); }
Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); }
Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); }
Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); }
Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); }
Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); }
Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); }
Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue);
void updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled);
float getRenderDistance();
int getRenderedCount();
QString getLODStatsRenderText();
static bool shouldRender(const RenderArgs* args, const AABox& bounds); static bool shouldRender(const RenderArgs* args, const AABox& bounds);
bool shouldRenderMesh(float largestDimension, float distanceToCamera); bool shouldRenderMesh(float largestDimension, float distanceToCamera);
void autoAdjustLOD(float currentFPS); void autoAdjustLOD(float currentFPS);
@ -116,6 +138,9 @@ private:
bool _shouldRenderTableNeedsRebuilding = true; bool _shouldRenderTableNeedsRebuilding = true;
QMap<float, float> _shouldRenderTable; QMap<float, float> _shouldRenderTable;
PIDController _renderDistanceController{};
SimpleMovingAverage _renderDistanceAverage{ 10 };
}; };
#endif // hifi_LODManager_h #endif // hifi_LODManager_h

View file

@ -64,6 +64,7 @@ public:
void saveSettings(); void saveSettings();
MenuWrapper* getMenu(const QString& menuName); MenuWrapper* getMenu(const QString& menuName);
MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu);
void triggerOption(const QString& menuOption); void triggerOption(const QString& menuOption);
QAction* getActionForOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption);
@ -130,7 +131,6 @@ private:
const QString& grouping = QString()); const QString& grouping = QString());
QAction* getActionFromName(const QString& menuName, MenuWrapper* menu); QAction* getActionFromName(const QString& menuName, MenuWrapper* menu);
MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu);
MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart); MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart);
QAction* getMenuAction(const QString& menuName); QAction* getMenuAction(const QString& menuName);

View file

@ -42,6 +42,7 @@
#include "Util.h" #include "Util.h"
#include "world.h" #include "world.h"
#include "InterfaceLogging.h" #include "InterfaceLogging.h"
#include "SoftAttachmentModel.h"
#include <Rig.h> #include <Rig.h>
using namespace std; using namespace std;
@ -108,9 +109,6 @@ Avatar::Avatar(RigPointer rig) :
Avatar::~Avatar() { Avatar::~Avatar() {
assert(_motionState == nullptr); assert(_motionState == nullptr);
for(auto attachment : _unusedAttachments) {
delete attachment;
}
} }
const float BILLBOARD_LOD_DISTANCE = 40.0f; const float BILLBOARD_LOD_DISTANCE = 40.0f;
@ -186,26 +184,6 @@ void Avatar::simulate(float deltaTime) {
qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance();
} }
const bool isControllerLogging = DependencyManager::get<AvatarManager>()->getRenderDistanceControllerIsLogging();
float renderDistance = DependencyManager::get<AvatarManager>()->getRenderDistance();
const float SKIP_HYSTERESIS_PROPORTION = isControllerLogging ? 0.0f : BILLBOARD_HYSTERESIS_PROPORTION;
float distance = glm::distance(qApp->getCamera()->getPosition(), getPosition());
if (_shouldSkipRender) {
if (distance < renderDistance * (1.0f - SKIP_HYSTERESIS_PROPORTION)) {
_shouldSkipRender = false;
_skeletonModel.setVisibleInScene(true, qApp->getMain3DScene());
if (!isControllerLogging) { // Test for isMyAvatar is prophylactic. Never occurs in current code.
qCDebug(interfaceapp) << "Rerendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance;
}
}
} else if (distance > renderDistance * (1.0f + SKIP_HYSTERESIS_PROPORTION)) {
_shouldSkipRender = true;
_skeletonModel.setVisibleInScene(false, qApp->getMain3DScene());
if (!isControllerLogging) {
qCDebug(interfaceapp) << "Unrendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance;
}
}
// simple frustum check // simple frustum check
float boundingRadius = getBillboardSize(); float boundingRadius = getBillboardSize();
bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) != bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) !=
@ -257,6 +235,8 @@ void Avatar::simulate(float deltaTime) {
// until velocity is included in AvatarData update message. // until velocity is included in AvatarData update message.
//_position += _velocity * deltaTime; //_position += _velocity * deltaTime;
measureMotionDerivatives(deltaTime); measureMotionDerivatives(deltaTime);
simulateAttachments(deltaTime);
} }
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {
@ -324,7 +304,7 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene>
_skeletonModel.addToScene(scene, pendingChanges); _skeletonModel.addToScene(scene, pendingChanges);
getHead()->getFaceModel().addToScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges);
for (auto attachmentModel : _attachmentModels) { for (auto& attachmentModel : _attachmentModels) {
attachmentModel->addToScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges);
} }
@ -335,7 +315,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::S
pendingChanges.removeItem(_renderItemID); pendingChanges.removeItem(_renderItemID);
_skeletonModel.removeFromScene(scene, pendingChanges); _skeletonModel.removeFromScene(scene, pendingChanges);
getHead()->getFaceModel().removeFromScene(scene, pendingChanges); getHead()->getFaceModel().removeFromScene(scene, pendingChanges);
for (auto attachmentModel : _attachmentModels) { for (auto& attachmentModel : _attachmentModels) {
attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->removeFromScene(scene, pendingChanges);
} }
} }
@ -565,15 +545,14 @@ void Avatar::fixupModelsInScene() {
faceModel.removeFromScene(scene, pendingChanges); faceModel.removeFromScene(scene, pendingChanges);
faceModel.addToScene(scene, pendingChanges); faceModel.addToScene(scene, pendingChanges);
} }
for (auto attachmentModel : _attachmentModels) { for (auto& attachmentModel : _attachmentModels) {
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->removeFromScene(scene, pendingChanges);
attachmentModel->addToScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges);
} }
} }
for (auto attachmentModelToRemove : _attachmentsToRemove) { for (auto& attachmentModelToRemove : _attachmentsToRemove) {
attachmentModelToRemove->removeFromScene(scene, pendingChanges); attachmentModelToRemove->removeFromScene(scene, pendingChanges);
_unusedAttachments << attachmentModelToRemove;
} }
_attachmentsToRemove.clear(); _attachmentsToRemove.clear();
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);
@ -603,13 +582,20 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const {
return true; return true;
} }
// virtual
void Avatar::simulateAttachments(float deltaTime) { void Avatar::simulateAttachments(float deltaTime) {
for (int i = 0; i < _attachmentModels.size(); i++) { for (int i = 0; i < _attachmentModels.size(); i++) {
const AttachmentData& attachment = _attachmentData.at(i); const AttachmentData& attachment = _attachmentData.at(i);
Model* model = _attachmentModels.at(i); auto& model = _attachmentModels.at(i);
int jointIndex = getJointIndex(attachment.jointName); int jointIndex = getJointIndex(attachment.jointName);
glm::vec3 jointPosition; glm::vec3 jointPosition;
glm::quat jointRotation; glm::quat jointRotation;
if (attachment.isSoft) {
// soft attachments do not have transform offsets
model->setTranslation(getPosition());
model->setRotation(getOrientation() * Quaternions::Y_180);
model->simulate(deltaTime);
} else {
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
_skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) {
model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale());
@ -621,6 +607,7 @@ void Avatar::simulateAttachments(float deltaTime) {
} }
} }
} }
}
void Avatar::updateJointMappings() { void Avatar::updateJointMappings() {
// no-op; joint mappings come from skeleton model // no-op; joint mappings come from skeleton model
@ -940,13 +927,48 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModel.setURL(_skeletonModelURL); _skeletonModel.setURL(_skeletonModelURL);
} }
// create new model, can return an instance of a SoftAttachmentModel rather then Model
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
if (isSoft) {
// cast to std::shared_ptr<Model>
return std::dynamic_pointer_cast<Model>(std::make_shared<SoftAttachmentModel>(std::make_shared<Rig>(), nullptr, rigOverride));
} else {
return std::make_shared<Model>(std::make_shared<Rig>());
}
}
void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) { void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
AvatarData::setAttachmentData(attachmentData);
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection,
Q_ARG(const QVector<AttachmentData>, attachmentData)); Q_ARG(const QVector<AttachmentData>, attachmentData));
return; return;
} }
auto oldAttachmentData = _attachmentData;
AvatarData::setAttachmentData(attachmentData);
// if number of attachments has been reduced, remove excess models.
while (_attachmentModels.size() > attachmentData.size()) {
auto attachmentModel = _attachmentModels.back();
_attachmentModels.pop_back();
_attachmentsToRemove.push_back(attachmentModel);
}
for (int i = 0; i < attachmentData.size(); i++) {
if (i == _attachmentModels.size()) {
// if number of attachments has been increased, we need to allocate a new model
_attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig()));
}
else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) {
// if the attachment has changed type, we need to re-allocate a new one.
_attachmentsToRemove.push_back(_attachmentModels[i]);
_attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig());
}
_attachmentModels[i]->setURL(attachmentData[i].modelURL);
}
// AJT: TODO REMOVE
/*
// make sure we have as many models as attachments // make sure we have as many models as attachments
while (_attachmentModels.size() < attachmentData.size()) { while (_attachmentModels.size() < attachmentData.size()) {
Model* model = nullptr; Model* model = nullptr;
@ -959,16 +981,20 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
_attachmentModels.append(model); _attachmentModels.append(model);
} }
while (_attachmentModels.size() > attachmentData.size()) { while (_attachmentModels.size() > attachmentData.size()) {
auto attachmentModel = _attachmentModels.takeLast(); auto attachmentModel = _attachmentModels.back();
_attachmentsToRemove << attachmentModel; _attachmentModels.pop_back();
_attachmentsToRemove.push_back(attachmentModel);
} }
*/
/*
// update the urls // update the urls
for (int i = 0; i < attachmentData.size(); i++) { for (int i = 0; i < attachmentData.size(); i++) {
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL); _attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
_attachmentModels[i]->setSnapModelToCenter(true); _attachmentModels[i]->setSnapModelToCenter(true);
_attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale); _attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale);
} }
*/
} }
void Avatar::setBillboard(const QByteArray& billboard) { void Avatar::setBillboard(const QByteArray& billboard) {

View file

@ -68,7 +68,7 @@ public:
void init(); void init();
void simulate(float deltaTime); void simulate(float deltaTime);
void simulateAttachments(float deltaTime); virtual void simulateAttachments(float deltaTime);
virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition);
@ -179,9 +179,9 @@ protected:
SkeletonModel _skeletonModel; SkeletonModel _skeletonModel;
glm::vec3 _skeletonOffset; glm::vec3 _skeletonOffset;
QVector<Model*> _attachmentModels; std::vector<std::shared_ptr<Model>> _attachmentModels;
QVector<Model*> _attachmentsToRemove; std::vector<std::shared_ptr<Model>> _attachmentsToRemove;
QVector<Model*> _unusedAttachments;
float _bodyYawDelta; // degrees/sec float _bodyYawDelta; // degrees/sec
// These position histories and derivatives are in the world-frame. // These position histories and derivatives are in the world-frame.

View file

@ -76,13 +76,6 @@ AvatarManager::AvatarManager(QObject* parent) :
packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket"); packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket");
} }
const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters
Setting::Handle<float> avatarRenderDistanceInverseHighLimit("avatarRenderDistanceHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON);
void AvatarManager::setRenderDistanceInverseHighLimit(float newValue) {
avatarRenderDistanceInverseHighLimit.set(newValue);
_renderDistanceController.setControlledValueHighLimit(newValue);
}
void AvatarManager::init() { void AvatarManager::init() {
_myAvatar->init(); _myAvatar->init();
{ {
@ -98,19 +91,6 @@ void AvatarManager::init() {
_myAvatar->addToScene(_myAvatar, scene, pendingChanges); _myAvatar->addToScene(_myAvatar, scene, pendingChanges);
} }
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);
const float target_fps = qApp->getTargetFrameRate();
_renderDistanceController.setMeasuredValueSetpoint(target_fps);
_renderDistanceController.setControlledValueHighLimit(avatarRenderDistanceInverseHighLimit.get());
_renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE);
// Advice for tuning parameters:
// See PIDController.h. There's a section on tuning in the reference.
// Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300))
//_renderDistanceController.setHistorySize("avatar render", target_fps * 4);
// Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on.
_renderDistanceController.setKP(0.0008f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0.
_renderDistanceController.setKI(0.0006f); // Big enough to bring us to target with the above KP.
_renderDistanceController.setKD(0.000001f); // A touch of kd increases the speed by which we get there.
} }
void AvatarManager::updateMyAvatar(float deltaTime) { void AvatarManager::updateMyAvatar(float deltaTime) {
@ -145,23 +125,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
PerformanceTimer perfTimer("otherAvatars"); PerformanceTimer perfTimer("otherAvatars");
float distance;
if (!qApp->isThrottleRendering()) {
_renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight.
// The PID controller raises the controlled value when the measured value goes up.
// The measured value is frame rate. When the controlled value (1 / render cutoff distance)
// goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate
// goes up.
const float deduced = qApp->getLastUnsynchronizedFps();
distance = 1.0f / _renderDistanceController.update(deduced, deltaTime);
} else {
// Here we choose to just use the maximum render cutoff distance if throttled.
distance = 1.0f / _renderDistanceController.getControlledValueLowLimit();
}
_renderDistanceAverage.updateAverage(distance);
_renderDistance = _renderDistanceAverage.getAverage();
int renderableCount = 0;
// simulate avatars // simulate avatars
auto hashCopy = getHashCopy(); auto hashCopy = getHashCopy();
@ -179,14 +142,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
} else { } else {
avatar->startUpdate(); avatar->startUpdate();
avatar->simulate(deltaTime); avatar->simulate(deltaTime);
if (avatar->getShouldRender()) {
renderableCount++;
}
avatar->endUpdate(); avatar->endUpdate();
++avatarIterator; ++avatarIterator;
} }
} }
_renderedAvatarCount = renderableCount;
// simulate avatar fades // simulate avatar fades
simulateAvatarFades(deltaTime); simulateAvatarFades(deltaTime);

View file

@ -45,7 +45,6 @@ public:
void clearOtherAvatars(); void clearOtherAvatars();
bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; }
PIDController& getRenderDistanceController() { return _renderDistanceController; }
class LocalLight { class LocalLight {
public: public:
@ -68,19 +67,6 @@ public:
void addAvatarToSimulation(Avatar* avatar); void addAvatarToSimulation(Avatar* avatar);
// Expose results and parameter-tuning operations to other systems, such as stats and javascript.
Q_INVOKABLE float getRenderDistance() { return _renderDistance; }
Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); }
Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); }
Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; }
Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); }
Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); }
Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); }
Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); }
Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); }
Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); }
Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue);
public slots: public slots:
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
void updateAvatarRenderStatus(bool shouldRenderAvatars); void updateAvatarRenderStatus(bool shouldRenderAvatars);
@ -106,10 +92,6 @@ private:
QVector<AvatarManager::LocalLight> _localLights; QVector<AvatarManager::LocalLight> _localLights;
bool _shouldShowReceiveStats = false; bool _shouldShowReceiveStats = false;
float _renderDistance { (float) TREE_SCALE };
int _renderedAvatarCount { 0 };
PIDController _renderDistanceController { };
SimpleMovingAverage _renderDistanceAverage { 10 };
SetOfAvatarMotionStates _avatarMotionStates; SetOfAvatarMotionStates _avatarMotionStates;
SetOfMotionStates _motionStatesToAdd; SetOfMotionStates _motionStatesToAdd;

View file

@ -201,6 +201,11 @@ MyAvatar::~MyAvatar() {
_lookAtTargetAvatar.reset(); _lookAtTargetAvatar.reset();
} }
// virtual
void MyAvatar::simulateAttachments(float deltaTime) {
// don't update attachments here, do it in harvestResultsFromPhysicsSimulation()
}
QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) {
CameraMode mode = qApp->getCamera()->getMode(); CameraMode mode = qApp->getCamera()->getMode();
_globalPosition = getPosition(); _globalPosition = getPosition();
@ -621,6 +626,7 @@ void MyAvatar::saveData() {
settings.setValue("rotation_y", eulers.y); settings.setValue("rotation_y", eulers.y);
settings.setValue("rotation_z", eulers.z); settings.setValue("rotation_z", eulers.z);
settings.setValue("scale", attachment.scale); settings.setValue("scale", attachment.scale);
settings.setValue("isSoft", attachment.isSoft);
} }
settings.endArray(); settings.endArray();
@ -702,6 +708,7 @@ void MyAvatar::loadData() {
eulers.z = loadSetting(settings, "rotation_z", 0.0f); eulers.z = loadSetting(settings, "rotation_z", 0.0f);
attachment.rotation = glm::quat(eulers); attachment.rotation = glm::quat(eulers);
attachment.scale = loadSetting(settings, "scale", 1.0f); attachment.scale = loadSetting(settings, "scale", 1.0f);
attachment.isSoft = settings.value("isSoft").toBool();
attachmentData.append(attachment); attachmentData.append(attachment);
} }
settings.endArray(); settings.endArray();
@ -1057,7 +1064,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
_characterController.setFollowVelocity(_followVelocity); _characterController.setFollowVelocity(_followVelocity);
} }
void MyAvatar::harvestResultsFromPhysicsSimulation() { void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaType) {
glm::vec3 position = getPosition(); glm::vec3 position = getPosition();
glm::quat orientation = getOrientation(); glm::quat orientation = getOrientation();
_characterController.getPositionAndOrientation(position, orientation); _characterController.getPositionAndOrientation(position, orientation);
@ -1068,6 +1075,9 @@ void MyAvatar::harvestResultsFromPhysicsSimulation() {
} else { } else {
setVelocity(_characterController.getLinearVelocity()); setVelocity(_characterController.getLinearVelocity());
} }
// now that physics has adjusted our position, we can update attachements.
Avatar::simulateAttachments(deltaType);
} }
void MyAvatar::adjustSensorTransform() { void MyAvatar::adjustSensorTransform() {
@ -1599,7 +1609,7 @@ void MyAvatar::maybeUpdateBillboard() {
if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) {
return; return;
} }
foreach (Model* model, _attachmentModels) { for (auto& model : _attachmentModels) {
if (!model->isLoadedWithTextures()) { if (!model->isLoadedWithTextures()) {
return; return;
} }

View file

@ -83,6 +83,8 @@ public:
MyAvatar(RigPointer rig); MyAvatar(RigPointer rig);
~MyAvatar(); ~MyAvatar();
virtual void simulateAttachments(float deltaTime) override;
AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; } AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; }
AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; }
AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; }
@ -204,7 +206,7 @@ public:
MyCharacterController* getCharacterController() { return &_characterController; } MyCharacterController* getCharacterController() { return &_characterController; }
void prepareForPhysicsSimulation(); void prepareForPhysicsSimulation();
void harvestResultsFromPhysicsSimulation(); void harvestResultsFromPhysicsSimulation(float deltaTime);
void adjustSensorTransform(); void adjustSensorTransform();
const QString& getCollisionSoundURL() { return _collisionSoundURL; } const QString& getCollisionSoundURL() { return _collisionSoundURL; }

View file

@ -0,0 +1,84 @@
//
// SoftAttachmentModel.cpp
// interface/src/avatar
//
// Created by Anthony J. Thibault on 12/17/15.
// Copyright 2013 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 "SoftAttachmentModel.h"
#include "InterfaceLogging.h"
SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) :
Model(rig, parent),
_rigOverride(rigOverride) {
assert(_rig);
assert(_rigOverride);
}
SoftAttachmentModel::~SoftAttachmentModel() {
}
// virtual
void SoftAttachmentModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_needsUpdateClusterMatrices = true;
}
int SoftAttachmentModel::getJointIndexOverride(int i) const {
QString name = _rig->nameOfJoint(i);
if (name.isEmpty()) {
return -1;
}
return _rigOverride->indexOfJoint(name);
}
// virtual
// use the _rigOverride matrices instead of the Model::_rig
void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) {
if (!_needsUpdateClusterMatrices) {
return;
}
_needsUpdateClusterMatrices = false;
const FBXGeometry& geometry = _geometry->getFBXGeometry();
glm::mat4 modelToWorld = glm::mat4_cast(modelOrientation);
for (int i = 0; i < _meshStates.size(); i++) {
MeshState& state = _meshStates[i];
const FBXMesh& mesh = geometry.meshes.at(i);
for (int j = 0; j < mesh.clusters.size(); j++) {
const FBXCluster& cluster = mesh.clusters.at(j);
// TODO: cache these look ups as an optimization
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
glm::mat4 jointMatrix(glm::mat4::_null);
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride->getJointStateCount()) {
jointMatrix = _rigOverride->getJointTransform(jointIndexOverride);
} else {
jointMatrix = _rig->getJointTransform(cluster.jointIndex);
}
state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix;
}
// Once computed the cluster matrices, update the buffer(s)
if (mesh.clusters.size() > 1) {
if (!state.clusterBuffer) {
state.clusterBuffer = std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.clusterMatrices.constData());
} else {
state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
(const gpu::Byte*) state.clusterMatrices.constData());
}
}
}
// post the blender if we're not currently waiting for one to finish
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
DependencyManager::get<ModelBlender>()->noteRequiresBlend(this);
}
}

View file

@ -0,0 +1,42 @@
//
// SoftAttachmentModel.h
// interface/src/avatar
//
// Created by Anthony J. Thibault on 12/17/15.
// Copyright 2015 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
//
#ifndef hifi_SoftAttachmentModel_h
#define hifi_SoftAttachmentModel_h
#include <Model.h>
// A model that allows the creator to specify a secondary rig instance.
// When the cluster matrices are created for rendering, the
// cluster matrices will use the secondary rig for the joint poses
// instead of the primary rig.
//
// This is used by Avatar instances to wear clothing that follows the same
// animated pose as the SkeletonModel.
class SoftAttachmentModel : public Model {
Q_OBJECT
public:
SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride);
~SoftAttachmentModel();
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) override;
protected:
int getJointIndexOverride(int i) const;
RigPointer _rigOverride;
};
#endif // hifi_SoftAttachmentModel_h

View file

@ -17,7 +17,6 @@ AccountScriptingInterface::AccountScriptingInterface() {
AccountManager& accountManager = AccountManager::getInstance(); AccountManager& accountManager = AccountManager::getInstance();
connect(&accountManager, &AccountManager::balanceChanged, this, connect(&accountManager, &AccountManager::balanceChanged, this,
&AccountScriptingInterface::updateBalance); &AccountScriptingInterface::updateBalance);
} }
AccountScriptingInterface* AccountScriptingInterface::getInstance() { AccountScriptingInterface* AccountScriptingInterface::getInstance() {
@ -39,3 +38,12 @@ void AccountScriptingInterface::updateBalance() {
AccountManager& accountManager = AccountManager::getInstance(); AccountManager& accountManager = AccountManager::getInstance();
emit balanceChanged(accountManager.getAccountInfo().getBalanceInSatoshis()); emit balanceChanged(accountManager.getAccountInfo().getBalanceInSatoshis());
} }
QString AccountScriptingInterface::getUsername() {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
return accountManager.getAccountInfo().getUsername();
} else {
return "Unknown user";
}
}

View file

@ -24,6 +24,7 @@ signals:
public slots: public slots:
static AccountScriptingInterface* getInstance(); static AccountScriptingInterface* getInstance();
float getBalance(); float getBalance();
QString getUsername();
bool isLoggedIn(); bool isLoggedIn();
void updateBalance(); void updateBalance();
}; };

View file

@ -86,7 +86,7 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
// Now render the overlay components together into a single texture // Now render the overlay components together into a single texture
renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line
renderAudioScope(renderArgs); // audio scope in the very back renderAudioScope(renderArgs); // audio scope in the very back - NOTE: this is the debug audio scope, not the VU meter
renderRearView(renderArgs); // renders the mirror view selfie renderRearView(renderArgs); // renders the mirror view selfie
renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts
renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope
@ -158,7 +158,7 @@ void ApplicationOverlay::renderRearViewToFbo(RenderArgs* renderArgs) {
} }
void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) {
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
gpu::Batch& batch = *renderArgs->_batch; gpu::Batch& batch = *renderArgs->_batch;
auto geometryCache = DependencyManager::get<GeometryCache>(); auto geometryCache = DependencyManager::get<GeometryCache>();

View file

@ -17,6 +17,7 @@
#include <QPushButton> #include <QPushButton>
#include <QScrollArea> #include <QScrollArea>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QCheckBox>
#include <avatar/AvatarManager.h> #include <avatar/AvatarManager.h>
#include <avatar/MyAvatar.h> #include <avatar/MyAvatar.h>
@ -147,6 +148,10 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData
_scale->setValue(data.scale); _scale->setValue(data.scale);
connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData()));
layout->addRow("Is Soft:", _isSoft = new QCheckBox());
_isSoft->setChecked(data.isSoft);
connect(_isSoft, SIGNAL(stateChanged(int)), SLOT(updateAttachmentData()));
QPushButton* remove = new QPushButton("Delete"); QPushButton* remove = new QPushButton("Delete");
layout->addRow(remove); layout->addRow(remove);
connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater()));
@ -160,6 +165,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const {
data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value()); data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value());
data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value()))); data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value())));
data.scale = _scale->value(); data.scale = _scale->value();
data.isSoft = _isSoft->isChecked();
return data; return data;
} }
@ -227,6 +233,7 @@ void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) {
_rotationY->setValue(eulers.y); _rotationY->setValue(eulers.y);
_rotationZ->setValue(eulers.z); _rotationZ->setValue(eulers.z);
_scale->setValue(attachment.scale); _scale->setValue(attachment.scale);
_isSoft->setChecked(attachment.isSoft);
_applying = false; _applying = false;
_dialog->updateAttachmentData(); _dialog->updateAttachmentData();
} }

View file

@ -77,6 +77,7 @@ private:
QDoubleSpinBox* _rotationY; QDoubleSpinBox* _rotationY;
QDoubleSpinBox* _rotationZ; QDoubleSpinBox* _rotationZ;
QDoubleSpinBox* _scale; QDoubleSpinBox* _scale;
QCheckBox* _isSoft;
bool _applying; bool _applying;
}; };

View file

@ -66,6 +66,7 @@ void AvatarInputs::update() {
&& !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)); && !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror));
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)); AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)); AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
AI_UPDATE(isHMD, qApp->isHMDMode());
auto audioIO = DependencyManager::get<AudioClient>(); auto audioIO = DependencyManager::get<AudioClient>();
const float AUDIO_METER_AVERAGING = 0.5; const float AUDIO_METER_AVERAGING = 0.5;

View file

@ -30,6 +30,7 @@ class AvatarInputs : public QQuickItem {
AI_PROPERTY(float, audioLevel, 0) AI_PROPERTY(float, audioLevel, 0)
AI_PROPERTY(bool, mirrorVisible, false) AI_PROPERTY(bool, mirrorVisible, false)
AI_PROPERTY(bool, mirrorZoomed, true) AI_PROPERTY(bool, mirrorZoomed, true)
AI_PROPERTY(bool, isHMD, false)
public: public:
static AvatarInputs* getInstance(); static AvatarInputs* getInstance();
@ -44,6 +45,7 @@ signals:
void audioLevelChanged(); void audioLevelChanged();
void mirrorVisibleChanged(); void mirrorVisibleChanged();
void mirrorZoomedChanged(); void mirrorZoomedChanged();
void isHMDChanged();
protected: protected:
Q_INVOKABLE void resetSensors(); Q_INVOKABLE void resetSensors();

View file

@ -48,6 +48,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) :
connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser);
connect(ui.appearanceDescription, &QLineEdit::editingFinished, this, &PreferencesDialog::changeFullAvatarURL); connect(ui.appearanceDescription, &QLineEdit::editingFinished, this, &PreferencesDialog::changeFullAvatarURL);
connect(ui.useAcuityCheckBox, &QCheckBox::clicked, this, &PreferencesDialog::changeUseAcuity);
connect(qApp, &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); connect(qApp, &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged);
@ -58,6 +59,16 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) :
UIUtil::scaleWidgetFontSizes(this); UIUtil::scaleWidgetFontSizes(this);
} }
void PreferencesDialog::changeUseAcuity() {
bool useAcuity = ui.useAcuityCheckBox->isChecked();
ui.label_desktopMinimumFPSSpin->setEnabled(useAcuity);
ui.desktopMinimumFPSSpin->setEnabled(useAcuity);
ui.label_hmdMinimumFPSSpin->setEnabled(useAcuity);
ui.hmdMinimumFPSSpin->setEnabled(useAcuity);
ui.label_smallestReasonableRenderHorizon->setText(useAcuity ? "Minimum Avatar Display Distance (@half speed)" : "Minimum Display Distance (@half speed)");
Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(useAcuity);
Menu::getInstance()->getSubMenuFromName(MenuOption::RenderResolution, Menu::getInstance()->getSubMenuFromName("Render", Menu::getInstance()->getMenu("Developer")))->setEnabled(useAcuity);
}
void PreferencesDialog::changeFullAvatarURL() { void PreferencesDialog::changeFullAvatarURL() {
DependencyManager::get<AvatarManager>()->getMyAvatar()->useFullAvatarURL(ui.appearanceDescription->text(), ""); DependencyManager::get<AvatarManager>()->getMyAvatar()->useFullAvatarURL(ui.appearanceDescription->text(), "");
this->fullAvatarURLChanged(ui.appearanceDescription->text(), ""); this->fullAvatarURLChanged(ui.appearanceDescription->text(), "");
@ -212,9 +223,11 @@ void PreferencesDialog::loadPreferences() {
// LOD items // LOD items
auto lodManager = DependencyManager::get<LODManager>(); auto lodManager = DependencyManager::get<LODManager>();
ui.useAcuityCheckBox->setChecked(lodManager->getUseAcuity());
ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS()); ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS());
ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS()); ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS());
ui.avatarRenderSmallestReasonableHorizon->setValue(1.0f / DependencyManager::get<AvatarManager>()->getRenderDistanceInverseHighLimit()); ui.smallestReasonableRenderHorizon->setValue(1.0f / lodManager->getRenderDistanceInverseHighLimit());
changeUseAcuity();
} }
void PreferencesDialog::savePreferences() { void PreferencesDialog::savePreferences() {
@ -303,7 +316,8 @@ void PreferencesDialog::savePreferences() {
// LOD items // LOD items
auto lodManager = DependencyManager::get<LODManager>(); auto lodManager = DependencyManager::get<LODManager>();
lodManager->setUseAcuity(ui.useAcuityCheckBox->isChecked());
lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value()); lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value());
lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value()); lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value());
DependencyManager::get<AvatarManager>()->setRenderDistanceInverseHighLimit(1.0f / ui.avatarRenderSmallestReasonableHorizon->value()); lodManager->setRenderDistanceInverseHighLimit(1.0f / ui.smallestReasonableRenderHorizon->value());
} }

View file

@ -51,6 +51,7 @@ private slots:
void openScriptsLocationBrowser(); void openScriptsLocationBrowser();
void changeFullAvatarURL(); void changeFullAvatarURL();
void fullAvatarURLChanged(const QString& newValue, const QString& modelName); void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
void changeUseAcuity();
}; };
#endif // hifi_PreferencesDialog_h #endif // hifi_PreferencesDialog_h

View file

@ -116,8 +116,6 @@ void Stats::updateStats(bool force) {
auto avatarManager = DependencyManager::get<AvatarManager>(); auto avatarManager = DependencyManager::get<AvatarManager>();
// we need to take one avatar out so we don't include ourselves // we need to take one avatar out so we don't include ourselves
STAT_UPDATE(avatarCount, avatarManager->size() - 1); STAT_UPDATE(avatarCount, avatarManager->size() - 1);
STAT_UPDATE(avatarRenderableCount, avatarManager->getNumberInRenderRange());
STAT_UPDATE(avatarRenderDistance, (int) round(avatarManager->getRenderDistance())); // deliberately truncating
STAT_UPDATE(serverCount, (int)nodeList->size()); STAT_UPDATE(serverCount, (int)nodeList->size());
STAT_UPDATE(renderrate, (int)qApp->getFps()); STAT_UPDATE(renderrate, (int)qApp->getFps());
if (qApp->getActiveDisplayPlugin()) { if (qApp->getActiveDisplayPlugin()) {
@ -285,7 +283,9 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount()); STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount());
// LOD Details // LOD Details
STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get<LODManager>()->getLODFeedbackText()); STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get<LODManager>()->getLODFeedbackText());
STAT_UPDATE(lodStatsRenderText, DependencyManager::get<LODManager>()->getLODStatsRenderText());
} }
STAT_UPDATE(showAcuity, (_expanded || force) && DependencyManager::get<LODManager>()->getUseAcuity());
bool performanceTimerIsActive = PerformanceTimer::isActive(); bool performanceTimerIsActive = PerformanceTimer::isActive();
bool displayPerf = _expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails); bool displayPerf = _expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails);

View file

@ -30,6 +30,7 @@ class Stats : public QQuickItem {
Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT) Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT)
Q_PROPERTY(float audioPacketlossUpstream READ getAudioPacketLossUpstream) Q_PROPERTY(float audioPacketlossUpstream READ getAudioPacketLossUpstream)
Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream) Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream)
Q_PROPERTY(bool showAcuity READ getShowAcuity WRITE setShowAcuity NOTIFY showAcuityChanged)
STATS_PROPERTY(int, serverCount, 0) STATS_PROPERTY(int, serverCount, 0)
STATS_PROPERTY(int, renderrate, 0) STATS_PROPERTY(int, renderrate, 0)
@ -37,8 +38,6 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, simrate, 0)
STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarSimrate, 0)
STATS_PROPERTY(int, avatarCount, 0) STATS_PROPERTY(int, avatarCount, 0)
STATS_PROPERTY(int, avatarRenderableCount, 0)
STATS_PROPERTY(int, avatarRenderDistance, 0)
STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetInCount, 0)
STATS_PROPERTY(int, packetOutCount, 0) STATS_PROPERTY(int, packetOutCount, 0)
STATS_PROPERTY(float, mbpsIn, 0) STATS_PROPERTY(float, mbpsIn, 0)
@ -77,6 +76,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(QString, packetStats, QString()) STATS_PROPERTY(QString, packetStats, QString())
STATS_PROPERTY(QString, lodStatus, QString()) STATS_PROPERTY(QString, lodStatus, QString())
STATS_PROPERTY(QString, timingStats, QString()) STATS_PROPERTY(QString, timingStats, QString())
STATS_PROPERTY(QString, lodStatsRenderText, QString())
STATS_PROPERTY(int, serverElements, 0) STATS_PROPERTY(int, serverElements, 0)
STATS_PROPERTY(int, serverInternal, 0) STATS_PROPERTY(int, serverInternal, 0)
STATS_PROPERTY(int, serverLeaves, 0) STATS_PROPERTY(int, serverLeaves, 0)
@ -108,12 +108,15 @@ public:
emit expandedChanged(); emit expandedChanged();
} }
} }
bool getShowAcuity() { return _showAcuity; }
void setShowAcuity(bool newValue) { _showAcuity = newValue; }
public slots: public slots:
void forceUpdateStats() { updateStats(true); } void forceUpdateStats() { updateStats(true); }
signals: signals:
void expandedChanged(); void expandedChanged();
void showAcuityChanged();
void timingExpandedChanged(); void timingExpandedChanged();
void serverCountChanged(); void serverCountChanged();
void renderrateChanged(); void renderrateChanged();
@ -121,8 +124,7 @@ signals:
void simrateChanged(); void simrateChanged();
void avatarSimrateChanged(); void avatarSimrateChanged();
void avatarCountChanged(); void avatarCountChanged();
void avatarRenderableCountChanged(); void lodStatsRenderTextChanged();
void avatarRenderDistanceChanged();
void packetInCountChanged(); void packetInCountChanged();
void packetOutCountChanged(); void packetOutCountChanged();
void mbpsInChanged(); void mbpsInChanged();
@ -172,6 +174,7 @@ private:
int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process
bool _resetRecentMaxPacketsSoon{ true }; bool _resetRecentMaxPacketsSoon{ true };
bool _expanded{ false }; bool _expanded{ false };
bool _showAcuity{ false };
bool _timingExpanded{ false }; bool _timingExpanded{ false };
QString _monospaceFont; QString _monospaceFont;
const AudioIOStats* _audioStats; const AudioIOStats* _audioStats;

View file

@ -742,6 +742,43 @@
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="useAcuityCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>28</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Render based on visual acuity</string>
</property>
<property name="iconSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_111x"> <layout class="QHBoxLayout" name="horizontalLayout_111x">
<property name="spacing"> <property name="spacing">
@ -757,7 +794,7 @@
<number>7</number> <number>7</number>
</property> </property>
<item> <item>
<widget class="QLabel" name="label_9x"> <widget class="QLabel" name="label_desktopMinimumFPSSpin">
<property name="font"> <property name="font">
<font> <font>
<family>Arial</family> <family>Arial</family>
@ -842,7 +879,7 @@
<number>7</number> <number>7</number>
</property> </property>
<item> <item>
<widget class="QLabel" name="label_9y"> <widget class="QLabel" name="label_hmdMinimumFPSSpin">
<property name="font"> <property name="font">
<font> <font>
<family>Arial</family> <family>Arial</family>
@ -927,7 +964,7 @@
<number>7</number> <number>7</number>
</property> </property>
<item> <item>
<widget class="QLabel" name="label_9yz"> <widget class="QLabel" name="label_smallestReasonableRenderHorizon">
<property name="font"> <property name="font">
<font> <font>
<family>Arial</family> <family>Arial</family>
@ -963,7 +1000,7 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="avatarRenderSmallestReasonableHorizon"> <widget class="QSpinBox" name="smallestReasonableRenderHorizon">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>100</width> <width>100</width>

View file

@ -245,6 +245,14 @@ int Rig::indexOfJoint(const QString& jointName) const {
} }
} }
QString Rig::nameOfJoint(int jointIndex) const {
if (_animSkeleton) {
return _animSkeleton->getJointName(jointIndex);
} else {
return "";
}
}
void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { void Rig::setModelOffset(const glm::mat4& modelOffsetMat) {
AnimPose newModelOffset = AnimPose(modelOffsetMat); AnimPose newModelOffset = AnimPose(modelOffsetMat);
if (!isEqual(_modelOffset.trans, newModelOffset.trans) || if (!isEqual(_modelOffset.trans, newModelOffset.trans) ||

View file

@ -91,6 +91,7 @@ public:
bool jointStatesEmpty(); bool jointStatesEmpty();
int getJointStateCount() const; int getJointStateCount() const;
int indexOfJoint(const QString& jointName) const; int indexOfJoint(const QString& jointName) const;
QString nameOfJoint(int jointIndex) const;
void setModelOffset(const glm::mat4& modelOffsetMat); void setModelOffset(const glm::mat4& modelOffsetMat);

View file

@ -1269,6 +1269,7 @@ void AvatarData::updateJointMappings() {
static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl");
static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName");
static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform");
static const QString JSON_ATTACHMENT_IS_SOFT = QStringLiteral("isSoft");
QJsonObject AttachmentData::toJson() const { QJsonObject AttachmentData::toJson() const {
QJsonObject result; QJsonObject result;
@ -1287,6 +1288,7 @@ QJsonObject AttachmentData::toJson() const {
if (!transform.isIdentity()) { if (!transform.isIdentity()) {
result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform); result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform);
} }
result[JSON_ATTACHMENT_IS_SOFT] = isSoft;
return result; return result;
} }
@ -1311,21 +1313,25 @@ void AttachmentData::fromJson(const QJsonObject& json) {
rotation = transform.getRotation(); rotation = transform.getRotation();
scale = transform.getScale().x; scale = transform.getScale().x;
} }
if (json.contains(JSON_ATTACHMENT_IS_SOFT)) {
isSoft = json[JSON_ATTACHMENT_IS_SOFT].toBool();
}
} }
bool AttachmentData::operator==(const AttachmentData& other) const { bool AttachmentData::operator==(const AttachmentData& other) const {
return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation && return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation &&
rotation == other.rotation && scale == other.scale; rotation == other.rotation && scale == other.scale && isSoft == other.isSoft;
} }
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) { QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) {
return out << attachment.modelURL << attachment.jointName << return out << attachment.modelURL << attachment.jointName <<
attachment.translation << attachment.rotation << attachment.scale; attachment.translation << attachment.rotation << attachment.scale << attachment.isSoft;
} }
QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) {
return in >> attachment.modelURL >> attachment.jointName >> return in >> attachment.modelURL >> attachment.jointName >>
attachment.translation >> attachment.rotation >> attachment.scale; attachment.translation >> attachment.rotation >> attachment.scale >> attachment.isSoft;
} }
void AttachmentDataObject::setModelURL(const QString& modelURL) const { void AttachmentDataObject::setModelURL(const QString& modelURL) const {

View file

@ -439,6 +439,7 @@ public:
glm::vec3 translation; glm::vec3 translation;
glm::quat rotation; glm::quat rotation;
float scale { 1.0f }; float scale { 1.0f };
bool isSoft { false };
bool isValid() const { return modelURL.isValid(); } bool isValid() const { return modelURL.isValid(); }

View file

@ -70,6 +70,12 @@ namespace controller {
makeAxisPair(Action::RETICLE_UP, "ReticleUp"), makeAxisPair(Action::RETICLE_UP, "ReticleUp"),
makeAxisPair(Action::RETICLE_DOWN, "ReticleDown"), makeAxisPair(Action::RETICLE_DOWN, "ReticleDown"),
makeAxisPair(Action::UI_NAV_LATERAL, "UiNavLateral"),
makeAxisPair(Action::UI_NAV_VERTICAL, "UiNavVertical"),
makeAxisPair(Action::UI_NAV_GROUP, "UiNavGroup"),
makeAxisPair(Action::UI_NAV_SELECT, "UiNavSelect"),
makeAxisPair(Action::UI_NAV_BACK, "UiNavBack"),
// Aliases and bisected versions // Aliases and bisected versions
makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"), makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"),
makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"), makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"),

View file

@ -55,6 +55,12 @@ enum class Action {
SHIFT, SHIFT,
UI_NAV_LATERAL,
UI_NAV_VERTICAL,
UI_NAV_GROUP,
UI_NAV_SELECT,
UI_NAV_BACK,
// Pointer/Reticle control // Pointer/Reticle control
RETICLE_CLICK, RETICLE_CLICK,
RETICLE_X, RETICLE_X,
@ -90,6 +96,7 @@ enum class Action {
BOOM_IN, BOOM_IN,
BOOM_OUT, BOOM_OUT,
NUM_ACTIONS, NUM_ACTIONS,
}; };

View file

@ -83,6 +83,7 @@ protected:
friend class UserInputMapper; friend class UserInputMapper;
virtual Input::NamedVector getAvailableInputs() const = 0; virtual Input::NamedVector getAvailableInputs() const = 0;
virtual QStringList getDefaultMappingConfigs() const { return QStringList() << getDefaultMappingConfig(); }
virtual QString getDefaultMappingConfig() const { return QString(); } virtual QString getDefaultMappingConfig() const { return QString(); }
virtual EndpointPointer createEndpoint(const Input& input) const; virtual EndpointPointer createEndpoint(const Input& input) const;

View file

@ -131,10 +131,10 @@ EndpointPointer StandardController::createEndpoint(const Input& input) const {
return std::make_shared<StandardEndpoint>(input); return std::make_shared<StandardEndpoint>(input);
} }
QString StandardController::getDefaultMappingConfig() const { QStringList StandardController::getDefaultMappingConfigs() const {
static const QString DEFAULT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard.json"; static const QString DEFAULT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard.json";
return DEFAULT_MAPPING_JSON; static const QString DEFAULT_NAV_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_navigation.json";
return QStringList() << DEFAULT_NAV_MAPPING_JSON << DEFAULT_MAPPING_JSON;
} }
} }

View file

@ -27,7 +27,7 @@ class StandardController : public QObject, public InputDevice {
public: public:
virtual EndpointPointer createEndpoint(const Input& input) const override; virtual EndpointPointer createEndpoint(const Input& input) const override;
virtual Input::NamedVector getAvailableInputs() const override; virtual Input::NamedVector getAvailableInputs() const override;
virtual QString getDefaultMappingConfig() const override; virtual QStringList getDefaultMappingConfigs() const override;
virtual void update(float deltaTime, bool jointsCaptured) override; virtual void update(float deltaTime, bool jointsCaptured) override;
virtual void focusOutEvent() override; virtual void focusOutEvent() override;

View file

@ -100,7 +100,7 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) {
} }
_registeredDevices[deviceID] = device; _registeredDevices[deviceID] = device;
auto mapping = loadMapping(device->getDefaultMappingConfig()); auto mapping = loadMappings(device->getDefaultMappingConfigs());
if (mapping) { if (mapping) {
_mappingsByDevice[deviceID] = mapping; _mappingsByDevice[deviceID] = mapping;
enableMapping(mapping); enableMapping(mapping);
@ -139,7 +139,7 @@ void UserInputMapper::loadDefaultMapping(uint16 deviceID) {
} }
auto mapping = loadMapping(proxyEntry->second->getDefaultMappingConfig()); auto mapping = loadMappings(proxyEntry->second->getDefaultMappingConfigs());
if (mapping) { if (mapping) {
auto prevMapping = _mappingsByDevice[deviceID]; auto prevMapping = _mappingsByDevice[deviceID];
disableMapping(prevMapping); disableMapping(prevMapping);
@ -710,6 +710,21 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) {
return parseMapping(json); return parseMapping(json);
} }
MappingPointer UserInputMapper::loadMappings(const QStringList& jsonFiles) {
Mapping::Pointer result;
for (const QString& jsonFile : jsonFiles) {
auto subMapping = loadMapping(jsonFile);
if (subMapping) {
if (!result) {
result = subMapping;
} else {
auto& routes = result->routes;
routes.insert(routes.end(), subMapping->routes.begin(), subMapping->routes.end());
}
}
}
return result;
}
static const QString JSON_NAME = QStringLiteral("name"); static const QString JSON_NAME = QStringLiteral("name");
@ -985,6 +1000,20 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) {
return result; return result;
} }
void injectConditional(Route::Pointer& route, Conditional::Pointer& conditional) {
if (!conditional) {
return;
}
if (!route->conditional) {
route->conditional = conditional;
return;
}
route->conditional = std::make_shared<AndConditional>(conditional, route->conditional);
}
Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) {
if (!json.isObject()) { if (!json.isObject()) {
return Mapping::Pointer(); return Mapping::Pointer();
@ -994,12 +1023,24 @@ Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) {
auto mapping = std::make_shared<Mapping>("default"); auto mapping = std::make_shared<Mapping>("default");
mapping->name = obj[JSON_NAME].toString(); mapping->name = obj[JSON_NAME].toString();
const auto& jsonChannels = obj[JSON_CHANNELS].toArray(); const auto& jsonChannels = obj[JSON_CHANNELS].toArray();
Conditional::Pointer globalConditional;
if (obj.contains(JSON_CHANNEL_WHEN)) {
auto conditionalsValue = obj[JSON_CHANNEL_WHEN];
globalConditional = parseConditional(conditionalsValue);
}
for (const auto& channelIt : jsonChannels) { for (const auto& channelIt : jsonChannels) {
Route::Pointer route = parseRoute(channelIt); Route::Pointer route = parseRoute(channelIt);
if (!route) { if (!route) {
qWarning() << "Couldn't parse route"; qWarning() << "Couldn't parse route";
continue; continue;
} }
if (globalConditional) {
injectConditional(route, globalConditional);
}
mapping->routes.push_back(route); mapping->routes.push_back(route);
} }
_mappingsByName[mapping->name] = mapping; _mappingsByName[mapping->name] = mapping;

View file

@ -107,6 +107,7 @@ namespace controller {
MappingPointer newMapping(const QString& mappingName); MappingPointer newMapping(const QString& mappingName);
MappingPointer parseMapping(const QString& json); MappingPointer parseMapping(const QString& json);
MappingPointer loadMapping(const QString& jsonFile); MappingPointer loadMapping(const QString& jsonFile);
MappingPointer loadMappings(const QStringList& jsonFiles);
void loadDefaultMapping(uint16 deviceID); void loadDefaultMapping(uint16 deviceID);
void enableMapping(const QString& mappingName, bool enable = true); void enableMapping(const QString& mappingName, bool enable = true);

View file

@ -18,7 +18,11 @@ class AndConditional : public Conditional {
public: public:
using Pointer = std::shared_ptr<AndConditional>; using Pointer = std::shared_ptr<AndConditional>;
AndConditional(Conditional::List children) : _children(children) { } AndConditional(Conditional::List children)
: _children(children) {}
AndConditional(Conditional::Pointer& first, Conditional::Pointer& second)
: _children({ first, second }) {}
virtual bool satisfied() override; virtual bool satisfied() override;

View file

@ -254,10 +254,33 @@ private:
_quit = true; _quit = true;
} }
static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 5;
void stop() { void stop() {
if (_thread.isRunning()) {
qDebug() << "Stopping QML render thread " << _thread.currentThreadId();
{
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
post(STOP); post(STOP);
_cond.wait(&_mutex); }
auto start = usecTimestampNow();
auto now = usecTimestampNow();
bool shutdownClean = false;
while (now - start < (MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) {
QMutexLocker lock(&_mutex);
if (_cond.wait(&_mutex, MSECS_PER_SECOND)) {
shutdownClean = true;
break;
}
now = usecTimestampNow();
}
if (!shutdownClean) {
qWarning() << "Failed to shut down the QML render thread";
}
} else {
qDebug() << "QML render thread already completed";
}
} }
bool allowNewFrame(uint8_t fps) { bool allowNewFrame(uint8_t fps) {

View file

@ -37,7 +37,7 @@ public:
using MouseTranslator = std::function<QPointF(const QPointF&)>; using MouseTranslator = std::function<QPointF(const QPointF&)>;
void create(QOpenGLContext* context); virtual void create(QOpenGLContext* context);
void resize(const QSize& size); void resize(const QSize& size);
QSize size() const; QSize size() const;
Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});

View file

@ -58,7 +58,7 @@ public:
const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; } const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; }
// BufferStream on the mesh vertices and attributes matching the vertex format // BufferStream on the mesh vertices and attributes matching the vertex format
const gpu::BufferStream getVertexStream() const { return _vertexStream; } const gpu::BufferStream& getVertexStream() const { return _vertexStream; }
// Index Buffer // Index Buffer
void setIndexBuffer(const BufferView& buffer); void setIndexBuffer(const BufferView& buffer);

View file

@ -23,16 +23,27 @@ QMutex ResourceManager::_prefixMapLock;
void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) {
QMutexLocker locker(&_prefixMapLock); QMutexLocker locker(&_prefixMapLock);
if (replacement.isEmpty()) {
_prefixMap.erase(prefix);
} else {
_prefixMap[prefix] = replacement; _prefixMap[prefix] = replacement;
} }
}
QString ResourceManager::normalizeURL(const QString& urlString) { QString ResourceManager::normalizeURL(const QString& urlString) {
QString result = urlString; QString result = urlString;
PrefixMap copy;
{
QMutexLocker locker(&_prefixMapLock); QMutexLocker locker(&_prefixMapLock);
foreach(const auto& entry, _prefixMap) { copy = _prefixMap;
}
foreach(const auto& entry, copy) {
const auto& prefix = entry.first; const auto& prefix = entry.first;
const auto& replacement = entry.second; const auto& replacement = entry.second;
if (result.startsWith(prefix)) { if (result.startsWith(prefix)) {
qDebug() << "Replacing " << prefix << " with " << replacement;
result.replace(0, prefix.size(), replacement); result.replace(0, prefix.size(), replacement);
} }
} }

View file

@ -0,0 +1,15 @@
//
// Created by Bradley Austin Davis on 2015/12/29
// Copyright 2015 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 "ResourceScriptingInterface.h"
#include "ResourceManager.h"
void ResourceScriptingInterface::overrideUrlPrefix(const QString& prefix, const QString& replacement) {
ResourceManager::setUrlPrefixOverride(prefix, replacement);
}

View file

@ -0,0 +1,31 @@
//
// AssetClient.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/21
// Copyright 2015 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
//
#ifndef hifi_networking_ResourceScriptingInterface_h
#define hifi_networking_ResourceScriptingInterface_h
#include <QtCore/QObject>
#include <DependencyManager.h>
class ResourceScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
Q_INVOKABLE void overrideUrlPrefix(const QString& prefix, const QString& replacement);
Q_INVOKABLE void restoreUrlPrefix(const QString& prefix) {
overrideUrlPrefix(prefix, "");
}
};
#endif

View file

@ -44,7 +44,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
return VERSION_ENTITITES_HAVE_QUERY_BOX; return VERSION_ENTITITES_HAVE_QUERY_BOX;
case PacketType::AvatarData: case PacketType::AvatarData:
case PacketType::BulkAvatarData: case PacketType::BulkAvatarData:
return 17; return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
default: default:
return 17; return 17;
} }

View file

@ -164,4 +164,9 @@ const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51;
const PacketVersion VERSION_ENTITITES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52; const PacketVersion VERSION_ENTITITES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52;
const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 53; const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 53;
enum class AvatarMixerPacketVersion : PacketVersion {
TranslationSupport = 17,
SoftAttachmentSupport
};
#endif // hifi_PacketHeaders_h #endif // hifi_PacketHeaders_h

View file

@ -79,6 +79,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() {
auto& container = PluginContainer::getInstance(); auto& container = PluginContainer::getInstance();
for (auto plugin : displayPlugins) { for (auto plugin : displayPlugins) {
plugin->setContainer(&container); plugin->setContainer(&container);
plugin->init();
} }
}); });
@ -104,6 +105,7 @@ const InputPluginList& PluginManager::getInputPlugins() {
auto& container = PluginContainer::getInstance(); auto& container = PluginContainer::getInstance();
for (auto plugin : inputPlugins) { for (auto plugin : inputPlugins) {
plugin->setContainer(&container); plugin->setContainer(&container);
plugin->init();
} }
}); });
return inputPlugins; return inputPlugins;

View file

@ -819,8 +819,6 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() {
//DEBUG: model::Mesh::Part part(0, indices, 0, model::Mesh::LINE_STRIP); //DEBUG: model::Mesh::Part part(0, indices, 0, model::Mesh::LINE_STRIP);
_spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(sizeof(part), (gpu::Byte*) &part), gpu::Element::PART_DRAWCALL)); _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(sizeof(part), (gpu::Byte*) &part), gpu::Element::PART_DRAWCALL));
_spotLightMesh->getVertexStream();
} }
return _spotLightMesh; return _spotLightMesh;
} }

View file

@ -974,11 +974,14 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
_needsUpdateClusterMatrices = true; _needsUpdateClusterMatrices = true;
_rig->updateAnimations(deltaTime, parentTransform); _rig->updateAnimations(deltaTime, parentTransform);
} }
void Model::simulateInternal(float deltaTime) { void Model::simulateInternal(float deltaTime) {
// update the world space transforms for all joints // update the world space transforms for all joints
glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
updateRig(deltaTime, parentTransform); updateRig(deltaTime, parentTransform);
} }
// virtual
void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) {
PerformanceTimer perfTimer("Model::updateClusterMatrices"); PerformanceTimer perfTimer("Model::updateClusterMatrices");

View file

@ -112,7 +112,7 @@ public:
bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
virtual void simulate(float deltaTime, bool fullUpdate = true); virtual void simulate(float deltaTime, bool fullUpdate = true);
void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation);
/// Returns a reference to the shared geometry. /// Returns a reference to the shared geometry.
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; } const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
@ -312,7 +312,7 @@ protected:
// hook for derived classes to be notified when setUrl invalidates the current model. // hook for derived classes to be notified when setUrl invalidates the current model.
virtual void onInvalidate() {}; virtual void onInvalidate() {};
private: protected:
void deleteGeometry(); void deleteGeometry();
void initJointTransforms(); void initJointTransforms();
@ -370,7 +370,6 @@ private:
bool _showCollisionHull = false; bool _showCollisionHull = false;
friend class ModelMeshPartPayload; friend class ModelMeshPartPayload;
protected:
RigPointer _rig; RigPointer _rig;
}; };

View file

@ -26,6 +26,7 @@
#include <EntityScriptingInterface.h> #include <EntityScriptingInterface.h>
#include <MessagesClient.h> #include <MessagesClient.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <ResourceScriptingInterface.h>
#include <NodeList.h> #include <NodeList.h>
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
#include <UUID.h> #include <UUID.h>
@ -391,7 +392,7 @@ void ScriptEngine::init() {
registerGlobalObject("Recording", recordingInterface.data()); registerGlobalObject("Recording", recordingInterface.data());
registerGlobalObject("Assets", &_assetScriptingInterface); registerGlobalObject("Assets", &_assetScriptingInterface);
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
} }
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {

View file

@ -31,7 +31,7 @@ public:
float update(float measuredValue, float dt, bool resetAccumulator = false); // returns the new computedValue float update(float measuredValue, float dt, bool resetAccumulator = false); // returns the new computedValue
void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging
bool getIsLogging() { return _history.capacity(); } bool getIsLogging() { return !_label.isEmpty(); }
float getMeasuredValueSetpoint() const { return _measuredValueSetpoint; } float getMeasuredValueSetpoint() const { return _measuredValueSetpoint; }
// In normal operation (where we can easily reach setpoint), controlledValue is typcially pinned at max. // In normal operation (where we can easily reach setpoint), controlledValue is typcially pinned at max.
// Defaults to [0, max float], but for 1/LODdistance, it might be, say, [0, 0.2 or 0.1] // Defaults to [0, max float], but for 1/LODdistance, it might be, say, [0, 0.2 or 0.1]

View file

@ -9,10 +9,13 @@
// 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
// //
#include "OffscreenUi.h" #include "OffscreenUi.h"
#include <QOpenGLDebugLogger>
#include <QQuickWindow> #include <QtQml/QtQml>
#include <QGLWidget> #include <QtQuick/QQuickWindow>
#include <QtQml>
#include <AbstractUriHandler.h>
#include <AccountManager.h>
#include "ErrorDialog.h" #include "ErrorDialog.h"
#include "MessageDialog.h" #include "MessageDialog.h"
@ -27,7 +30,62 @@ public:
} }
}; };
class OffscreenFlags : public QObject {
Q_OBJECT
Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged)
public:
OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {}
bool isNavigationFocused() const { return _navigationFocused; }
void setNavigationFocused(bool focused) {
if (_navigationFocused != focused) {
_navigationFocused = focused;
emit navigationFocusedChanged();
}
}
signals:
void navigationFocusedChanged();
private:
bool _navigationFocused { false };
};
class UrlHandler : public QObject {
Q_OBJECT
public:
Q_INVOKABLE bool canHandleUrl(const QString& url) {
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
return handler->canAcceptURL(url);
}
Q_INVOKABLE bool handleUrl(const QString& url) {
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
return handler->acceptURL(url);
}
// FIXME hack for authentication, remove when we migrate to Qt 5.6
Q_INVOKABLE QString fixupUrl(const QString& originalUrl) {
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
QString result = originalUrl;
QUrl url(originalUrl);
QUrlQuery query(url);
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
qDebug() << "Updating URL with auth token";
AccountManager& accountManager = AccountManager::getInstance();
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token);
url.setQuery(query.query());
result = url.toString();
}
return result;
}
};
static UrlHandler * urlHandler { nullptr };
static OffscreenFlags* offscreenFlags { nullptr };
// This hack allows the QML UI to work with keys that are also bound as // This hack allows the QML UI to work with keys that are also bound as
// shortcuts at the application level. However, it seems as though the // shortcuts at the application level. However, it seems as though the
@ -58,9 +116,15 @@ OffscreenUi::OffscreenUi() {
::qmlRegisterType<OffscreenUiRoot>("Hifi", 1, 0, "Root"); ::qmlRegisterType<OffscreenUiRoot>("Hifi", 1, 0, "Root");
} }
OffscreenUi::~OffscreenUi() { void OffscreenUi::create(QOpenGLContext* context) {
} OffscreenQmlSurface::create(context);
auto rootContext = getRootContext();
offscreenFlags = new OffscreenFlags();
rootContext->setContextProperty("offscreenFlags", offscreenFlags);
urlHandler = new UrlHandler();
rootContext->setContextProperty("urlHandler", urlHandler);
}
void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) { void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name); QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);
@ -139,7 +203,14 @@ void OffscreenUi::error(const QString& text) {
pDialog->setEnabled(true); pDialog->setEnabled(true);
} }
OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {}; OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {};
bool OffscreenUi::navigationFocused() {
return offscreenFlags->isNavigationFocused();
}
void OffscreenUi::setNavigationFocused(bool focused) {
offscreenFlags->setNavigationFocused(focused);
}
#include "OffscreenUi.moc" #include "OffscreenUi.moc"

View file

@ -25,10 +25,12 @@ class OffscreenUi : public OffscreenQmlSurface, public Dependency {
public: public:
OffscreenUi(); OffscreenUi();
virtual ~OffscreenUi(); virtual void create(QOpenGLContext* context) override;
void show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); void show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
void toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}); void toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
bool shouldSwallowShortcut(QEvent* event); bool shouldSwallowShortcut(QEvent* event);
bool navigationFocused();
void setNavigationFocused(bool focused);
// Messagebox replacement functions // Messagebox replacement functions
using ButtonCallback = std::function<void(QMessageBox::StandardButton)>; using ButtonCallback = std::function<void(QMessageBox::StandardButton)>;

View file

@ -8,237 +8,50 @@
#include "QmlWebWindowClass.h" #include "QmlWebWindowClass.h"
#include <mutex>
#include <QtCore/QCoreApplication>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtCore/QUrlQuery> #include <QtCore/QUrlQuery>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtQml/QQmlContext> #include <QtQml/QQmlContext>
#include <QtScript/QScriptContext> #include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
#include <QtWebChannel/QWebChannel>
#include <QtWebSockets/QWebSocketServer> #include <QtQuick/QQuickItem>
#include <QtWebSockets/QWebSocket>
#include <AbstractUriHandler.h> #include <AbstractUriHandler.h>
#include <AccountManager.h>
#include <AddressManager.h> #include <AddressManager.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include "OffscreenUi.h" #include "OffscreenUi.h"
QWebSocketServer* QmlWebWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
static const char* const URL_PROPERTY = "source"; static const char* const URL_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title";
static const QRegExp HIFI_URL_PATTERN { "^hifi://" };
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
}
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWebWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
class UrlFixer : public QObject {
Q_OBJECT
public:
Q_INVOKABLE QString fixupUrl(const QString& originalUrl) {
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
QString result = originalUrl;
QUrl url(originalUrl);
QUrlQuery query(url);
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
qDebug() << "Updating URL with auth token";
AccountManager& accountManager = AccountManager::getInstance();
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token);
url.setQuery(query.query());
result = url.toString();
}
return result;
}
};
static UrlFixer URL_FIXER;
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
QmlWebWindowClass* retVal { nullptr }; return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine,
const QString title = context->argument(0).toString(); [&](QQmlContext* context, QObject* object) { return new QmlWebWindowClass(object); });
QString url = context->argument(1).toString();
if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) {
url = QUrl::fromLocalFile(url).toString();
}
const int width = std::max(100, std::min(1280, context->argument(2).toInt32()));;
const int height = std::max(100, std::min(720, context->argument(3).toInt32()));;
// Build the event bridge and wrapper on the main thread
QMetaObject::invokeMethod(DependencyManager::get<OffscreenUi>().data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, "QmlWebWindow.qml"),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = new QmlWebWindowClass(object);
webChannel.registerObject(url.toLower(), retVal);
context->setContextProperty("urlFixer", &URL_FIXER);
retVal->setTitle(title);
retVal->setURL(url);
retVal->setSize(width, height);
}));
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWebWindowClass::deleteLater);
return engine->newQObject(retVal);
} }
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString))); QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString)));
} }
void QmlWebWindowClass::handleNavigation(const QString& url) { void QmlWebWindowClass::handleNavigation(const QString& url) {
bool handled = false; bool handled = false;
if (url.contains(HIFI_URL_PATTERN)) {
DependencyManager::get<AddressManager>()->handleLookupString(url);
handled = true;
} else {
static auto handler = dynamic_cast<AbstractUriHandler*>(qApp); static auto handler = dynamic_cast<AbstractUriHandler*>(qApp);
if (handler) { if (handler) {
if (handler->canAcceptURL(url)) { if (handler->canAcceptURL(url)) {
handled = handler->acceptURL(url); handled = handler->acceptURL(url);
} }
} }
}
if (handled) { if (handled) {
QMetaObject::invokeMethod(_qmlWindow, "stop", Qt::AutoConnection); QMetaObject::invokeMethod(_qmlWindow, "stop", Qt::AutoConnection);
} }
} }
void QmlWebWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
return;
}
auto qmlWindow = asQuickItem();
if (qmlWindow->isEnabled() != visible) {
qmlWindow->setEnabled(visible);
emit visibilityChanged(visible);
}
}
QQuickItem* QmlWebWindowClass::asQuickItem() const {
return dynamic_cast<QQuickItem*>(_qmlWindow);
}
bool QmlWebWindowClass::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
return result;
}
return asQuickItem()->isEnabled();
}
glm::vec2 QmlWebWindowClass::getPosition() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
}
void QmlWebWindowClass::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position));
return;
}
asQuickItem()->setPosition(QPointF(position.x, position.y));
}
void QmlWebWindowClass::setPosition(int x, int y) {
setPosition(glm::vec2(x, y));
}
glm::vec2 QmlWebWindowClass::getSize() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
}
void QmlWebWindowClass::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
}
asQuickItem()->setSize(QSizeF(size.x, size.y));
}
void QmlWebWindowClass::setSize(int width, int height) {
setSize(glm::vec2(width, height));
}
QString QmlWebWindowClass::getURL() const { QString QmlWebWindowClass::getURL() const {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QString result; QString result;
@ -255,30 +68,3 @@ void QmlWebWindowClass::setURL(const QString& urlString) {
} }
_qmlWindow->setProperty(URL_PROPERTY, urlString); _qmlWindow->setProperty(URL_PROPERTY, urlString);
} }
void QmlWebWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title));
}
_qmlWindow->setProperty(TITLE_PROPERTY, title);
}
void QmlWebWindowClass::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
}
_qmlWindow->setProperty("destroyOnInvisible", true);
_qmlWindow->setProperty("visible", false);
_qmlWindow->deleteLater();
}
void QmlWebWindowClass::hasClosed() {
}
void QmlWebWindowClass::raise() {
// FIXME
}
#include "QmlWebWindowClass.moc"

View file

@ -9,97 +9,26 @@
#ifndef hifi_ui_QmlWebWindowClass_h #ifndef hifi_ui_QmlWebWindowClass_h
#define hifi_ui_QmlWebWindowClass_h #define hifi_ui_QmlWebWindowClass_h
#include <QtCore/QObject> #include "QmlWindowClass.h"
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
#include <GLMHelpers.h>
class QScriptEngine;
class QScriptContext;
class QmlWebWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
public:
QmlScriptEventBridge(const QmlWebWindowClass* webWindow) : _webWindow(webWindow) {}
public slots :
void emitWebEvent(const QString& data);
void emitScriptEvent(const QString& data);
signals:
void webEventReceived(const QString& data);
void scriptEventReceived(int windowId, const QString& data);
private:
const QmlWebWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWebWindowClass : public QObject { class QmlWebWindowClass : public QmlWindowClass {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
Q_PROPERTY(int windowId READ getWindowId CONSTANT)
Q_PROPERTY(QString url READ getURL CONSTANT) Q_PROPERTY(QString url READ getURL CONSTANT)
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
public: public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
QmlWebWindowClass(QObject* qmlWindow); QmlWebWindowClass(QObject* qmlWindow);
public slots: public slots:
bool isVisible() const;
void setVisible(bool visible);
glm::vec2 getPosition() const;
void setPosition(const glm::vec2& position);
void setPosition(int x, int y);
glm::vec2 getSize() const;
void setSize(const glm::vec2& size);
void setSize(int width, int height);
QString getURL() const; QString getURL() const;
void setURL(const QString& url); void setURL(const QString& url);
void setTitle(const QString& title);
// Ugh.... do not want to do
Q_INVOKABLE void raise();
Q_INVOKABLE void close();
Q_INVOKABLE int getWindowId() const { return _windowId; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
signals: signals:
void visibilityChanged(bool visible); // Tool window
void urlChanged(); void urlChanged();
void moved(glm::vec2 position);
void resized(QSizeF size);
void closed();
private slots: private slots:
void hasClosed();
void handleNavigation(const QString& url); void handleNavigation(const QString& url);
private:
static void setupServer();
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
const bool _isToolWindow { false };
const int _windowId;
QObject* const _qmlWindow;
}; };
#endif #endif

View file

@ -0,0 +1,280 @@
//
// Created by Bradley Austin Davis on 2015-12-15
// Copyright 2015 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 "QmlWindowClass.h"
#include <mutex>
#include <QtCore/QThread>
#include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine>
#include <QtQuick/QQuickItem>
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtWebChannel/QWebChannel>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include "OffscreenUi.h"
QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
static const char* const SOURCE_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title";
static const char* const WIDTH_PROPERTY = "width";
static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible";
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
}
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function)
{
const auto argumentCount = context->argumentCount();
QString url;
QString title;
int width = 100, height = 100;
bool isToolWindow = false;
bool visible = true;
if (argumentCount > 1) {
if (!context->argument(0).isUndefined()) {
title = context->argument(0).toString();
}
if (!context->argument(1).isUndefined()) {
url = context->argument(1).toString();
}
if (context->argument(2).isNumber()) {
width = context->argument(2).toInt32();
}
if (context->argument(3).isNumber()) {
height = context->argument(3).toInt32();
}
} else {
auto argumentObject = context->argument(0);
qDebug() << argumentObject.toString();
if (!argumentObject.property(TITLE_PROPERTY).isUndefined()) {
title = argumentObject.property(TITLE_PROPERTY).toString();
}
if (!argumentObject.property(SOURCE_PROPERTY).isUndefined()) {
url = argumentObject.property(SOURCE_PROPERTY).toString();
}
if (argumentObject.property(WIDTH_PROPERTY).isNumber()) {
width = argumentObject.property(WIDTH_PROPERTY).toInt32();
}
if (argumentObject.property(HEIGHT_PROPERTY).isNumber()) {
height = argumentObject.property(HEIGHT_PROPERTY).toInt32();
}
if (argumentObject.property(VISIBILE_PROPERTY).isBool()) {
visible = argumentObject.property(VISIBILE_PROPERTY).toBool();
}
}
if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) {
url = QUrl::fromLocalFile(url).toString();
}
width = std::max(100, std::min(1280, width));
height = std::max(100, std::min(720, height));
QmlWindowClass* retVal{ nullptr };
// Build the event bridge and wrapper on the main thread
QMetaObject::invokeMethod(DependencyManager::get<OffscreenUi>().data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, qmlSource),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = function(context, object);
registerObject(url.toLower(), retVal);
if (!title.isEmpty()) {
retVal->setTitle(title);
}
retVal->setSize(width, height);
object->setProperty(SOURCE_PROPERTY, url);
if (visible) {
object->setProperty("enabled", true);
}
}));
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
return engine->newQObject(retVal);
}
// Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
return internalConstructor("QmlWindow.qml", context, engine, [&](QQmlContext* context, QObject* object){
return new QmlWindowClass(object);
});
}
QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
}
void QmlWindowClass::registerObject(const QString& name, QObject* object) {
webChannel.registerObject(name, object);
}
void QmlWindowClass::deregisterObject(QObject* object) {
webChannel.deregisterObject(object);
}
void QmlWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
return;
}
auto qmlWindow = asQuickItem();
if (qmlWindow->isEnabled() != visible) {
qmlWindow->setEnabled(visible);
emit visibilityChanged(visible);
}
}
QQuickItem* QmlWindowClass::asQuickItem() const {
return dynamic_cast<QQuickItem*>(_qmlWindow);
}
bool QmlWindowClass::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result;
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result));
return result;
}
return asQuickItem()->isEnabled();
}
glm::vec2 QmlWindowClass::getPosition() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
}
void QmlWindowClass::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position));
return;
}
asQuickItem()->setPosition(QPointF(position.x, position.y));
}
void QmlWindowClass::setPosition(int x, int y) {
setPosition(glm::vec2(x, y));
}
glm::vec2 QmlWindowClass::getSize() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
QMetaObject::invokeMethod(const_cast<QmlWindowClass*>(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result));
return result;
}
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
}
void QmlWindowClass::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
}
asQuickItem()->setSize(QSizeF(size.x, size.y));
}
void QmlWindowClass::setSize(int width, int height) {
setSize(glm::vec2(width, height));
}
void QmlWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title));
}
_qmlWindow->setProperty(TITLE_PROPERTY, title);
}
void QmlWindowClass::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
}
_qmlWindow->setProperty("destroyOnInvisible", true);
_qmlWindow->setProperty("visible", false);
_qmlWindow->deleteLater();
}
void QmlWindowClass::hasClosed() {
}
void QmlWindowClass::raise() {
// FIXME
}
#include "QmlWindowClass.moc"

View file

@ -0,0 +1,103 @@
//
// Created by Bradley Austin Davis on 2015-12-15
// Copyright 2015 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
//
#ifndef hifi_ui_QmlWindowClass_h
#define hifi_ui_QmlWindowClass_h
#include <QtCore/QObject>
#include <GLMHelpers.h>
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
class QScriptEngine;
class QScriptContext;
class QmlWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
public:
QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {}
public slots :
void emitWebEvent(const QString& data);
void emitScriptEvent(const QString& data);
signals:
void webEventReceived(const QString& data);
void scriptEventReceived(int windowId, const QString& data);
private:
const QmlWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWindowClass : public QObject {
Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
Q_PROPERTY(int windowId READ getWindowId CONSTANT)
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
QmlWindowClass(QObject* qmlWindow);
public slots:
bool isVisible() const;
void setVisible(bool visible);
glm::vec2 getPosition() const;
void setPosition(const glm::vec2& position);
void setPosition(int x, int y);
glm::vec2 getSize() const;
void setSize(const glm::vec2& size);
void setSize(int width, int height);
void setTitle(const QString& title);
// Ugh.... do not want to do
Q_INVOKABLE void raise();
Q_INVOKABLE void close();
Q_INVOKABLE int getWindowId() const { return _windowId; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
signals:
void visibilityChanged(bool visible); // Tool window
void moved(glm::vec2 position);
void resized(QSizeF size);
void closed();
protected slots:
void hasClosed();
protected:
static QScriptValue internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QQmlContext*, QObject*)> function);
static void setupServer();
static void registerObject(const QString& name, QObject* object);
static void deregisterObject(QObject* object);
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
const bool _isToolWindow { false };
const int _windowId;
QObject* const _qmlWindow;
};
#endif

View file

@ -170,10 +170,12 @@
function createRaveStick(position) { function createRaveStick(position) {
var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx"; var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx";
var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0);
var stick = Entities.addEntity({ var stick = Entities.addEntity({
type: "Model", type: "Model",
name: "raveStick", name: "raveStick",
modelURL: modelURL, modelURL: modelURL,
rotation: rotation,
position: position, position: position,
shapeType: 'box', shapeType: 'box',
collisionsWillMove: true, collisionsWillMove: true,
@ -206,6 +208,66 @@
}) })
}); });
var forwardVec = Quat.getFront(rotation);
var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec);
var color = {
red: 150,
green: 20,
blue: 100
}
var raveGlowEmitter = Entities.addEntity({
type: "ParticleEffect",
name: "Rave Stick Glow Emitter",
position: position,
parentID: stick,
isEmitting: true,
colorStart: color,
color: {
red: 200,
green: 200,
blue: 255
},
colorFinish: color,
maxParticles: 100000,
lifespan: 0.8,
emitRate: 1000,
emitOrientation: forwardQuat,
emitSpeed: 0.2,
speedSpread: 0.0,
emitDimensions: {
x: 0,
y: 0,
z: 0
},
polarStart: 0,
polarFinish: 0,
azimuthStart: 0.1,
azimuthFinish: 0.01,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: 0.00,
y: 0.00,
z: 0.00
},
radiusStart: 0.01,
radiusFinish: 0.005,
alpha: 0.7,
alphaSpread: 0.1,
alphaStart: 0.1,
alphaFinish: 0.1,
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png",
emitterShouldTrail: false,
userData: JSON.stringify({
resetMe: {
resetMe: true
}
})
});
} }
function createGun(position) { function createGun(position) {

View file

@ -149,10 +149,12 @@ MasterReset = function() {
function createRaveStick(position) { function createRaveStick(position) {
var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx"; var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx";
var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0);
var stick = Entities.addEntity({ var stick = Entities.addEntity({
type: "Model", type: "Model",
name: "raveStick", name: "raveStick",
modelURL: modelURL, modelURL: modelURL,
rotation: rotation,
position: position, position: position,
shapeType: 'box', shapeType: 'box',
collisionsWillMove: true, collisionsWillMove: true,
@ -189,6 +191,66 @@ MasterReset = function() {
} }
}) })
}); });
var forwardVec = Quat.getFront(rotation);
var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec);
var color = {
red: 150,
green: 20,
blue: 100
}
var raveGlowEmitter = Entities.addEntity({
type: "ParticleEffect",
name: "Rave Stick Glow Emitter",
position: position,
parentID: stick,
isEmitting: true,
colorStart: color,
color: {
red: 200,
green: 200,
blue: 255
},
colorFinish: color,
maxParticles: 100000,
lifespan: 0.8,
emitRate: 1000,
emitOrientation: forwardQuat,
emitSpeed: 0.2,
speedSpread: 0.0,
emitDimensions: {
x: 0,
y: 0,
z: 0
},
polarStart: 0,
polarFinish: 0,
azimuthStart: 0.1,
azimuthFinish: 0.01,
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
accelerationSpread: {
x: 0.00,
y: 0.00,
z: 0.00
},
radiusStart: 0.01,
radiusFinish: 0.005,
alpha: 0.7,
alphaSpread: 0.1,
alphaStart: 0.1,
alphaFinish: 0.1,
textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png",
emitterShouldTrail: false,
userData: JSON.stringify({
resetMe: {
resetMe: true
}
})
});
} }
function createGun(position) { function createGun(position) {