Merge branch 'master' into polylineOptimizations

This commit is contained in:
ericrius1 2015-12-21 12:49:00 -08:00
commit 1cdee11784
62 changed files with 1911 additions and 481 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

@ -0,0 +1,87 @@
//
// reticleTest.js
// examples/controllers
//
// Created by Brad Hefta-Gaub 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
//
function length(posA, posB) {
var dx = posA.x - posB.x;
var dy = posA.y - posB.y;
var length = Math.sqrt((dx*dx) + (dy*dy))
return length;
}
var PITCH_DEADZONE = 1.0;
var PITCH_MAX = 20.0;
var YAW_DEADZONE = 1.0;
var YAW_MAX = 20.0;
var PITCH_SCALING = 10.0;
var YAW_SCALING = 10.0;
var EXPECTED_CHANGE = 50;
var lastPos = Controller.getReticlePosition();
function moveReticle(dY, dX) {
var globalPos = Controller.getReticlePosition();
// some debugging to see if position is jumping around on us...
var distanceSinceLastMove = length(lastPos, globalPos);
if (distanceSinceLastMove > EXPECTED_CHANGE) {
print("distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
}
if (Math.abs(dX) > EXPECTED_CHANGE) {
print("UNEXPECTED dX:" + dX + "----------------------------");
dX = 0;
}
if (Math.abs(dY) > EXPECTED_CHANGE) {
print("UNEXPECTED dY:" + dY + "----------------------------");
dY = 0;
}
globalPos.x += dX;
globalPos.y += dY;
Controller.setReticlePosition(globalPos);
lastPos = globalPos;
}
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHand";
var mapping = Controller.newMapping(MAPPING_NAME);
var lastHandPitch = 0;
var lastHandYaw = 0;
mapping.from(Controller.Standard.LeftHand).peek().to(function(pose) {
var handEulers = Quat.safeEulerAngles(pose.rotation);
//Vec3.print("handEulers:", handEulers);
var handPitch = handEulers.y;
var handYaw = handEulers.x;
var changePitch = (handPitch - lastHandPitch) * PITCH_SCALING;
var changeYaw = (handYaw - lastHandYaw) * YAW_SCALING;
if (Math.abs(changePitch) > PITCH_MAX) {
print("Pitch: " + changePitch);
changePitch = 0;
}
if (Math.abs(changeYaw) > YAW_MAX) {
print("Yaw: " + changeYaw);
changeYaw = 0;
}
changePitch = Math.abs(changePitch) < PITCH_DEADZONE ? 0 : changePitch;
changeYaw = Math.abs(changeYaw) < YAW_DEADZONE ? 0 : changeYaw;
moveReticle(changePitch, changeYaw);
lastHandPitch = handPitch;
lastHandYaw = handYaw;
});
mapping.enable();
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,76 @@
//
// proceduralHandPoseExample.js
// examples/controllers
//
// Created by Brad Hefta-Gaub 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
//
var MAPPING_NAME = "com.highfidelity.examples.proceduralHandPose";
var mapping = Controller.newMapping(MAPPING_NAME);
var translation = { x: 0, y: 0.1, z: 0 };
var translationDx = 0.01;
var translationDy = 0.01;
var translationDz = -0.01;
var TRANSLATION_LIMIT = 0.5;
var pitch = 45;
var yaw = 0;
var roll = 45;
var pitchDelta = 1;
var yawDelta = -1;
var rollDelta = 1;
var ROTATION_MIN = -90;
var ROTATION_MAX = 90;
mapping.from(function() {
// adjust the hand translation in a periodic back and forth motion for each of the 3 axes
translation.x = translation.x + translationDx;
translation.y = translation.y + translationDy;
translation.z = translation.z + translationDz;
if ((translation.x > TRANSLATION_LIMIT) || (translation.x < (-1 * TRANSLATION_LIMIT))) {
translationDx = translationDx * -1;
}
if ((translation.y > TRANSLATION_LIMIT) || (translation.y < (-1 * TRANSLATION_LIMIT))) {
translationDy = translationDy * -1;
}
if ((translation.z > TRANSLATION_LIMIT) || (translation.z < (-1 * TRANSLATION_LIMIT))) {
translationDz = translationDz * -1;
}
// adjust the hand rotation in a periodic back and forth motion for each of pitch/yaw/roll
pitch = pitch + pitchDelta;
yaw = yaw + yawDelta;
roll = roll + rollDelta;
if ((pitch > ROTATION_MAX) || (pitch < ROTATION_MIN)) {
pitchDelta = pitchDelta * -1;
}
if ((yaw > ROTATION_MAX) || (yaw < ROTATION_MIN)) {
yawDelta = yawDelta * -1;
}
if ((roll > ROTATION_MAX) || (roll < ROTATION_MIN)) {
rollDelta = rollDelta * -1;
}
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll);
var pose = {
translation: translation,
rotation: rotation,
velocity: { x: 0, y: 0, z: 0 },
angularVelocity: { x: 0, y: 0, z: 0 }
};
return pose;
}).debug(true).to(Controller.Standard.LeftHand);
Controller.enableMapping(MAPPING_NAME);
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,121 @@
//
// reticleHandAngularVelocityTest.js
// examples/controllers
//
// Created by Brad Hefta-Gaub 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
//
// If you set this to true, you will get the raw instantaneous angular velocity.
// note: there is a LOT of noise in the hydra rotation, you will probably be very
// frustrated with the level of jitter.
var USE_INSTANTANEOUS_ANGULAR_VELOCITY = false;
var whichHand = Controller.Standard.RightHand;
var whichTrigger = Controller.Standard.RT;
function msecTimestampNow() {
var d = new Date();
return d.getTime();
}
function length(posA, posB) {
var dx = posA.x - posB.x;
var dy = posA.y - posB.y;
var length = Math.sqrt((dx*dx) + (dy*dy))
return length;
}
var EXPECTED_CHANGE = 50;
var lastPos = Controller.getReticlePosition();
function moveReticle(dX, dY) {
var globalPos = Controller.getReticlePosition();
// some debugging to see if position is jumping around on us...
var distanceSinceLastMove = length(lastPos, globalPos);
if (distanceSinceLastMove > EXPECTED_CHANGE) {
print("------------------ distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
}
if (Math.abs(dX) > EXPECTED_CHANGE) {
print("surpressing unexpectedly large change dX:" + dX + "----------------------------");
dX = 0;
}
if (Math.abs(dY) > EXPECTED_CHANGE) {
print("surpressing unexpectedly large change dY:" + dY + "----------------------------");
dY = 0;
}
globalPos.x += dX;
globalPos.y += dY;
Controller.setReticlePosition(globalPos);
lastPos = globalPos;
}
var firstTime = true;
var lastTime = msecTimestampNow();
var previousRotation;
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHand";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from(whichTrigger).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
mapping.from(whichHand).peek().to(function(pose) {
var MSECS_PER_SECOND = 1000;
var now = msecTimestampNow();
var deltaMsecs = (now - lastTime);
var deltaTime = deltaMsecs / MSECS_PER_SECOND;
if (firstTime) {
previousRotation = pose.rotation;
lastTime = msecTimestampNow();
firstTime = false;
}
// pose.angularVelocity - is the angularVelocity in a "physics" sense, that
// means the direction of the vector is the axis of symetry of rotation
// and the scale of the vector is the speed in radians/second of rotation
// around that axis.
//
// we want to deconstruct that in the portion of the rotation on the Y axis
// and make that portion move our reticle in the horizontal/X direction
// and the portion of the rotation on the X axis and make that portion
// move our reticle in the veritcle/Y direction
var xPart = -pose.angularVelocity.y;
var yPart = -pose.angularVelocity.x;
// pose.angularVelocity is "smoothed", we can calculate our own instantaneous
// angular velocity as such:
if (USE_INSTANTANEOUS_ANGULAR_VELOCITY) {
var previousConjugate = Quat.conjugate(previousRotation);
var deltaRotation = Quat.multiply(pose.rotation, previousConjugate);
var normalizedDeltaRotation = Quat.normalize(deltaRotation);
var axis = Quat.axis(normalizedDeltaRotation);
var speed = Quat.angle(normalizedDeltaRotation) / deltaTime;
var instantaneousAngularVelocity = Vec3.multiply(speed, axis);
xPart = -instantaneousAngularVelocity.y;
yPart = -instantaneousAngularVelocity.x;
previousRotation = pose.rotation;
}
var MOVE_SCALE = 1;
lastTime = now;
var dX = (xPart * MOVE_SCALE) / deltaTime;
var dY = (yPart * MOVE_SCALE) / deltaTime;
moveReticle(dX, dY);
});
mapping.enable();
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,103 @@
//
// reticleHandRotationTest.js
// examples/controllers
//
// Created by Brad Hefta-Gaub 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
//
Math.clamp=function(a,b,c) {
return Math.max(b,Math.min(c,a));
}
var whichHand = Controller.Standard.RightHand;
var whichTrigger = Controller.Standard.RT;
function length(posA, posB) {
var dx = posA.x - posB.x;
var dy = posA.y - posB.y;
var length = Math.sqrt((dx*dx) + (dy*dy))
return length;
}
var EXPECTED_CHANGE = 50;
var lastPos = Controller.getReticlePosition();
function moveReticleAbsolute(x, y) {
var globalPos = Controller.getReticlePosition();
var dX = x - globalPos.x;
var dY = y - globalPos.y;
// some debugging to see if position is jumping around on us...
var distanceSinceLastMove = length(lastPos, globalPos);
if (distanceSinceLastMove > EXPECTED_CHANGE) {
print("------------------ distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------");
}
if (Math.abs(dX) > EXPECTED_CHANGE) {
print("surpressing unexpectedly large change dX:" + dX + "----------------------------");
}
if (Math.abs(dY) > EXPECTED_CHANGE) {
print("surpressing unexpectedly large change dY:" + dY + "----------------------------");
}
globalPos.x = x;
globalPos.y = y;
Controller.setReticlePosition(globalPos);
lastPos = globalPos;
}
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
var mapping = Controller.newMapping(MAPPING_NAME);
mapping.from(whichTrigger).peek().constrainToInteger().to(Controller.Actions.ReticleClick);
mapping.from(whichHand).peek().to(function(pose) {
// NOTE: hack for now
var screenSizeX = 1920;
var screenSizeY = 1080;
var rotated = Vec3.multiplyQbyV(pose.rotation, Vec3.UNIT_NEG_Y); //
var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
var absoluteYaw = rotated.z; // from -1 left to 1 right
//print("absolutePitch:" + absolutePitch);
//print("absoluteYaw:" + absoluteYaw);
//Vec3.print("rotated:", rotated);
var ROTATION_BOUND = 0.6;
var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND);
var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND);
//var clampYaw = absoluteYaw;
//print("clampYaw:" + clampYaw);
//print("clampPitch:" + clampPitch);
// if using entire span...
//var xRatio = (absoluteYaw + 1) / 2;
//var yRatio = (absolutePitch + 1) / 2;
// if using only from -0.5 to 0.5
var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND);
var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND);
//print("xRatio:" + xRatio);
//print("yRatio:" + yRatio);
//print("ratio x:" + xRatio + " y:" + yRatio);
var x = screenSizeX * xRatio;
var y = screenSizeY * yRatio;
//print("position x:" + x + " y:" + y);
if (!(xRatio == 0.5 && yRatio == 0)) {
moveReticleAbsolute(x, y);
}
});
mapping.enable();
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -33,7 +33,6 @@ var mappingJSON = {
mapping = Controller.parseMapping(JSON.stringify(mappingJSON)); mapping = Controller.parseMapping(JSON.stringify(mappingJSON));
mapping.enable(); mapping.enable();
Script.scriptEnding.connect(function(){ Script.scriptEnding.connect(function(){
mapping.disable(); mapping.disable();
}); });

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)
@ -209,5 +219,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();
@ -686,13 +703,37 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
VrMenu::toggle(); // show context menu even on non-stereo displays VrMenu::toggle(); // show context menu even on non-stereo displays
} else if (action == controller::toInt(controller::Action::RETICLE_X)) { } else if (action == controller::toInt(controller::Action::RETICLE_X)) {
auto globalPos = QCursor::pos(); auto oldPos = QCursor::pos();
globalPos.setX(globalPos.x() + state); auto newPos = oldPos;
QCursor::setPos(globalPos); newPos.setX(oldPos.x() + state);
QCursor::setPos(newPos);
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
// remove it after we're done
const float REASONABLE_CHANGE = 50.0f;
glm::vec2 oldPosG = { oldPos.x(), oldPos.y() };
glm::vec2 newPosG = { newPos.x(), newPos.y() };
auto distance = glm::distance(oldPosG, newPosG);
if (distance > REASONABLE_CHANGE) {
qDebug() << "Action::RETICLE_X... UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPosG << " newPos:" << newPosG;
}
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) { } else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
auto globalPos = QCursor::pos(); auto oldPos = QCursor::pos();
globalPos.setY(globalPos.y() + state); auto newPos = oldPos;
QCursor::setPos(globalPos); newPos.setY(oldPos.y() + state);
QCursor::setPos(newPos);
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
// remove it after we're done
const float REASONABLE_CHANGE = 50.0f;
glm::vec2 oldPosG = { oldPos.x(), oldPos.y() };
glm::vec2 newPosG = { newPos.x(), newPos.y() };
auto distance = glm::distance(oldPosG, newPosG);
if (distance > REASONABLE_CHANGE) {
qDebug() << "Action::RETICLE_Y... UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPosG << " newPos:" << newPosG;
}
} }
} }
}); });
@ -2988,6 +3029,9 @@ void Application::update(float deltaTime) {
_physicsEngine->changeObjects(motionStates); _physicsEngine->changeObjects(motionStates);
myAvatar->prepareForPhysicsSimulation(); myAvatar->prepareForPhysicsSimulation();
_physicsEngine->forEachAction([&](EntityActionPointer action) {
action->prepareForPhysicsSimulation();
});
getEntities()->getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation(); _physicsEngine->stepSimulation();
@ -4139,6 +4183,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
LocationScriptingInterface::locationSetter); LocationScriptingInterface::locationSetter);
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance());

