mirror of
https://github.com/overte-org/overte.git
synced 2025-07-23 16:24:09 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into near-grab-via-parenting
This commit is contained in:
commit
ce1b8ae6f0
76 changed files with 1851 additions and 715 deletions
1
CMakeGraphvizOptions.cmake
Normal file
1
CMakeGraphvizOptions.cmake
Normal file
|
@ -0,0 +1 @@
|
||||||
|
set(GRAPHVIZ_EXTERNAL_LIBS FALSE)
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
40
examples/tests/qmlTest.js
Normal 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)
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
61
interface/resources/controllers/standard_navigation.json
Normal file
61
interface/resources/controllers/standard_navigation.json
Normal 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 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)"
|
||||||
|
|
55
interface/resources/qml/QmlWindow.qml
Normal file
55
interface/resources/qml/QmlWindow.qml
Normal 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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
77
interface/resources/qml/VrMenuView.qml
Normal file
77
interface/resources/qml/VrMenuView.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
84
interface/src/avatar/SoftAttachmentModel.cpp
Normal file
84
interface/src/avatar/SoftAttachmentModel.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
42
interface/src/avatar/SoftAttachmentModel.h
Normal file
42
interface/src/avatar/SoftAttachmentModel.h
Normal 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
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ private:
|
||||||
QDoubleSpinBox* _rotationY;
|
QDoubleSpinBox* _rotationY;
|
||||||
QDoubleSpinBox* _rotationZ;
|
QDoubleSpinBox* _rotationZ;
|
||||||
QDoubleSpinBox* _scale;
|
QDoubleSpinBox* _scale;
|
||||||
|
QCheckBox* _isSoft;
|
||||||
bool _applying;
|
bool _applying;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) ||
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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*) {});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
libraries/networking/src/ResourceScriptingInterface.cpp
Normal file
15
libraries/networking/src/ResourceScriptingInterface.cpp
Normal 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);
|
||||||
|
}
|
31
libraries/networking/src/ResourceScriptingInterface.h
Normal file
31
libraries/networking/src/ResourceScriptingInterface.h
Normal 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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)>;
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
280
libraries/ui/src/QmlWindowClass.cpp
Normal file
280
libraries/ui/src/QmlWindowClass.cpp
Normal 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"
|
103
libraries/ui/src/QmlWindowClass.h
Normal file
103
libraries/ui/src/QmlWindowClass.h
Normal 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
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue