This commit is contained in:
Leonardo Murillo 2015-12-19 18:44:21 -06:00
commit aca60e23c2
38 changed files with 1219 additions and 416 deletions

View file

@ -33,7 +33,7 @@ bool OctreeQueryNode::packetIsDuplicate() const {
// of the entire packet, we need to compare only the packet content... // of the entire packet, we need to compare only the packet content...
if (_lastOctreePacketLength == _octreePacket->getPayloadSize()) { if (_lastOctreePacketLength == _octreePacket->getPayloadSize()) {
if (memcmp(&_lastOctreePayload + OCTREE_PACKET_EXTRA_HEADERS_SIZE, if (memcmp(_lastOctreePayload.data() + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
_octreePacket->getPayload() + OCTREE_PACKET_EXTRA_HEADERS_SIZE, _octreePacket->getPayload() + OCTREE_PACKET_EXTRA_HEADERS_SIZE,
_octreePacket->getPayloadSize() - OCTREE_PACKET_EXTRA_HEADERS_SIZE) == 0) { _octreePacket->getPayloadSize() - OCTREE_PACKET_EXTRA_HEADERS_SIZE) == 0) {
return true; return true;
@ -101,7 +101,7 @@ void OctreeQueryNode::resetOctreePacket() {
// scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing // scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing
// packet send rate. // packet send rate.
_lastOctreePacketLength = _octreePacket->getPayloadSize(); _lastOctreePacketLength = _octreePacket->getPayloadSize();
memcpy(&_lastOctreePayload, _octreePacket->getPayload(), _lastOctreePacketLength); memcpy(_lastOctreePayload.data(), _octreePacket->getPayload(), _lastOctreePacketLength);
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
// the clients requested color state. // the clients requested color state.

View file

@ -40,7 +40,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
add_custom_command( add_custom_command(
TARGET ${TARGET_NAME} TARGET ${TARGET_NAME}
POST_BUILD POST_BUILD
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>" COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
) )
elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE) elseif (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE)
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH)

View file

@ -62,7 +62,7 @@ var directory = (function () {
function setUp() { function setUp() {
viewport = Controller.getViewportDimensions(); viewport = Controller.getViewportDimensions();
directoryWindow = new WebWindow('Directory', DIRECTORY_URL, 900, 700, false); directoryWindow = new OverlayWebWindow('Directory', DIRECTORY_URL, 900, 700, false);
directoryWindow.setVisible(false); directoryWindow.setVisible(false);
directoryButton = Overlays.addOverlay("image", { directoryButton = Overlays.addOverlay("image", {

View file

@ -60,7 +60,7 @@
}); });
break; break;
case 3: case 3:
print("Radius spread"); print("Radius spread - temporarily not working");
Entities.editEntity(particles, { Entities.editEntity(particles, {
accelerationSpread: { x: 0.0, y: 0.0, z: 0.0 }, accelerationSpread: { x: 0.0, y: 0.0, z: 0.0 },
radiusSpread: 0.035 radiusSpread: 0.035
@ -71,6 +71,7 @@
Entities.editEntity(particles, { Entities.editEntity(particles, {
radiusSpread: 0.0, radiusSpread: 0.0,
radiusStart: 0.0, radiusStart: 0.0,
particleRadius: 2 * PARTICLE_RADIUS, // Bezier interpolation used means that middle value isn't intersected
radiusFinish: 0.0 radiusFinish: 0.0
}); });
break; break;
@ -78,12 +79,13 @@
print("Alpha 0.5"); print("Alpha 0.5");
Entities.editEntity(particles, { Entities.editEntity(particles, {
radiusStart: PARTICLE_RADIUS, radiusStart: PARTICLE_RADIUS,
particleRadius: PARTICLE_RADIUS,
radiusFinish: PARTICLE_RADIUS, radiusFinish: PARTICLE_RADIUS,
alpha: 0.5 alpha: 0.5
}); });
break; break;
case 6: case 6:
print("Alpha spread"); print("Alpha spread - temporarily not working");
Entities.editEntity(particles, { Entities.editEntity(particles, {
alpha: 0.5, alpha: 0.5,
alphaSpread: 0.5 alphaSpread: 0.5
@ -99,7 +101,7 @@
}); });
break; break;
case 8: case 8:
print("Color spread"); print("Color spread - temporarily not working");
Entities.editEntity(particles, { Entities.editEntity(particles, {
alpha: 1.0, alpha: 1.0,
alphaStart: 1.0, alphaStart: 1.0,
@ -255,7 +257,6 @@
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
color: { red: 255, green: 255, blue: 255 }, color: { red: 255, green: 255, blue: 255 },
lifespan: 5.0, lifespan: 5.0,
visible: false,
locked: false, locked: false,
isEmitting: false, isEmitting: false,
lifetime: 3600 // 1 hour; just in case lifetime: 3600 // 1 hour; just in case

View file

@ -0,0 +1,59 @@
//public slots:
// void emitWebEvent(const QString& data);
// void emitScriptEvent(const QString& data);
//
//signals:
// void webEventReceived(const QString& data);
// void scriptEventReceived(const QString& data);
//
EventBridgeConnectionProxy = function(parent) {
this.parent = parent;
this.realSignal = this.parent.realBridge.scriptEventReceived
this.webWindowId = this.parent.webWindow.windowId;
}
EventBridgeConnectionProxy.prototype.connect = function(callback) {
var that = this;
this.realSignal.connect(function(id, message) {
if (id === that.webWindowId) { callback(message); }
});
}
EventBridgeProxy = function(webWindow) {
this.webWindow = webWindow;
this.realBridge = this.webWindow.eventBridge;
this.scriptEventReceived = new EventBridgeConnectionProxy(this);
}
EventBridgeProxy.prototype.emitWebEvent = function(data) {
this.realBridge.emitWebEvent(data);
}
openEventBridge = function(callback) {
EVENT_BRIDGE_URI = "ws://localhost:51016";
socket = new WebSocket(this.EVENT_BRIDGE_URI);
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
channel = new QWebChannel(socket, function(channel) {
console.log("Document url is " + document.URL);
for(var key in channel.objects){
console.log("registered object: " + key);
}
var webWindow = channel.objects[document.URL.toLowerCase()];
console.log("WebWindow is " + webWindow)
eventBridgeProxy = new EventBridgeProxy(webWindow);
if (callback) { callback(eventBridgeProxy); }
});
}
}

View file

@ -0,0 +1,31 @@
<html>
<head>
<title>Properties</title>
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script>
<script>
var myBridge;
window.onload = function() {
openEventBridge(function(eventBridge) {
myBridge = eventBridge;
myBridge.scriptEventReceived.connect(function(message) {
console.log("HTML side received message: " + message);
});
});
}
testClick = function() {
myBridge.emitWebEvent("HTML side sending message - button click");
}
</script>
</head>
<body class="properties">
<button name="Test" title="Test" onclick="testClick()">Test</button>
</body>
</html>
</body>

View file

@ -0,0 +1,32 @@
print("Launching web window");
webWindow = new OverlayWebWindow('Test Event Bridge', "file:///C:/Users/bdavis/Git/hifi/examples/html/qmlWebTest.html", 320, 240, false);
print("JS Side window: " + webWindow);
print("JS Side bridge: " + webWindow.eventBridge);
webWindow.eventBridge.webEventReceived.connect(function(data) {
print("JS Side event received: " + data);
});
var titles = ["A", "B", "C"];
var titleIndex = 0;
Script.setInterval(function() {
webWindow.eventBridge.emitScriptEvent("JS Event sent");
var size = webWindow.size;
var position = webWindow.position;
print("Window url: " + webWindow.url)
print("Window visible: " + webWindow.visible)
print("Window size: " + size.x + "x" + size.y)
print("Window pos: " + position.x + "x" + position.y)
webWindow.setVisible(!webWindow.visible);
webWindow.setTitle(titles[titleIndex]);
webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
titleIndex += 1;
titleIndex %= titles.length;
}, 2 * 1000);
Script.setTimeout(function() {
print("Closing script");
webWindow.close();
Script.stop();
}, 15 * 1000)

View file

@ -45,7 +45,9 @@ else ()
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
endif () endif ()
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets) find_package(Qt5 COMPONENTS
Gui Multimedia Network OpenGL Qml Quick Script Svg
WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets)
# grab the ui files in resources/ui # grab the ui files in resources/ui
file (GLOB_RECURSE QT_UI_FILES ui/*.ui) file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
@ -175,9 +177,17 @@ include_directories("${PROJECT_SOURCE_DIR}/src")
target_link_libraries( target_link_libraries(
${TARGET_NAME} ${TARGET_NAME}
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Script Qt5::Svg Qt5::WebKitWidgets Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets
) )
# Issue causes build failure unless we add this directory.
# See https://bugreports.qt.io/browse/QTBUG-43351
if (WIN32)
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
endif()
# assume we are using a Qt build without bearer management # assume we are using a Qt build without bearer management
add_definitions(-DQT_NO_BEARERMANAGEMENT) add_definitions(-DQT_NO_BEARERMANAGEMENT)
@ -212,5 +222,9 @@ else (APPLE)
endif() endif()
endif (APPLE) endif (APPLE)
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/resources/qml")
endif()
package_libraries_for_deployment() package_libraries_for_deployment()
consolidate_stack_components() consolidate_stack_components()

View file

@ -415,10 +415,25 @@
"states": [ "states": [
{ {
"id": "idle", "id": "idle",
"interpTarget": 15, "interpTarget": 10,
"interpDuration": 15, "interpDuration": 10,
"transitions": [ "transitions": [
{ "var": "isMovingForward", "state": "walkFwd" }, { "var": "isMovingForward", "state": "idleToWalkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" },
{ "var": "isAway", "state": "awayIntro" }
]
},
{
"id": "idleToWalkFwd",
"interpTarget": 3,
"interpDuration": 3,
"transitions": [
{ "var": "idleToWalkFwdOnDone", "state": "walkFwd" },
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingBackward", "state": "walkBwd" }, { "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" }, { "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" }, { "var": "isMovingLeft", "state": "strafeLeft" },
@ -429,7 +444,7 @@
}, },
{ {
"id": "walkFwd", "id": "walkFwd",
"interpTarget": 6, "interpTarget": 15,
"interpDuration": 6, "interpDuration": 6,
"transitions": [ "transitions": [
{ "var": "isNotMoving", "state": "idle" }, { "var": "isNotMoving", "state": "idle" },
@ -638,6 +653,18 @@
} }
] ]
}, },
{
"id": "idleToWalkFwd",
"type": "clip",
"data": {
"url": "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims/idle_to_walk.fbx",
"startFrame": 1.0,
"endFrame": 19.0,
"timeScale": 1.0,
"loopFlag": false
},
"children": []
},
{ {
"id": "walkBwd", "id": "walkBwd",
"type": "blendLinearMove", "type": "blendLinearMove",

View file

@ -1,6 +1,6 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebKit 3.0 import QtWebEngine 1.1
import "controls" import "controls"
import "styles" import "styles"
@ -39,9 +39,10 @@ VrDialog {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: scrollView.top anchors.bottom: webview.top
color: "white" color: "white"
} }
Row { Row {
id: buttons id: buttons
spacing: 4 spacing: 4
@ -112,26 +113,22 @@ VrDialog {
} }
} }
ScrollView { WebEngineView {
id: scrollView id: webview
url: "http://highfidelity.com"
anchors.top: buttons.bottom anchors.top: buttons.bottom
anchors.topMargin: 8 anchors.topMargin: 8
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
WebView { onLoadingChanged: {
id: webview if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
url: "http://highfidelity.com" addressBar.text = loadRequest.url
anchors.fill: parent
onLoadingChanged: {
if (loadRequest.status == WebView.LoadSucceededStarted) {
addressBar.text = loadRequest.url
}
}
onIconChanged: {
barIcon.source = icon
} }
} }
onIconChanged: {
console.log("New icon: " + icon)
}
} }
} // item } // item
@ -146,5 +143,4 @@ VrDialog {
break; break;
} }
} }
} // dialog } // dialog

View file

@ -2,7 +2,7 @@ import Hifi 1.0 as Hifi
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3 import QtQuick.Controls.Styles 1.3
import QtWebKit 3.0 import QtWebEngine 1.1
import "controls" import "controls"
VrDialog { VrDialog {
@ -18,15 +18,11 @@ VrDialog {
anchors.margins: parent.margins anchors.margins: parent.margins
anchors.topMargin: parent.topMargin anchors.topMargin: parent.topMargin
ScrollView { WebEngineView {
id: webview
objectName: "WebView"
anchors.fill: parent anchors.fill: parent
WebView { url: infoView.url
objectName: "WebView" }
id: webview
url: infoView.url
anchors.fill: parent
}
}
} }
} }

View file

@ -2,7 +2,7 @@ import Hifi 1.0
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.3 import QtQuick.Controls.Styles 1.3
import QtWebKit 3.0 import QtWebEngine 1.1
import "controls" import "controls"
VrDialog { VrDialog {
@ -24,27 +24,22 @@ VrDialog {
anchors.margins: parent.margins anchors.margins: parent.margins
anchors.topMargin: parent.topMargin anchors.topMargin: parent.topMargin
WebEngineView {
ScrollView { objectName: "WebView"
id: webview
url: "https://metaverse.highfidelity.com/marketplace"
anchors.fill: parent anchors.fill: parent
WebView { onNavigationRequested: {
objectName: "WebView" console.log(request.url)
id: webview if (!marketplaceDialog.navigationRequested(request.url)) {
url: "https://metaverse.highfidelity.com/marketplace" console.log("Application absorbed the request")
anchors.fill: parent request.action = WebView.IgnoreRequest;
onNavigationRequested: {
console.log(request.url)
if (!marketplaceDialog.navigationRequested(request.url)) {
console.log("Application absorbed the request")
request.action = WebView.IgnoreRequest;
return;
}
console.log("Application passed on the request")
request.action = WebView.AcceptRequest;
return; return;
} }
console.log("Application passed on the request")
request.action = WebView.AcceptRequest;
return;
} }
} }
}
}
} }

View file

@ -0,0 +1,62 @@
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
import QtWebChannel 1.0
import QtWebSockets 1.0
import "controls"
import "styles"
VrDialog {
id: root
HifiConstants { id: hifi }
title: "WebWindow"
resizable: true
contentImplicitWidth: clientArea.implicitWidth
contentImplicitHeight: clientArea.implicitHeight
backgroundColor: "#7f000000"
property url source: "about:blank"
signal navigating(string url)
Component.onCompleted: {
enabled = true
console.log("Web Window Created " + root);
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
});
webview.loadingChanged.connect(handleWebviewLoading)
}
function handleWebviewLoading(loadRequest) {
var HIFI_URL_PATTERN = /^hifi:\/\//;
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
var newUrl = loadRequest.url.toString();
if (newUrl.match(HIFI_URL_PATTERN)) {
root.navigating(newUrl);
}
}
}
Item {
id: clientArea
implicitHeight: 600
implicitWidth: 800
x: root.clientX
y: root.clientY
width: root.clientWidth
height: root.clientHeight
WebEngineView {
id: webview
url: root.source
anchors.fill: parent
profile: WebEngineProfile {
httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)"
}
}
} // item
} // dialog

View file

@ -4,116 +4,7 @@ import Hifi 1.0
// Currently for testing a pure QML replacement menu // Currently for testing a pure QML replacement menu
Item { Item {
Item {
objectName: "AllActions"
Action {
id: aboutApp
objectName: "HifiAction_" + MenuConstants.AboutApp
text: qsTr("About Interface")
}
//
// File Menu
//
Action {
id: login
objectName: "HifiAction_" + MenuConstants.Login
text: qsTr("Login")
}
Action {
id: quit
objectName: "HifiAction_" + MenuConstants.Quit
text: qsTr("Quit")
//shortcut: StandardKey.Quit
shortcut: "Ctrl+Q"
}
//
// Edit menu
//
Action {
id: undo
text: "Undo"
shortcut: StandardKey.Undo
}
Action {
id: redo
text: "Redo"
shortcut: StandardKey.Redo
}
Action {
id: animations
objectName: "HifiAction_" + MenuConstants.Animations
text: qsTr("Animations...")
}
Action {
id: attachments
text: qsTr("Attachments...")
}
Action {
id: explode
text: qsTr("Explode on quit")
checkable: true
checked: true
}
Action {
id: freeze
text: qsTr("Freeze on quit")
checkable: true
checked: false
}
ExclusiveGroup {
Action {
id: visibleToEveryone
objectName: "HifiAction_" + MenuConstants.VisibleToEveryone
text: qsTr("Everyone")
checkable: true
checked: true
}
Action {
id: visibleToFriends
objectName: "HifiAction_" + MenuConstants.VisibleToFriends
text: qsTr("Friends")
checkable: true
}
Action {
id: visibleToNoOne
objectName: "HifiAction_" + MenuConstants.VisibleToNoOne
text: qsTr("No one")
checkable: true
}
}
}
Menu { Menu {
objectName: "rootMenu"; objectName: "rootMenu";
Menu {
title: "File"
MenuItem { action: login }
MenuItem { action: explode }
MenuItem { action: freeze }
MenuItem { action: quit }
}
Menu {
title: "Tools"
Menu {
title: "I Am Visible To"
MenuItem { action: visibleToEveryone }
MenuItem { action: visibleToFriends }
MenuItem { action: visibleToNoOne }
}
MenuItem { action: animations }
}
Menu {
title: "Long menu name top menu"
MenuItem { action: aboutApp }
}
Menu {
title: "Help"
MenuItem { action: aboutApp }
}
} }
} }

View file

@ -1,10 +1,10 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebKit 3.0 import QtWebEngine 1.1
WebView { WebEngineView {
id: root id: root
objectName: "webview"
anchors.fill: parent anchors.fill: parent
objectName: "webview"
url: "about:blank" url: "about:blank"
} }

View file

@ -101,6 +101,7 @@
#include <VrMenu.h> #include <VrMenu.h>
#include <recording/Deck.h> #include <recording/Deck.h>
#include <recording/Recorder.h> #include <recording/Recorder.h>
#include <QmlWebWindowClass.h>
#include "AnimDebugDraw.h" #include "AnimDebugDraw.h"
#include "AudioClient.h" #include "AudioClient.h"
@ -362,6 +363,17 @@ Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
int _keyboardFocusHighlightID{ -1 }; int _keyboardFocusHighlightID{ -1 };
PluginContainer* _pluginContainer; PluginContainer* _pluginContainer;
// FIXME hack access to the internal share context for the Chromium helper
// Normally we'd want to use QWebEngine::initialize(), but we can't because
// our primary context is a QGLWidget, which can't easily be initialized to share
// from a QOpenGLContext.
//
// So instead we create a new offscreen context to share with the QGLWidget,
// and manually set THAT to be the shared context for the Chromium helper
OffscreenGLCanvas* _chromiumShareContext { nullptr };
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
QApplication(argc, argv), QApplication(argc, argv),
_dependencyManagerIsSetup(setupEssentials(argc, argv)), _dependencyManagerIsSetup(setupEssentials(argc, argv)),
@ -623,6 +635,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_glWidget->makeCurrent(); _glWidget->makeCurrent();
_glWidget->initializeGL(); _glWidget->initializeGL();
_chromiumShareContext = new OffscreenGLCanvas();
_chromiumShareContext->create(_glWidget->context()->contextHandle());
_chromiumShareContext->makeCurrent();
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
_offscreenContext = new OffscreenGLCanvas(); _offscreenContext = new OffscreenGLCanvas();
_offscreenContext->create(_glWidget->context()->contextHandle()); _offscreenContext->create(_glWidget->context()->contextHandle());
_offscreenContext->makeCurrent(); _offscreenContext->makeCurrent();

View file

@ -509,25 +509,25 @@ glm::vec3 MyAvatar::getRightHandTipPosition() const {
controller::Pose MyAvatar::getLeftHandPose() const { controller::Pose MyAvatar::getLeftHandPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand); auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(), return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(),
palmData.getVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); palmData.getVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
} }
controller::Pose MyAvatar::getRightHandPose() const { controller::Pose MyAvatar::getRightHandPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand); auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(), return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(),
palmData.getVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); palmData.getVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
} }
controller::Pose MyAvatar::getLeftHandTipPose() const { controller::Pose MyAvatar::getLeftHandTipPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand); auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand);
return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(), return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(),
palmData.getTipVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); palmData.getTipVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
} }
controller::Pose MyAvatar::getRightHandTipPose() const { controller::Pose MyAvatar::getRightHandTipPose() const {
auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand); auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand);
return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(), return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(),
palmData.getTipVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); palmData.getTipVelocity(), palmData.getRawAngularVelocity()) : controller::Pose();
} }
// virtual // virtual
@ -536,7 +536,7 @@ void MyAvatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
if (!_shouldRender) { if (!_shouldRender) {
return; // exit early return; // exit early
} }
Avatar::render(renderArgs, cameraPosition); Avatar::render(renderArgs, cameraPosition);
} }
@ -799,7 +799,7 @@ void MyAvatar::updateLookAtTargetAvatar() {
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; const float GREATEST_LOOKING_AT_DISTANCE = 10.0f;
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy(); AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
foreach (const AvatarSharedPointer& avatarPointer, hash) { foreach (const AvatarSharedPointer& avatarPointer, hash) {
auto avatar = static_pointer_cast<Avatar>(avatarPointer); auto avatar = static_pointer_cast<Avatar>(avatarPointer);
bool isCurrentTarget = avatar->getIsLookAtTarget(); bool isCurrentTarget = avatar->getIsLookAtTarget();
@ -1175,7 +1175,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
if (!_skeletonModel.isRenderable()) { if (!_skeletonModel.isRenderable()) {
return; // wait until all models are loaded return; // wait until all models are loaded
} }
fixupModelsInScene(); fixupModelsInScene();
// Render head so long as the camera isn't inside it // Render head so long as the camera isn't inside it

View file

@ -122,5 +122,5 @@ void AnimBlendLinearMove::setCurrentFrameInternal(float frame) {
auto clipNode = std::dynamic_pointer_cast<AnimClip>(_children.front()); auto clipNode = std::dynamic_pointer_cast<AnimClip>(_children.front());
assert(clipNode); assert(clipNode);
const float NUM_FRAMES = (clipNode->getEndFrame() - clipNode->getStartFrame()) + 1.0f; const float NUM_FRAMES = (clipNode->getEndFrame() - clipNode->getStartFrame()) + 1.0f;
_phase = fmodf(frame, NUM_FRAMES); _phase = fmodf(frame / NUM_FRAMES, 1.0f);
} }

View file

@ -140,6 +140,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints)
} }
#ifndef NDEBUG #ifndef NDEBUG
#define DUMP_FBX_JOINTS
void AnimSkeleton::dump() const { void AnimSkeleton::dump() const {
qCDebug(animation) << "["; qCDebug(animation) << "[";
for (int i = 0; i < getNumJoints(); i++) { for (int i = 0; i < getNumJoints(); i++) {
@ -151,21 +152,22 @@ void AnimSkeleton::dump() const {
qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i); qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i);
qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i); qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i);
#ifdef DUMP_FBX_JOINTS #ifdef DUMP_FBX_JOINTS
qCDebug(animation) << " isFree =" << _joints[i].isFree; qCDebug(animation) << " fbxJoint =";
qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage; qCDebug(animation) << " isFree =" << _joints[i].isFree;
qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex; qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage;
qCDebug(animation) << " translation =" << _joints[i].translation; qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex;
qCDebug(animation) << " preTransform =" << _joints[i].preTransform; qCDebug(animation) << " translation =" << _joints[i].translation;
qCDebug(animation) << " preRotation =" << _joints[i].preRotation; qCDebug(animation) << " preTransform =" << _joints[i].preTransform;
qCDebug(animation) << " rotation =" << _joints[i].rotation; qCDebug(animation) << " preRotation =" << _joints[i].preRotation;
qCDebug(animation) << " postRotation =" << _joints[i].postRotation; qCDebug(animation) << " rotation =" << _joints[i].rotation;
qCDebug(animation) << " postTransform =" << _joints[i].postTransform; qCDebug(animation) << " postRotation =" << _joints[i].postRotation;
qCDebug(animation) << " transform =" << _joints[i].transform; qCDebug(animation) << " postTransform =" << _joints[i].postTransform;
qCDebug(animation) << " rotationMin =" << _joints[i].rotationMin << ", rotationMax =" << _joints[i].rotationMax; qCDebug(animation) << " transform =" << _joints[i].transform;
qCDebug(animation) << " inverseDefaultRotation" << _joints[i].inverseDefaultRotation; qCDebug(animation) << " rotationMin =" << _joints[i].rotationMin << ", rotationMax =" << _joints[i].rotationMax;
qCDebug(animation) << " inverseBindRotation" << _joints[i].inverseBindRotation; qCDebug(animation) << " inverseDefaultRotation" << _joints[i].inverseDefaultRotation;
qCDebug(animation) << " bindTransform" << _joints[i].bindTransform; qCDebug(animation) << " inverseBindRotation" << _joints[i].inverseBindRotation;
qCDebug(animation) << " isSkeletonJoint" << _joints[i].isSkeletonJoint; qCDebug(animation) << " bindTransform" << _joints[i].bindTransform;
qCDebug(animation) << " isSkeletonJoint" << _joints[i].isSkeletonJoint;
#endif #endif
if (getParentIndex(i) >= 0) { if (getParentIndex(i) >= 0) {
qCDebug(animation) << " parent =" << getJointName(getParentIndex(i)); qCDebug(animation) << " parent =" << getJointName(getParentIndex(i));

View file

@ -14,7 +14,7 @@
#include <GeometryUtil.h> #include <GeometryUtil.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include "AvatarData.h" #include "AvatarData.h"
#include "HandData.h" #include "HandData.h"
@ -38,7 +38,7 @@ PalmData& HandData::addNewPalm(Hand whichHand) {
PalmData HandData::getCopyOfPalmData(Hand hand) const { PalmData HandData::getCopyOfPalmData(Hand hand) const {
QReadLocker locker(&_palmsLock); QReadLocker locker(&_palmsLock);
// the palms are not necessarily added in left-right order, // the palms are not necessarily added in left-right order,
// so we have to search for the correct hand // so we have to search for the correct hand
for (const auto& palm : _palms) { for (const auto& palm : _palms) {
if (palm.whichHand() == hand && palm.isActive()) { if (palm.whichHand() == hand && palm.isActive()) {
@ -64,7 +64,7 @@ void PalmData::addToPosition(const glm::vec3& delta) {
_rawPosition += _owningHandData->worldToLocalVector(delta); _rawPosition += _owningHandData->worldToLocalVector(delta);
} }
bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration, bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
const PalmData*& collidingPalm) const { const PalmData*& collidingPalm) const {
QReadLocker locker(&_palmsLock); QReadLocker locker(&_palmsLock);
@ -93,7 +93,7 @@ glm::vec3 HandData::getBasePosition() const {
float HandData::getBaseScale() const { float HandData::getBaseScale() const {
return _owningAvatarData->getTargetScale(); return _owningAvatarData->getTargetScale();
} }
glm::vec3 PalmData::getFingerDirection() const { glm::vec3 PalmData::getFingerDirection() const {
// finger points along yAxis in hand-frame // finger points along yAxis in hand-frame
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f); const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f);

View file

@ -38,7 +38,7 @@ public:
HandData(AvatarData* owningAvatar); HandData(AvatarData* owningAvatar);
virtual ~HandData() {} virtual ~HandData() {}
// position conversion // position conversion
glm::vec3 localToWorldPosition(const glm::vec3& localPosition) { glm::vec3 localToWorldPosition(const glm::vec3& localPosition) {
return getBasePosition() + getBaseOrientation() * localPosition * getBaseScale(); return getBasePosition() + getBaseOrientation() * localPosition * getBaseScale();
@ -60,7 +60,7 @@ public:
/// \param penetration[out] the vector in which to store the penetration /// \param penetration[out] the vector in which to store the penetration
/// \param collidingPalm[out] a const PalmData* to the palm that was collided with /// \param collidingPalm[out] a const PalmData* to the palm that was collided with
/// \return whether or not the sphere penetrated /// \return whether or not the sphere penetrated
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration, bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
const PalmData*& collidingPalm) const; const PalmData*& collidingPalm) const;
glm::quat getBaseOrientation() const; glm::quat getBaseOrientation() const;
@ -74,7 +74,7 @@ protected:
AvatarData* _owningAvatarData; AvatarData* _owningAvatarData;
std::vector<PalmData> _palms; std::vector<PalmData> _palms;
mutable QReadWriteLock _palmsLock{ QReadWriteLock::Recursive }; mutable QReadWriteLock _palmsLock{ QReadWriteLock::Recursive };
glm::vec3 getBasePosition() const; glm::vec3 getBasePosition() const;
float getBaseScale() const; float getBaseScale() const;
@ -112,13 +112,12 @@ public:
void setRawAngularVelocity(const glm::vec3& angularVelocity) { _rawAngularVelocity = angularVelocity; } void setRawAngularVelocity(const glm::vec3& angularVelocity) { _rawAngularVelocity = angularVelocity; }
const glm::vec3& getRawAngularVelocity() const { return _rawAngularVelocity; } const glm::vec3& getRawAngularVelocity() const { return _rawAngularVelocity; }
glm::quat getRawAngularVelocityAsQuat() const { return glm::quat(_rawAngularVelocity); }
void addToPosition(const glm::vec3& delta); void addToPosition(const glm::vec3& delta);
void addToPenetration(const glm::vec3& penetration) { _totalPenetration += penetration; } void addToPenetration(const glm::vec3& penetration) { _totalPenetration += penetration; }
void resolvePenetrations() { addToPosition(-_totalPenetration); _totalPenetration = glm::vec3(0.0f); } void resolvePenetrations() { addToPosition(-_totalPenetration); _totalPenetration = glm::vec3(0.0f); }
void setTipPosition(const glm::vec3& position) { _tipPosition = position; } void setTipPosition(const glm::vec3& position) { _tipPosition = position; }
const glm::vec3 getTipPosition() const { return _owningHandData->localToWorldPosition(_tipPosition); } const glm::vec3 getTipPosition() const { return _owningHandData->localToWorldPosition(_tipPosition); }
const glm::vec3& getTipRawPosition() const { return _tipPosition; } const glm::vec3& getTipRawPosition() const { return _tipPosition; }
@ -126,16 +125,16 @@ public:
void setTipVelocity(const glm::vec3& velocity) { _tipVelocity = velocity; } void setTipVelocity(const glm::vec3& velocity) { _tipVelocity = velocity; }
const glm::vec3 getTipVelocity() const { return _owningHandData->localToWorldDirection(_tipVelocity); } const glm::vec3 getTipVelocity() const { return _owningHandData->localToWorldDirection(_tipVelocity); }
const glm::vec3& getTipRawVelocity() const { return _tipVelocity; } const glm::vec3& getTipRawVelocity() const { return _tipVelocity; }
void incrementFramesWithoutData() { _numFramesWithoutData++; } void incrementFramesWithoutData() { _numFramesWithoutData++; }
void resetFramesWithoutData() { _numFramesWithoutData = 0; } void resetFramesWithoutData() { _numFramesWithoutData = 0; }
int getFramesWithoutData() const { return _numFramesWithoutData; } int getFramesWithoutData() const { return _numFramesWithoutData; }
// FIXME - these are used in SkeletonModel::updateRig() the skeleton/rig should probably get this information // FIXME - these are used in SkeletonModel::updateRig() the skeleton/rig should probably get this information
// from an action and/or the UserInputMapper instead of piping it through here. // from an action and/or the UserInputMapper instead of piping it through here.
void setTrigger(float trigger) { _trigger = trigger; } void setTrigger(float trigger) { _trigger = trigger; }
float getTrigger() const { return _trigger; } float getTrigger() const { return _trigger; }
// return world-frame: // return world-frame:
glm::vec3 getFingerDirection() const; glm::vec3 getFingerDirection() const;
glm::vec3 getNormal() const; glm::vec3 getNormal() const;
@ -148,13 +147,13 @@ private:
glm::vec3 _rawAngularVelocity; glm::vec3 _rawAngularVelocity;
glm::quat _rawDeltaRotation; glm::quat _rawDeltaRotation;
glm::quat _lastRotation; glm::quat _lastRotation;
glm::vec3 _tipPosition; glm::vec3 _tipPosition;
glm::vec3 _tipVelocity; glm::vec3 _tipVelocity;
glm::vec3 _totalPenetration; /// accumulator for per-frame penetrations glm::vec3 _totalPenetration; /// accumulator for per-frame penetrations
float _trigger; float _trigger;
bool _isActive; /// This has current valid data bool _isActive; /// This has current valid data
int _numFramesWithoutData; /// after too many frames without data, this tracked object assumed lost. int _numFramesWithoutData; /// after too many frames without data, this tracked object assumed lost.
HandData* _owningHandData; HandData* _owningHandData;

View file

@ -16,7 +16,7 @@
namespace controller { namespace controller {
Pose::Pose(const vec3& translation, const quat& rotation, Pose::Pose(const vec3& translation, const quat& rotation,
const vec3& velocity, const quat& angularVelocity) : const vec3& velocity, const vec3& angularVelocity) :
translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity), valid (true) { } translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity), valid (true) { }
bool Pose::operator==(const Pose& right) const { bool Pose::operator==(const Pose& right) const {
@ -26,7 +26,7 @@ namespace controller {
} }
// FIXME add margin of error? Or add an additional withinEpsilon function? // FIXME add margin of error? Or add an additional withinEpsilon function?
return translation == right.getTranslation() && rotation == right.getRotation() && return translation == right.getTranslation() && rotation == right.getRotation() &&
velocity == right.getVelocity() && angularVelocity == right.getAngularVelocity(); velocity == right.getVelocity() && angularVelocity == right.getAngularVelocity();
} }
@ -35,7 +35,7 @@ namespace controller {
obj.setProperty("translation", vec3toScriptValue(engine, pose.translation)); obj.setProperty("translation", vec3toScriptValue(engine, pose.translation));
obj.setProperty("rotation", quatToScriptValue(engine, pose.rotation)); obj.setProperty("rotation", quatToScriptValue(engine, pose.rotation));
obj.setProperty("velocity", vec3toScriptValue(engine, pose.velocity)); obj.setProperty("velocity", vec3toScriptValue(engine, pose.velocity));
obj.setProperty("angularVelocity", quatToScriptValue(engine, pose.angularVelocity)); obj.setProperty("angularVelocity", vec3toScriptValue(engine, pose.angularVelocity));
obj.setProperty("valid", pose.valid); obj.setProperty("valid", pose.valid);
return obj; return obj;

View file

@ -23,12 +23,12 @@ namespace controller {
vec3 translation; vec3 translation;
quat rotation; quat rotation;
vec3 velocity; vec3 velocity;
quat angularVelocity; vec3 angularVelocity;
bool valid{ false }; bool valid{ false };
Pose() {} Pose() {}
Pose(const vec3& translation, const quat& rotation, Pose(const vec3& translation, const quat& rotation,
const vec3& velocity = vec3(), const quat& angularVelocity = quat()); const vec3& velocity = vec3(), const vec3& angularVelocity = vec3());
Pose(const Pose&) = default; Pose(const Pose&) = default;
Pose& operator = (const Pose&) = default; Pose& operator = (const Pose&) = default;
@ -38,7 +38,7 @@ namespace controller {
vec3 getTranslation() const { return translation; } vec3 getTranslation() const { return translation; }
quat getRotation() const { return rotation; } quat getRotation() const { return rotation; }
vec3 getVelocity() const { return velocity; } vec3 getVelocity() const { return velocity; }
quat getAngularVelocity() const { return angularVelocity; } vec3 getAngularVelocity() const { return angularVelocity; }
static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event); static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event);
static void fromScriptValue(const QScriptValue& object, Pose& event); static void fromScriptValue(const QScriptValue& object, Pose& event);

View file

@ -82,7 +82,7 @@ void main(void) {
varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age); varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age);
// anchor point in eye space // anchor point in eye space
float radius = bezierInterpolate(particle.radius.start, particle.radius.middle, particle.radius.finish , age); float radius = bezierInterpolate(particle.radius.start, particle.radius.middle, particle.radius.finish, age);
vec4 quadPos = radius * UNIT_QUAD[twoTriID]; vec4 quadPos = radius * UNIT_QUAD[twoTriID];
vec4 anchorPoint; vec4 anchorPoint;

View file

@ -13,6 +13,7 @@
#include <ByteCountCoding.h> #include <ByteCountCoding.h>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <glm/gtx/transform.hpp>
#include "EntitiesLogging.h" #include "EntitiesLogging.h"
#include "EntityItemProperties.h" #include "EntityItemProperties.h"
@ -243,13 +244,22 @@ void ModelEntityItem::getAnimationFrame(bool& newFrame,
_lastKnownFrameDataRotations.resize(_jointMapping.size()); _lastKnownFrameDataRotations.resize(_jointMapping.size());
_lastKnownFrameDataTranslations.resize(_jointMapping.size()); _lastKnownFrameDataTranslations.resize(_jointMapping.size());
for (int j = 0; j < _jointMapping.size(); j++) { for (int j = 0; j < _jointMapping.size(); j++) {
int index = _jointMapping[j]; int index = _jointMapping[j];
if (index != -1 && index < rotations.size()) { if (index >= 0) {
_lastKnownFrameDataRotations[j] = fbxJoints[index].preRotation * rotations[index]; glm::mat4 translationMat;
} if (index < translations.size()) {
if (index != -1 && index < translations.size()) { translationMat = glm::translate(translations[index]);
_lastKnownFrameDataTranslations[j] = translations[index]; }
glm::mat4 rotationMat;
if (index < rotations.size()) {
rotationMat = glm::mat4_cast(rotations[index]);
}
glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform *
rotationMat * fbxJoints[index].postTransform);
_lastKnownFrameDataTranslations[j] = extractTranslation(finalMat);
_lastKnownFrameDataRotations[j] = glmExtractRotation(finalMat);
} }
} }
} }

View file

@ -320,10 +320,13 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
void OffscreenQmlSurface::resize(const QSize& newSize) { void OffscreenQmlSurface::resize(const QSize& newSize) {
if (!_renderer || !_renderer->_quickWindow) { if (!_renderer || !_renderer->_quickWindow) {
QSize currentSize = _renderer->_quickWindow->geometry().size(); return;
if (newSize == currentSize) { }
return;
}
QSize currentSize = _renderer->_quickWindow->geometry().size();
if (newSize == currentSize) {
return;
} }
_qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize); _qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize);
@ -437,7 +440,9 @@ void OffscreenQmlSurface::updateQuick() {
} }
if (_render) { if (_render) {
QMutexLocker lock(&(_renderer->_mutex));
_renderer->post(RENDER); _renderer->post(RENDER);
_renderer->_cond.wait(&(_renderer->_mutex));
_render = false; _render = false;
} }

View file

@ -40,8 +40,8 @@ public:
void create(QOpenGLContext* context); void create(QOpenGLContext* context);
void resize(const QSize& size); void resize(const QSize& size);
QSize size() const; QSize size() const;
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*) {});
QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) { Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {}) {
return load(QUrl(qmlSourceFile), f); return load(QUrl(qmlSourceFile), f);
} }

View file

@ -39,6 +39,11 @@ void QOpenGLContextWrapper::doneCurrent() {
_context->doneCurrent(); _context->doneCurrent();
} }
void QOpenGLContextWrapper::setShareContext(QOpenGLContext* otherContext) {
_context->setShareContext(otherContext);
}
bool isCurrentContext(QOpenGLContext* context) { bool isCurrentContext(QOpenGLContext* context) {
return QOpenGLContext::currentContext() == context; return QOpenGLContext::currentContext() == context;
} }

View file

@ -25,6 +25,12 @@ public:
void swapBuffers(QSurface* surface); void swapBuffers(QSurface* surface);
bool makeCurrent(QSurface* surface); bool makeCurrent(QSurface* surface);
void doneCurrent(); void doneCurrent();
void setShareContext(QOpenGLContext* otherContext);
QOpenGLContext* getContext() {
return _context;
}
private: private:
QOpenGLContext* _context { nullptr }; QOpenGLContext* _context { nullptr };

View file

@ -1,3 +1,3 @@
set(TARGET_NAME script-engine) set(TARGET_NAME script-engine)
setup_hifi_library(Gui Network Script WebSockets Widgets) setup_hifi_library(Gui Network Script WebSockets Widgets)
link_hifi_libraries(shared networking octree gpu procedural model model-networking recording avatars fbx entities controllers animation audio physics) link_hifi_libraries(shared networking ui octree gpu procedural model model-networking recording avatars fbx entities controllers animation audio physics)

View file

@ -30,6 +30,7 @@
#include <udt/PacketHeaders.h> #include <udt/PacketHeaders.h>
#include <UUID.h> #include <UUID.h>
#include <QmlWebWindowClass.h>
#include <controllers/ScriptingInterface.h> #include <controllers/ScriptingInterface.h>
#include <AnimationObject.h> #include <AnimationObject.h>
@ -350,6 +351,8 @@ void ScriptEngine::init() {
qScriptRegisterSequenceMetaType<QVector<glm::quat> >(this); qScriptRegisterSequenceMetaType<QVector<glm::quat> >(this);
qScriptRegisterSequenceMetaType<QVector<QString> >(this); qScriptRegisterSequenceMetaType<QVector<QString> >(this);
registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor); QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor);
globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue); globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue);

View file

@ -1,3 +1,3 @@
set(TARGET_NAME ui) set(TARGET_NAME ui)
setup_hifi_library(OpenGL Network Qml Quick Script XmlPatterns) setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebSockets XmlPatterns)
link_hifi_libraries(shared networking gl) link_hifi_libraries(shared networking gl)

View file

@ -0,0 +1,240 @@
//
// 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 "QmlWebWindowClass.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 <AddressManager.h>
#include <DependencyManager.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 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()));
});
}
}
// Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
QmlWebWindowClass* retVal { nullptr };
const QString title = context->argument(0).toString();
QString url = context->argument(1).toString();
if (!url.startsWith("http") && !url.startsWith("file://")) {
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);
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)
: _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)));
}
void QmlWebWindowClass::handleNavigation(const QString& url) {
DependencyManager::get<AddressManager>()->handleLookupString(url);
}
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 {
if (QThread::currentThread() != thread()) {
QString result;
QMetaObject::invokeMethod(const_cast<QmlWebWindowClass*>(this), "getURL", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, result));
return result;
}
return _qmlWindow->property(URL_PROPERTY).toString();
}
void QmlWebWindowClass::setURL(const QString& urlString) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setURL", Qt::QueuedConnection, Q_ARG(QString, urlString));
}
_qmlWindow->setProperty(URL_PROPERTY, urlString);
}
void QmlWebWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title));
}
_qmlWindow->setProperty(TITLE_PROPERTY, title);
}
void QmlWebWindowClass::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
}
_qmlWindow->setProperty("destroyOnInvisible", true);
_qmlWindow->setProperty("visible", false);
_qmlWindow->deleteLater();
}
void QmlWebWindowClass::hasClosed() {
}
void QmlWebWindowClass::raise() {
// FIXME
}
#include "QmlWebWindowClass.moc"

View file

@ -0,0 +1,104 @@
//
// 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_QmlWebWindowClass_h
#define hifi_ui_QmlWebWindowClass_h
#include <QtCore/QObject>
#include <GLMHelpers.h>
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
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
class QmlWebWindowClass : public QObject {
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(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);
QmlWebWindowClass(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);
QString getURL() const;
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:
void visibilityChanged(bool visible); // Tool window
void urlChanged();
void moved(glm::vec2 position);
void resized(QSizeF size);
void closed();
private slots:
void hasClosed();
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

View file

@ -191,7 +191,7 @@ void SixenseManager::InputDevice::update(float deltaTime, bool jointsCaptured) {
// Rotation of Palm // Rotation of Palm
glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]); glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]);
handlePoseEvent(deltaTime, position, rotation, left); handlePoseEvent(deltaTime, position, rotation, left);
rawPoses[i] = controller::Pose(position, rotation, glm::vec3(0), glm::quat()); rawPoses[i] = controller::Pose(position, rotation, Vectors::ZERO, Vectors::ZERO);
} else { } else {
_poseStateMap.clear(); _poseStateMap.clear();
_collectedSamples.clear(); _collectedSamples.clear();
@ -457,25 +457,22 @@ void SixenseManager::InputDevice::handlePoseEvent(float deltaTime, glm::vec3 pos
rotation = _avatarRotation * postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand; rotation = _avatarRotation * postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand;
glm::vec3 velocity(0.0f); glm::vec3 velocity(0.0f);
glm::quat angularVelocity; glm::vec3 angularVelocity(0.0f);
if (prevPose.isValid() && deltaTime > std::numeric_limits<float>::epsilon()) { if (prevPose.isValid() && deltaTime > std::numeric_limits<float>::epsilon()) {
auto& samples = _collectedSamples[hand];
velocity = (position - prevPose.getTranslation()) / deltaTime; velocity = (position - prevPose.getTranslation()) / deltaTime;
auto deltaRot = rotation * glm::conjugate(prevPose.getRotation());
auto axis = glm::axis(deltaRot);
auto angle = glm::angle(deltaRot);
angularVelocity = glm::angleAxis(angle / deltaTime, axis);
// Average
auto& samples = _collectedSamples[hand];
samples.first.addSample(velocity); samples.first.addSample(velocity);
velocity = samples.first.average; velocity = samples.first.average;
// FIXME: // Not using quaternion average yet for angular velocity because it s probably wrong but keep the MovingAverage in place auto deltaRot = glm::normalize(rotation * glm::conjugate(prevPose.getRotation()));
//samples.second.addSample(glm::vec4(angularVelocity.x, angularVelocity.y, angularVelocity.z, angularVelocity.w)); auto axis = glm::axis(deltaRot);
//angularVelocity = glm::quat(samples.second.average.w, samples.second.average.x, samples.second.average.y, samples.second.average.z); auto speed = glm::angle(deltaRot) / deltaTime;
assert(!glm::isnan(speed));
angularVelocity = speed * axis;
samples.second.addSample(angularVelocity);
angularVelocity = samples.second.average;
} else if (!prevPose.isValid()) { } else if (!prevPose.isValid()) {
_collectedSamples[hand].first.clear(); _collectedSamples[hand].first.clear();
_collectedSamples[hand].second.clear(); _collectedSamples[hand].second.clear();

View file

@ -49,12 +49,12 @@ private:
static const int CALIBRATION_STATE_IDLE = 0; static const int CALIBRATION_STATE_IDLE = 0;
static const int CALIBRATION_STATE_IN_PROGRESS = 1; static const int CALIBRATION_STATE_IN_PROGRESS = 1;
static const int CALIBRATION_STATE_COMPLETE = 2; static const int CALIBRATION_STATE_COMPLETE = 2;
static const glm::vec3 DEFAULT_AVATAR_POSITION; static const glm::vec3 DEFAULT_AVATAR_POSITION;
static const float CONTROLLER_THRESHOLD; static const float CONTROLLER_THRESHOLD;
template<typename T> template<typename T>
using SampleAverage = MovingAverage<T, MAX_NUM_AVERAGING_SAMPLES>; using SampleAverage = MovingAverage<T, MAX_NUM_AVERAGING_SAMPLES>;
using Samples = std::pair<SampleAverage<glm::vec3>, SampleAverage<glm::vec4>>; using Samples = std::pair<SampleAverage<glm::vec3>, SampleAverage<glm::vec3>>;
using MovingAverageMap = std::map<int, Samples>; using MovingAverageMap = std::map<int, Samples>;
class InputDevice : public controller::InputDevice { class InputDevice : public controller::InputDevice {
@ -81,7 +81,7 @@ private:
// these are calibration results // these are calibration results
glm::vec3 _avatarPosition { DEFAULT_AVATAR_POSITION }; // in hydra-frame glm::vec3 _avatarPosition { DEFAULT_AVATAR_POSITION }; // in hydra-frame
glm::quat _avatarRotation; // in hydra-frame glm::quat _avatarRotation; // in hydra-frame
float _lastDistance; float _lastDistance;
bool _requestReset { false }; bool _requestReset { false };
bool _debugDrawRaw { false }; bool _debugDrawRaw { false };

View file

@ -2,15 +2,32 @@
set(TARGET_NAME "ui-test") set(TARGET_NAME "ui-test")
# This is not a testcase -- just set it up as a regular hifi project # This is not a testcase -- just set it up as a regular hifi project
setup_hifi_project(Widgets OpenGL Network Qml Quick Script) setup_hifi_project(Network OpenGL Qml Quick Script WebChannel WebEngine WebSockets)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
if (WIN32) if (WIN32)
target_link_libraries(${TARGET_NAME} wsock32.lib opengl32.lib Winmm.lib) target_link_libraries(${TARGET_NAME} wsock32.lib opengl32.lib Winmm.lib)
# Issue causes build failure unless we add this directory.
# See https://bugreports.qt.io/browse/QTBUG-43351
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
endif() endif()
# link in the shared libraries # link in the shared libraries
link_hifi_libraries(shared networking gl gpu ui) link_hifi_libraries(shared networking gl gpu ui)
# copy the resources files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/qml"
$<TARGET_FILE_DIR:${TARGET_NAME}>/qml
)
target_glew()
if (WIN32)
set(EXTRA_DEPLOY_OPTIONS "--qmldir ${PROJECT_SOURCE_DIR}/../../interface/resources/qml")
endif()
package_libraries_for_deployment() package_libraries_for_deployment()

View file

@ -1,41 +1,91 @@
// //
// main.cpp // Created by Bradley Austin Davis on 2015-04-22
// tests/render-utils/src // Copyright 2013-2015 High Fidelity, Inc.
//
// Copyright 2014 High Fidelity, Inc.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "OffscreenUi.h" #include <gl/Config.h>
#include <QWindow> #include <gl/OglplusHelpers.h>
#include <QFile> #include <gl/GLHelpers.h>
#include <QTime>
#include <QImage>
#include <QTimer>
#include <QElapsedTimer>
#include <QOpenGLContext>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QResizeEvent>
#include <QLoggingCategory>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QApplication>
#include <QOpenGLDebugLogger>
#include <QOpenGLFunctions>
#include <QQmlContext>
#include <QtQml/QQmlApplicationEngine>
#include <PathUtils.h>
#include <memory> #include <memory>
#include <glm/glm.hpp>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QTime>
#include <QtCore/QTimer>
#include <QtCore/QElapsedTimer>
#include <QtCore/QLoggingCategory>
#include <QtCore/QThread>
#include <QtCore/QUuid>
#include <QtGui/QWindow>
#include <QtGui/QImage>
#include <QtGui/QGuiApplication>
#include <QtGui/QResizeEvent>
#include <QtGui/QScreen>
#include <gl/QOpenGLContextWrapper.h>
#include <QtScript/QScriptEngine>
#include <QtQml/QQmlContext>
#include <QtQml/QQmlApplicationEngine>
#include <GLMHelpers.h>
#include <gl/OffscreenGLCanvas.h>
#include <OffscreenUi.h>
#include <PathUtils.h> #include <PathUtils.h>
#include <QDir> #include <PathUtils.h>
#include "MessageDialog.h" #include <MessageDialog.h>
#include "VrMenu.h" #include <VrMenu.h>
#include "InfoView.h" #include <InfoView.h>
#include <QDesktopWidget> #include <QmlWebWindowClass.h>
#include <RegisteredMetaTypes.h>
const QString& getResourcesDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/";
qDebug() << "Resources Path: " << dir;
}
return dir;
}
const QString& getExamplesDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../../../examples/")) + "/";
qDebug() << "Resources Path: " << dir;
}
return dir;
}
const QString& getInterfaceQmlDir() {
static QString dir;
if (dir.isEmpty()) {
dir = getResourcesDir() + "qml/";
qDebug() << "Qml Path: " << dir;
}
return dir;
}
const QString& getTestQmlDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../")) + "/";
qDebug() << "Qml Test Path: " << dir;
}
return dir;
}
class RateCounter { class RateCounter {
std::vector<float> times; std::vector<float> times;
@ -74,142 +124,394 @@ public:
}; };
class MenuConstants : public QObject{
Q_OBJECT
Q_ENUMS(Item)
public:
enum Item {
RenderLookAtTargets,
};
public: extern QOpenGLContext* qt_gl_global_share_context();
MenuConstants(QObject* parent = nullptr) : QObject(parent) {
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {
if (engine.hasUncaughtException()) {
const auto backtrace = engine.uncaughtExceptionBacktrace();
const auto exception = engine.uncaughtException().toString();
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
engine.clearExceptions();
auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line);
if (!backtrace.empty()) {
static const auto lineSeparator = "\n ";
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
}
qWarning() << qPrintable(message);
return true;
} }
return false;
}
const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0f / 60.0f) * 1000 * 1000) + 0.5f);
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
QString message = "";
for (int i = 0; i < context->argumentCount(); i++) {
if (i > 0) {
message += " ";
}
message += context->argument(i).toString();
}
qDebug().noquote() << "script:print()<<" << message; // noquote() so that \n is treated as newline
message = message.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("'", "\\'");
engine->evaluate("Script.print('" + message + "')");
return QScriptValue();
}
class ScriptEngine : public QScriptEngine {
Q_OBJECT
public:
void loadFile(const QString& scriptPath) {
if (_isRunning) {
return;
}
qDebug() << "Loading script from " << scriptPath;
_fileNameString = scriptPath;
QFile file(scriptPath);
if (file.exists()) {
file.open(QIODevice::ReadOnly);
_scriptContents = file.readAll();
} else {
qFatal("Missing file ");
}
runInThread();
}
Q_INVOKABLE void stop() {
if (!_isFinished) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "stop");
return;
}
_isFinished = true;
if (_wantSignals) {
emit runningStateChanged();
}
}
}
Q_INVOKABLE void print(const QString& message) {
if (_wantSignals) {
emit printedMessage(message);
}
}
Q_INVOKABLE QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
// create the timer, add it to the map, and start it
QTimer* newTimer = new QTimer(this);
newTimer->setSingleShot(isSingleShot);
connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired);
// make sure the timer stops when the script does
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
_timerFunctionMap.insert(newTimer, function);
newTimer->start(intervalMS);
return newTimer;
}
Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS) {
return setupTimerWithInterval(function, intervalMS, false);
}
Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS) {
return setupTimerWithInterval(function, timeoutMS, true);
}
private:
void runInThread() {
QThread* workerThread = new QThread();
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
connect(workerThread, &QThread::started, this, &ScriptEngine::run);
connect(workerThread, &QThread::finished, this, &ScriptEngine::deleteLater);
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
moveToThread(workerThread);
workerThread->start();
}
void init() {
_isInitialized = true;
registerMetaTypes(this);
registerGlobalObject("Script", this);
qScriptRegisterSequenceMetaType<QVector<QUuid>>(this);
qScriptRegisterSequenceMetaType<QVector<QString>>(this);
globalObject().setProperty("OverlayWebWindow", newFunction(QmlWebWindowClass::constructor));
QScriptValue printConstructorValue = newFunction(debugPrint);
globalObject().setProperty("print", printConstructorValue);
}
void timerFired() {
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
if (!callingTimer->isActive()) {
// this timer is done, we can kill it
_timerFunctionMap.remove(callingTimer);
delete callingTimer;
}
// call the associated JS function, if it exists
if (timerFunction.isValid()) {
timerFunction.call();
}
}
void run() {
if (!_isInitialized) {
init();
}
_isRunning = true;
if (_wantSignals) {
emit runningStateChanged();
}
QScriptValue result = evaluate(_scriptContents, _fileNameString);
QElapsedTimer startTime;
startTime.start();
int thisFrame = 0;
qint64 lastUpdate = usecTimestampNow();
while (!_isFinished) {
int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec
if (usecToSleep > 0) {
usleep(usecToSleep);
}
if (_isFinished) {
break;
}
QCoreApplication::processEvents();
if (_isFinished) {
break;
}
qint64 now = usecTimestampNow();
float deltaTime = (float)(now - lastUpdate) / (float)USECS_PER_SECOND;
if (!_isFinished) {
if (_wantSignals) {
emit update(deltaTime);
}
}
lastUpdate = now;
// Debug and clear exceptions
hadUncaughtExceptions(*this, _fileNameString);
}
if (_wantSignals) {
emit scriptEnding();
}
if (_wantSignals) {
emit finished(_fileNameString, this);
}
_isRunning = false;
if (_wantSignals) {
emit runningStateChanged();
emit doneRunning();
}
}
void registerGlobalObject(const QString& name, QObject* object) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "registerGlobalObject",
Q_ARG(const QString&, name),
Q_ARG(QObject*, object));
return;
}
if (!globalObject().property(name).isValid()) {
if (object) {
QScriptValue value = newQObject(object);
globalObject().setProperty(name, value);
} else {
globalObject().setProperty(name, QScriptValue());
}
}
}
void registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
globalObject().setProperty(name, scriptFun);
}
void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "registerFunction",
Q_ARG(const QString&, name),
Q_ARG(QScriptEngine::FunctionSignature, functionSignature),
Q_ARG(int, numArguments));
return;
}
QScriptValue object = globalObject().property(parent);
if (object.isValid()) {
QScriptValue scriptFun = newFunction(functionSignature, numArguments);
object.setProperty(name, scriptFun);
}
}
signals:
void scriptLoaded(const QString& scriptFilename);
void errorLoadingScript(const QString& scriptFilename);
void update(float deltaTime);
void scriptEnding();
void finished(const QString& fileNameString, ScriptEngine* engine);
void cleanupMenuItem(const QString& menuItemString);
void printedMessage(const QString& message);
void errorMessage(const QString& message);
void runningStateChanged();
void evaluationFinished(QScriptValue result, bool isException);
void loadScript(const QString& scriptName, bool isUserLoaded);
void reloadScript(const QString& scriptName, bool isUserLoaded);
void doneRunning();
private:
QString _scriptContents;
QString _fileNameString;
QString _parentURL;
bool _isInitialized { false };
std::atomic<bool> _isFinished { false };
std::atomic<bool> _isRunning { false };
bool _wantSignals { true };
QHash<QTimer*, QScriptValue> _timerFunctionMap;
}; };
const QString& getResourcesDir() {
static QString dir;
if (dir.isEmpty()) { ScriptEngine* loadScript(const QString& scriptFilename) {
QDir path(__FILE__); ScriptEngine* scriptEngine = new ScriptEngine();
path.cdUp(); scriptEngine->loadFile(scriptFilename);
dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/"; return scriptEngine;
qDebug() << "Resources Path: " << dir;
}
return dir;
} }
const QString& getQmlDir() { OffscreenGLCanvas* _chromiumShareContext { nullptr };
static QString dir; Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
if (dir.isEmpty()) {
dir = getResourcesDir() + "qml/";
qDebug() << "Qml Path: " << dir;
}
return dir;
}
const QString& getTestQmlDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../")) + "/";
qDebug() << "Qml Test Path: " << dir;
}
return dir;
}
// Create a simple OpenGL window that renders text in various ways // Create a simple OpenGL window that renders text in various ways
class QTestWindow : public QWindow, private QOpenGLFunctions { class QTestWindow : public QWindow {
Q_OBJECT Q_OBJECT
QOpenGLContext* _context{ nullptr }; QOpenGLContextWrapper* _context{ nullptr };
QSize _size; QSize _size;
bool _altPressed{ false }; bool _altPressed{ false };
RateCounter fps; RateCounter fps;
QTimer _timer; QTimer _timer;
int testQmlTexture{ 0 }; int testQmlTexture{ 0 };
ProgramPtr _program;
ShapeWrapperPtr _plane;
QScriptEngine* _scriptEngine { nullptr };
public: public:
QObject* rootMenu; QObject* rootMenu;
QTestWindow() { QTestWindow() {
_scriptEngine = new ScriptEngine();
_timer.setInterval(1); _timer.setInterval(1);
connect(&_timer, &QTimer::timeout, [=] { QObject::connect(&_timer, &QTimer::timeout, this, &QTestWindow::draw);
draw();
});
DependencyManager::set<OffscreenUi>(); _chromiumShareContext = new OffscreenGLCanvas();
setSurfaceType(QSurface::OpenGLSurface); _chromiumShareContext->create();
_chromiumShareContext->makeCurrent();
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
QSurfaceFormat format; {
format.setDepthBufferSize(16); setSurfaceType(QSurface::OpenGLSurface);
format.setStencilBufferSize(8); QSurfaceFormat format = getDefaultOpenGLSurfaceFormat();
format.setVersion(4, 1); setFormat(format);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); _context = new QOpenGLContextWrapper();
format.setOption(QSurfaceFormat::DebugContext); _context->setFormat(format);
_context->setShareContext(_chromiumShareContext->getContext());
}
setFormat(format);
_context = new QOpenGLContext;
_context->setFormat(format);
if (!_context->create()) { if (!_context->create()) {
qFatal("Could not create OpenGL context"); qFatal("Could not create OpenGL context");
} }
show(); show();
makeCurrent(); makeCurrent();
initializeOpenGLFunctions();
{ glewExperimental = true;
QOpenGLDebugLogger* logger = new QOpenGLDebugLogger(this); glewInit();
logger->initialize(); // initializes in the current context, i.e. ctx glGetError();
logger->enableMessages();
connect(logger, &QOpenGLDebugLogger::messageLogged, this, [&](const QOpenGLDebugMessage & debugMessage) {
qDebug() << debugMessage;
});
// logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
}
qDebug() << (const char*)this->glGetString(GL_VERSION); using namespace oglplus;
glEnable(GL_BLEND); Context::Enable(Capability::Blend);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha);
glClearColor(0.2f, 0.2f, 0.2f, 1); Context::Disable(Capability::DepthTest);
glDisable(GL_DEPTH_TEST); Context::Disable(Capability::CullFace);
Context::ClearColor(0.2f, 0.2f, 0.2f, 1);
MessageDialog::registerType(); MessageDialog::registerType();
VrMenu::registerType();
InfoView::registerType(); InfoView::registerType();
auto offscreenUi = DependencyManager::set<OffscreenUi>();
{
offscreenUi->create(_context->getContext());
offscreenUi->setProxyWindow(this);
auto offscreenUi = DependencyManager::get<OffscreenUi>(); connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
offscreenUi->create(_context); testQmlTexture = textureId;
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) { });
testQmlTexture = textureId;
});
makeCurrent(); makeCurrent();
}
offscreenUi->setProxyWindow(this);
QDesktopWidget* desktop = QApplication::desktop(); auto primaryScreen = QGuiApplication::primaryScreen();
QRect rect = desktop->availableGeometry(desktop->screenCount() - 1); auto targetScreen = primaryScreen;
int height = rect.height(); auto screens = QGuiApplication::screens();
//rect.setHeight(height / 2); if (screens.size() > 1) {
rect.setY(rect.y() + height / 2); for (auto screen : screens) {
if (screen != targetScreen) {
targetScreen = screen;
break;
}
}
}
auto rect = targetScreen->availableGeometry();
rect.setWidth(rect.width() * 0.8f);
rect.setHeight(rect.height() * 0.8f);
rect.moveTo(QPoint(20, 20));
setGeometry(rect); setGeometry(rect);
// setFramePosition(QPoint(-1000, 0));
// resize(QSize(800, 600));
#ifdef QML_CONTROL_GALLERY #ifdef QML_CONTROL_GALLERY
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir())); offscreenUi->setBaseUrl(QUrl::fromLocalFile(getTestQmlDir()));
offscreenUi->load(QUrl("main.qml")); offscreenUi->load(QUrl("main.qml"));
#else #else
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir())); offscreenUi->setBaseUrl(QUrl::fromLocalFile(getInterfaceQmlDir()));
offscreenUi->load(QUrl("TestRoot.qml")); offscreenUi->load(QUrl("TestRoot.qml"));
offscreenUi->load(QUrl("TestMenu.qml"));
// Requires a root menu to have been loaded before it can load
VrMenu::load();
#endif #endif
installEventFilter(offscreenUi.data()); installEventFilter(offscreenUi.data());
offscreenUi->resume(); offscreenUi->resume();
@ -227,16 +529,35 @@ private:
} }
makeCurrent(); makeCurrent();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); auto error = glGetError();
glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio()); if (error != GL_NO_ERROR) {
qDebug() << "GL error in entering draw " << error;
}
renderQml(); using namespace oglplus;
Context::Clear().ColorBuffer().DepthBuffer();
ivec2 size(_size.width(), _size.height());
size *= devicePixelRatio();
size = glm::max(size, ivec2(100, 100));
Context::Viewport(size.x, size.y);
if (!_program) {
_program = loadDefaultShader();
_plane = loadPlane(_program);
}
if (testQmlTexture > 0) {
glBindTexture(GL_TEXTURE_2D, testQmlTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
_program->Bind();
_plane->Use();
_plane->Draw();
_context->swapBuffers(this); _context->swapBuffers(this);
glFinish();
fps.increment(); fps.increment();
if (fps.elapsed() >= 2.0f) { if (fps.elapsed() >= 10.0f) {
qDebug() << "FPS: " << fps.rate(); qDebug() << "FPS: " << fps.rate();
fps.reset(); fps.reset();
} }
@ -246,8 +567,6 @@ private:
_context->makeCurrent(this); _context->makeCurrent(this);
} }
void renderQml();
void resizeWindow(const QSize & size) { void resizeWindow(const QSize & size) {
_size = size; _size = size;
DependencyManager::get<OffscreenUi>()->resize(_size); DependencyManager::get<OffscreenUi>()->resize(_size);
@ -269,11 +588,13 @@ protected:
offscreenUi->load("Browser.qml"); offscreenUi->load("Browser.qml");
} }
break; break;
case Qt::Key_L:
case Qt::Key_J:
if (event->modifiers() & Qt::CTRL) { if (event->modifiers() & Qt::CTRL) {
InfoView::show(getResourcesDir() + "html/interface-welcome.html", true); loadScript(getExamplesDir() + "tests/qmlWebTest.js");
} }
break; break;
case Qt::Key_K: case Qt::Key_K:
if (event->modifiers() & Qt::CTRL) { if (event->modifiers() & Qt::CTRL) {
OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){ OffscreenUi::question("Message title", "Message contents", [](QMessageBox::Button b){
@ -281,22 +602,9 @@ protected:
}); });
} }
break; break;
case Qt::Key_J:
if (event->modifiers() & Qt::CTRL) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
rootMenu = offscreenUi->getRootItem()->findChild<QObject*>("rootMenu");
QMetaObject::invokeMethod(rootMenu, "popup");
}
break;
} }
QWindow::keyPressEvent(event); QWindow::keyPressEvent(event);
} }
QQmlContext* menuContext{ nullptr };
void keyReleaseEvent(QKeyEvent *event) override {
if (_altPressed && Qt::Key_Alt == event->key()) {
VrMenu::toggle();
}
}
void moveEvent(QMoveEvent* event) override { void moveEvent(QMoveEvent* event) override {
static qreal oldPixelRatio = 0.0; static qreal oldPixelRatio = 0.0;
@ -308,40 +616,26 @@ protected:
} }
}; };
void QTestWindow::renderQml() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (testQmlTexture > 0) {
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, testQmlTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
glBegin(GL_QUADS);
{
glTexCoord2f(0, 0);
glVertex2f(-1, -1);
glTexCoord2f(0, 1);
glVertex2f(-1, 1);
glTexCoord2f(1, 1);
glVertex2f(1, 1);
glTexCoord2f(1, 0);
glVertex2f(1, -1);
}
glEnd();
}
const char * LOG_FILTER_RULES = R"V0G0N( const char * LOG_FILTER_RULES = R"V0G0N(
hifi.offscreen.focus.debug=false hifi.offscreen.focus.debug=false
qt.quick.mouse.debug=false qt.quick.mouse.debug=false
)V0G0N"; )V0G0N";
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
QString logMessage = message;
#ifdef Q_OS_WIN
if (!logMessage.isEmpty()) {
OutputDebugStringA(logMessage.toLocal8Bit().constData());
OutputDebugStringA("\n");
}
#endif
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
QApplication app(argc, argv); QGuiApplication app(argc, argv);
qInstallMessageHandler(messageHandler);
QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QLoggingCategory::setFilterRules(LOG_FILTER_RULES);
QTestWindow window; QTestWindow window;
app.exec(); app.exec();