View file

@ -157,7 +157,7 @@ void Avatar::animateScaleChanges(float deltaTime) {
// snap to the end when we get close enough // snap to the end when we get close enough
const float MIN_RELATIVE_SCALE_ERROR = 0.03f; const float MIN_RELATIVE_SCALE_ERROR = 0.03f;
if (fabsf(_targetScale - currentScale) / _targetScale < 0.03f) { if (fabsf(_targetScale - currentScale) / _targetScale < MIN_RELATIVE_SCALE_ERROR) {
animatedScale = _targetScale; animatedScale = _targetScale;
} }

View file

@ -14,6 +14,7 @@
#include <QVariantGLM.h> #include <QVariantGLM.h>
#include "avatar/AvatarManager.h" #include "avatar/AvatarManager.h"
#include "CharacterController.h"
const uint16_t AvatarActionHold::holdVersion = 1; const uint16_t AvatarActionHold::holdVersion = 1;
@ -32,6 +33,64 @@ AvatarActionHold::~AvatarActionHold() {
#endif #endif
} }
bool AvatarActionHold::getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
MyCharacterController* controller = myAvatar ? myAvatar->getCharacterController() : nullptr;
if (!controller) {
qDebug() << "AvatarActionHold::getAvatarRigidBodyLocation failed to get character controller";
return false;
}
controller->getRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
return true;
}
void AvatarActionHold::prepareForPhysicsSimulation() {
auto avatarManager = DependencyManager::get<AvatarManager>();
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
if (!holdingAvatar || !holdingAvatar->isMyAvatar()) {
return;
}
withWriteLock([&]{
if (_ignoreIK) {
return;
}
glm::vec3 palmPosition;
glm::quat palmRotation;
if (_hand == "right") {
palmPosition = holdingAvatar->getRightPalmPosition();
palmRotation = holdingAvatar->getRightPalmRotation();
} else {
palmPosition = holdingAvatar->getLeftPalmPosition();
palmRotation = holdingAvatar->getLeftPalmRotation();
}
glm::vec3 avatarRigidBodyPosition;
glm::quat avatarRigidBodyRotation;
getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
// determine the difference in translation and rotation between the avatar's
// rigid body and the palm position. The avatar's rigid body will be moved by bullet
// between this call and the call to getTarget, below. A call to get*PalmPosition in
// getTarget would get the palm position of the previous location of the avatar (because
// bullet has moved the av's rigid body but the rigid body's location has not yet been
// copied out into the Avatar class.
//glm::quat avatarRotationInverse = glm::inverse(avatarRigidBodyRotation);
// the offset should be in the frame of the avatar, but something about the order
// things are updated makes this wrong:
// _palmOffsetFromRigidBody = avatarRotationInverse * (palmPosition - avatarRigidBodyPosition);
// I'll leave it here as a comment in case avatar handling changes.
_palmOffsetFromRigidBody = palmPosition - avatarRigidBodyPosition;
// rotation should also be needed, but again, the order of updates makes this unneeded. leaving
// code here for future reference.
// _palmRotationFromRigidBody = avatarRotationInverse * palmRotation;
});
}
std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) { std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) {
auto avatarManager = DependencyManager::get<AvatarManager>(); auto avatarManager = DependencyManager::get<AvatarManager>();
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID)); auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
@ -40,11 +99,11 @@ std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::ve
return holdingAvatar; return holdingAvatar;
} }
withTryReadLock([&]{ withReadLock([&]{
bool isRightHand = (_hand == "right"); bool isRightHand = (_hand == "right");
glm::vec3 palmPosition { Vectors::ZERO }; glm::vec3 palmPosition { Vectors::ZERO };
glm::quat palmRotation { Quaternions::IDENTITY }; glm::quat palmRotation { Quaternions::IDENTITY };
if (_ignoreIK && holdingAvatar->isMyAvatar()) { if (_ignoreIK && holdingAvatar->isMyAvatar()) {
// We cannot ignore other avatars IK and this is not the point of this option // We cannot ignore other avatars IK and this is not the point of this option
// This is meant to make the grabbing behavior more reactive. // This is meant to make the grabbing behavior more reactive.
@ -55,6 +114,31 @@ std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::ve
palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getPosition(); palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getPosition();
palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getRotation(); palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getRotation();
} }
} else if (holdingAvatar->isMyAvatar()) {
glm::vec3 avatarRigidBodyPosition;
glm::quat avatarRigidBodyRotation;
getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);
// the offset and rotation between the avatar's rigid body and the palm were determined earlier
// in prepareForPhysicsSimulation. At this point, the avatar's rigid body has been moved by bullet
// and the data in the Avatar class is stale. This means that the result of get*PalmPosition will
// be stale. Instead, determine the current palm position with the current avatar's rigid body
// location and the saved offsets.
// this line is more correct but breaks for the current way avatar data is updated.
// palmPosition = avatarRigidBodyPosition + avatarRigidBodyRotation * _palmOffsetFromRigidBody;
// instead, use this for now:
palmPosition = avatarRigidBodyPosition + _palmOffsetFromRigidBody;
// the item jitters the least by getting the rotation based on the opinion of Avatar.h rather
// than that of the rigid body. leaving this next line here for future reference:
// palmRotation = avatarRigidBodyRotation * _palmRotationFromRigidBody;
if (isRightHand) {
palmRotation = holdingAvatar->getRightPalmRotation();
} else {
palmRotation = holdingAvatar->getLeftPalmRotation();
}
} else { } else {
if (isRightHand) { if (isRightHand) {
palmPosition = holdingAvatar->getRightPalmPosition(); palmPosition = holdingAvatar->getRightPalmPosition();
@ -103,21 +187,19 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
if (valid && holdCount > 0) { if (valid && holdCount > 0) {
position /= holdCount; position /= holdCount;
bool gotLock = withTryWriteLock([&]{ withWriteLock([&]{
_positionalTarget = position; _positionalTarget = position;
_rotationalTarget = rotation; _rotationalTarget = rotation;
_positionalTargetSet = true; _positionalTargetSet = true;
_rotationalTargetSet = true; _rotationalTargetSet = true;
_active = true; _active = true;
}); });
if (gotLock) { if (_kinematic) {
if (_kinematic) { doKinematicUpdate(deltaTimeStep);
doKinematicUpdate(deltaTimeStep); } else {
} else { activateBody();
activateBody(); forceBodyNonStatic();
forceBodyNonStatic(); ObjectActionSpring::updateActionWorker(deltaTimeStep);
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
} }
} }
} }

View file

@ -25,18 +25,21 @@ public:
AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity); AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~AvatarActionHold(); virtual ~AvatarActionHold();
virtual bool updateArguments(QVariantMap arguments); virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments(); virtual QVariantMap getArguments() override;
virtual void updateActionWorker(float deltaTimeStep); virtual void updateActionWorker(float deltaTimeStep) override;
QByteArray serialize() const; QByteArray serialize() const;
virtual void deserialize(QByteArray serializedArguments); virtual void deserialize(QByteArray serializedArguments) override;
virtual bool shouldSuppressLocationEdits() { return _active && !_ownerEntity.expired(); } virtual bool shouldSuppressLocationEdits() override { return _active && !_ownerEntity.expired(); }
bool getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
std::shared_ptr<Avatar> getTarget(glm::quat& rotation, glm::vec3& position); std::shared_ptr<Avatar> getTarget(glm::quat& rotation, glm::vec3& position);
virtual void prepareForPhysicsSimulation() override;
private: private:
void doKinematicUpdate(float deltaTimeStep); void doKinematicUpdate(float deltaTimeStep);
@ -56,6 +59,10 @@ private:
float _previousDeltaTimeStep = 0.0f; float _previousDeltaTimeStep = 0.0f;
glm::vec3 _previousPositionalDelta; glm::vec3 _previousPositionalDelta;
glm::vec3 _palmOffsetFromRigidBody;
// leaving this here for future refernece.
// glm::quat _palmRotationFromRigidBody;
}; };
#endif // hifi_AvatarActionHold_h #endif // hifi_AvatarActionHold_h

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

@ -34,7 +34,6 @@
static const quint64 MSECS_TO_USECS = 1000ULL; static const quint64 MSECS_TO_USECS = 1000ULL;
static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
static const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f };
static const float reticleSize = TWO_PI / 100.0f; static const float reticleSize = TWO_PI / 100.0f;
static const float CURSOR_PIXEL_SIZE = 32.0f; static const float CURSOR_PIXEL_SIZE = 32.0f;

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,14 +35,29 @@ 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;
} }
void Pose::fromScriptValue(const QScriptValue& object, Pose& pose) { void Pose::fromScriptValue(const QScriptValue& object, Pose& pose) {
// nothing for now... auto translation = object.property("translation");
auto rotation = object.property("rotation");
auto velocity = object.property("velocity");
auto angularVelocity = object.property("angularVelocity");
if (translation.isValid() &&
rotation.isValid() &&
velocity.isValid() &&
angularVelocity.isValid()) {
vec3FromScriptValue(translation, pose.translation);
quatFromScriptValue(rotation, pose.rotation);
vec3FromScriptValue(velocity, pose.velocity);
vec3FromScriptValue(angularVelocity, pose.angularVelocity);
pose.valid = true;
} else {
pose.valid = false;
}
} }
} }

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

