diff --git a/README.md b/README.md index a2eb058ae6..48e0de03af 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,14 @@ like to get paid for your work, make sure you report the bug via a job on [Worklist.net](https://worklist.net). We're hiring! We're looking for skilled developers; -send your resume to hiring@highfidelity.io +send your resume to hiring@highfidelity.com ##### Chat with us Come chat with us in [our Gitter](http://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi! Documentation ========= -Documentation is available at [docs.highfidelity.io](http://docs.highfidelity.io), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project). +Documentation is available at [docs.highfidelity.com](http://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project). Build Instructions ========= diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 779307c19d..cc6c4930ff 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -369,14 +369,6 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) { reverbTime = _zoneReverbSettings[i].reverbTime; wetLevel = _zoneReverbSettings[i].wetLevel; - // Modulate wet level with distance to wall - float MIN_ATTENUATION_DISTANCE = 2.0f; - float MAX_ATTENUATION = -12; // dB - glm::vec3 distanceToWalls = (box.getDimensions() / 2.0f) - glm::abs(streamPosition - box.calcCenter()); - float distanceToClosestWall = glm::min(distanceToWalls.x, distanceToWalls.z); - if (distanceToClosestWall < MIN_ATTENUATION_DISTANCE) { - wetLevel += MAX_ATTENUATION * (1.0f - distanceToClosestWall / MIN_ATTENUATION_DISTANCE); - } break; } } diff --git a/cmake/externals/openvr/CMakeLists.txt b/cmake/externals/openvr/CMakeLists.txt index 3fe7df44d0..930a339d12 100644 --- a/cmake/externals/openvr/CMakeLists.txt +++ b/cmake/externals/openvr/CMakeLists.txt @@ -7,8 +7,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) ExternalProject_Add( ${EXTERNAL_NAME} - URL https://github.com/ValveSoftware/openvr/archive/v0.9.15.zip - URL_MD5 0ff8560b49b6da1150fcc47360e8ceca + URL https://github.com/ValveSoftware/openvr/archive/v0.9.19.zip + URL_MD5 843f9dde488584d8af1f3ecf2252b4e0 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 80ee32efa1..44a1796a8d 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -308,7 +308,7 @@ "name": "reverb", "type": "table", "label": "Reverb Settings", - "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet level of -10 db. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet level of -5 db.", + "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, "columns": [ { @@ -325,9 +325,9 @@ }, { "name": "wet_level", - "label": "Wet Level", + "label": "Wet/Dry Mix", "can_set": true, - "placeholder": "(in db)" + "placeholder": "(in percent)" } ] } diff --git a/examples/airship/airship.js b/examples/airship/airship.js new file mode 100644 index 0000000000..80f37603eb --- /dev/null +++ b/examples/airship/airship.js @@ -0,0 +1,301 @@ +// +// airship.js +// +// Animates a pirate airship that chases people and shoots cannonballs at them +// +// Created by Philip Rosedale on March 7, 2016 +// Copyright 2016 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 () { + var entityID, + minimumDelay = 100, // milliseconds + distanceScale = 2.0, // distance at which 100% chance of hopping + timeScale = 300.0, // crash + hopStrength = 0.4, // meters / second + spotlight = null, + wantDebug = false, + timeoutID = undefined, + bullet = null, + particles = null, + nearbyAvatars = 0, + nearbyAvatarsInRange = 0, + averageAvatarLocation = { x: 0, y: 0, z: 0 }, + properties, + lightTimer = 0, + lightTimeoutID = undefined, + audioInjector = null; + + var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + var cannonSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/cannonShot.wav"); + var explosionSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/explosion.wav"); + + var NO_SHOOT_COLOR = { red: 100, green: 100, blue: 100 }; + + var MAX_TARGET_RANGE = 200; + + function printDebug(message) { + if (wantDebug) { + print(message); + } + } + + var LIGHT_UPDATE_INTERVAL = 50; + var LIGHT_LIFETIME = 700; + + function randomVector(size) { + return { x: (Math.random() - 0.5) * size, + y: (Math.random() - 0.5) * size, + z: (Math.random() - 0.5) * size }; + } + + function makeLight(parent, position, colorDivisor) { + // Create a flickering light somewhere for a while + if (spotlight !== null) { + // light still exists, do nothing + printDebug("light still there"); + return; + } + printDebug("making light"); + + var colorIndex = 180 + Math.random() * 50; + + spotlight = Entities.addEntity({ + type: "Light", + name: "Test Light", + intensity: 50.0, + falloffRadius: 20.0, + dimensions: { + x: 150, + y: 150, + z: 150 + }, + position: position, + parentID: parent, + color: { + red: colorIndex, + green: colorIndex / colorDivisor, + blue: 0 + }, + lifetime: LIGHT_LIFETIME * 2 + }); + + lightTimer = 0; + lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL); + + }; + + function updateLight() { + lightTimer += LIGHT_UPDATE_INTERVAL; + if ((spotlight !== null) && (lightTimer > LIGHT_LIFETIME)) { + printDebug("deleting light!"); + Entities.deleteEntity(spotlight); + spotlight = null; + } else { + Entities.editEntity(spotlight, { + intensity: 5 + Math.random() * 50, + falloffRadius: 5 + Math.random() * 10 + }); + lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL); + } + } + + function move() { + + var HOVER_DISTANCE = 30.0; + var RUN_TOWARD_STRENGTH = 0.002; + var VELOCITY_FOLLOW_RATE = 0.01; + + var range = Vec3.distance(properties.position, averageAvatarLocation); + var impulse = { x: 0, y: 0, z: 0 }; + + // move in the XZ plane + var away = Vec3.subtract(properties.position, averageAvatarLocation); + away.y = 0.0; + + if (range > HOVER_DISTANCE) { + impulse = Vec3.multiply(-RUN_TOWARD_STRENGTH, Vec3.normalize(away)); + } + + var rotation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, properties.velocity); + Entities.editEntity(entityID, {velocity: Vec3.sum(properties.velocity, impulse), rotation: Quat.mix(properties.rotation, rotation, VELOCITY_FOLLOW_RATE)}); + } + + + function countAvatars() { + var workQueue = AvatarList.getAvatarIdentifiers(); + var averageLocation = {x: 0, y: 0, z: 0}; + var summed = 0; + var inRange = 0; + for (var i = 0; i < workQueue.length; i++) { + var avatar = AvatarList.getAvatar(workQueue[i]), avatarPosition = avatar && avatar.position; + if (avatarPosition) { + averageLocation = Vec3.sum(averageLocation, avatarPosition); + summed++; + if (Vec3.distance(avatarPosition, properties.position) < MAX_TARGET_RANGE) { + inRange++; + } + } + } + if (summed > 0) { + averageLocation = Vec3.multiply(1 / summed, averageLocation); + } + nearbyAvatars = summed; + nearbyAvatarsInRange = inRange; + averageAvatarLocation = averageLocation; + + //printDebug(" Avatars: " + summed + "in range: " + nearbyAvatarsInRange); + //Vec3.print(" location: ", averageAvatarLocation); + + return; + } + + function shoot() { + if (bullet !== null) { + return; + } + if (Vec3.distance(MyAvatar.position, properties.position) > MAX_TARGET_RANGE) { + return; + } + + var SPEED = 7.5; + var GRAVITY = -2.0; + var DIAMETER = 0.5; + var direction = Vec3.subtract(MyAvatar.position, properties.position); + var range = Vec3.distance(MyAvatar.position, properties.position); + var timeOfFlight = range / SPEED; + + var fall = 0.5 * -GRAVITY * (timeOfFlight * timeOfFlight); + + var velocity = Vec3.multiply(SPEED, Vec3.normalize(direction)); + velocity.y += 0.5 * fall / timeOfFlight * 2.0; + + var DISTANCE_TO_DECK = 3; + var bulletStart = properties.position; + bulletStart.y -= DISTANCE_TO_DECK; + + makeLight(entityID, Vec3.sum(properties.position, randomVector(10.0)), 2); + + bullet = Entities.addEntity({ + type: "Sphere", + name: "cannonball", + position: properties.position, + dimensions: { x: DIAMETER, y: DIAMETER, z: DIAMETER }, + color: { red: 10, green: 10, blue: 10 }, + velocity: velocity, + damping: 0.01, + dynamic: true, + ignoreForCollisions: true, + gravity: { x: 0, y: GRAVITY, z: 0 }, + lifetime: timeOfFlight * 2 + }); + + Audio.playSound(cannonSound, { + position: Vec3.sum(MyAvatar.position, velocity), + volume: 1.0 + }); + makeParticles(properties.position, bullet, timeOfFlight * 2); + Script.setTimeout(explode, timeOfFlight * 1000); + } + + function explode() { + var properties = Entities.getEntityProperties(bullet); + var direction = Vec3.normalize(Vec3.subtract(MyAvatar.position, properties.position)); + makeLight(null, properties.position, 10); + Audio.playSound(explosionSound, { position: Vec3.sum(MyAvatar.position, direction), volume: 1.0 }); + bullet = null; + } + + + function maybe() { // every user checks their distance and tries to claim if close enough. + var PROBABILITY_OF_SHOOT = 0.015; + properties = Entities.getEntityProperties(entityID); + countAvatars(); + + if (nearbyAvatars && (Math.random() < 1 / nearbyAvatars)) { + move(); + } + + if (nearbyAvatarsInRange && (Math.random() < PROBABILITY_OF_SHOOT / nearbyAvatarsInRange)) { + shoot(); + } + + var TIME_TO_NEXT_CHECK = 33; + timeoutID = Script.setTimeout(maybe, TIME_TO_NEXT_CHECK); + } + + this.preload = function (givenEntityID) { + printDebug("preload airship v1..."); + + entityID = givenEntityID; + properties = Entities.getEntityProperties(entityID); + timeoutID = Script.setTimeout(maybe, minimumDelay); + }; + + this.unload = function () { + printDebug("unload airship..."); + if (timeoutID !== undefined) { + Script.clearTimeout(timeoutID); + } + if (lightTimeoutID !== undefined) { + Script.clearTimeout(lightTimeoutID); + } + if (spotlight !== null) { + Entities.deleteEntity(spotlight); + } + }; + + function makeParticles(position, parent, lifespan) { + particles = Entities.addEntity({ + type: 'ParticleEffect', + position: position, + parentID: parent, + color: { + red: 70, + green: 70, + blue: 70 + }, + isEmitting: 1, + maxParticles: 1000, + lifetime: lifespan, + lifespan: lifespan / 3, + emitRate: 80, + emitSpeed: 0, + speedSpread: 1.0, + emitRadiusStart: 1, + polarStart: -Math.PI/8, + polarFinish: Math.PI/8, + azimuthStart: -Math.PI/4, + azimuthFinish: Math.PI/4, + emitAcceleration: { x: 0, y: 0, z: 0 }, + particleRadius: 0.25, + radiusSpread: 0.1, + radiusStart: 0.3, + radiusFinish: 0.15, + colorSpread: { + red: 100, + green: 100, + blue: 0 + }, + colorStart: { + red: 125, + green: 125, + blue: 125 + }, + colorFinish: { + red: 10, + green: 10, + blue: 10 + }, + alpha: 0.5, + alphaSpread: 0, + alphaStart: 1, + alphaFinish: 0, + emitterShouldTrail: true, + textures: 'https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png' + }); + } +}) diff --git a/examples/airship/makeAirship.js b/examples/airship/makeAirship.js new file mode 100644 index 0000000000..8a25ac6703 --- /dev/null +++ b/examples/airship/makeAirship.js @@ -0,0 +1,59 @@ +// makeAirship.js +// +// // Created by Philip Rosedale on March 7, 2016 +// Copyright 2016 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 SIZE = 0.2; +var TYPE = "Model"; // Right now this can be "Box" or "Model" or "Sphere" + +var MODEL_URL = "https://s3.amazonaws.com/hifi-public/philip/airship_compact.fbx" + +var MODEL_DIMENSION = { x: 19.257, y: 24.094, z: 40.3122 }; +var ENTITY_URL = "https://s3.amazonaws.com/hifi-public/scripts/airship/airship.js"; + + +var LIFETIME = 3600 * 48; + +var GRAVITY = { x: 0, y: 0, z: 0 }; +var DAMPING = 0.05; +var ANGULAR_DAMPING = 0.01; + +var collidable = true; +var gravity = true; + +var HOW_FAR_IN_FRONT_OF_ME = 30; +var HOW_FAR_ABOVE_ME = 15; + +var leaveBehind = true; + +var shipLocation = Vec3.sum(MyAvatar.position, Vec3.multiply(HOW_FAR_IN_FRONT_OF_ME, Quat.getFront(Camera.orientation))); +shipLocation.y += HOW_FAR_ABOVE_ME; + + +var airship = Entities.addEntity({ + type: TYPE, + modelURL: MODEL_URL, + name: "airship", + position: shipLocation, + dimensions: (TYPE == "Model") ? MODEL_DIMENSION : { x: SIZE, y: SIZE, z: SIZE }, + damping: DAMPING, + angularDamping: ANGULAR_DAMPING, + gravity: (gravity ? GRAVITY : { x: 0, y: 0, z: 0}), + dynamic: collidable, + lifetime: LIFETIME, + animation: {url: MODEL_URL, running: true, currentFrame: 0, loop: true}, + script: ENTITY_URL + }); + +function scriptEnding() { + if (!leaveBehind) { + Entities.deleteEntity(airship); + } +} + +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index e3eb19dc4a..225a1d7957 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -451,6 +451,37 @@ var elPreviewCameraButton = document.getElementById("preview-camera-button"); + var urlUpdaters = document.getElementsByClassName("update-url-version"); + var PARAM_REGEXP = /(?:\?)(\S+)/; // Check if this has any parameters. + var TIMESTAMP_REGEXP = /(&?HFTime=\d+)/; + + var refreshEvent = function(event){ + var urlElement = event.target.parentElement.getElementsByClassName("url")[0]; + var content = urlElement.value; + var date = new Date(); + var timeStamp = date.getTime(); + + if(content.length > 0){ + if(PARAM_REGEXP.test(content)){ + // Has params, so lets remove existing definition and append again. + content = content.replace(TIMESTAMP_REGEXP,"") + "&"; + }else{ + content += "?"; + } + content = content.replace("?&","?"); + urlElement.value = content + "HFTime=" + timeStamp; + } + + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", true, true ); + urlElement.dispatchEvent(evt); + }; + + for(var index = 0; index < urlUpdaters.length; index++){ + var urlUpdater = urlUpdaters[index]; + urlUpdater.addEventListener("click", refreshEvent); + } + if (window.EventBridge !== undefined) { var properties; EventBridge.scriptEventReceived.connect(function(data) { @@ -1185,6 +1216,7 @@
Ambient URL
+
@@ -1262,6 +1294,7 @@
Skybox URL
+
@@ -1273,6 +1306,7 @@
Source URL
+
@@ -1286,12 +1320,14 @@
Href - Hifi://address
+
@@ -1375,16 +1411,19 @@
X-axis Texture URL
+
Y-axis Texture URL
+
Z-axis Texture URL
+
@@ -1566,6 +1605,7 @@
Collision Sound URL
+
@@ -1583,6 +1623,7 @@
+
@@ -1595,6 +1636,7 @@
Model URL
+
@@ -1613,12 +1655,14 @@
Compound Shape URL
+
Animation URL
+
diff --git a/examples/html/eventBridgeLoader.js b/examples/html/eventBridgeLoader.js index ebfb6dc740..de6553ee1c 100644 --- a/examples/html/eventBridgeLoader.js +++ b/examples/html/eventBridgeLoader.js @@ -10,50 +10,11 @@ var EventBridge; -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); } +openEventBridge = function(callback) { + new QWebChannel(qt.webChannelTransport, function(channel) { + console.log("uid " + EventBridgeUid); + EventBridge = channel.objects[EventBridgeUid]; + callback(EventBridge); }); } -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); - var webWindow = channel.objects[document.URL.toLowerCase()]; - console.log("WebWindow is " + webWindow) - eventBridgeProxy = new EventBridgeProxy(webWindow); - EventBridge = eventBridgeProxy; - if (callback) { callback(eventBridgeProxy); } - }); - } -} - diff --git a/examples/html/qmlWebTest.html b/examples/html/qmlWebTest.html index e59535701d..553ce83417 100644 --- a/examples/html/qmlWebTest.html +++ b/examples/html/qmlWebTest.html @@ -4,21 +4,17 @@ - diff --git a/examples/html/style.css b/examples/html/style.css index 83982dab15..3ae1c1739b 100644 --- a/examples/html/style.css +++ b/examples/html/style.css @@ -134,8 +134,18 @@ textarea { resize: vertical; } +.update-url-version{ + width:17px; + height:17px; + float:right; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAQAAACRZI9xAAABAUlEQVQoz33RvyvEARjH8SdlUX6UbhFJfgwmE3aTxWEiitxqN/gH7IxCKTcymLiJFIOJsihiUKcrCYnuXoZv+F45n2d4lvfz6fPpCSFs6BO1JllvLmXk5F37dC0vJ5NGmlBxoFp7Rn6RDpRRtG5KtynrijhLoBBGQcllKkFWAXsyCbKEFxeGqmJmFZFLkE639vX+12hCSVft0nUR8RbLcRN/aSHGIqL2tVYPconL32qLtaiL88Rl0qtXc+pTDv1OPGMlidtgS8Wp2RR05FEFM98/GlRQ9mTHvB7jVt2rKGPAT9xh2z6qfnToHV1SjZpN23Tl051di1oco9W/pdttaBRfEhFXOZV7vEsAAAAASUVORK5CYII=); + padding:0 !important; + margin:0 2px 0 0 !important; +} + input.url { - width: 100%; + width:85%; + padding-right: 20px; } input.coord { diff --git a/examples/particle_explorer/particleExplorer.js b/examples/particle_explorer/particleExplorer.js index 307e361ff1..4fd0978a84 100644 --- a/examples/particle_explorer/particleExplorer.js +++ b/examples/particle_explorer/particleExplorer.js @@ -198,6 +198,16 @@ function createColorPicker(key) { settings[key] = colorArray; var controller = gui.addColor(settings, key); controller.onChange(function(value) { + // Handle hex colors + if(_.isString(value) && value[0] === '#') { + const BASE_HEX = 16; + var colorRegExResult = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(value); + value = [ + parseInt(colorRegExResult[1], BASE_HEX), + parseInt(colorRegExResult[2], BASE_HEX), + parseInt(colorRegExResult[3], BASE_HEX) + ]; + } var obj = {}; obj[key] = convertColorArrayToObject(value); writeVec3ToInterface(obj); diff --git a/examples/tests/qmlWebTest.js b/examples/tests/qmlWebTest.js index 5faa68668d..d29f2ba002 100644 --- a/examples/tests/qmlWebTest.js +++ b/examples/tests/qmlWebTest.js @@ -8,26 +8,14 @@ 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); + var message = [ Math.random(), Math.random() ]; + print("JS Side sending: " + message); + webWindow.emitScriptEvent(message); +}, 5 * 1000); -Script.setTimeout(function() { - print("Closing script"); +Script.scriptEnding.connect(function(){ webWindow.close(); - Script.stop(); -}, 15 * 1000) + webWindow.deleteLater(); +}); + diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index fd4e629568..0058770462 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -1,6 +1,7 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 +import QtWebChannel 1.0 import "windows" as Windows import "controls" as Controls @@ -15,11 +16,22 @@ Windows.Window { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url + property alias webChannel: webview.webChannel + // A unique identifier to let the HTML JS find the event bridge + // object (our C++ wrapper) + property string uid; + + // This is for JS/QML communication, which is unused in a WebWindow, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); Controls.WebView { id: webview url: "about:blank" anchors.fill: parent focus: true + onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";"); + Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";"); } } // dialog diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index 75aa50aa34..5313ca23e9 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -37,14 +37,33 @@ Windows.Window { Repeater { model: 4 Tab { + // Force loading of the content even if the tab is not visible + // (required for letting the C++ code access the webview) active: true - enabled: false; - // we need to store the original url here for future identification + enabled: false property string originalUrl: ""; - onEnabledChanged: toolWindow.updateVisiblity(); + Controls.WebView { id: webView; + // we need to store the original url here for future identification + // A unique identifier to let the HTML JS find the event bridge + // object (our C++ wrapper) + property string uid; anchors.fill: parent + enabled: false + + // This is for JS/QML communication, which is unused in a WebWindow, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + + onUrlChanged: webView.runJavaScript("EventBridgeUid = \"" + uid + "\";"); + onEnabledChanged: toolWindow.updateVisiblity(); + onLoadingChanged: { + if (loadRequest.status == WebEngineView.LoadSucceededStatus) { + webView.runJavaScript("EventBridgeUid = \"" + uid + "\";"); + } + } } } } @@ -113,20 +132,23 @@ Windows.Window { var tab = tabView.getTab(index); tab.title = ""; - tab.originalUrl = ""; tab.enabled = false; + tab.originalUrl = ""; + tab.item.url = "about:blank"; + tab.item.enabled = false; } function addWebTab(properties) { if (!properties.source) { - console.warn("Attempted to open Web Tool Pane without URL") + console.warn("Attempted to open Web Tool Pane without URL"); return; } var existingTabIndex = findIndexForUrl(properties.source); if (existingTabIndex >= 0) { - console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source) - return tabView.getTab(existingTabIndex); + console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source); + var tab = tabView.getTab(existingTabIndex); + return tab.item; } var freeTabIndex = findFreeTab(); @@ -135,25 +157,22 @@ Windows.Window { return; } - var newTab = tabView.getTab(freeTabIndex); - newTab.title = properties.title || "Unknown"; - newTab.originalUrl = properties.source; - newTab.item.url = properties.source; - newTab.active = true; - if (properties.width) { - tabView.width = Math.min(Math.max(tabView.width, properties.width), - toolWindow.maxSize.x); + tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x); } if (properties.height) { - tabView.height = Math.min(Math.max(tabView.height, properties.height), - toolWindow.maxSize.y); + tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y); } - console.log("Updating visibility based on child tab added"); - newTab.enabledChanged.connect(updateVisiblity) - updateVisiblity(); - return newTab + var tab = tabView.getTab(freeTabIndex); + tab.title = properties.title || "Unknown"; + tab.enabled = true; + tab.originalUrl = properties.source; + + var result = tab.item; + result.enabled = true; + result.url = properties.source; + return result; } } diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml old mode 100644 new mode 100755 index 888e8ccbc1..d25a8c5940 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -162,6 +162,30 @@ FocusScope { height: 480 width: root.width + 4 + style: ScrollViewStyle { + decrementControl: Item { + visible: false + } + incrementControl: Item { + visible: false + } + scrollBarBackground: Rectangle{ + implicitWidth: 14 + color: hifi.colors.baseGray + } + + handle: + Rectangle { + implicitWidth: 8 + anchors.left: parent.left + anchors.leftMargin: 3 + anchors.top: parent.top + anchors.bottom: parent.bottom + radius: 3 + color: hifi.colors.lightGrayText + } + } + ListView { id: listView height: textField.height * count * 1.4 diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml old mode 100644 new mode 100755 index 5dac1710f0..ec7821851f --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -35,7 +35,6 @@ SpinBox { style: SpinBoxStyle { id: spinStyle background: Rectangle { - id: backgrondRec color: isLightColorScheme ? (spinBox.focus ? hifi.colors.white : hifi.colors.lightGray) : (spinBox.focus ? hifi.colors.black : hifi.colors.baseGrayShadow) @@ -91,4 +90,27 @@ SpinBox { color: spinBox.colorLabelInside visible: spinBox.labelInside != "" } + + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + onWheel: { + if(spinBox.focus) + wheel.accepted = false + else + wheel.accepted = true + } + onPressed: { + mouse.accepted = false + } + onReleased: { + mouse.accepted = false + } + onClicked: { + mouse.accepted = false + } + onDoubleClicked: { + mouse.accepted = false + } + } } diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 18080cd448..1361e6e322 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -59,6 +59,7 @@ WebEngineView { request.openIn(newWindow.webView) } - - profile: desktop.browserProfile + // This breaks the webchannel used for passing messages. Fixed in Qt 5.6 + // See https://bugreports.qt.io/browse/QTBUG-49521 + //profile: desktop.browserProfile } diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml old mode 100644 new mode 100755 index f82ef8d86c..01be4ddbde --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -4,18 +4,14 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 import QtQuick.Controls.Styles 1.4 -//import "../../windows" - - import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows-uit" - import "attachments" Window { id: root - title: "Attachments Dialog" + title: "Attachments" objectName: "AttachmentsDialog" width: 600 height: 600 @@ -43,6 +39,7 @@ Window { listView.model.append({}); } } + Column { width: pane.contentWidth @@ -55,14 +52,22 @@ Window { Rectangle { id: attachmentsBackground anchors { left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; margins: 8 } - color: hifi.colors.lightGrayText + color: hifi.colors.baseGrayShadow radius: 4 - ScrollView{ + ScrollView { id: scrollView anchors.fill: parent anchors.margins: 4 + style: ScrollViewStyle { + + padding { + top: 0 + right: 0 + bottom: 0 + } + decrementControl: Item { visible: false } @@ -72,16 +77,29 @@ Window { scrollBarBackground: Rectangle{ implicitWidth: 14 color: hifi.colors.baseGray - } + radius: 4 + Rectangle { + // Make top left corner of scrollbar appear square + width: 8 + height: 4 + color: hifi.colors.baseGray + anchors.top: parent.top + anchors.horizontalCenter: parent.left + } + } handle: Rectangle { implicitWidth: 8 - anchors.left: parent.left - anchors.leftMargin: 3 - anchors.top: parent.top - anchors.bottom: parent.bottom - radius: 3 + anchors { + left: parent.left + leftMargin: 3 + top: parent.top + topMargin: 3 + bottom: parent.bottom + bottomMargin: 4 + } + radius: 4 color: hifi.colors.lightGrayText } } @@ -90,8 +108,9 @@ Window { id: listView model: ListModel {} delegate: Item { + id: attachmentDelegate implicitHeight: attachmentView.height + 8; - implicitWidth: attachmentView.width; + implicitWidth: attachmentView.width Attachment { id: attachmentView width: scrollView.width @@ -113,7 +132,7 @@ Window { anchors { left: parent.left; right: parent.right; bottom: buttonRow.top; margins: 8 } text: "New Attachment" color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark + colorScheme: hifi.colorSchemes.dark onClicked: { var template = { modelUrl: "", @@ -133,15 +152,15 @@ Window { id: buttonRow spacing: 8 anchors { right: parent.right; bottom: parent.bottom; margins: 8 } - HifiControls.Button { - action: cancelAction - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - } HifiControls.Button { action: okAction color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark + colorScheme: hifi.colorSchemes.dark + } + HifiControls.Button { + action: cancelAction + color: hifi.buttons.black + colorScheme: hifi.colorSchemes.dark } } diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml old mode 100644 new mode 100755 index c88e991ed3..1277c459ce --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -4,11 +4,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -//import "../../../windows" -//import "../../../controls" as VrControls import "." import ".." - import "../../../styles-uit" import "../../../controls-uit" as HifiControls import "../../../windows-uit" @@ -33,13 +30,13 @@ Item { Column { y: 8 id: column - anchors { left: parent.left; right: parent.right; margins: 8 } + anchors { left: parent.left; right: parent.right; margins: 20 } spacing: 8 Item { - height: modelChooserButton.height + urlLabel.height + height: modelChooserButton.height + urlLabel.height + 4 anchors { left: parent.left; right: parent.right;} - Text { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL:"; width: 80; anchors.top: parent.top;} + HifiControls.Label { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL"; anchors.top: parent.top;} HifiControls.TextField { id: modelUrl; height: jointChooser.height; @@ -60,12 +57,12 @@ Item { colorScheme: hifi.colorSchemes.dark anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter } Component { - id: modelBrowserBuiler; + id: modelBrowserBuilder; ModelBrowserDialog {} } onClicked: { - var browser = modelBrowserBuiler.createObject(desktop); + var browser = modelBrowserBuilder.createObject(desktop); browser.selected.connect(function(newModelUrl){ modelUrl.text = newModelUrl; }) @@ -74,12 +71,11 @@ Item { } Item { - height: jointChooser.height + jointLabel.height + height: jointChooser.height + jointLabel.height + 4 anchors { left: parent.left; right: parent.right; } - Text { + HifiControls.Label { id: jointLabel; - width: 80; - text: "Joint:"; + text: "Joint"; color: hifi.colors.lightGrayText; anchors.top: parent.top } @@ -99,9 +95,9 @@ Item { } Item { - height: translation.height + translationLabel.height + height: translation.height + translationLabel.height + 4 anchors { left: parent.left; right: parent.right; } - Text { id: translationLabel; width: 80; color: hifi.colors.lightGrayText; text: "Translation:"; anchors.top: parent.top; } + HifiControls.Label { id: translationLabel; color: hifi.colors.lightGrayText; text: "Translation"; anchors.top: parent.top; } Translation { id: translation; anchors { left: parent.left; right: parent.right; bottom: parent.bottom} @@ -116,9 +112,9 @@ Item { } Item { - height: rotation.height + rotationLabel.height + height: rotation.height + rotationLabel.height + 4 anchors { left: parent.left; right: parent.right; } - Text { id: rotationLabel; width: 80; color: hifi.colors.lightGrayText; text: "Rotation:"; anchors.top: parent.top; } + HifiControls.Label { id: rotationLabel; color: hifi.colors.lightGrayText; text: "Rotation"; anchors.top: parent.top; } Rotation { id: rotation; anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } @@ -133,45 +129,58 @@ Item { } Item { - height: scaleSpinner.height + scaleLabel.height + height: scaleItem.height anchors { left: parent.left; right: parent.right; } - Text { id: scaleLabel; width: 80; color: hifi.colors.lightGrayText; text: "Scale:"; anchors.top: parent.top; } - HifiControls.SpinBox { - id: scaleSpinner; - anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } - decimals: 1; - minimumValue: 0.1 - maximumValue: 10 - stepSize: 0.1; - value: attachment ? attachment.scale : 1.0 - colorScheme: hifi.colorSchemes.dark - onValueChanged: { - if (completed && attachment && attachment.scale !== value) { - attachment.scale = value; - updateAttachment(); + + Item { + id: scaleItem + height: scaleSpinner.height + scaleLabel.height + 4 + width: parent.width / 3 - 8 + anchors { right: parent.right; } + HifiControls.Label { id: scaleLabel; color: hifi.colors.lightGrayText; text: "Scale"; anchors.top: parent.top; } + HifiControls.SpinBox { + id: scaleSpinner; + anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } + decimals: 1; + minimumValue: 0.1 + maximumValue: 10 + stepSize: 0.1; + value: attachment ? attachment.scale : 1.0 + colorScheme: hifi.colorSchemes.dark + onValueChanged: { + if (completed && attachment && attachment.scale !== value) { + attachment.scale = value; + updateAttachment(); + } + } + } + } + + Item { + id: isSoftItem + height: scaleSpinner.height + anchors { + left: parent.left + bottom: parent.bottom + } + HifiControls.CheckBox { + id: soft + text: "Is soft" + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + } + checked: attachment ? attachment.soft : false + colorScheme: hifi.colorSchemes.dark + onCheckedChanged: { + if (completed && attachment && attachment.soft !== checked) { + attachment.soft = checked; + updateAttachment(); + } } } } } - - Item { - height: soft.height - anchors { left: parent.left; right: parent.right; } - Text { id: softLabel; width: 80; color: hifi.colors.lightGrayText; text: "Is soft"; anchors.left: soft.right; anchors.leftMargin: 8; } - HifiControls.CheckBox { - id: soft; - anchors { left: parent.left; bottom: parent.bottom;} - checked: attachment ? attachment.soft : false - colorScheme: hifi.colorSchemes.dark - onCheckedChanged: { - if (completed && attachment && attachment.soft !== checked) { - attachment.soft = checked; - updateAttachment(); - } - } - } - } - HifiControls.Button { color: hifi.buttons.black colorScheme: hifi.colorSchemes.dark diff --git a/interface/resources/qml/hifi/dialogs/attachments/Translation.qml b/interface/resources/qml/hifi/dialogs/attachments/Translation.qml index f3a90cdf94..39ac6da55a 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Translation.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Translation.qml @@ -1,7 +1,7 @@ import "." Vector3 { - decimals: 2; + decimals: 3; stepSize: 0.01; maximumValue: 10 minimumValue: -10 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 65a8f83871..b9effa444b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -195,6 +195,7 @@ static const QString FBX_EXTENSION = ".fbx"; static const QString OBJ_EXTENSION = ".obj"; static const QString AVA_JSON_EXTENSION = ".ava.json"; +static const int MSECS_PER_SEC = 1000; static const int MIRROR_VIEW_TOP_PADDING = 5; static const int MIRROR_VIEW_LEFT_PADDING = 10; static const int MIRROR_VIEW_WIDTH = 265; @@ -239,7 +240,7 @@ class DeadlockWatchdogThread : public QThread { public: static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1; static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; - static const unsigned long MAX_HEARTBEAT_AGE_USECS = 10 * USECS_PER_SECOND; + static const unsigned long MAX_HEARTBEAT_AGE_USECS = 15 * USECS_PER_SECOND; // Set the heartbeat on launch DeadlockWatchdogThread() { @@ -365,7 +366,7 @@ bool setupEssentials(int& argc, char** argv) { Setting::preInit(); - CrashHandler::checkForAndHandleCrash(); + bool previousSessionCrashed = CrashHandler::checkForAndHandleCrash(); CrashHandler::writeRunningMarkerFiler(); qAddPostRoutine(CrashHandler::deleteRunningMarkerFile); @@ -427,7 +428,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(true, qApp, qApp); DependencyManager::set(); - return true; + return previousSessionCrashed; } // FIXME move to header, or better yet, design some kind of UI manager @@ -448,10 +449,13 @@ PluginContainer* _pluginContainer; OffscreenGLCanvas* _chromiumShareContext { nullptr }; Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; + Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : QApplication(argc, argv), _window(new MainWindow(desktop())), - _dependencyManagerIsSetup(setupEssentials(argc, argv)), + _sessionRunTimer(startupTimer), + _previousSessionCrashed(setupEssentials(argc, argv)), _undoStackScriptingInterface(&_undoStack), _frameCount(0), _fps(60.0f), @@ -601,7 +605,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one - const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; + const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC; auto discoverabilityManager = DependencyManager::get(); connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); @@ -623,7 +627,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); - const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000; + const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * MSECS_PER_SEC; connect(&balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance); balanceUpdateTimer.start(BALANCE_UPDATE_INTERVAL_MSECS); @@ -638,7 +642,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : accountManager.setIsAgent(true); accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); - UserActivityLogger::getInstance().launch(applicationVersion()); + // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. + // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. + UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get()); // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); @@ -1409,7 +1415,7 @@ void Application::paintGL() { _lastFramesPerSecondUpdate = now; } - PROFILE_RANGE(__FUNCTION__); + PROFILE_RANGE_EX(__FUNCTION__, 0xff0000ff, (uint64_t)_frameCount); PerformanceTimer perfTimer("paintGL"); if (nullptr == _displayPlugin) { @@ -2548,11 +2554,12 @@ void Application::idle(uint64_t now) { return; } + PROFILE_RANGE(__FUNCTION__); + // We're going to execute idle processing, so restart the last idle timer _lastTimeUpdated.start(); { - PROFILE_RANGE(__FUNCTION__); static uint64_t lastIdleStart{ now }; uint64_t idleStartToStartDuration = now - lastIdleStart; if (idleStartToStartDuration != 0) { @@ -2804,6 +2811,7 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa void Application::loadSettings() { + sessionRunTime.set(0); // Just clean living. We're about to saveSettings, which will update value. DependencyManager::get()->loadSettings(); DependencyManager::get()->loadSettings(); @@ -2817,6 +2825,7 @@ void Application::loadSettings() { } void Application::saveSettings() { + sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SEC); DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); @@ -3138,6 +3147,9 @@ void Application::updateDialogs(float deltaTime) { } void Application::update(float deltaTime) { + + PROFILE_RANGE_EX(__FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1); + bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); @@ -3228,7 +3240,9 @@ void Application::update(float deltaTime) { controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND); controller::Pose rightHandPose = userInputMapper->getPoseState(controller::Action::RIGHT_HAND); auto myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()); - myAvatar->setHandControllerPosesInWorldFrame(leftHandPose.transform(myAvatarMatrix), rightHandPose.transform(myAvatarMatrix)); + auto worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix()); + auto avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix; + myAvatar->setHandControllerPosesInSensorFrame(leftHandPose.transform(avatarToSensorMatrix), rightHandPose.transform(avatarToSensorMatrix)); updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... updateDialogs(deltaTime); // update various stats dialogs if present @@ -3236,9 +3250,13 @@ void Application::update(float deltaTime) { QSharedPointer avatarManager = DependencyManager::get(); if (_physicsEnabled) { + PROFILE_RANGE_EX("Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); + PerformanceTimer perfTimer("physics"); { + PROFILE_RANGE_EX("UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); + PerformanceTimer perfTimer("updateStates)"); static VectorOfMotionStates motionStates; _entitySimulation.getObjectsToRemoveFromPhysics(motionStates); @@ -3271,12 +3289,14 @@ void Application::update(float deltaTime) { }); } { + PROFILE_RANGE_EX("StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("stepSimulation"); getEntities()->getTree()->withWriteLock([&] { _physicsEngine->stepSimulation(); }); } { + PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("havestChanges"); if (_physicsEngine->hasOutgoingChanges()) { getEntities()->getTree()->withWriteLock([&] { @@ -3311,14 +3331,24 @@ void Application::update(float deltaTime) { qApp->setAvatarSimrateSample(1.0f / deltaTime); - avatarManager->updateOtherAvatars(deltaTime); + { + PROFILE_RANGE_EX("OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount()); + avatarManager->updateOtherAvatars(deltaTime); + } qApp->updateMyAvatarLookAtPosition(); - avatarManager->updateMyAvatar(deltaTime); + // update sensorToWorldMatrix for camera and hand controllers + myAvatar->updateSensorToWorldMatrix(); + + { + PROFILE_RANGE_EX("MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount()); + avatarManager->updateMyAvatar(deltaTime); + } } { + PROFILE_RANGE_EX("Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); _overlays.update(deltaTime); } @@ -3338,6 +3368,7 @@ void Application::update(float deltaTime) { // Update my voxel servers with my current voxel query... { + PROFILE_RANGE_EX("QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("queryOctree"); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; @@ -3374,9 +3405,6 @@ void Application::update(float deltaTime) { QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); } } - - // update sensorToWorldMatrix for rendering camera. - myAvatar->updateSensorToWorldMatrix(); } @@ -4667,13 +4695,18 @@ qreal Application::getDevicePixelRatio() { } DisplayPlugin* Application::getActiveDisplayPlugin() { - std::unique_lock lock(_displayPluginLock); - if (nullptr == _displayPlugin && QThread::currentThread() == thread()) { - updateDisplayMode(); - Q_ASSERT(_displayPlugin); + DisplayPlugin* result = nullptr; + if (QThread::currentThread() == thread()) { + if (nullptr == _displayPlugin) { + updateDisplayMode(); + Q_ASSERT(_displayPlugin); + } + result = _displayPlugin.get(); + } else { + std::unique_lock lock(_displayPluginLock); + result = _displayPlugin.get(); } - - return _displayPlugin.get(); + return result; } const DisplayPlugin* Application::getActiveDisplayPlugin() const { @@ -4791,20 +4824,26 @@ void Application::updateDisplayMode() { return; } - if (_displayPlugin) { - _displayPlugin->deactivate(); - } - auto offscreenUi = DependencyManager::get(); - // FIXME probably excessive and useless context switching - _offscreenContext->makeCurrent(); - newDisplayPlugin->activate(); - _offscreenContext->makeCurrent(); - offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); - _offscreenContext->makeCurrent(); - getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); - _displayPlugin = newDisplayPlugin; + // Make the switch atomic from the perspective of other threads + { + std::unique_lock lock(_displayPluginLock); + + if (_displayPlugin) { + _displayPlugin->deactivate(); + } + + // FIXME probably excessive and useless context switching + _offscreenContext->makeCurrent(); + newDisplayPlugin->activate(); + _offscreenContext->makeCurrent(); + offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); + _offscreenContext->makeCurrent(); + getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); + _displayPlugin = newDisplayPlugin; + } + emit activeDisplayPluginChanged(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 3b906a7d17..d21e647bc7 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -377,12 +377,13 @@ private: void maybeToggleMenuVisible(QMouseEvent* event); MainWindow* _window; + QElapsedTimer& _sessionRunTimer; - bool _dependencyManagerIsSetup; + bool _previousSessionCrashed; OffscreenGLCanvas* _offscreenContext { nullptr }; DisplayPluginPointer _displayPlugin; - std::recursive_mutex _displayPluginLock; + std::mutex _displayPluginLock; InputPluginList _activeInputPlugins; bool _activatingDisplayPlugin { false }; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index 5a194973b4..50cca15c88 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -27,7 +27,7 @@ static const QString RUNNING_MARKER_FILENAME = "Interface.running"; -void CrashHandler::checkForAndHandleCrash() { +bool CrashHandler::checkForAndHandleCrash() { QFile runningMarkerFile(runningMarkerFilePath()); if (runningMarkerFile.exists()) { QSettings::setDefaultFormat(QSettings::IniFormat); @@ -42,7 +42,9 @@ void CrashHandler::checkForAndHandleCrash() { handleCrash(action); } } + return true; } + return false; } CrashHandler::Action CrashHandler::promptUserForAction() { diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index 61361b6107..18c935b595 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -17,7 +17,7 @@ class CrashHandler { public: - static void checkForAndHandleCrash(); + static bool checkForAndHandleCrash(); static void writeRunningMarkerFiler(); static void deleteRunningMarkerFile(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 35bc2aa696..63c67f6b9f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -107,7 +107,7 @@ Menu::Menu() { auto scriptEngines = DependencyManager::get(); // Edit > Stop All Scripts... [advanced] - addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0, + addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0, scriptEngines.data(), SLOT(stopAllScripts()), QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); @@ -140,7 +140,7 @@ Menu::Menu() { // Edit > Reload All Content [advanced] addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()), QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - + // Edit > Package Model... [advanced] addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, @@ -153,7 +153,7 @@ Menu::Menu() { auto audioIO = DependencyManager::get(); // Audio > Mute - addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, + addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, audioIO.data(), SLOT(toggleMute())); // Audio > Show Level Meter @@ -458,7 +458,7 @@ Menu::Menu() { avatar, SLOT(setEnableMeshVisible(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, false, + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, true, avatar, SLOT(setUseAnimPreAndPostRotations(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true, avatar, SLOT(setEnableInverseKinematics(bool))); @@ -534,7 +534,7 @@ Menu::Menu() { // Developer > Audio >>> MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio"); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, audioIO.data(), SLOT(toggleAudioNoiseReduction())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, audioIO.data(), SLOT(toggleServerEcho())); @@ -617,7 +617,7 @@ Menu::Menu() { QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); #endif } @@ -651,7 +651,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { } else if (properties.isCheckable) { menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence, properties.isChecked, - MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()), + MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()), requestedPosition, properties.grouping); } else { menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence, diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 7b63b7fc5a..f722210a8e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -187,7 +187,7 @@ void Avatar::simulate(float deltaTime) { // simple frustum check float boundingRadius = getBoundingRadius(); - bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius); + bool inView = qApp->getDisplayViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius); if (_shouldAnimate && !_shouldSkipRender && inView) { { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4405204b47..8f11c635e9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -418,7 +418,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); } -// best called at end of main loop, just before rendering. +// best called at end of main loop, after physics. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. void MyAvatar::updateSensorToWorldMatrix() { @@ -1087,24 +1087,32 @@ static controller::Pose applyLowVelocityFilter(const controller::Pose& oldPose, return finalPose; } -void MyAvatar::setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right) { +void MyAvatar::setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) { if (controller::InputDevice::getLowVelocityFilter()) { - auto oldLeftPose = getLeftHandControllerPoseInWorldFrame(); - auto oldRightPose = getRightHandControllerPoseInWorldFrame(); - _leftHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldLeftPose, left)); - _rightHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldRightPose, right)); + auto oldLeftPose = getLeftHandControllerPoseInSensorFrame(); + auto oldRightPose = getRightHandControllerPoseInSensorFrame(); + _leftHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left)); + _rightHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right)); } else { - _leftHandControllerPoseInWorldFrameCache.set(left); - _rightHandControllerPoseInWorldFrameCache.set(right); + _leftHandControllerPoseInSensorFrameCache.set(left); + _rightHandControllerPoseInSensorFrameCache.set(right); } } +controller::Pose MyAvatar::getLeftHandControllerPoseInSensorFrame() const { + return _leftHandControllerPoseInSensorFrameCache.get(); +} + +controller::Pose MyAvatar::getRightHandControllerPoseInSensorFrame() const { + return _rightHandControllerPoseInSensorFrameCache.get(); +} + controller::Pose MyAvatar::getLeftHandControllerPoseInWorldFrame() const { - return _leftHandControllerPoseInWorldFrameCache.get(); + return _leftHandControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix()); } controller::Pose MyAvatar::getRightHandControllerPoseInWorldFrame() const { - return _rightHandControllerPoseInWorldFrameCache.get(); + return _rightHandControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix()); } controller::Pose MyAvatar::getLeftHandControllerPoseInAvatarFrame() const { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3554d9b8bc..92bf9e7614 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -247,7 +247,9 @@ public: virtual void rebuildCollisionShape() override; - void setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right); + void setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right); + controller::Pose getLeftHandControllerPoseInSensorFrame() const; + controller::Pose getRightHandControllerPoseInSensorFrame() const; controller::Pose getLeftHandControllerPoseInWorldFrame() const; controller::Pose getRightHandControllerPoseInWorldFrame() const; controller::Pose getLeftHandControllerPoseInAvatarFrame() const; @@ -451,9 +453,9 @@ private: bool _hoverReferenceCameraFacingIsCaptured { false }; glm::vec3 _hoverReferenceCameraFacing { 0.0f, 0.0f, -1.0f }; // hmd sensor space - // These are stored in WORLD frame - ThreadSafeValueCache _leftHandControllerPoseInWorldFrameCache { controller::Pose() }; - ThreadSafeValueCache _rightHandControllerPoseInWorldFrameCache { controller::Pose() }; + // These are stored in SENSOR frame + ThreadSafeValueCache _leftHandControllerPoseInSensorFrameCache { controller::Pose() }; + ThreadSafeValueCache _rightHandControllerPoseInSensorFrameCache { controller::Pose() }; float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f }; float AUDIO_ENERGY_CONSTANT { 0.000001f }; diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index e8f429f22c..a5747e4f96 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -13,7 +13,7 @@ #include "AnimationLogging.h" #include "AnimUtil.h" -bool AnimClip::usePreAndPostPoseFromAnim = false; +bool AnimClip::usePreAndPostPoseFromAnim = true; AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) : AnimNode(AnimNode::Type::Clip, id), diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index ae9adc71c2..a2b664d064 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "AnimationLogging.h" #include "AnimClip.h" @@ -852,6 +853,8 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { + PROFILE_RANGE_EX(__FUNCTION__, 0xffff00ff, 0); + setModelOffset(rootTransform); if (_animNode) { diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4d44a771f7..7e01196dc7 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -565,10 +565,10 @@ void AudioClient::updateReverbOptions() { _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); reverbChanged = true; } - //if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { - // _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); - // reverbChanged = true; - //} + if (_zoneReverbOptions.getWetDryMix() != _receivedAudioStream.getWetLevel()) { + _zoneReverbOptions.setWetDryMix(_receivedAudioStream.getWetLevel()); + reverbChanged = true; + } if (_reverbOptions != &_zoneReverbOptions) { _reverbOptions = &_zoneReverbOptions; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index ca36ab35f0..d842dc553b 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -21,7 +21,7 @@ #include #include #include - +#include #include #include #include @@ -404,7 +404,11 @@ void OpenGLDisplayPlugin::submitOverlayTexture(const gpu::TexturePointer& overla void OpenGLDisplayPlugin::updateTextures() { // FIXME intrduce a GPU wait instead of a CPU/GPU sync point? +#if THREADED_PRESENT if (_sceneTextureEscrow.fetchSignaledAndRelease(_currentSceneTexture)) { +#else + if (_sceneTextureEscrow.fetchAndReleaseWithGpuWait(_currentSceneTexture)) { +#endif updateFrameData(); } @@ -527,6 +531,9 @@ void OpenGLDisplayPlugin::internalPresent() { void OpenGLDisplayPlugin::present() { incrementPresentCount(); + + PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) + updateTextures(); if (_currentSceneTexture) { // Write all layers to a local framebuffer diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 501232f7e7..7295b07ad3 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -17,9 +17,9 @@ #include #include #include -#include #define THREADED_PRESENT 1 +#include class OpenGLDisplayPlugin : public DisplayPlugin { protected: diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index c3782b907f..b022b10887 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "../Logging.h" #include "../CompositorHelper.h" @@ -106,6 +107,9 @@ void HmdDisplayPlugin::compositePointer() { } void HmdDisplayPlugin::internalPresent() { + + PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) + // Composite together the scene, overlay and mouse cursor hmdPresent(); @@ -149,6 +153,8 @@ void HmdDisplayPlugin::internalPresent() { }); swapBuffers(); } + + postPreview(); } void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 659a3a16fa..fede16c3a5 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -31,6 +31,7 @@ public: protected: virtual void hmdPresent() = 0; virtual bool isHmdMounted() const = 0; + virtual void postPreview() {}; void internalActivate() override; void compositeOverlay() override; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index ff4ed28150..98559a56a4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -113,14 +113,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) { return _originalTexturesMap; } - QString jsonTextures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + // Legacy: a ,\n-delimited list of filename:"texturepath" + if (*textures.cbegin() != '{') { + textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + } + QJsonParseError error; - QJsonDocument texturesAsJson = QJsonDocument::fromJson(jsonTextures.toUtf8(), &error); + QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures; + return _originalTexturesMap; } - QJsonObject texturesAsJsonObject = texturesAsJson.object(); - return texturesAsJsonObject.toVariantMap(); + return texturesJson.object().toVariantMap(); } void RenderableModelEntityItem::remapTextures() { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 03226342da..069b7385b5 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -38,12 +38,6 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - virtual void somethingChangedNotification() override { - // FIX ME: this is overly aggressive. We only really need to simulate() if something about - // the world space transform has changed and/or if some animation is occurring. - _needsInitialSimulation = true; - } - virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr); virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 21919922b9..bbc94e9910 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -516,7 +516,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef EntityTreePointer tree = getTree(); if (tree && tree->isDeletedEntity(_id)) { #ifdef WANT_DEBUG - qDebug() << "Recieved packet for previously deleted entity [" << _id << "] ignoring. " + qDebug() << "Received packet for previously deleted entity [" << _id << "] ignoring. " "(inside " << __FUNCTION__ << ")"; #endif ignoreServerPacket = true; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 6f9880171b..7f87c4e0b1 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -956,8 +956,10 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int entityItem->recordCreationTime(); } } else { - qDebug() << "Recieved packet for previously deleted entity [" << + #ifdef WANT_DEBUG + qDebug() << "Received packet for previously deleted entity [" << entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")"; + #endif } } } diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index de8c001566..65acedfc96 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1102,8 +1102,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("tex_color_map")) { diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type.contains("transparentcolor")) { // it should be TransparentColor... - // THis is how Maya assign a texture that affect diffuse color AND transparency ? + } else if (type.contains("transparentcolor")) { // Maya way of passing TransparentMap + transparentTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + } else if (type.contains("transparencyfactor")) { // Blender way of passing TransparentMap transparentTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else if (type.contains("bump")) { bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index fb272a1af9..11c6dad2f2 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -102,16 +102,20 @@ void FBXReader::consolidateFBXMaterials() { detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity()); } - FBXTexture transparentTexture; QString transparentTextureID = transparentTextures.value(material.materialID); + // If PBS Material, systematically bind the albedo texture as transparency texture and check for the alpha channel + if (material.isPBSMaterial) { + transparentTextureID = diffuseTextureID; + } if (!transparentTextureID.isNull()) { transparentTexture = getTexture(transparentTextureID); - material.opacityTexture = transparentTexture; detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity()); } + + FBXTexture normalTexture; QString bumpTextureID = bumpTextures.value(material.materialID); QString normalTextureID = normalTextures.value(material.materialID); diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index d3699b54e6..e224adad07 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "OffscreenGLCanvas.h" #include "GLEscrow.h" @@ -84,6 +85,7 @@ protected: Queue _queue; QMutex _mutex; QWaitCondition _waitCondition; + std::atomic _rendering { false }; private: // Event-driven methods @@ -271,15 +273,25 @@ void OffscreenQmlRenderThread::resize() { } void OffscreenQmlRenderThread::render() { - if (_surface->_paused) { + // Ensure we always release the main thread + Finally releaseMainThread([this] { _waitCondition.wakeOne(); + }); + + if (_surface->_paused) { return; } - QMutexLocker locker(&_mutex); - _renderControl->sync(); - _waitCondition.wakeOne(); - locker.unlock(); + _rendering = true; + Finally unmarkRenderingFlag([this] { + _rendering = false; + }); + + { + QMutexLocker locker(&_mutex); + _renderControl->sync(); + releaseMainThread.trigger(); + } using namespace oglplus; @@ -292,6 +304,7 @@ void OffscreenQmlRenderThread::render() { _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0); _fbo->Complete(Framebuffer::Target::Draw); { + PROFILE_RANGE("qml_render->rendercontrol") _renderControl->render(); // FIXME The web browsers seem to be leaving GL in an error state. // Need a debug context with sync logging to figure out why. @@ -380,8 +393,6 @@ void OffscreenQmlSurface::resize(const QSize& newSize_) { std::max(static_cast(scale * newSize.height()), 10)); } - - QSize currentSize = _renderer->_quickWindow->geometry().size(); if (newSize == currentSize) { return; @@ -492,7 +503,12 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::functionallowNewFrame(_maxFps)) { + // If we're + // a) not set up + // b) already rendering a frame + // c) rendering too fast + // then skip this + if (!_renderer || _renderer->_rendering || !_renderer->allowNewFrame(_maxFps)) { return; } @@ -502,11 +518,11 @@ void OffscreenQmlSurface::updateQuick() { } if (_render) { + PROFILE_RANGE(__FUNCTION__); // Lock the GUI size while syncing QMutexLocker locker(&(_renderer->_mutex)); _renderer->_queue.add(RENDER); _renderer->_waitCondition.wait(&(_renderer->_mutex)); - _render = false; } diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 86aa20fa1c..b0b769d5e9 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -139,7 +139,7 @@ bool NetworkGeometry::isLoadedWithTextures() const { } if (!_isLoadedWithTextures) { - _hasTransparentTextures = true; + _hasTransparentTextures = false; for (auto&& material : _materials) { if ((material->albedoTexture && !material->albedoTexture->isLoaded()) || @@ -152,12 +152,11 @@ bool NetworkGeometry::isLoadedWithTextures() const { return false; } if (material->albedoTexture && material->albedoTexture->getGPUTexture()) { - // Reset the materialKey transparentTexture key only, as it is albedoTexture-dependent + // Reassign the texture to make sure that itsalbedo alpha channel material key is detected correctly + material->_material->setTextureMap(model::MaterialKey::ALBEDO_MAP, material->_material->getTextureMap(model::MaterialKey::ALBEDO_MAP)); const auto& usage = material->albedoTexture->getGPUTexture()->getUsage(); bool isTransparentTexture = usage.isAlpha() && !usage.isAlphaMask(); - material->_material->setTransparentTexture(isTransparentTexture); - // FIXME: Materials with *some* transparent textures seem to give all *other* textures alphas of 0. - _hasTransparentTextures = isTransparentTexture && _hasTransparentTextures; + _hasTransparentTextures |= isTransparentTexture; } } @@ -177,9 +176,9 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u auto albedoMap = model::TextureMapPointer(new model::TextureMap()); albedoMap->setTextureSource(material->albedoTexture->_textureSource); - albedoMap->setTextureTransform( - oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform()); - + albedoMap->setTextureTransform(oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform()); + // when reassigning the albedo texture we also check for the alpha channel used as opacity + albedoMap->setUseAlphaChannel(true); networkMaterial->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap); } else if (material->normalTextureName == name) { material->normalTexture = textureCache->getTexture(url); @@ -212,10 +211,10 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u networkMaterial->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap); } else if (material->lightmapTextureName == name) { - material->emissiveTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE); + material->lightmapTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE); auto lightmapMap = model::TextureMapPointer(new model::TextureMap()); - lightmapMap->setTextureSource(material->emissiveTexture->_textureSource); + lightmapMap->setTextureSource(material->lightmapTexture->_textureSource); lightmapMap->setTextureTransform( oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getTextureTransform()); glm::vec2 oldOffsetScale = @@ -380,9 +379,20 @@ static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FB auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE, networkMaterial->albedoTexture, networkMaterial->albedoTextureName); albedoMap->setTextureTransform(material.albedoTexture.transform); + + if (!material.opacityTexture.filename.isEmpty()) { + if (material.albedoTexture.filename == material.opacityTexture.filename) { + // Best case scenario, just indicating that the albedo map contains transparency + albedoMap->setUseAlphaChannel(true); + } else { + // Opacity Map is different from the Abledo map, not supported + } + } + material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap); } + if (!material.normalTexture.filename.isEmpty()) { auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture, (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE), diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 7f01bdafaa..1c76a0b878 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -199,7 +199,6 @@ public: QSharedPointer occlusionTexture; QString lightmapTextureName; QSharedPointer lightmapTexture; - }; diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 58a82d5f11..28f4882b86 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -320,13 +320,12 @@ void ImageReader::run() { } QMetaObject::invokeMethod(texture.data(), "setImage", - Q_ARG(const QImage&, image), Q_ARG(void*, theTexture), Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); QThread::currentThread()->setPriority(originalPriority); } -void NetworkTexture::setImage(const QImage& image, void* voidTexture, int originalWidth, +void NetworkTexture::setImage(void* voidTexture, int originalWidth, int originalHeight) { _originalWidth = originalWidth; _originalHeight = originalHeight; diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 6fb0cc3177..858a40de36 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -136,7 +136,7 @@ protected: Q_INVOKABLE void loadContent(const QByteArray& content); // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on... - Q_INVOKABLE void setImage(const QImage& image, void* texture, int originalWidth, int originalHeight); + Q_INVOKABLE void setImage(void* texture, int originalWidth, int originalHeight); private: diff --git a/libraries/model/src/model/Material.cpp b/libraries/model/src/model/Material.cpp index 306867c204..0a6a4bb695 100755 --- a/libraries/model/src/model/Material.cpp +++ b/libraries/model/src/model/Material.cpp @@ -51,7 +51,7 @@ void Material::setEmissive(const Color& emissive, bool isSRGB) { } void Material::setOpacity(float opacity) { - _key.setTransparent((opacity < 1.0f)); + _key.setTranslucentFactor((opacity < 1.0f)); _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); _schemaBuffer.edit()._opacity = opacity; } @@ -80,19 +80,52 @@ void Material::setMetallic(float metallic) { _schemaBuffer.edit()._metallic = metallic; } -void Material::setTransparentTexture(bool isTransparent) { - _key.setTransparentTexture(isTransparent); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); -} - void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { if (textureMap) { _key.setMapChannel(channel, (true)); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + + if (channel == MaterialKey::ALBEDO_MAP) { + // clear the previous flags whatever they were: + _key.setOpacityMaskMap(false); + _key.setTranslucentMap(false); + + if (textureMap->useAlphaChannel() && textureMap->isDefined() && textureMap->getTextureView().isValid()) { + auto usage = textureMap->getTextureView()._texture->getUsage(); + if (usage.isAlpha()) { + // Texture has alpha, is not just a mask or a true transparent channel + if (usage.isAlphaMask()) { + _key.setOpacityMaskMap(true); + _key.setTranslucentMap(false); + } else { + _key.setOpacityMaskMap(false); + _key.setTranslucentMap(true); + } + } + } + } + _textureMaps[channel] = textureMap; } else { _key.setMapChannel(channel, (false)); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + + if (channel == MaterialKey::ALBEDO_MAP) { + _key.setOpacityMaskMap(false); + _key.setTranslucentMap(false); + } + _textureMaps.erase(channel); } + + _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + +} + + +const TextureMapPointer Material::getTextureMap(MapChannel channel) const { + auto result = _textureMaps.find(channel); + if (result != _textureMaps.end()) { + return (result->second); + } else { + return TextureMapPointer(); + } } diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index 078fc6499a..5a7b919994 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -31,14 +31,15 @@ public: ALBEDO_VAL_BIT, METALLIC_VAL_BIT, GLOSSY_VAL_BIT, - TRANSPARENT_VAL_BIT, - TRANSPARENT_TEX_VAL_BIT, + OPACITY_VAL_BIT, + OPACITY_MASK_MAP_BIT, // OPacity Map and Opacity MASK map are mutually exclusive + OPACITY_TRANSLUCENT_MAP_BIT, + // THe map bits must be in the smae sequence as the enum names for the map channels EMISSIVE_MAP_BIT, ALBEDO_MAP_BIT, METALLIC_MAP_BIT, ROUGHNESS_MAP_BIT, - TRANSPARENT_MAP_BIT, NORMAL_MAP_BIT, OCCLUSION_MAP_BIT, LIGHTMAP_MAP_BIT, @@ -52,7 +53,6 @@ public: ALBEDO_MAP, METALLIC_MAP, ROUGHNESS_MAP, - TRANSPARENT_MAP, NORMAL_MAP, OCCLUSION_MAP, LIGHTMAP_MAP, @@ -77,13 +77,15 @@ public: Builder& withAlbedo() { _flags.set(ALBEDO_VAL_BIT); return (*this); } Builder& withMetallic() { _flags.set(METALLIC_VAL_BIT); return (*this); } Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); } - Builder& withTransparent() { _flags.set(TRANSPARENT_VAL_BIT); return (*this); } + Builder& withTranslucentFactor() { _flags.set(OPACITY_VAL_BIT); return (*this); } Builder& withEmissiveMap() { _flags.set(EMISSIVE_MAP_BIT); return (*this); } Builder& withAlbedoMap() { _flags.set(ALBEDO_MAP_BIT); return (*this); } Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); } Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); } - Builder& withTransparentMap() { _flags.set(TRANSPARENT_MAP_BIT); return (*this); } + + Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); } + Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); } Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); } Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); } @@ -102,9 +104,6 @@ public: void setAlbedo(bool value) { _flags.set(ALBEDO_VAL_BIT, value); } bool isAlbedo() const { return _flags[ALBEDO_VAL_BIT]; } - void setTransparentTexture(bool value) { _flags.set(TRANSPARENT_TEX_VAL_BIT, value); } - bool isTransparentTexture() const { return _flags[TRANSPARENT_TEX_VAL_BIT]; } - void setAlbedoMap(bool value) { _flags.set(ALBEDO_MAP_BIT, value); } bool isAlbedoMap() const { return _flags[ALBEDO_MAP_BIT]; } @@ -121,13 +120,15 @@ public: void setRoughnessMap(bool value) { _flags.set(ROUGHNESS_MAP_BIT, value); } bool isRoughnessMap() const { return _flags[ROUGHNESS_MAP_BIT]; } - void setTransparent(bool value) { _flags.set(TRANSPARENT_VAL_BIT, value); } - bool isTransparent() const { return _flags[TRANSPARENT_VAL_BIT]; } - bool isOpaque() const { return !_flags[TRANSPARENT_VAL_BIT]; } + void setTranslucentFactor(bool value) { _flags.set(OPACITY_VAL_BIT, value); } + bool isTranslucentFactor() const { return _flags[OPACITY_VAL_BIT]; } - void setTransparentMap(bool value) { _flags.set(TRANSPARENT_MAP_BIT, value); } - bool isTransparentMap() const { return _flags[TRANSPARENT_MAP_BIT]; } + void setTranslucentMap(bool value) { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT, value); } + bool isTranslucentMap() const { return _flags[OPACITY_TRANSLUCENT_MAP_BIT]; } + void setOpacityMaskMap(bool value) { _flags.set(OPACITY_MASK_MAP_BIT, value); } + bool isOpacityMaskMap() const { return _flags[OPACITY_MASK_MAP_BIT]; } + void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); } bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; } @@ -140,6 +141,12 @@ public: void setMapChannel(MapChannel channel, bool value) { _flags.set(EMISSIVE_MAP_BIT + channel, value); } bool isMapChannel(MapChannel channel) const { return _flags[EMISSIVE_MAP_BIT + channel]; } + + // Translucency and Opacity Heuristics are combining several flags: + bool isTranslucent() const { return isTranslucentFactor() || isTranslucentMap(); } + bool isOpaque() const { return !isTranslucent(); } + bool isSurfaceOpaque() const { return isOpaque() && !isOpacityMaskMap(); } + bool isTexelOpaque() const { return isOpaque() && isOpacityMaskMap(); } }; @@ -168,9 +175,6 @@ public: Builder& withoutAlbedo() { _value.reset(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); } Builder& withAlbedo() { _value.set(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); } - Builder& withoutTransparentTexture() { _value.reset(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); } - Builder& withTransparentTexture() { _value.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); } - Builder& withoutAlbedoMap() { _value.reset(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); } Builder& withAlbedoMap() { _value.set(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); } @@ -186,11 +190,14 @@ public: Builder& withoutRoughnessMap() { _value.reset(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); } Builder& withRoughnessMap() { _value.set(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); } - Builder& withoutTransparent() { _value.reset(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); } - Builder& withTransparent() { _value.set(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); } + Builder& withoutTranslucentFactor() { _value.reset(MaterialKey::OPACITY_VAL_BIT); _mask.set(MaterialKey::OPACITY_VAL_BIT); return (*this); } + Builder& withTranslucentFactor() { _value.set(MaterialKey::OPACITY_VAL_BIT); _mask.set(MaterialKey::OPACITY_VAL_BIT); return (*this); } - Builder& withoutTransparentMap() { _value.reset(MaterialKey::TRANSPARENT_MAP_BIT); _mask.set(MaterialKey::TRANSPARENT_MAP_BIT); return (*this); } - Builder& withTransparentMap() { _value.set(MaterialKey::TRANSPARENT_MAP_BIT); _mask.set(MaterialKey::TRANSPARENT_MAP_BIT); return (*this); } + Builder& withoutTranslucentMap() { _value.reset(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); _mask.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); return (*this); } + Builder& withTranslucentMap() { _value.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); _mask.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); return (*this); } + + Builder& withoutMaskMap() { _value.reset(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); } + Builder& withMaskMap() { _value.set(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); } Builder& withoutNormalMap() { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } Builder& withNormalMap() { _value.set(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } @@ -202,7 +209,7 @@ public: Builder& withLightmapMap() { _value.set(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); } // Convenient standard keys that we will keep on using all over the place - static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTransparent().build(); } + static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTranslucentFactor().build(); } }; // Item Filter operator testing if a key pass the filter @@ -255,7 +262,6 @@ public: void setRoughness(float roughness); float getRoughness() const { return _schemaBuffer.get()._roughness; } - void setTransparentTexture(bool isTransparent); // Schema to access the attribute values of the material class Schema { @@ -283,6 +289,7 @@ public: // The texture map to channel association void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap); const TextureMaps& getTextureMaps() const { return _textureMaps; } + const TextureMapPointer getTextureMap(MapChannel channel) const; // conversion from legacy material properties to PBR equivalent static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; } diff --git a/libraries/model/src/model/Material.slh b/libraries/model/src/model/Material.slh index da185ddf0b..28f9769a8b 100644 --- a/libraries/model/src/model/Material.slh +++ b/libraries/model/src/model/Material.slh @@ -39,21 +39,21 @@ float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); } int getMaterialKey(Material m) { return floatBitsToInt(m._spareKey.w); } -const int EMISSIVE_VAL_BIT = 0x00000001; -const int ALBEDO_VAL_BIT = 0x00000002; -const int METALLIC_VAL_BIT = 0x00000004; -const int GLOSSY_VAL_BIT = 0x00000008; -const int TRANSPARENT_VAL_BIT = 0x00000010; -const int TRANSPARENT_TEX_VAL_BIT = 0x00000020; +const int EMISSIVE_VAL_BIT = 0x00000001; +const int ALBEDO_VAL_BIT = 0x00000002; +const int METALLIC_VAL_BIT = 0x00000004; +const int GLOSSY_VAL_BIT = 0x00000008; +const int OPACITY_VAL_BIT = 0x00000010; +const int OPACITY_MASK_MAP_BIT = 0x00000020; +const int OPACITY_TRANSLUCENT_MAP_BIT = 0x00000040; -const int EMISSIVE_MAP_BIT = 0x00000040; -const int ALBEDO_MAP_BIT = 0x00000080; -const int METALLIC_MAP_BIT = 0x00000100; -const int ROUGHNESS_MAP_BIT = 0x00000200; -const int TRANSPARENT_MAP_BIT = 0x00000400; -const int NORMAL_MAP_BIT = 0x00000800; -const int OCCLUSION_MAP_BIT = 0x00001000; +const int EMISSIVE_MAP_BIT = 0x00000080; +const int ALBEDO_MAP_BIT = 0x00000100; +const int METALLIC_MAP_BIT = 0x00000200; +const int ROUGHNESS_MAP_BIT = 0x00000400; +const int NORMAL_MAP_BIT = 0x00000800; +const int OCCLUSION_MAP_BIT = 0x00001000; +const int LIGHTMAP_MAP_BIT = 0x00002000; -const int LIGHTMAP_MAP_BIT = 0x00002000; <@endif@> diff --git a/libraries/model/src/model/TextureMap.h b/libraries/model/src/model/TextureMap.h index 228adb25e6..e845aebb81 100755 --- a/libraries/model/src/model/TextureMap.h +++ b/libraries/model/src/model/TextureMap.h @@ -56,6 +56,9 @@ public: void setTextureTransform(const Transform& texcoordTransform); const Transform& getTextureTransform() const { return _texcoordTransform; } + void setUseAlphaChannel(bool useAlpha) { _useAlphaChannel = useAlpha; } + bool useAlphaChannel() const { return _useAlphaChannel; } + void setLightmapOffsetScale(float offset, float scale); const glm::vec2& getLightmapOffsetScale() const { return _lightmapOffsetScale; } @@ -64,6 +67,8 @@ protected: Transform _texcoordTransform; glm::vec2 _lightmapOffsetScale{ 0.0f, 1.0f }; + + bool _useAlphaChannel{ false }; }; typedef std::shared_ptr< TextureMap > TextureMapPointer; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 295af8c5ee..3de94ed839 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -216,17 +216,13 @@ Resource* ResourceCacheSharedItems::getHighestPendingRequest() { bool ResourceCache::attemptRequest(Resource* resource) { auto sharedItems = DependencyManager::get(); - // Disable request limiting for ATP - if (resource->getURL().scheme() != URL_SCHEME_ATP) { - if (_requestsActive >= _requestLimit) { - // wait until a slot becomes available - sharedItems->appendPendingRequest(resource); - return false; - } - - ++_requestsActive; + if (_requestsActive >= _requestLimit) { + // wait until a slot becomes available + sharedItems->appendPendingRequest(resource); + return false; } - + + ++_requestsActive; sharedItems->appendActiveRequest(resource); resource->makeRequest(); return true; @@ -235,9 +231,7 @@ bool ResourceCache::attemptRequest(Resource* resource) { void ResourceCache::requestCompleted(Resource* resource) { auto sharedItems = DependencyManager::get(); sharedItems->removeRequest(resource); - if (resource->getURL().scheme() != URL_SCHEME_ATP) { - --_requestsActive; - } + --_requestsActive; attemptHighestPriorityRequest(); } @@ -380,6 +374,7 @@ void Resource::finishedLoading(bool success) { _failedToLoad = true; } _loadPriorities.clear(); + emit finished(success); } void Resource::reinsert() { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index ed938f6cf4..77878794b5 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -201,6 +201,9 @@ signals: /// This can be used instead of downloadFinished to access data before it is processed. void loaded(const QByteArray& request); + /// Fired when the resource has finished loading. + void finished(bool success); + /// Fired when the resource failed to load. void failed(QNetworkReply::NetworkError error); @@ -224,9 +227,6 @@ protected: /// This should be called by subclasses that override downloadFinished to mark the end of processing. Q_INVOKABLE void finishedLoading(bool success); - /// Reinserts this resource into the cache. - virtual void reinsert(); - QUrl _url; QUrl _activeUrl; bool _startedLoading = false; @@ -246,6 +246,7 @@ private: void makeRequest(); void retry(); + void reinsert(); friend class ResourceCache; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index f2019ba9a9..2e74a5166c 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -78,11 +78,15 @@ void UserActivityLogger::requestError(QNetworkReply& errorReply) { qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString(); } -void UserActivityLogger::launch(QString applicationVersion) { +void UserActivityLogger::launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime) { const QString ACTION_NAME = "launch"; QJsonObject actionDetails; QString VERSION_KEY = "version"; + QString CRASH_KEY = "previousSessionCrashed"; + QString RUNTIME_KEY = "previousSessionRuntime"; actionDetails.insert(VERSION_KEY, applicationVersion); + actionDetails.insert(CRASH_KEY, previousSessionCrashed); + actionDetails.insert(RUNTIME_KEY, previousSessionRuntime); logAction(ACTION_NAME, actionDetails); } diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 2811be86a8..8eda086521 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -29,7 +29,7 @@ public slots: void disable(bool disable); void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); - void launch(QString applicationVersion); + void launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime); void changedDisplayName(QString displayName); void changedModel(QString typeOfModel, QString modelURL); diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp index 1d1a6628fe..d30be2c139 100644 --- a/libraries/networking/src/udt/CongestionControl.cpp +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -201,7 +201,7 @@ void DefaultCC::onTimeout() { void DefaultCC::stopSlowStart() { _slowStart = false; - + if (_receiveRate > 0) { // Set the sending rate to the receiving rate. setPacketSendPeriod(USECS_PER_SECOND / _receiveRate); diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index e5f3508b81..af70295840 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -103,6 +103,7 @@ SendQueue& Connection::getSendQueue() { QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission); QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive); QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout); + QObject::connect(_sendQueue.get(), &SendQueue::shortCircuitLoss, this, &Connection::queueShortCircuitLoss); // set defaults on the send queue from our congestion control object and estimatedTimeout() _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod); @@ -140,6 +141,12 @@ void Connection::queueTimeout() { }); } +void Connection::queueShortCircuitLoss(quint32 sequenceNumber) { + updateCongestionControlAndSendQueue([this, sequenceNumber]{ + _congestionControl->onLoss(SequenceNumber { sequenceNumber }, SequenceNumber { sequenceNumber }); + }); +} + void Connection::sendReliablePacket(std::unique_ptr packet) { Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably."); getSendQueue().queuePacket(std::move(packet)); diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 4f5a8793e7..08a2df9b97 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -87,6 +87,7 @@ private slots: void recordRetransmission(); void queueInactive(); void queueTimeout(); + void queueShortCircuitLoss(quint32 sequenceNumber); private: void sendACK(bool wasCausedBySyncTimeout = true); diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 9701561ec7..2ffa42cb82 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -128,13 +128,13 @@ void SendQueue::stop() { _emptyCondition.notify_one(); } -void SendQueue::sendPacket(const Packet& packet) { - _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination); +int SendQueue::sendPacket(const Packet& packet) { + return _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination); } void SendQueue::ack(SequenceNumber ack) { // this is a response from the client, re-set our timeout expiry and our last response time - _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); + _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); if (_lastACKSequenceNumber == (uint32_t) ack) { return; @@ -164,7 +164,7 @@ void SendQueue::ack(SequenceNumber ack) { void SendQueue::nak(SequenceNumber start, SequenceNumber end) { // this is a response from the client, re-set our timeout expiry - _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); + _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); { std::lock_guard nakLocker(_naksLock); @@ -177,8 +177,8 @@ void SendQueue::nak(SequenceNumber start, SequenceNumber end) { void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { // this is a response from the client, re-set our timeout expiry - _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); - + _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); + { std::lock_guard nakLocker(_naksLock); _naks.clear(); @@ -232,15 +232,16 @@ SequenceNumber SendQueue::getNextSequenceNumber() { return _currentSequenceNumber; } -void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, SequenceNumber sequenceNumber) { +bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, SequenceNumber sequenceNumber) { // write the sequence number and send the packet newPacket->writeSequenceNumber(sequenceNumber); - sendPacket(*newPacket); - + // Save packet/payload size before we move it auto packetSize = newPacket->getDataSize(); auto payloadSize = newPacket->getPayloadSize(); + auto bytesWritten = sendPacket(*newPacket); + { // Insert the packet we have just sent in the sent list QWriteLocker locker(&_sentLock); @@ -249,8 +250,24 @@ void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, entry.second.swap(newPacket); } Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list"); - + emit packetSent(packetSize, payloadSize); + + if (bytesWritten < 0) { + // this is a short-circuit loss - we failed to put this packet on the wire + // so immediately add it to the loss list + + { + std::lock_guard nakLocker(_naksLock); + _naks.append(sequenceNumber); + } + + emit shortCircuitLoss(quint32(sequenceNumber)); + + return false; + } else { + return true; + } } void SendQueue::run() { @@ -285,12 +302,14 @@ void SendQueue::run() { auto nextPacketTimestamp = p_high_resolution_clock::now(); while (_state == State::Running) { - bool sentAPacket = maybeResendPacket(); + bool attemptedToSendPacket = maybeResendPacket(); // if we didn't find a packet to re-send AND we think we can fit a new packet on the wire // (this is according to the current flow window size) then we send out a new packet - if (!sentAPacket) { - sentAPacket = maybeSendNewPacket(); + auto newPacketCount = 0; + if (!attemptedToSendPacket) { + newPacketCount = maybeSendNewPacket(); + attemptedToSendPacket = (newPacketCount > 0); } // since we're a while loop, give the thread a chance to process events @@ -300,12 +319,13 @@ void SendQueue::run() { // If the send queue has been innactive, skip the sleep for // Either _isRunning will have been set to false and we'll break // Or something happened and we'll keep going - if (_state != State::Running || isInactive(sentAPacket)) { + if (_state != State::Running || isInactive(attemptedToSendPacket)) { return; } // push the next packet timestamp forwards by the current packet send period - nextPacketTimestamp += std::chrono::microseconds(_packetSendPeriod); + auto nextPacketDelta = (newPacketCount == 2 ? 2 : 1) * _packetSendPeriod; + nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta); // sleep as long as we need until next packet send, if we can const auto timeToSleep = duration_cast(nextPacketTimestamp - p_high_resolution_clock::now()); @@ -314,7 +334,7 @@ void SendQueue::run() { } } -bool SendQueue::maybeSendNewPacket() { +int SendQueue::maybeSendNewPacket() { if (!isFlowWindowFull()) { // we didn't re-send a packet, so time to send a new one @@ -324,38 +344,43 @@ bool SendQueue::maybeSendNewPacket() { // grab the first packet we will send std::unique_ptr firstPacket = _packets.takePacket(); Q_ASSERT(firstPacket); - - std::unique_ptr secondPacket; - bool shouldSendPairTail = false; - - if (((uint32_t) nextNumber & 0xF) == 0) { - // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets - // pull off a second packet if we can before we unlock - shouldSendPairTail = true; - - secondPacket = _packets.takePacket(); + + + // attempt to send the first packet + if (sendNewPacketAndAddToSentList(move(firstPacket), nextNumber)) { + std::unique_ptr secondPacket; + bool shouldSendPairTail = false; + + if (((uint32_t) nextNumber & 0xF) == 0) { + // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets + // pull off a second packet if we can before we unlock + shouldSendPairTail = true; + + secondPacket = _packets.takePacket(); + } + + // do we have a second in a pair to send as well? + if (secondPacket) { + sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber()); + } else if (shouldSendPairTail) { + // we didn't get a second packet to send in the probe pair + // send a control packet of type ProbePairTail so the receiver can still do + // proper bandwidth estimation + static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail); + _socket->writeBasePacket(*pairTailPacket, _destination); + } + + // we attempted to send two packets, return 2 + return 2; + } else { + // we attempted to send a single packet, return 1 + return 1; } - - // definitely send the first packet - sendNewPacketAndAddToSentList(move(firstPacket), nextNumber); - - // do we have a second in a pair to send as well? - if (secondPacket) { - sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber()); - } else if (shouldSendPairTail) { - // we didn't get a second packet to send in the probe pair - // send a control packet of type ProbePairTail so the receiver can still do - // proper bandwidth estimation - static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail); - _socket->writeBasePacket(*pairTailPacket, _destination); - } - - // We sent our packet(s), return here - return true; } } + // No packets were sent - return false; + return 0; } bool SendQueue::maybeResendPacket() { @@ -375,8 +400,9 @@ bool SendQueue::maybeResendPacket() { // see if we can find the packet to re-send auto it = _sentPackets.find(resendNumber); - + if (it != _sentPackets.end()) { + auto& entry = it->second; // we found the packet - grab it auto& resendPacket = *(entry.second); @@ -437,7 +463,7 @@ bool SendQueue::maybeResendPacket() { return false; } -bool SendQueue::isInactive(bool sentAPacket) { +bool SendQueue::isInactive(bool attemptedToSendPacket) { // check for connection timeout first // that will be the case if we have had 16 timeouts since hearing back from the client, and it has been @@ -447,7 +473,8 @@ bool SendQueue::isInactive(bool sentAPacket) { auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse); - if (sinceLastResponse >= quint64(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) && + if (sinceLastResponse > 0 && + sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) && _lastReceiverResponse > 0 && sinceLastResponse > MIN_MS_BEFORE_INACTIVE) { // If the flow window has been full for over CONSIDER_INACTIVE_AFTER, @@ -462,7 +489,7 @@ bool SendQueue::isInactive(bool sentAPacket) { return true; } - if (!sentAPacket) { + if (!attemptedToSendPacket) { // During our processing above we didn't send any packets // If that is still the case we should use a condition_variable_any to sleep until we have data to handle. diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 9400ae8352..21f6141c3c 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -79,6 +79,7 @@ signals: void queueInactive(); + void shortCircuitLoss(quint32 sequenceNumber); void timeout(); private slots: @@ -91,13 +92,13 @@ private: void sendHandshake(); - void sendPacket(const Packet& packet); - void sendNewPacketAndAddToSentList(std::unique_ptr newPacket, SequenceNumber sequenceNumber); + int sendPacket(const Packet& packet); + bool sendNewPacketAndAddToSentList(std::unique_ptr newPacket, SequenceNumber sequenceNumber); - bool maybeSendNewPacket(); // Figures out what packet to send next + int maybeSendNewPacket(); // Figures out what packet to send next bool maybeResendPacket(); // Determines whether to resend a packet and which one - bool isInactive(bool sentAPacket); + bool isInactive(bool attemptedToSendPacket); void deactivate(); // makes the queue inactive and cleans it up bool isFlowWindowFull() const; @@ -122,7 +123,7 @@ private: std::atomic _estimatedTimeout { 0 }; // Estimated timeout, set from CC std::atomic _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC - std::atomic _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK) + std::atomic _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK) std::atomic _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index f956e562cd..de3e9cc794 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -120,3 +120,21 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { } return shape; } + +void ShapeFactory::deleteShape(btCollisionShape* shape) { + assert(shape); + if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) { + btCompoundShape* compoundShape = static_cast(shape); + const int numChildShapes = compoundShape->getNumChildShapes(); + for (int i = 0; i < numChildShapes; i ++) { + btCollisionShape* childShape = compoundShape->getChildShape(i); + if (childShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) { + // recurse + ShapeFactory::deleteShape(childShape); + } else { + delete childShape; + } + } + } + delete shape; +} diff --git a/libraries/physics/src/ShapeFactory.h b/libraries/physics/src/ShapeFactory.h index f6a6dfb3e6..1ba2bdb619 100644 --- a/libraries/physics/src/ShapeFactory.h +++ b/libraries/physics/src/ShapeFactory.h @@ -22,6 +22,7 @@ namespace ShapeFactory { btConvexHullShape* createConvexHull(const QVector& points); btCollisionShape* createShapeFromInfo(const ShapeInfo& info); + void deleteShape(btCollisionShape* shape); }; #endif // hifi_ShapeFactory_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 7a3065dfff..4231d1eb60 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -23,7 +23,7 @@ ShapeManager::~ShapeManager() { int numShapes = _shapeMap.size(); for (int i = 0; i < numShapes; ++i) { ShapeReference* shapeRef = _shapeMap.getAtIndex(i); - delete shapeRef->shape; + ShapeFactory::deleteShape(shapeRef->shape); } _shapeMap.clear(); } @@ -32,13 +32,14 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { if (info.getType() == SHAPE_TYPE_NONE) { return NULL; } - // Very small or large objects are not supported. - float diagonal = 4.0f * glm::length2(info.getHalfExtents()); - const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube - //const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e6f; // 1000 m cube - if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED /* || diagonal > MAX_SHAPE_DIAGONAL_SQUARED*/ ) { - // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; - return NULL; + if (info.getType() != SHAPE_TYPE_COMPOUND) { + // Very small or large non-compound objects are not supported. + float diagonal = 4.0f * glm::length2(info.getHalfExtents()); + const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube + if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) { + // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; + return NULL; + } } DoubleHashKey key = info.getHash(); ShapeReference* shapeRef = _shapeMap.find(key); @@ -58,14 +59,14 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { } // private helper method -bool ShapeManager::releaseShape(const DoubleHashKey& key) { +bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) { ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { if (shapeRef->refCount > 0) { shapeRef->refCount--; if (shapeRef->refCount == 0) { _pendingGarbage.push_back(key); - const int MAX_GARBAGE_CAPACITY = 127; + const int MAX_GARBAGE_CAPACITY = 255; if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) { collectGarbage(); } @@ -82,16 +83,12 @@ bool ShapeManager::releaseShape(const DoubleHashKey& key) { return false; } -bool ShapeManager::releaseShape(const ShapeInfo& info) { - return releaseShape(info.getHash()); -} - bool ShapeManager::releaseShape(const btCollisionShape* shape) { int numShapes = _shapeMap.size(); for (int i = 0; i < numShapes; ++i) { ShapeReference* shapeRef = _shapeMap.getAtIndex(i); if (shape == shapeRef->shape) { - return releaseShape(shapeRef->key); + return releaseShapeByKey(shapeRef->key); } } return false; @@ -103,17 +100,7 @@ void ShapeManager::collectGarbage() { DoubleHashKey& key = _pendingGarbage[i]; ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef && shapeRef->refCount == 0) { - // if the shape we're about to delete is compound, delete the children first. - if (shapeRef->shape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE) { - const btCompoundShape* compoundShape = static_cast(shapeRef->shape); - const int numChildShapes = compoundShape->getNumChildShapes(); - for (int i = 0; i < numChildShapes; i ++) { - const btCollisionShape* childShape = compoundShape->getChildShape(i); - delete childShape; - } - } - - delete shapeRef->shape; + ShapeFactory::deleteShape(shapeRef->shape); _shapeMap.remove(key); } } diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h index e04fa1280c..0c411f5f62 100644 --- a/libraries/physics/src/ShapeManager.h +++ b/libraries/physics/src/ShapeManager.h @@ -29,7 +29,6 @@ public: btCollisionShape* getShape(const ShapeInfo& info); /// \return true if shape was found and released - bool releaseShape(const ShapeInfo& info); bool releaseShape(const btCollisionShape* shape); /// delete shapes that have zero references @@ -39,10 +38,10 @@ public: int getNumShapes() const { return _shapeMap.size(); } int getNumReferences(const ShapeInfo& info) const; int getNumReferences(const btCollisionShape* shape) const; - bool hasShape(const btCollisionShape* shape) const; + bool hasShape(const btCollisionShape* shape) const; private: - bool releaseShape(const DoubleHashKey& key); + bool releaseShapeByKey(const DoubleHashKey& key); struct ShapeReference { int refCount; diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index 8904ae34b2..34bb7c429b 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -59,7 +59,7 @@ float fetchOcclusionMap(vec2 uv) { <@func fetchMaterialTextures(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, occlusion)@> <@if albedo@> - vec4 <$albedo$> = (((<$matKey$> & ALBEDO_MAP_BIT) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0)); + vec4 <$albedo$> = (((<$matKey$> & (ALBEDO_MAP_BIT | OPACITY_MASK_MAP_BIT | OPACITY_TRANSLUCENT_MAP_BIT)) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0)); <@endif@> <@if roughness@> float <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? fetchRoughnessMap(<$texcoord0$>) : 1.0); @@ -112,6 +112,23 @@ vec3 fetchLightmapMap(vec2 uv) { } <@endfunc@> +<@func evalMaterialOpacity(fetchedOpacity, materialOpacity, matKey, opacity)@> +{ + const float OPACITY_MASK_THRESHOLD = 0.5; + <$opacity$> = (((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0) ? + (((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0) ? step(OPACITY_MASK_THRESHOLD, <$fetchedOpacity$>) : <$fetchedOpacity$>) : + 1.0) * <$materialOpacity$>; +} +<@endfunc@> + +<@func $discardTransparent(opacity)@> +{ + if (<$opacity$> < 1.0) { + discard; + } +} +<@endfunc@> + <@func evalMaterialRoughness(fetchedRoughness, materialRoughness, matKey, roughness)@> { <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? <$fetchedRoughness$> : <$materialRoughness$>); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 4ae5b4532a..363def05a1 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -81,7 +81,7 @@ ItemKey MeshPartPayload::getKey() const { if (_drawMaterial) { auto matKey = _drawMaterial->getKey(); - if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) { + if (matKey.isTranslucent()) { builder.withTransparent(); } } @@ -100,7 +100,7 @@ ShapeKey MeshPartPayload::getShapeKey() const { } ShapeKey::Builder builder; - if (drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap()) { + if (drawMaterialKey.isTranslucent()) { builder.withTranslucent(); } if (drawMaterialKey.isNormalMap()) { @@ -365,7 +365,7 @@ ItemKey ModelMeshPartPayload::getKey() const { if (_drawMaterial) { auto matKey = _drawMaterial->getKey(); - if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) { + if (matKey.isTranslucent()) { builder.withTransparent(); } } @@ -412,8 +412,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { drawMaterialKey = _drawMaterial->getKey(); } - bool isTranslucent = - drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap(); + bool isTranslucent = drawMaterialKey.isTranslucent(); bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty(); bool hasSpecular = drawMaterialKey.isMetallicMap(); bool hasLightmap = drawMaterialKey.isLightmapMap(); diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index c9ca6d9eb7..ddfd83d1d4 100755 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -29,6 +29,10 @@ void main(void) { int matKey = getMaterialKey(mat); <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>; + <$discardTransparent(opacity)$>; + vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; @@ -41,7 +45,7 @@ void main(void) { packDeferredFragment( normalize(_normal.xyz), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + opacity, albedo, roughness, getMaterialMetallic(mat), diff --git a/libraries/render-utils/src/model_normal_map.slf b/libraries/render-utils/src/model_normal_map.slf index a584427005..10ae6ee880 100755 --- a/libraries/render-utils/src/model_normal_map.slf +++ b/libraries/render-utils/src/model_normal_map.slf @@ -30,6 +30,10 @@ void main(void) { int matKey = getMaterialKey(mat); <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>; + <$discardTransparent(opacity)$>; + vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; @@ -45,7 +49,7 @@ void main(void) { packDeferredFragment( viewNormal, - evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + opacity, albedo, roughness, getMaterialMetallic(mat), diff --git a/libraries/render-utils/src/model_normal_specular_map.slf b/libraries/render-utils/src/model_normal_specular_map.slf index cf461db7ef..2529596818 100755 --- a/libraries/render-utils/src/model_normal_specular_map.slf +++ b/libraries/render-utils/src/model_normal_specular_map.slf @@ -30,6 +30,10 @@ void main(void) { int matKey = getMaterialKey(mat); <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex, occlusionTex)$> + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>; + <$discardTransparent(opacity)$>; + vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; @@ -49,7 +53,7 @@ void main(void) { packDeferredFragment( normalize(viewNormal.xyz), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + opacity, albedo, roughness, metallic, diff --git a/libraries/render-utils/src/model_specular_map.slf b/libraries/render-utils/src/model_specular_map.slf index 32e5823430..9b2f4ae640 100755 --- a/libraries/render-utils/src/model_specular_map.slf +++ b/libraries/render-utils/src/model_specular_map.slf @@ -30,6 +30,10 @@ void main(void) { int matKey = getMaterialKey(mat); <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex, occlusionTex)$> + float opacity = 1.0; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>; + <$discardTransparent(opacity)$>; + vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; @@ -45,7 +49,7 @@ void main(void) { packDeferredFragment( normalize(_normal), - evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), + opacity, albedo, roughness, metallic, diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index b99b5c8a2a..12a7b9299f 100755 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -36,6 +36,10 @@ void main(void) { int matKey = getMaterialKey(mat); <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$> + float opacity = getMaterialOpacity(mat) * _alpha; + <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>; + <$discardTransparent(opacity)$>; + vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; albedo *= _color; @@ -52,8 +56,6 @@ void main(void) { vec3 fragPosition = _position.xyz; vec3 fragNormal = normalize(_normal); - float fragOpacity = getMaterialOpacity(mat) * albedoTex.a * _alpha; - TransformCamera cam = getTransformCamera(); _fragColor = vec4(evalAmbientSphereGlobalColor( @@ -66,5 +68,5 @@ void main(void) { metallic, emissive, roughness), - fragOpacity); + opacity); } diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 484f049944..56805e8f83 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -114,6 +114,7 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render void CullSpatialSelection::configure(const Config& config) { _justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum); _freezeFrustum = config.freezeFrustum; + _skipCulling = config.skipCulling; } void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, @@ -191,60 +192,112 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re // visibility cull if partially selected ( octree cell contianing it was partial) // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) - // inside & fit items: easy, just filter - { - PerformanceTimer perfTimer("insideFitItems"); - for (auto id : inSelection.insideItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - outItems.emplace_back(itemBound); - } - } - } - - // inside & subcell items: filter & distance cull - { - PerformanceTimer perfTimer("insideSmallItems"); - for (auto id : inSelection.insideSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.solidAngleTest(itemBound.bound)) { + if (_skipCulling) { + // inside & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); } } } - } - // partial & fit items: filter & frustum cull - { - PerformanceTimer perfTimer("partialFitItems"); - for (auto id : inSelection.partialItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.frustumTest(itemBound.bound)) { + // inside & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); } } } - } - // partial & subcell items:: filter & frutum cull & solidangle cull - { - PerformanceTimer perfTimer("partialSmallItems"); - for (auto id : inSelection.partialSubcellItems) { - auto& item = scene->getItem(id); - if (_filter.test(item.getKey())) { - ItemBound itemBound(id, item.getBound()); - if (test.frustumTest(itemBound.bound)) { + // partial & fit items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // partial & subcell items: filter only, culling is disabled + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + } else { + + // inside & fit items: easy, just filter + { + PerformanceTimer perfTimer("insideFitItems"); + for (auto id : inSelection.insideItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + outItems.emplace_back(itemBound); + } + } + } + + // inside & subcell items: filter & distance cull + { + PerformanceTimer perfTimer("insideSmallItems"); + for (auto id : inSelection.insideSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); } } } } + + // partial & fit items: filter & frustum cull + { + PerformanceTimer perfTimer("partialFitItems"); + for (auto id : inSelection.partialItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + if (test.frustumTest(itemBound.bound)) { + outItems.emplace_back(itemBound); + } + } + } + } + + // partial & subcell items:: filter & frutum cull & solidangle cull + { + PerformanceTimer perfTimer("partialSmallItems"); + for (auto id : inSelection.partialSubcellItems) { + auto& item = scene->getItem(id); + if (_filter.test(item.getKey())) { + ItemBound itemBound(id, item.getBound()); + if (test.frustumTest(itemBound.bound)) { + if (test.solidAngleTest(itemBound.bound)) { + outItems.emplace_back(itemBound); + } + } + } + } + } } details._rendered += (int)outItems.size(); diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index a6a32e4561..e84f018e91 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -70,14 +70,16 @@ namespace render { Q_OBJECT Q_PROPERTY(int numItems READ getNumItems) Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum) + Q_PROPERTY(bool skipCulling MEMBER skipCulling WRITE setSkipCulling) public: int numItems{ 0 }; int getNumItems() { return numItems; } bool freezeFrustum{ false }; + bool skipCulling{ false }; public slots: void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); } - + void setSkipCulling(bool enabled) { skipCulling = enabled; emit dirty(); } signals: void dirty(); }; @@ -85,6 +87,7 @@ namespace render { class CullSpatialSelection { bool _freezeFrustum{ false }; // initialized by Config bool _justFrozeFrustum{ false }; + bool _skipCulling{ false }; ViewFrustum _frozenFrutstum; public: using Config = CullSpatialSelectionConfig; diff --git a/libraries/shared/src/Finally.h b/libraries/shared/src/Finally.h index 59e8be0228..9070d49647 100644 --- a/libraries/shared/src/Finally.h +++ b/libraries/shared/src/Finally.h @@ -20,6 +20,10 @@ public: template Finally(F f) : _f(f) {} ~Finally() { _f(); } + void trigger() { + _f(); + _f = [] {}; + } private: std::function _f; }; diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index a3fbe55f36..79390b6680 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -63,7 +63,7 @@ public: void appendToPoints (const QVector& newPoints) { _points << newPoints; } float computeVolume() const; - + /// Returns whether point is inside the shape /// For compound shapes it will only return whether it is inside the bounding box bool contains(const glm::vec3& point) const; diff --git a/libraries/shared/src/shared/NsightHelpers.cpp b/libraries/shared/src/shared/NsightHelpers.cpp index e48e228588..2539ff8864 100644 --- a/libraries/shared/src/shared/NsightHelpers.cpp +++ b/libraries/shared/src/shared/NsightHelpers.cpp @@ -8,6 +8,7 @@ #include "NsightHelpers.h" +#ifdef _WIN32 #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" @@ -15,8 +16,28 @@ ProfileRange::ProfileRange(const char *name) { nvtxRangePush(name); } +ProfileRange::ProfileRange(const char *name, uint32_t argbColor, uint64_t payload) { + + nvtxEventAttributes_t eventAttrib = {0}; + eventAttrib.version = NVTX_VERSION; + eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE; + eventAttrib.colorType = NVTX_COLOR_ARGB; + eventAttrib.color = argbColor; + eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII; + eventAttrib.message.ascii = name; + eventAttrib.payload.llValue = payload; + eventAttrib.payloadType = NVTX_PAYLOAD_TYPE_UNSIGNED_INT64; + + nvtxRangePushEx(&eventAttrib); +} + ProfileRange::~ProfileRange() { nvtxRangePop(); } +#else +ProfileRange::ProfileRange(const char *name) {} +ProfileRange::ProfileRange(const char *name, uint32_t argbColor, uint64_t payload) {} +ProfileRange::~ProfileRange() {} #endif +#endif // _WIN32 diff --git a/libraries/shared/src/shared/NsightHelpers.h b/libraries/shared/src/shared/NsightHelpers.h index 3acdf14411..9853171b34 100644 --- a/libraries/shared/src/shared/NsightHelpers.h +++ b/libraries/shared/src/shared/NsightHelpers.h @@ -9,16 +9,21 @@ #ifndef hifi_gl_NsightHelpers_h #define hifi_gl_NsightHelpers_h -#if defined(NSIGHT_FOUND) - class ProfileRange { - public: - ProfileRange(const char *name); - ~ProfileRange(); - }; +#ifdef _WIN32 +#include + +class ProfileRange { +public: + ProfileRange(const char *name); + ProfileRange(const char *name, uint32_t argbColor, uint64_t payload); + ~ProfileRange(); +}; + #define PROFILE_RANGE(name) ProfileRange profileRangeThis(name); +#define PROFILE_RANGE_EX(name, argbColor, payload) ProfileRange profileRangeThis(name, argbColor, payload); #else #define PROFILE_RANGE(name) +#define PROFILE_RANGE_EX(name, argbColor, payload) #endif - -#endif \ No newline at end of file +#endif diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index f12fb51b19..43fd5a64df 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -14,6 +14,8 @@ #include +#include + #include #include @@ -31,19 +33,39 @@ static const char* const URL_PROPERTY = "source"; // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine, - [&](QObject* object) { return new QmlWebWindowClass(object); }); + [&](QObject* object) { return new QmlWebWindowClass(object); }); } QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { + _uid = QUuid::createUuid().toString(); + asQuickItem()->setProperty("uid", _uid); + auto webchannelVar = qmlWindow->property("webChannel"); + _webchannel = qvariant_cast(webchannelVar); + Q_ASSERT(_webchannel); + _webchannel->registerObject(_uid, this); } +void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage)); + } else { + emit scriptEventReceived(scriptMessage); + } +} -// FIXME remove. -void QmlWebWindowClass::handleNavigation(const QString& url) { +void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage)); + } else { + emit webEventReceived(webMessage); + } } QString QmlWebWindowClass::getURL() const { QVariant result = DependencyManager::get()->returnFromUiThread([&]()->QVariant { + if (_qmlWindow.isNull()) { + return QVariant(); + } return _qmlWindow->property(URL_PROPERTY); }); return result.toString(); @@ -54,6 +76,8 @@ extern QString fixupHifiUrl(const QString& urlString); void QmlWebWindowClass::setURL(const QString& urlString) { DependencyManager::get()->executeOnUiThread([=] { - _qmlWindow->setProperty(URL_PROPERTY, fixupHifiUrl(urlString)); + if (!_qmlWindow.isNull()) { + _qmlWindow->setProperty(URL_PROPERTY, fixupHifiUrl(urlString)); + } }); } diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 14e533c7b4..35322ef0ea 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -11,10 +11,13 @@ #include "QmlWindowClass.h" +class QWebChannel; + // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWebWindowClass : public QmlWindowClass { Q_OBJECT Q_PROPERTY(QString url READ getURL CONSTANT) + Q_PROPERTY(QString uid READ getUid CONSTANT) public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); @@ -23,12 +26,18 @@ public: public slots: QString getURL() const; void setURL(const QString& url); + const QString& getUid() const { return _uid; } + void emitScriptEvent(const QVariant& scriptMessage); + void emitWebEvent(const QVariant& webMessage); signals: void urlChanged(); + void scriptEventReceived(const QVariant& message); + void webEventReceived(const QVariant& message); -private slots: - void handleNavigation(const QString& url); +private: + QString _uid; + QWebChannel* _webchannel { nullptr }; }; #endif diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 679e86f4ae..37d461acd0 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -26,10 +26,6 @@ #include "OffscreenUi.h" -QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr }; -static QWebChannel webChannel; -static const uint16_t WEB_CHANNEL_PORT = 51016; -static std::atomic nextWindowId; static const char* const SOURCE_PROPERTY = "source"; static const char* const TITLE_PROPERTY = "title"; static const char* const WIDTH_PROPERTY = "width"; @@ -37,54 +33,6 @@ static const char* const HEIGHT_PROPERTY = "height"; static const char* const VISIBILE_PROPERTY = "visible"; static const char* const TOOLWINDOW_PROPERTY = "toolWindow"; -void QmlScriptEventBridge::emitWebEvent(const QString& data) { - QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); -} - -void QmlScriptEventBridge::emitScriptEvent(const QString& data) { - QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, - Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data)); -} - -class QmlWebTransport : public QWebChannelAbstractTransport { - Q_OBJECT -public: - QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) { - // Translate from the websocket layer to the webchannel layer - connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) { - QJsonParseError error; - QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error); - if (error.error || !document.isObject()) { - qWarning() << "Unable to parse incoming JSON message" << message; - return; - } - emit messageReceived(document.object(), this); - }); - } - - virtual void sendMessage(const QJsonObject &message) override { - // Translate from the webchannel layer to the websocket layer - _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact)); - } - -private: - QWebSocket* const _webSocket; -}; - - -void QmlWindowClass::setupServer() { - if (!_webChannelServer) { - _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode); - if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) { - qFatal("Failed to open web socket server."); - } - - QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] { - webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection())); - }); - } -} - QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QScriptContext* context, QScriptEngine* engine, std::function builder) @@ -168,10 +116,8 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, } offscreenUi->returnFromUiThread([&] { - setupServer(); retVal = builder(newTab); retVal->_toolWindow = true; - registerObject(url.toLower(), retVal); return QVariant(); }); } else { @@ -179,10 +125,8 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection, Q_ARG(const QString&, qmlSource), Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { - setupServer(); retVal = builder(object); context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); - registerObject(url.toLower(), retVal); if (!title.isEmpty()) { retVal->setTitle(title); } @@ -209,12 +153,9 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* }); } -QmlWindowClass::QmlWindowClass(QObject* qmlWindow) - : _windowId(++nextWindowId), _qmlWindow(qmlWindow) -{ - qDebug() << "Created window with ID " << _windowId; +QmlWindowClass::QmlWindowClass(QObject* qmlWindow) : _qmlWindow(qmlWindow) { Q_ASSERT(_qmlWindow); - Q_ASSERT(dynamic_cast(_qmlWindow)); + Q_ASSERT(dynamic_cast(_qmlWindow.data())); // Forward messages received from QML on to the script connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection); } @@ -228,19 +169,11 @@ QmlWindowClass::~QmlWindowClass() { close(); } -void QmlWindowClass::registerObject(const QString& name, QObject* object) { - webChannel.registerObject(name, object); -} - -void QmlWindowClass::deregisterObject(QObject* object) { - webChannel.deregisterObject(object); -} - QQuickItem* QmlWindowClass::asQuickItem() const { if (_toolWindow) { return DependencyManager::get()->getToolWindow(); } - return dynamic_cast(_qmlWindow); + return _qmlWindow.isNull() ? nullptr : dynamic_cast(_qmlWindow.data()); } void QmlWindowClass::setVisible(bool visible) { @@ -260,32 +193,34 @@ void QmlWindowClass::setVisible(bool visible) { bool QmlWindowClass::isVisible() const { // The tool window itself has special logic based on whether any tabs are enabled - if (_toolWindow) { - auto targetTab = dynamic_cast(_qmlWindow); - return DependencyManager::get()->returnFromUiThread([&] { - return QVariant::fromValue(targetTab->isEnabled()); - }).toBool(); - } else { - QQuickItem* targetWindow = asQuickItem(); - return DependencyManager::get()->returnFromUiThread([&] { - return QVariant::fromValue(targetWindow->isVisible()); - }).toBool(); - } + return DependencyManager::get()->returnFromUiThread([&] { + if (_qmlWindow.isNull()) { + return QVariant::fromValue(false); + } + if (_toolWindow) { + return QVariant::fromValue(dynamic_cast(_qmlWindow.data())->isEnabled()); + } else { + return QVariant::fromValue(asQuickItem()->isVisible()); + } + }).toBool(); } glm::vec2 QmlWindowClass::getPosition() const { - QQuickItem* targetWindow = asQuickItem(); QVariant result = DependencyManager::get()->returnFromUiThread([&]()->QVariant { - return targetWindow->position(); + if (_qmlWindow.isNull()) { + return QVariant(QPointF(0, 0)); + } + return asQuickItem()->position(); }); return toGlm(result.toPointF()); } void QmlWindowClass::setPosition(const glm::vec2& position) { - QQuickItem* targetWindow = asQuickItem(); DependencyManager::get()->executeOnUiThread([=] { - targetWindow->setPosition(QPointF(position.x, position.y)); + if (!_qmlWindow.isNull()) { + asQuickItem()->setPosition(QPointF(position.x, position.y)); + } }); } @@ -299,17 +234,21 @@ glm::vec2 toGlm(const QSizeF& size) { } glm::vec2 QmlWindowClass::getSize() const { - QQuickItem* targetWindow = asQuickItem(); QVariant result = DependencyManager::get()->returnFromUiThread([&]()->QVariant { + if (_qmlWindow.isNull()) { + return QVariant(QSizeF(0, 0)); + } + QQuickItem* targetWindow = asQuickItem(); return QSizeF(targetWindow->width(), targetWindow->height()); }); return toGlm(result.toSizeF()); } void QmlWindowClass::setSize(const glm::vec2& size) { - QQuickItem* targetWindow = asQuickItem(); DependencyManager::get()->executeOnUiThread([=] { - targetWindow->setSize(QSizeF(size.x, size.y)); + if (!_qmlWindow.isNull()) { + asQuickItem()->setSize(QSizeF(size.x, size.y)); + } }); } @@ -318,9 +257,10 @@ void QmlWindowClass::setSize(int width, int height) { } void QmlWindowClass::setTitle(const QString& title) { - QQuickItem* targetWindow = asQuickItem(); DependencyManager::get()->executeOnUiThread([=] { - targetWindow->setProperty(TITLE_PROPERTY, title); + if (!_qmlWindow.isNull()) { + asQuickItem()->setProperty(TITLE_PROPERTY, title); + } }); } @@ -345,7 +285,12 @@ void QmlWindowClass::hasClosed() { } void QmlWindowClass::raise() { - QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::QueuedConnection); + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + if (!_qmlWindow.isNull()) { + QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection); + } + }); } #include "QmlWindowClass.moc" diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 26152b1f24..eda6ce674c 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -10,42 +10,21 @@ #define hifi_ui_QmlWindowClass_h #include -#include +#include #include #include -#include + +#include class QScriptEngine; class QScriptContext; -class QmlWindowClass; -class QWebSocketServer; -class QWebSocket; - -class QmlScriptEventBridge : public QObject { - Q_OBJECT -public: - QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {} - -public slots : - void emitWebEvent(const QString& data); - void emitScriptEvent(const QString& data); - -signals: - void webEventReceived(const QString& data); - void scriptEventReceived(int windowId, const QString& data); - -private: - const QmlWindowClass* _webWindow { nullptr }; - QWebSocket *_socket { nullptr }; -}; // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWindowClass : public QObject { Q_OBJECT Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) - Q_PROPERTY(int windowId READ getWindowId CONSTANT) - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) - Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) + Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged) + Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) public: @@ -64,21 +43,19 @@ public slots: glm::vec2 getSize() const; void setSize(const glm::vec2& size); void setSize(int width, int height); - void setTitle(const QString& title); - - // Ugh.... do not want to do Q_INVOKABLE void raise(); Q_INVOKABLE void close(); - Q_INVOKABLE int getWindowId() const { return _windowId; }; - Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; }; + Q_INVOKABLE QObject* getEventBridge() { return this; }; // Scripts can use this to send a message to the QML object void sendToQml(const QVariant& message); signals: void visibilityChanged(bool visible); // Tool window + void positionChanged(); + void sizeChanged(); void moved(glm::vec2 position); void resized(QSizeF size); void closed(); @@ -92,19 +69,13 @@ protected: static QScriptValue internalConstructor(const QString& qmlSource, QScriptContext* context, QScriptEngine* engine, std::function function); - static void setupServer(); - static void registerObject(const QString& name, QObject* object); - static void deregisterObject(QObject* object); - static QWebSocketServer* _webChannelServer; QQuickItem* asQuickItem() const; - QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) }; // FIXME needs to be initialized in the ctor once we have support // for tool window panes in QML bool _toolWindow { false }; - const int _windowId; - QObject* _qmlWindow; + QPointer _qmlWindow; QString _source; }; diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 5ab56e1659..71a858e1e8 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -6,6 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OculusDisplayPlugin.h" +#include #include "OculusHelpers.h" const QString OculusDisplayPlugin::NAME("Oculus Rift"); @@ -54,6 +55,9 @@ void OculusDisplayPlugin::updateFrameData() { } void OculusDisplayPlugin::hmdPresent() { + + PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex) + if (!_currentSceneTexture) { return; } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 0cd9bac15f..0e7541066e 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -21,7 +21,7 @@ #include #include #include - +#include #include "OpenVrHelpers.h" Q_DECLARE_LOGGING_CATEGORY(displayplugins) @@ -69,6 +69,9 @@ void OpenVrDisplayPlugin::internalActivate() { _compositor = vr::VRCompositor(); Q_ASSERT(_compositor); + // enable async time warp + // _compositor->ForceInterleavedReprojectionOn(true); + // set up default sensor space such that the UI overlay will align with the front of the room. auto chaperone = vr::VRChaperone(); if (chaperone) { @@ -119,14 +122,11 @@ void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) { float vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); #if THREADED_PRESENT - // TODO: this seems awfuly long, 44ms total, but it produced the best results. + // 3 frames of prediction + vsyncToPhotons = 44ms total const float NUM_PREDICTION_FRAMES = 3.0f; float predictedSecondsFromNow = NUM_PREDICTION_FRAMES * frameDuration + vsyncToPhotons; #else - uint64_t frameCounter; - float timeSinceLastVsync; - _system->GetTimeSinceLastVsync(&timeSinceLastVsync, &frameCounter); - float predictedSecondsFromNow = 3.0f * frameDuration - timeSinceLastVsync + vsyncToPhotons; + float predictedSecondsFromNow = frameDuration + vsyncToPhotons; #endif vr::TrackedDevicePose_t predictedTrackedDevicePose[vr::k_unMaxTrackedDeviceCount]; @@ -144,6 +144,9 @@ void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) { } void OpenVrDisplayPlugin::hmdPresent() { + + PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex) + // Flip y-axis since GL UV coords are backwards. static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; @@ -152,6 +155,10 @@ void OpenVrDisplayPlugin::hmdPresent() { _compositor->Submit(vr::Eye_Left, &texture, &leftBounds); _compositor->Submit(vr::Eye_Right, &texture, &rightBounds); +} + +void OpenVrDisplayPlugin::postPreview() { + PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex) vr::TrackedDevicePose_t currentTrackedDevicePose[vr::k_unMaxTrackedDeviceCount]; _compositor->WaitGetPoses(currentTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, nullptr, 0); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index caaf75a4d0..78b76cb78d 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -35,6 +35,7 @@ protected: void hmdPresent() override; bool isHmdMounted() const override; + void postPreview() override; private: vr::IVRSystem* _system { nullptr }; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index d2c3649933..8ddf028dd2 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -51,7 +51,7 @@ vr::IVRSystem* acquireOpenVrSystem() { if (!activeHmd) { qCDebug(displayplugins) << "openvr: No vr::IVRSystem instance active, building"; vr::EVRInitError eError = vr::VRInitError_None; - activeHmd = vr::VR_Init(&eError); + activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); qCDebug(displayplugins) << "openvr display: HMD is " << activeHmd << " error is " << eError; } if (activeHmd) { diff --git a/server-console/src/log.js b/server-console/src/log.js index e45848e5a5..3634eaeaa7 100644 --- a/server-console/src/log.js +++ b/server-console/src/log.js @@ -44,6 +44,14 @@ ready = function() { var domainServer = remote.getGlobal('domainServer'); var acMonitor = remote.getGlobal('acMonitor'); + var pendingLines = { + 'ds': new Array(), + 'ac': new Array() + }; + + var UPDATE_INTERVAL = 16; // Update log at ~60 fps + var interval = setInterval(flushPendingLines, UPDATE_INTERVAL); + var logWatchers = { 'ds': { }, @@ -83,7 +91,7 @@ ready = function() { var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 }); logTail.on('line', function(msg) { - appendLogMessage(msg, stream); + pendingLines[stream].push(msg); }); logTail.on('error', function(error) { @@ -107,6 +115,7 @@ ready = function() { } window.onbeforeunload = function(e) { + clearInterval(interval); domainServer.removeListener('logs-updated', updateLogFiles); acMonitor.removeListener('logs-updated', updateLogFiles); }; @@ -164,14 +173,23 @@ ready = function() { return !filter || message.toLowerCase().indexOf(filter) >= 0; } - function appendLogMessage(msg, name) { + function appendLogMessages(name) { + var array = pendingLines[name]; + if (array.length === 0) { + return; + } + if (array.length > maxLogLines) { + array = array.slice(-maxLogLines); + } + + console.log(name, array.length); + var id = name == "ds" ? "domain-server" : "assignment-client"; var $pidLog = $('#' + id); - var size = ++tabStates[id].size; + var size = tabStates[id].size + array.length; if (size > maxLogLines) { - $pidLog.find('div.log-line:first').remove(); - removed = true; + $pidLog.find('div.log-line:lt(' + (size - maxLogLines) + ')').remove(); } var wasAtBottom = false; @@ -179,17 +197,25 @@ ready = function() { wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height()); } - var $logLine = $('
').text(msg); - if (!shouldDisplayLogMessage(msg)) { - $logLine.hide(); + for (line in array) { + var $logLine = $('
').text(array[line]); + if (!shouldDisplayLogMessage(array[line])) { + $logLine.hide(); + } + + $pidLog.append($logLine); } - $pidLog.append($logLine); + delete pendingLines[name]; + pendingLines[name] = new Array(); if (wasAtBottom) { $pidLog.scrollTop($pidLog[0].scrollHeight); } - + } + function flushPendingLines() { + appendLogMessages('ds'); + appendLogMessages('ac'); } // handle filtering of table rows on input change diff --git a/tests/physics/src/BulletTestUtils.h b/tests/physics/src/BulletTestUtils.h index 9166f80ba1..c047a2c1ce 100644 --- a/tests/physics/src/BulletTestUtils.h +++ b/tests/physics/src/BulletTestUtils.h @@ -14,6 +14,8 @@ #include +#include + // Implements functionality in QTestExtensions.h for glm types // There are 3 functions in here (which need to be defined for all types that use them): // diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index 825721641f..0b22ea1223 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -33,7 +33,7 @@ void MeshMassPropertiesTests::testParallelAxisTheorem() { // verity we can compute the inertia tensor of a box in two different ways: // (a) as one box // (b) as a combination of two partial boxes. - + btScalar bigBoxX = 7.0f; btScalar bigBoxY = 9.0f; btScalar bigBoxZ = 11.0f; @@ -62,9 +62,9 @@ void MeshMassPropertiesTests::testParallelAxisTheorem() { } void MeshMassPropertiesTests::testTetrahedron(){ - // given the four vertices of a tetrahedron verify the analytic formula for inertia + // given the four vertices of a tetrahedron verify the analytic formula for inertia // agrees with expected results - + // these numbers from the Tonon paper: btVector3 points[4]; points[0] = btVector3(8.33220f, -11.86875f, 0.93355f); @@ -102,14 +102,14 @@ void MeshMassPropertiesTests::testTetrahedron(){ } btMatrix3x3 inertia; computeTetrahedronInertia(volume, points, inertia); - + QCOMPARE_WITH_ABS_ERROR(volume, expectedVolume, acceptableRelativeError * volume); - + QCOMPARE_WITH_RELATIVE_ERROR(inertia, expectedInertia, acceptableRelativeError); } void MeshMassPropertiesTests::testOpenTetrahedonMesh() { - // given the simplest possible mesh (open, with one triangle) + // given the simplest possible mesh (open, with one triangle) // verify MeshMassProperties computes the right nubers // these numbers from the Tonon paper: @@ -155,7 +155,7 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { void MeshMassPropertiesTests::testClosedTetrahedronMesh() { // given a tetrahedron as a closed mesh of four tiangles // verify MeshMassProperties computes the right nubers - + // these numbers from the Tonon paper: VectorOfPoints points; points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); @@ -186,7 +186,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { // compute mass properties MeshMassProperties mesh(points, triangles); - + // verify QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume); QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError); @@ -210,7 +210,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { void MeshMassPropertiesTests::testBoxAsMesh() { // verify that a mesh box produces the same mass properties as the analytic box. - + // build a box: // / // y @@ -265,7 +265,7 @@ void MeshMassPropertiesTests::testBoxAsMesh() { MeshMassProperties mesh(points, triangles); // verify - + QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume); QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError); diff --git a/tests/physics/src/ShapeManagerTests.cpp b/tests/physics/src/ShapeManagerTests.cpp index 1ee7cd561e..66ac9d0c4a 100644 --- a/tests/physics/src/ShapeManagerTests.cpp +++ b/tests/physics/src/ShapeManagerTests.cpp @@ -21,7 +21,7 @@ void ShapeManagerTests::testShapeAccounting() { ShapeManager shapeManager; ShapeInfo info; info.setBox(glm::vec3(1.0f, 1.0f, 1.0f)); - + int numReferences = shapeManager.getNumReferences(info); QCOMPARE(numReferences, 0); @@ -42,10 +42,10 @@ void ShapeManagerTests::testShapeAccounting() { QCOMPARE(numReferences, expectedNumReferences); // release all references - bool released = shapeManager.releaseShape(info); + bool released = shapeManager.releaseShape(shape); numReferences--; while (numReferences > 0) { - released = shapeManager.releaseShape(info) && released; + released = shapeManager.releaseShape(shape) && released; numReferences--; } QCOMPARE(released, true); @@ -69,7 +69,7 @@ void ShapeManagerTests::testShapeAccounting() { QCOMPARE(numReferences, 1); // release reference and verify that it is collected as garbage - released = shapeManager.releaseShape(info); + released = shapeManager.releaseShape(shape); shapeManager.collectGarbage(); QCOMPARE(shapeManager.getNumShapes(), 0); QCOMPARE(shapeManager.hasShape(shape), false); @@ -183,3 +183,58 @@ void ShapeManagerTests::addCapsuleShape() { QCOMPARE(shape, otherShape); */ } + +void ShapeManagerTests::addCompoundShape() { + // initialize some points for generating tetrahedral convex hulls + QVector tetrahedron; + tetrahedron.push_back(glm::vec3(1.0f, 1.0f, 1.0f)); + tetrahedron.push_back(glm::vec3(1.0f, -1.0f, -1.0f)); + tetrahedron.push_back(glm::vec3(-1.0f, 1.0f, -1.0f)); + tetrahedron.push_back(glm::vec3(-1.0f, -1.0f, 1.0f)); + int numHullPoints = tetrahedron.size(); + + // compute the points of the hulls + QVector< QVector > hulls; + int numHulls = 5; + glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f); + for (int i = 0; i < numHulls; ++i) { + glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal; + QVector hull; + float radius = (float)(i + 1); + for (int j = 0; j < numHullPoints; ++j) { + glm::vec3 point = radius * tetrahedron[j] + offset; + hull.push_back(point); + } + hulls.push_back(hull); + } + + // create the ShapeInfo + ShapeInfo info; + info.setConvexHulls(hulls); + + // create the shape + ShapeManager shapeManager; + btCollisionShape* shape = shapeManager.getShape(info); + QVERIFY(shape != nullptr); + + // verify the shape is correct type + QCOMPARE(shape->getShapeType(), (int)COMPOUND_SHAPE_PROXYTYPE); + + // verify the shape has correct number of children + btCompoundShape* compoundShape = static_cast(shape); + QCOMPARE(compoundShape->getNumChildShapes(), numHulls); + + // verify manager has only one shape + QCOMPARE(shapeManager.getNumShapes(), 1); + QCOMPARE(shapeManager.getNumReferences(info), 1); + + // release the shape + shapeManager.releaseShape(shape); + QCOMPARE(shapeManager.getNumShapes(), 1); + QCOMPARE(shapeManager.getNumReferences(info), 0); + + // collect garbage + shapeManager.collectGarbage(); + QCOMPARE(shapeManager.getNumShapes(), 0); + QCOMPARE(shapeManager.getNumReferences(info), 0); +} diff --git a/tests/physics/src/ShapeManagerTests.h b/tests/physics/src/ShapeManagerTests.h index a4b7fbecd1..cff02dcfa4 100644 --- a/tests/physics/src/ShapeManagerTests.h +++ b/tests/physics/src/ShapeManagerTests.h @@ -16,7 +16,7 @@ class ShapeManagerTests : public QObject { Q_OBJECT - + private slots: void testShapeAccounting(); void addManyShapes(); @@ -24,6 +24,7 @@ private slots: void addSphereShape(); void addCylinderShape(); void addCapsuleShape(); + void addCompoundShape(); }; #endif // hifi_ShapeManagerTests_h diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index 533e6371e9..2b5e306b09 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -77,7 +77,7 @@ UDTTest::UDTTest(int& argc, char** argv) : // randomize the seed for packet size randomization srand(time(NULL)); - + _socket.bind(QHostAddress::AnyIPv4, _argumentParser.value(PORT_OPTION).toUInt()); qDebug() << "Test socket is listening on" << _socket.localPort();