@ -21,6 +21,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <QCursor>
#include <QThread> #include <QThread>
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QVariant> #include <QtCore/QVariant>
@ -29,6 +30,7 @@
#include <QtScript/QScriptValue> #include <QtScript/QScriptValue>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <StreamUtils.h>
#include "UserInputMapper.h" #include "UserInputMapper.h"
#include "StandardControls.h" #include "StandardControls.h"
@ -87,6 +89,21 @@ namespace controller {
Q_INVOKABLE QObject* parseMapping(const QString& json); Q_INVOKABLE QObject* parseMapping(const QString& json);
Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl); Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl);
Q_INVOKABLE glm::vec2 getReticlePosition() {
return toGlm(QCursor::pos());
}
Q_INVOKABLE void setReticlePosition(glm::vec2 position) {
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
// remove it after we're done
const float REASONABLE_CHANGE = 50.0f;
glm::vec2 oldPos = toGlm(QCursor::pos());
auto distance = glm::distance(oldPos, position);
if (distance > REASONABLE_CHANGE) {
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPos << " newPos:" << position;
}
QCursor::setPos(position.x, position.y);
}
//Q_INVOKABLE bool isPrimaryButtonPressed() const; //Q_INVOKABLE bool isPrimaryButtonPressed() const;
//Q_INVOKABLE glm::vec2 getPrimaryJoystickPosition() const; //Q_INVOKABLE glm::vec2 getPrimaryJoystickPosition() const;

View file

@ -10,6 +10,8 @@
#include <QtCore/QThread> #include <QtCore/QThread>
#include <StreamUtils.h>
using namespace controller; using namespace controller;
float ScriptEndpoint::peek() const { float ScriptEndpoint::peek() const {
@ -23,7 +25,16 @@ void ScriptEndpoint::updateValue() {
return; return;
} }
_lastValueRead = (float)_callable.call().toNumber(); QScriptValue result = _callable.call();
// If the callable ever returns a non-number, we assume it's a pose
// and start reporting ourselves as a pose.
if (result.isNumber()) {
_lastValueRead = (float)_callable.call().toNumber();
} else {
Pose::fromScriptValue(result, _lastPoseRead);
_returnPose = true;
}
} }
void ScriptEndpoint::apply(float value, const Pointer& source) { void ScriptEndpoint::apply(float value, const Pointer& source) {
@ -44,3 +55,36 @@ void ScriptEndpoint::internalApply(float value, int sourceID) {
_callable.call(QScriptValue(), _callable.call(QScriptValue(),
QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) })); QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) }));
} }
Pose ScriptEndpoint::peekPose() const {
const_cast<ScriptEndpoint*>(this)->updatePose();
return _lastPoseRead;
}
void ScriptEndpoint::updatePose() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updatePose", Qt::QueuedConnection);
return;
}
QScriptValue result = _callable.call();
Pose::fromScriptValue(result, _lastPoseRead);
}
void ScriptEndpoint::apply(const Pose& newPose, const Pointer& source) {
if (newPose == _lastPoseWritten) {
return;
}
internalApply(newPose, source->getInput().getID());
}
void ScriptEndpoint::internalApply(const Pose& newPose, int sourceID) {
_lastPoseWritten = newPose;
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection,
Q_ARG(const Pose&, newPose),
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValueList({ Pose::toScriptValue(_callable.engine(), newPose), QScriptValue(sourceID) }));
}

View file

@ -27,13 +27,26 @@ public:
virtual float peek() const override; virtual float peek() const override;
virtual void apply(float newValue, const Pointer& source) override; virtual void apply(float newValue, const Pointer& source) override;
virtual Pose peekPose() const override;
virtual void apply(const Pose& newValue, const Pointer& source) override;
virtual bool isPose() const override { return _returnPose; }
protected: protected:
Q_INVOKABLE void updateValue(); Q_INVOKABLE void updateValue();
Q_INVOKABLE virtual void internalApply(float newValue, int sourceID); Q_INVOKABLE virtual void internalApply(float newValue, int sourceID);
Q_INVOKABLE void updatePose();
Q_INVOKABLE virtual void internalApply(const Pose& newValue, int sourceID);
private: private:
QScriptValue _callable; QScriptValue _callable;
float _lastValueRead { 0.0f }; float _lastValueRead { 0.0f };
float _lastValueWritten { 0.0f }; float _lastValueWritten { 0.0f };
bool _returnPose { false };
Pose _lastPoseRead;
Pose _lastPoseWritten;
}; };
} }

View file

@ -385,7 +385,7 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
_needsInitialSimulation = true; _needsInitialSimulation = true;
} }
} }
return result; return result;
} }
@ -398,14 +398,14 @@ void RenderableModelEntityItem::update(const quint64& now) {
EntityItemProperties properties; EntityItemProperties properties;
auto extents = _model->getMeshExtents(); auto extents = _model->getMeshExtents();
properties.setDimensions(extents.maximum - extents.minimum); properties.setDimensions(extents.maximum - extents.minimum);
qCDebug(entitiesrenderer) << "Autoresizing:" << (!getName().isEmpty() ? getName() : getModelURL()); qCDebug(entitiesrenderer) << "Autoresizing:" << (!getName().isEmpty() ? getName() : getModelURL());
QMetaObject::invokeMethod(DependencyManager::get<EntityScriptingInterface>().data(), "editEntity", QMetaObject::invokeMethod(DependencyManager::get<EntityScriptingInterface>().data(), "editEntity",
Qt::QueuedConnection, Qt::QueuedConnection,
Q_ARG(QUuid, getEntityItemID()), Q_ARG(QUuid, getEntityItemID()),
Q_ARG(EntityItemProperties, properties)); Q_ARG(EntityItemProperties, properties));
} }
ModelEntityItem::update(now); ModelEntityItem::update(now);
} }
@ -427,7 +427,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
// << precisionPicking; // << precisionPicking;
QString extraInfo; QString extraInfo;
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance,
face, surfaceNormal, extraInfo, precisionPicking); face, surfaceNormal, extraInfo, precisionPicking);
} }
@ -447,24 +447,22 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
ShapeType type = getShapeType(); ShapeType type = getShapeType();
if (type == SHAPE_TYPE_COMPOUND) { if (type == SHAPE_TYPE_COMPOUND) {
if (!_model) { if (!_model || _model->getCollisionURL().isEmpty()) {
EntityTreePointer tree = getTree(); EntityTreePointer tree = getTree();
if (tree) { if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
} }
return false; // hmm... return false;
} }
assert(!_model->getCollisionURL().isEmpty());
if (_model->getURL().isEmpty()) { if (_model->getURL().isEmpty()) {
// we need a render geometry with a scale to proceed, so give up. // we need a render geometry with a scale to proceed, so give up.
return false; return false;
} }
const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry(); const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry();
const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry(); const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry();
if ((collisionNetworkGeometry && collisionNetworkGeometry->isLoaded()) && if ((collisionNetworkGeometry && collisionNetworkGeometry->isLoaded()) &&
(renderNetworkGeometry && renderNetworkGeometry->isLoaded())) { (renderNetworkGeometry && renderNetworkGeometry->isLoaded())) {
// we have both URLs AND both geometries AND they are both fully loaded. // we have both URLs AND both geometries AND they are both fully loaded.
@ -625,3 +623,11 @@ glm::vec3 RenderableModelEntityItem::getAbsoluteJointTranslationInObjectFrame(in
} }
return glm::vec3(0.0f); return glm::vec3(0.0f);
} }
void RenderableModelEntityItem::locationChanged() {
EntityItem::locationChanged();
if (_model && _model->isActive()) {
_model->setRotation(getRotation());
_model->setTranslation(getPosition());
}
}

View file

@ -73,6 +73,7 @@ public:
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
virtual void loader() override; virtual void loader() override;
virtual void locationChanged() override;
private: private:
void remapTextures(); void remapTextures();

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

@ -58,6 +58,8 @@ public:
virtual bool shouldSuppressLocationEdits() { return false; } virtual bool shouldSuppressLocationEdits() { return false; }
virtual void prepareForPhysicsSimulation() { }
// these look in the arguments map for a named argument. if it's not found or isn't well formed, // these look in the arguments map for a named argument. if it's not found or isn't well formed,
// ok will be set to false (note that it's never set to true -- set it to true before calling these). // ok will be set to false (note that it's never set to true -- set it to true before calling these).
// if required is true, failure to extract an argument will cause a warning to be printed. // if required is true, failure to extract an argument will cause a warning to be printed.

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

@ -91,7 +91,7 @@ Assignment::Assignment(ReceivedMessage& message) :
#endif #endif
Assignment::Assignment(const Assignment& otherAssignment) { Assignment::Assignment(const Assignment& otherAssignment) : QObject() {
_uuid = otherAssignment._uuid; _uuid = otherAssignment._uuid;
_command = otherAssignment._command; _command = otherAssignment._command;
_type = otherAssignment._type; _type = otherAssignment._type;

View file

@ -15,6 +15,7 @@
#include "BulletUtil.h" #include "BulletUtil.h"
#include "PhysicsCollisionGroups.h" #include "PhysicsCollisionGroups.h"
#include "ObjectMotionState.h"
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f); const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
const float JUMP_SPEED = 3.5f; const float JUMP_SPEED = 3.5f;
@ -379,3 +380,15 @@ void CharacterController::preSimulation() {
void CharacterController::postSimulation() { void CharacterController::postSimulation() {
// postSimulation() exists for symmetry and just in case we need to do something here later // postSimulation() exists for symmetry and just in case we need to do something here later
} }
bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
if (!_rigidBody) {
return false;
}
const btTransform& worldTrans = _rigidBody->getCenterOfMassTransform();
avatarRigidBodyPosition = bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset();
avatarRigidBodyRotation = bulletToGLM(worldTrans.getRotation());
return true;
}

View file

@ -79,6 +79,8 @@ public:
void setEnabled(bool enabled); void setEnabled(bool enabled);
bool isEnabled() const { return _enabled && _dynamicsWorld; } bool isEnabled() const { return _enabled && _dynamicsWorld; }
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
protected: protected:
void updateUpAxis(const glm::quat& rotation); void updateUpAxis(const glm::quat& rotation);

View file

@ -29,12 +29,12 @@ public:
ObjectAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity); ObjectAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectAction(); virtual ~ObjectAction();
virtual void removeFromSimulation(EntitySimulation* simulation) const; virtual void removeFromSimulation(EntitySimulation* simulation) const override;
virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; } virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; }
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; } virtual void setOwnerEntity(const EntityItemPointer ownerEntity) override { _ownerEntity = ownerEntity; }
virtual bool updateArguments(QVariantMap arguments); virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments(); virtual QVariantMap getArguments() override;
// this is called from updateAction and should be overridden by subclasses // this is called from updateAction and should be overridden by subclasses
virtual void updateActionWorker(float deltaTimeStep) = 0; virtual void updateActionWorker(float deltaTimeStep) = 0;
@ -43,25 +43,25 @@ public:
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep);
virtual void debugDraw(btIDebugDraw* debugDrawer); virtual void debugDraw(btIDebugDraw* debugDrawer);
virtual QByteArray serialize() const = 0; virtual QByteArray serialize() const override = 0;
virtual void deserialize(QByteArray serializedArguments) = 0; virtual void deserialize(QByteArray serializedArguments) override = 0;
virtual bool lifetimeIsOver(); virtual bool lifetimeIsOver() override;
virtual quint64 getExpires() { return _expires; } virtual quint64 getExpires() override { return _expires; }
protected: protected:
quint64 localTimeToServerTime(quint64 timeValue) const; quint64 localTimeToServerTime(quint64 timeValue) const;
quint64 serverTimeToLocalTime(quint64 timeValue) const; quint64 serverTimeToLocalTime(quint64 timeValue) const;
virtual btRigidBody* getRigidBody(); virtual btRigidBody* getRigidBody();
virtual glm::vec3 getPosition(); virtual glm::vec3 getPosition() override;
virtual void setPosition(glm::vec3 position); virtual void setPosition(glm::vec3 position) override;
virtual glm::quat getRotation(); virtual glm::quat getRotation() override;
virtual void setRotation(glm::quat rotation); virtual void setRotation(glm::quat rotation) override;
virtual glm::vec3 getLinearVelocity(); virtual glm::vec3 getLinearVelocity() override;
virtual void setLinearVelocity(glm::vec3 linearVelocity); virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
virtual glm::vec3 getAngularVelocity(); virtual glm::vec3 getAngularVelocity() override;
virtual void setAngularVelocity(glm::vec3 angularVelocity); virtual void setAngularVelocity(glm::vec3 angularVelocity) override;
virtual void activateBody(); virtual void activateBody();
virtual void forceBodyNonStatic(); virtual void forceBodyNonStatic();

View file

@ -22,13 +22,13 @@ public:
ObjectActionOffset(const QUuid& id, EntityItemPointer ownerEntity); ObjectActionOffset(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectActionOffset(); virtual ~ObjectActionOffset();
virtual bool updateArguments(QVariantMap arguments); virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments(); virtual QVariantMap getArguments() override;
virtual void updateActionWorker(float deltaTimeStep); virtual void updateActionWorker(float deltaTimeStep) override;
virtual QByteArray serialize() const; virtual QByteArray serialize() const override;
virtual void deserialize(QByteArray serializedArguments); virtual void deserialize(QByteArray serializedArguments) override;
private: private:
static const uint16_t offsetVersion; static const uint16_t offsetVersion;

View file

@ -19,13 +19,13 @@ public:
ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity); ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~ObjectActionSpring(); virtual ~ObjectActionSpring();
virtual bool updateArguments(QVariantMap arguments); virtual bool updateArguments(QVariantMap arguments) override;
virtual QVariantMap getArguments(); virtual QVariantMap getArguments() override;
virtual void updateActionWorker(float deltaTimeStep); virtual void updateActionWorker(float deltaTimeStep) override;
virtual QByteArray serialize() const; virtual QByteArray serialize() const override;
virtual void deserialize(QByteArray serializedArguments); virtual void deserialize(QByteArray serializedArguments) override;
protected: protected:
static const uint16_t springVersion; static const uint16_t springVersion;

View file

@ -44,7 +44,7 @@ void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) {
void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
assert(entity); assert(entity);
if (entity->shouldBePhysical()) { if (entity->shouldBePhysical()) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo()); EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (!motionState) { if (!motionState) {
_pendingAdds.insert(entity); _pendingAdds.insert(entity);
@ -117,6 +117,7 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
_pendingRemoves.clear(); _pendingRemoves.clear();
_pendingAdds.clear(); _pendingAdds.clear();
_pendingChanges.clear(); _pendingChanges.clear();
_outgoingChanges.clear();
} }
// end EntitySimulation overrides // end EntitySimulation overrides

View file

@ -497,3 +497,11 @@ void PhysicsEngine::removeAction(const QUuid actionID) {
_objectActions.remove(actionID); _objectActions.remove(actionID);
} }
} }
void PhysicsEngine::forEachAction(std::function<void(EntityActionPointer)> actor) {
QHashIterator<QUuid, EntityActionPointer> iter(_objectActions);
while (iter.hasNext()) {
iter.next();
actor(iter.value());
}
}

View file

@ -97,6 +97,7 @@ public:
EntityActionPointer getActionByID(const QUuid& actionID) const; EntityActionPointer getActionByID(const QUuid& actionID) const;
void addAction(EntityActionPointer action); void addAction(EntityActionPointer action);
void removeAction(const QUuid actionID); void removeAction(const QUuid actionID);
void forEachAction(std::function<void(EntityActionPointer)> actor);
private: private:
void removeContacts(ObjectMotionState* motionState); void removeContacts(ObjectMotionState* motionState);

View file

@ -22,6 +22,10 @@ quat Quat::normalize(const glm::quat& q) {
return glm::normalize(q); return glm::normalize(q);
} }
quat Quat::conjugate(const glm::quat& q) {
return glm::conjugate(q);
}
glm::quat Quat::rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { glm::quat Quat::rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
return ::rotationBetween(v1, v2); return ::rotationBetween(v1, v2);
} }

View file

@ -26,6 +26,7 @@ class Quat : public QObject {
public slots: public slots:
glm::quat multiply(const glm::quat& q1, const glm::quat& q2); glm::quat multiply(const glm::quat& q1, const glm::quat& q2);
glm::quat normalize(const glm::quat& q); glm::quat normalize(const glm::quat& q);
glm::quat conjugate(const glm::quat& q);
glm::quat lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up); glm::quat lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up);
glm::quat lookAtSimple(const glm::vec3& eye, const glm::vec3& center); glm::quat lookAtSimple(const glm::vec3& eye, const glm::vec3& center);
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);

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