diff --git a/CMakeLists.txt b/CMakeLists.txt index 38bcb42e26..b2f35b1443 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,3 +213,12 @@ endif () if (ANDROID OR DESKTOP_GVR) add_subdirectory(gvr-interface) endif () + +if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + SET( HIFI_MEMORY_DEBUGGING true ) +endif () +if (HIFI_MEMORY_DEBUGGING) + if (UNIX) + MESSAGE("-- Memory debugging is enabled") + endif (UNIX) +endif () diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index edd68e12bf..315eeb6b83 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -18,4 +18,7 @@ if (UNIX) endif (UNIX) include_application_version() -copy_dlls_beside_windows_executable() \ No newline at end of file + +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake new file mode 100644 index 0000000000..cb907efa96 --- /dev/null +++ b/cmake/macros/MemoryDebugger.cmake @@ -0,0 +1,21 @@ +# +# MemoryDebugger.cmake +# +# Copyright 2015 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(SETUP_MEMORY_DEBUGGER) +if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + SET( HIFI_MEMORY_DEBUGGING true ) +endif () + +if (HIFI_MEMORY_DEBUGGING) + if (UNIX) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") + endif (UNIX) +endif () +endmacro(SETUP_MEMORY_DEBUGGER) diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index e4fa1d874d..d2f30b6c25 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME domain-server) +setup_memory_debugger() + if (UPPER_CMAKE_BUILD_TYPE MATCHES DEBUG AND NOT WIN32) set(_SHOULD_SYMLINK_RESOURCES TRUE) else () diff --git a/domain-server/resources/web/index.shtml b/domain-server/resources/web/index.shtml index 0f720ebe79..ea941a73fd 100644 --- a/domain-server/resources/web/index.shtml +++ b/domain-server/resources/web/index.shtml @@ -31,7 +31,7 @@ <%- node.username %> <%- node.public.ip %>:<%- node.public.port %> <%- node.local.ip %>:<%- node.local.port %> - <%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %> + <%- node.uptime %> <%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %> diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index 0b29d4e6c9..09f85a7047 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -9,9 +9,9 @@ $(document).ready(function(){ json.nodes.sort(function(a, b){ if (a.type === b.type) { - if (a.wake_timestamp < b.wake_timestamp) { + if (a.uptime < b.uptime) { return 1; - } else if (a.wake_timestamp > b.wake_timestamp) { + } else if (a.uptime > b.uptime) { return -1; } else { return 0; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 071626ef1e..23957380e6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1480,7 +1480,7 @@ const char JSON_KEY_PUBLIC_SOCKET[] = "public"; const char JSON_KEY_LOCAL_SOCKET[] = "local"; const char JSON_KEY_POOL[] = "pool"; const char JSON_KEY_PENDING_CREDITS[] = "pending_credits"; -const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp"; +const char JSON_KEY_UPTIME[] = "uptime"; const char JSON_KEY_USERNAME[] = "username"; const char JSON_KEY_VERSION[] = "version"; QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { @@ -1502,7 +1502,7 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket()); // add the node uptime in our list - nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp()); + nodeJson[JSON_KEY_UPTIME] = QString::number(double(QDateTime::currentMSecsSinceEpoch() - node->getWakeTimestamp()) / 1000.0); // if the node has pool information, add it DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); diff --git a/examples/toys/basketball.js b/examples/toys/basketball.js new file mode 100644 index 0000000000..d30dce6e72 --- /dev/null +++ b/examples/toys/basketball.js @@ -0,0 +1,82 @@ +// +// basketball.js +// examples +// +// Created by Philip Rosedale on August 20, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var basketballURL = HIFI_PUBLIC_BUCKET + "models/content/basketball2.fbx"; +var collisionSoundURL = HIFI_PUBLIC_BUCKET + "sounds/basketball/basketball.wav"; + + +var basketball = null; +var originalPosition = null; +var hasMoved = false; + +var GRAVITY = -9.8; +var DISTANCE_IN_FRONT_OF_ME = 1.0; +var START_MOVE = 0.01; +var DIAMETER = 0.30; + +function makeBasketball() { + var position = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, + { x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME })); + var rotation = Quat.multiply(MyAvatar.orientation, + Quat.fromPitchYawRollDegrees(0, -90, 0)); + basketball = Entities.addEntity({ + type: "Model", + position: position, + rotation: rotation, + dimensions: { x: DIAMETER, + y: DIAMETER, + z: DIAMETER }, + collisionsWillMove: true, + collisionSoundURL: collisionSoundURL, + modelURL: basketballURL, + restitution: 1.0, + linearDamping: 0.00001, + shapeType: "sphere" + }); + originalPosition = position; +} + +function update() { + if (!basketball) { + makeBasketball(); + } else { + var newProperties = Entities.getEntityProperties(basketball); + var moved = Vec3.length(Vec3.subtract(originalPosition, newProperties.position)); + if (!hasMoved && (moved > START_MOVE)) { + hasMoved = true; + Entities.editEntity(basketball, { gravity: {x: 0, y: GRAVITY, z: 0 }}); + } + var MAX_DISTANCE = 10.0; + var distance = Vec3.length(Vec3.subtract(MyAvatar.position, newProperties.position)); + if (distance > MAX_DISTANCE) { + deleteStuff(); + } + } +} + +function scriptEnding() { + deleteStuff(); +} + +function deleteStuff() { + if (basketball != null) { + Entities.deleteEntity(basketball); + basketball = null; + hasMoved = false; + } +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + diff --git a/examples/toys/grenade.js b/examples/toys/grenade.js new file mode 100644 index 0000000000..b2dfebb888 --- /dev/null +++ b/examples/toys/grenade.js @@ -0,0 +1,201 @@ +// +// Grenade.js +// examples +// +// Created by Philip Rosedale on August 20, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var grenadeURL = HIFI_PUBLIC_BUCKET + "models/props/grenade/grenade.fbx"; +var fuseSoundURL = HIFI_PUBLIC_BUCKET + "sounds/burningFuse.wav"; +var boomSoundURL = HIFI_PUBLIC_BUCKET + "sounds/explosion.wav"; + +var AudioRotationOffset = Quat.fromPitchYawRollDegrees(0, -90, 0); +var audioOptions = { + volume: 0.5, + loop: true +} + +var injector = null; + +var fuseSound = SoundCache.getSound(fuseSoundURL, audioOptions.isStereo); +var boomSound = SoundCache.getSound(boomSoundURL, audioOptions.isStereo); + +var grenade = null; +var particles = null; +var properties = null; +var originalPosition = null; +var isGrenade = false; +var isBurning = false; + +var animationSettings = JSON.stringify({ + running: true, + loop: true + }); +var explodeAnimationSettings = JSON.stringify({ + running: true, + loop: false + }); + +var GRAVITY = -9.8; +var TIME_TO_EXPLODE = 2500; +var DISTANCE_IN_FRONT_OF_ME = 1.0; + +function makeGrenade() { + var position = Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, + { x: 0, y: 0.0, z: -DISTANCE_IN_FRONT_OF_ME })); + var rotation = Quat.multiply(MyAvatar.orientation, + Quat.fromPitchYawRollDegrees(0, -90, 0)); + grenade = Entities.addEntity({ + type: "Model", + position: position, + rotation: rotation, + dimensions: { x: 0.09, + y: 0.20, + z: 0.09 }, + collisionsWillMove: true, + modelURL: grenadeURL, + shapeType: "box" + }); + + properties = Entities.getEntityProperties(grenade); + audioOptions.position = position; + audioOptions.orientation = rotation; + originalPosition = position; +} + +function update() { + if (!grenade) { + makeGrenade(); + } else { + var newProperties = Entities.getEntityProperties(grenade); + if (!isBurning) { + // If moved, start fuse + var FUSE_START_MOVE = 0.01; + if (Vec3.length(Vec3.subtract(newProperties.position, originalPosition)) > FUSE_START_MOVE) { + isBurning = true; + // Create fuse particles + particles = Entities.addEntity({ + type: "ParticleEffect", + animationSettings: animationSettings, + position: newProperties.position, + textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png', + emitRate: 100, + emitStrength: 2.0, + emitDirection: { x: 0.0, y: 1.0, z: 0.0 }, + color: { red: 200, green: 0, blue: 0 }, + lifespan: 10.0, + visible: true, + locked: false + + }); + // Start fuse sound + injector = Audio.playSound(fuseSound, audioOptions); + // Start explosion timer + Script.setTimeout(boom, TIME_TO_EXPLODE); + originalPosition = newProperties.position; + // Add gravity + Entities.editEntity(grenade, { gravity: {x: 0, y: GRAVITY, z: 0 }}); + } + } + + if (newProperties.type === "Model") { + if (newProperties.position != properties.position) { + audioOptions.position = newProperties.position; + } + if (newProperties.orientation != properties.orientation) { + audioOptions.orientation = newProperties.orientation; + } + + properties = newProperties; + // Update sound location if playing + if (injector) { + injector.options = audioOptions; + } + if (particles) { + Entities.editEntity(particles, { position: newProperties.position }); + } + } else { + grenade = null; + Script.update.disconnect(update); + Script.scriptEnding.connect(scriptEnding); + scriptEnding(); + Script.stop(); + } + } +} +function boom() { + injector.stop(); + isBurning = false; + var audioOptions = { + position: properties.position, + volume: 0.75, + loop: false + } + Audio.playSound(boomSound, audioOptions); + Entities.addEntity({ + type: "ParticleEffect", + animationSettings: explodeAnimationSettings, + position: properties.position, + textures: 'https://raw.githubusercontent.com/ericrius1/SantasLair/santa/assets/smokeparticle.png', + emitRate: 200, + emitStrength: 3.0, + emitDirection: { x: 0.0, y: 1.0, z: 0.0 }, + color: { red: 255, green: 255, blue: 0 }, + lifespan: 2.0, + visible: true, + lifetime: 2, + locked: false + + }); + var BLAST_RADIUS = 20.0; + var LIFT_DEPTH = 2.0; + var epicenter = properties.position; + epicenter.y -= LIFT_DEPTH; + blowShitUp(epicenter, BLAST_RADIUS); + deleteStuff(); +} + +function blowShitUp(position, radius) { + var stuff = Entities.findEntities(position, radius); + var numMoveable = 0; + var STRENGTH = 3.5; + var SPIN_RATE = 20.0; + for (var i = 0; i < stuff.length; i++) { + var properties = Entities.getEntityProperties(stuff[i]); + if (properties.collisionsWillMove) { + var diff = Vec3.subtract(properties.position, position); + var distance = Vec3.length(diff); + var velocity = Vec3.sum(properties.velocity, Vec3.multiply(STRENGTH * 1.0 / distance, Vec3.normalize(diff))); + var angularVelocity = { x: Math.random() * SPIN_RATE, y: Math.random() * SPIN_RATE, z: Math.random() * SPIN_RATE }; + angularVelocity = Vec3.multiply( 1.0 / distance, angularVelocity); + Entities.editEntity(stuff[i], { velocity: velocity, + angularVelocity: angularVelocity }); + } + } +} + +function scriptEnding() { + deleteStuff(); +} + +function deleteStuff() { + if (grenade != null) { + Entities.deleteEntity(grenade); + grenade = null; + } + if (particles != null) { + Entities.deleteEntity(particles); + particles = null; + } +} + +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); + diff --git a/examples/utilities/tools/cookies.js b/examples/utilities/tools/cookies.js index 1a8695188b..d9fa999a13 100644 --- a/examples/utilities/tools/cookies.js +++ b/examples/utilities/tools/cookies.js @@ -183,6 +183,24 @@ var CHECK_MARK_COLOR = { this.onValueChanged(resetValue); }; + + Slider.prototype.setMinValue = function(minValue) { + var currentValue = this.getValue(); + this.minValue = minValue; + this.setValue(currentValue); + }; + Slider.prototype.getMinValue = function() { + return this.minValue; + }; + Slider.prototype.setMaxValue = function(maxValue) { + var currentValue = this.getValue(); + this.maxValue = maxValue; + this.setValue(currentValue); + }; + Slider.prototype.getMaxValue = function() { + return this.maxValue; + }; + Slider.prototype.onValueChanged = function(value) {}; Slider.prototype.getHeight = function() { @@ -1396,6 +1414,14 @@ var CHECK_MARK_COLOR = { return null; }; + Panel.prototype.getWidget = function(name) { + var item = this.items[name]; + if (item != null) { + return item.widget; + } + return null; + }; + Panel.prototype.update = function(name) { var item = this.items[name]; if (item != null) { diff --git a/examples/utilities/tools/renderEngineDebug.js b/examples/utilities/tools/renderEngineDebug.js index d50a9c545c..49ac923436 100755 --- a/examples/utilities/tools/renderEngineDebug.js +++ b/examples/utilities/tools/renderEngineDebug.js @@ -12,59 +12,55 @@ Script.include("cookies.js"); var panel = new Panel(10, 100); -panel.newSlider("Num Feed Opaques", 0, 1000, - function(value) { }, - function() { return Scene.getEngineNumFeedOpaqueItems(); }, - function(value) { return (value); } +function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) { + this.subPanel = panel.newSubPanel(name); + + this.subPanel.newSlider("Num Feed", 0, 1, + function(value) { }, + feedGetter, + function(value) { return (value); }); + this.subPanel.newSlider("Num Drawn", 0, 1, + function(value) { }, + drawGetter, + function(value) { return (value); }); + this.subPanel.newSlider("Max Drawn", -1, 1, + capSetter, + capGetter, + function(value) { return (value); }); + + this.update = function () { + var numFeed = this.subPanel.get("Num Feed"); + this.subPanel.set("Num Feed", numFeed); + this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn")); + + var numMax = Math.max(numFeed, 1); + this.subPanel.getWidget("Num Feed").setMaxValue(numMax); + this.subPanel.getWidget("Num Drawn").setMaxValue(numMax); + this.subPanel.getWidget("Max Drawn").setMaxValue(numMax); + }; +}; + +var opaquesCounter = new CounterWidget(panel, "Opaques", + function () { return Scene.getEngineNumFeedOpaqueItems(); }, + function () { return Scene.getEngineNumDrawnOpaqueItems(); }, + function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); }, + function () { return Scene.getEngineMaxDrawnOpaqueItems(); } ); -panel.newSlider("Num Drawn Opaques", 0, 1000, - function(value) { }, - function() { return Scene.getEngineNumDrawnOpaqueItems(); }, - function(value) { return (value); } +var transparentsCounter = new CounterWidget(panel, "Transparents", + function () { return Scene.getEngineNumFeedTransparentItems(); }, + function () { return Scene.getEngineNumDrawnTransparentItems(); }, + function(value) { Scene.setEngineMaxDrawnTransparentItems(value); }, + function () { return Scene.getEngineMaxDrawnTransparentItems(); } ); -panel.newSlider("Max Drawn Opaques", -1, 1000, - function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); }, - function() { return Scene.getEngineMaxDrawnOpaqueItems(); }, - function(value) { return (value); } +var overlaysCounter = new CounterWidget(panel, "Overlays", + function () { return Scene.getEngineNumFeedOverlay3DItems(); }, + function () { return Scene.getEngineNumDrawnOverlay3DItems(); }, + function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); }, + function () { return Scene.getEngineMaxDrawnOverlay3DItems(); } ); -panel.newSlider("Num Feed Transparents", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumFeedTransparentItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Num Drawn Transparents", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumDrawnTransparentItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Max Drawn Transparents", -1, 100, - function(value) { Scene.setEngineMaxDrawnTransparentItems(value); }, - function() { return Scene.getEngineMaxDrawnTransparentItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Num Feed Overlay3Ds", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumFeedOverlay3DItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Num Drawn Overlay3Ds", 0, 100, - function(value) { }, - function() { return Scene.getEngineNumDrawnOverlay3DItems(); }, - function(value) { return (value); } -); - -panel.newSlider("Max Drawn Overlay3Ds", -1, 100, - function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); }, - function() { return Scene.getEngineMaxDrawnOverlay3DItems(); }, - function(value) { return (value); } -); panel.newCheckbox("Display status", function(value) { Scene.setEngineDisplayItemStatus(value); }, @@ -75,31 +71,9 @@ panel.newCheckbox("Display status", var tickTackPeriod = 500; function updateCounters() { - var numFeedOpaques = panel.get("Num Feed Opaques"); - var numFeedTransparents = panel.get("Num Feed Transparents"); - var numFeedOverlay3Ds = panel.get("Num Feed Overlay3Ds"); - - panel.set("Num Feed Opaques", numFeedOpaques); - panel.set("Num Drawn Opaques", panel.get("Num Drawn Opaques")); - panel.set("Num Feed Transparents", numFeedTransparents); - panel.set("Num Drawn Transparents", panel.get("Num Drawn Transparents")); - panel.set("Num Feed Overlay3Ds", numFeedOverlay3Ds); - panel.set("Num Drawn Overlay3Ds", panel.get("Num Drawn Overlay3Ds")); - - var numMax = Math.max(numFeedOpaques * 1.2, 1); - panel.getWidget("Num Feed Opaques").setMaxValue(numMax); - panel.getWidget("Num Drawn Opaques").setMaxValue(numMax); - panel.getWidget("Max Drawn Opaques").setMaxValue(numMax); - - numMax = Math.max(numFeedTransparents * 1.2, 1); - panel.getWidget("Num Feed Transparents").setMaxValue(numMax); - panel.getWidget("Num Drawn Transparents").setMaxValue(numMax); - panel.getWidget("Max Drawn Transparents").setMaxValue(numMax); - - numMax = Math.max(numFeedOverlay3Ds * 1.2, 1); - panel.getWidget("Num Feed Overlay3Ds").setMaxValue(numMax); - panel.getWidget("Num Drawn Overlay3Ds").setMaxValue(numMax); - panel.getWidget("Max Drawn Overlay3Ds").setMaxValue(numMax); + opaquesCounter.update(); + transparentsCounter.update(); + overlaysCounter.update(); } Script.setInterval(updateCounters, tickTackPeriod); diff --git a/gvr-interface/CMakeLists.txt b/gvr-interface/CMakeLists.txt index a986fcae0d..c4880a80b6 100644 --- a/gvr-interface/CMakeLists.txt +++ b/gvr-interface/CMakeLists.txt @@ -88,4 +88,6 @@ if (ANDROID) endif (ANDROID) -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index 13d89fc4a2..d62192bcec 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -1,9 +1,11 @@ set(TARGET_NAME ice-server) +setup_memory_debugger() + # setup the project and link required Qt modules setup_hifi_project(Network) # link the shared hifi libraries link_hifi_libraries(embedded-webserver networking shared) -copy_dlls_beside_windows_executable() \ No newline at end of file +copy_dlls_beside_windows_executable() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d7ee9228f4..0c531dfacb 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -205,4 +205,6 @@ else (APPLE) endif() endif (APPLE) +setup_memory_debugger() + copy_dlls_beside_windows_executable() diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a28435e258..aa18e602bf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -737,6 +737,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : }); connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); + + setVSyncEnabled(); // make sure VSync is set properly at startup } void Application::aboutToQuit() { @@ -1744,6 +1746,27 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { return; } +#ifndef Q_OS_MAC + // If in full screen, and our main windows menu bar is hidden, and we're close to the top of the QMainWindow + // then show the menubar. + if (_window->isFullScreen()) { + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + static const int MENU_TOGGLE_AREA = 10; + if (!menuBar->isVisible()) { + if (event->pos().y() <= MENU_TOGGLE_AREA) { + menuBar->setVisible(true); + } + } else { + if (event->pos().y() > MENU_TOGGLE_AREA) { + menuBar->setVisible(false); + } + } + } + } +#endif + + _entities.mouseMoveEvent(event, deviceID); _controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts @@ -4985,6 +5008,14 @@ void Application::setFullscreen(const QScreen* target) { #endif _window->windowHandle()->setScreen((QScreen*)target); _window->showFullScreen(); + +#ifndef Q_OS_MAC + // also hide the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + menuBar->setVisible(false); + } +#endif } void Application::unsetFullscreen(const QScreen* avoid) { @@ -5015,6 +5046,14 @@ void Application::unsetFullscreen(const QScreen* avoid) { #else _window->setGeometry(targetGeometry); #endif + +#ifndef Q_OS_MAC + // also show the QMainWindow's menuBar + QMenuBar* menuBar = _window->menuBar(); + if (menuBar) { + menuBar->setVisible(true); + } +#endif } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f074dc5ac7..11bc38c85e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -444,6 +444,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Connexion, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index ca46b80f92..278da363d1 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -168,6 +168,7 @@ namespace MenuOption { const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DeleteBookmark = "Delete Bookmark..."; const QString DisableActivityLogger = "Disable Activity Logger"; + const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment"; const QString DisableLightEntities = "Disable Light Entities"; const QString DisableNackPackets = "Disable Entity NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 09d572c31d..0b564f3574 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -106,16 +106,17 @@ bool ModelPackager::loadModel() { } qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath(); QByteArray fbxContents = fbx.readAll(); - _geometry = readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath()); - + + _geometry.reset(readFBX(fbxContents, QVariantHash(), _fbxInfo.filePath())); + // make sure we have some basic mappings - populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry); + populateBasicMapping(_mapping, _fbxInfo.filePath(), *_geometry); return true; } bool ModelPackager::editProperties() { // open the dialog to configure the rest - ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry); + ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), *_geometry); if (properties.exec() == QDialog::Rejected) { return false; } @@ -339,7 +340,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename void ModelPackager::listTextures() { _textures.clear(); - foreach (FBXMesh mesh, _geometry.meshes) { + foreach (FBXMesh mesh, _geometry->meshes) { foreach (FBXMeshPart part, mesh.parts) { if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() && !_textures.contains(part.diffuseTexture.filename)) { diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index c681ae436f..10942833f9 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -39,11 +39,11 @@ private: QString _texDir; QVariantHash _mapping; - FBXGeometry _geometry; + std::unique_ptr _geometry; QStringList _textures; }; -#endif // hifi_ModelPackager_h \ No newline at end of file +#endif // hifi_ModelPackager_h diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index c1a1d11268..19f84018f8 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -196,7 +196,6 @@ void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("hand"); getHand()->simulate(deltaTime, false); } - _skeletonModel.setLODDistance(getLODDistance()); if (!_shouldRenderBillboard && inViewFrustum) { { @@ -562,24 +561,22 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { } void Avatar::fixupModelsInScene() { - if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { - return; - } // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene render::ScenePointer scene = Application::getInstance()->getMain3DScene(); render::PendingChanges pendingChanges; - if (_skeletonModel.needsFixupInScene()) { + if (_skeletonModel.isRenderable() && _skeletonModel.needsFixupInScene()) { _skeletonModel.removeFromScene(scene, pendingChanges); _skeletonModel.addToScene(scene, pendingChanges); } - if (getHead()->getFaceModel().needsFixupInScene()) { - getHead()->getFaceModel().removeFromScene(scene, pendingChanges); - getHead()->getFaceModel().addToScene(scene, pendingChanges); + Model& faceModel = getHead()->getFaceModel(); + if (faceModel.isRenderable() && faceModel.needsFixupInScene()) { + faceModel.removeFromScene(scene, pendingChanges); + faceModel.addToScene(scene, pendingChanges); } for (auto attachmentModel : _attachmentModels) { - if (attachmentModel->needsFixupInScene()) { + if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges); } @@ -621,11 +618,8 @@ void Avatar::simulateAttachments(float deltaTime) { int jointIndex = getJointIndex(attachment.jointName); glm::vec3 jointPosition; glm::quat jointRotation; - if (!isMyAvatar()) { - model->setLODDistance(getLODDistance()); - } if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && - _skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) { + _skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) { model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale @@ -978,12 +972,12 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { void Avatar::setFaceModelURL(const QUrl& faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); - getHead()->getFaceModel().setURL(_faceModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar()); + getHead()->getFaceModel().setURL(_faceModelURL); } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); - _skeletonModel.setURL(_skeletonModelURL, AvatarData::defaultFullAvatarModelUrl(), true, !isMyAvatar()); + _skeletonModel.setURL(_skeletonModelURL); } void Avatar::setAttachmentData(const QVector& attachmentData) { diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 9ab2c83a79..3806dd6edc 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -233,9 +233,6 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { _saccade = glm::vec3(); } - if (!isMine) { - _faceModel.setLODDistance(static_cast(_owningAvatar)->getLODDistance()); - } _leftEyePosition = _rightEyePosition = getPosition(); if (!billboard) { _faceModel.simulate(deltaTime); @@ -277,6 +274,10 @@ void Head::calculateMouthShapes() { void Head::applyEyelidOffset(glm::quat headOrientation) { // Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches. + if (Menu::getInstance()->isOptionChecked(MenuOption::DisableEyelidAdjustment)) { + return; + } + glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FRONT, getLookAtPosition() - _eyePosition); eyeRotation = eyeRotation * glm::angleAxis(safeEulerAngles(headOrientation).y, IDENTITY_UP); // Rotation w.r.t. head float eyePitch = safeEulerAngles(eyeRotation).x; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 52473a6d47..eada41eb29 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -843,10 +843,11 @@ void MyAvatar::sendKillAvatar() { DependencyManager::get()->broadcastToNodes(std::move(killPacket), NodeSet() << NodeType::AvatarMixer); } +static int counter = 0; void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head - // + // And set the correctedLookAt for all (nearby) avatars that are looking at me. _lookAtTargetAvatar.reset(); _targetAvatarPosition = glm::vec3(0.0f); @@ -870,14 +871,51 @@ void MyAvatar::updateLookAtTargetAvatar() { smallestAngleTo = angleTo; } if (Application::getInstance()->isLookingAtMyAvatar(avatar)) { + // Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face. - // Offset their gaze according to whether they're looking at one of my eyes or my mouth. - glm::vec3 gazeOffset = avatar->getHead()->getLookAtPosition() - getHead()->getEyePosition(); - const float HUMAN_EYE_SEPARATION = 0.065f; - float myEyeSeparation = glm::length(getHead()->getLeftEyePosition() - getHead()->getRightEyePosition()); - gazeOffset = gazeOffset * HUMAN_EYE_SEPARATION / myEyeSeparation; - avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() - + gazeOffset); + glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); // A position, in world space, on my avatar. + + // The camera isn't at the point midway between the avatar eyes. (Even without an HMD, the head can be offset a bit.) + // Let's get everything to world space: + glm::vec3 avatarLeftEye = getHead()->getLeftEyePosition(); + glm::vec3 avatarRightEye = getHead()->getRightEyePosition(); + // When not in HMD, these might both answer identity (i.e., the bridge of the nose). That's ok. + // By my inpsection of the code and live testing, getEyeOffset and getEyePose are the same. (Application hands identity as offset matrix.) + // This might be more work than needed for any given use, but as we explore different formulations, we go mad if we don't work in world space. + glm::mat4 leftEye = Application::getInstance()->getEyeOffset(Eye::Left); + glm::mat4 rightEye = Application::getInstance()->getEyeOffset(Eye::Right); + glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); + glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); + auto humanSystem = Application::getInstance()->getViewFrustum(); + glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal); + glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal); + + + // First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point. + // (We will be adding that offset to the camera position, after making some other adjustments.) + glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition(); + + // Scale by proportional differences between avatar and human. + float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye); + float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye); + gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation; + + // If the camera is also not oriented with the head, adjust by getting the offset in head-space... + /* Not needed (i.e., code is a no-op), but I'm leaving the example code here in case something like this is needed someday. + glm::quat avatarHeadOrientation = getHead()->getOrientation(); + glm::vec3 gazeOffsetLocalToHead = glm::inverse(avatarHeadOrientation) * gazeOffset; + // ... and treat that as though it were in camera space, bringing it back to world space. + // But camera is fudged to make the picture feel like the avatar's orientation. + glm::quat humanOrientation = humanSystem->getOrientation(); // or just avatar getOrienation() ? + gazeOffset = humanOrientation * gazeOffsetLocalToHead; + glm::vec3 corrected = humanSystem->getPosition() + gazeOffset; + */ + + // And now we can finally add that offset to the camera. + glm::vec3 corrected = Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset; + + avatar->getHead()->setCorrectedLookAtPosition(corrected); + } else { avatar->getHead()->clearCorrectedLookAtPosition(); } @@ -1114,6 +1152,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl getHead()->render(renderArgs, 1.0f, renderFrustum); } + // This is drawing the lookat vectors from our avatar to wherever we're looking. if (qApp->isHMDMode()) { glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); diff --git a/libraries/animation/CMakeLists.txt b/libraries/animation/CMakeLists.txt index 8c75d5620c..fc7fa23dcc 100644 --- a/libraries/animation/CMakeLists.txt +++ b/libraries/animation/CMakeLists.txt @@ -3,4 +3,6 @@ set(TARGET_NAME animation) # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Script) -link_hifi_libraries(shared gpu model fbx) \ No newline at end of file +setup_memory_debugger() + +link_hifi_libraries(shared gpu model fbx) diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 634e0589b7..7f3f393a8b 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -13,6 +13,7 @@ #include #include "AnimationCache.h" +#include "AnimationLogging.h" static int animationPointerMetaTypeId = qRegisterMetaType(); @@ -62,11 +63,15 @@ void AnimationReader::run() { QSharedPointer animation = _animation.toStrongRef(); if (!animation.isNull()) { QMetaObject::invokeMethod(animation.data(), "setGeometry", - Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString()))); + Q_ARG(FBXGeometry*, readFBX(_reply->readAll(), QVariantHash(), _reply->property("url").toString()))); } _reply->deleteLater(); } +bool Animation::isLoaded() const { + return _loaded && _geometry; +} + QStringList Animation::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; @@ -75,7 +80,7 @@ QStringList Animation::getJointNames() const { return result; } QStringList names; - foreach (const FBXJoint& joint, _geometry.joints) { + foreach (const FBXJoint& joint, _geometry->joints) { names.append(joint.name); } return names; @@ -88,15 +93,15 @@ QVector Animation::getFrames() const { Q_RETURN_ARG(QVector, result)); return result; } - return _geometry.animationFrames; + return _geometry->animationFrames; } const QVector& Animation::getFramesReference() const { - return _geometry.animationFrames; + return _geometry->animationFrames; } -void Animation::setGeometry(const FBXGeometry& geometry) { - _geometry = geometry; +void Animation::setGeometry(FBXGeometry* geometry) { + _geometry.reset(geometry); finishedLoading(true); } diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 6a0a77f659..3ff5957fa2 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -52,7 +52,10 @@ public: Animation(const QUrl& url); - const FBXGeometry& getGeometry() const { return _geometry; } + const FBXGeometry& getGeometry() const { return *_geometry; } + + virtual bool isLoaded() const override; + Q_INVOKABLE QStringList getJointNames() const; @@ -62,13 +65,13 @@ public: protected: - Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); + Q_INVOKABLE void setGeometry(FBXGeometry* geometry); virtual void downloadFinished(QNetworkReply* reply); private: - FBXGeometry _geometry; + std::unique_ptr _geometry; }; diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index 43a2016acf..c313aecbc0 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME audio-client) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Multimedia) @@ -25,4 +27,4 @@ if (APPLE) find_library(CoreAudio CoreAudio) find_library(CoreFoundation CoreFoundation) target_link_libraries(${TARGET_NAME} ${CoreAudio} ${CoreFoundation}) -endif () \ No newline at end of file +endif () diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index c03f588d94..a0d40b1a10 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME audio) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 22d57176a5..8fd7cb9ce5 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -77,9 +77,9 @@ void AudioInjector::injectAudio() { int byteOffset = (int) floorf(AudioConstants::SAMPLE_RATE * _options.secondOffset * (_options.stereo ? 2.0f : 1.0f)); byteOffset *= sizeof(int16_t); - _currentSendPosition = byteOffset; + _currentSendOffset = byteOffset; } else { - _currentSendPosition = 0; + _currentSendOffset = 0; } if (_options.localOnly) { @@ -119,7 +119,7 @@ void AudioInjector::injectLocally() { _localBuffer->setVolume(_options.volume); // give our current send position to the local buffer - _localBuffer->setCurrentOffset(_currentSendPosition); + _localBuffer->setCurrentOffset(_currentSendOffset); success = _localAudioInterface->outputLocalInjector(_options.stereo, this); @@ -144,9 +144,9 @@ void AudioInjector::injectLocally() { const uchar MAX_INJECTOR_VOLUME = 0xFF; void AudioInjector::injectToMixer() { - if (_currentSendPosition < 0 || - _currentSendPosition >= _audioData.size()) { - _currentSendPosition = 0; + if (_currentSendOffset < 0 || + _currentSendOffset >= _audioData.size()) { + _currentSendOffset = 0; } auto nodeList = DependencyManager::get(); @@ -203,15 +203,15 @@ void AudioInjector::injectToMixer() { // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks quint16 outgoingInjectedAudioSequenceNumber = 0; - while (_currentSendPosition < _audioData.size() && !_shouldStop) { + while (_currentSendOffset < _audioData.size() && !_shouldStop) { int bytesToCopy = std::min(((_options.stereo) ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, - _audioData.size() - _currentSendPosition); + _audioData.size() - _currentSendOffset); // Measure the loudness of this frame _loudness = 0.0f; for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) { - _loudness += abs(*reinterpret_cast(_audioData.data() + _currentSendPosition + i)) / + _loudness += abs(*reinterpret_cast(_audioData.data() + _currentSendOffset + i)) / (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); } _loudness /= (float)(bytesToCopy / sizeof(int16_t)); @@ -220,7 +220,7 @@ void AudioInjector::injectToMixer() { // pack the sequence number audioPacket->writePrimitive(outgoingInjectedAudioSequenceNumber); - + audioPacket->seek(positionOptionOffset); audioPacket->writePrimitive(_options.position); audioPacket->writePrimitive(_options.orientation); @@ -232,7 +232,7 @@ void AudioInjector::injectToMixer() { audioPacket->seek(audioDataOffset); // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet - audioPacket->write(_audioData.data() + _currentSendPosition, bytesToCopy); + audioPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy); // set the correct size used for this packet audioPacket->setPayloadSize(audioPacket->pos()); @@ -246,11 +246,11 @@ void AudioInjector::injectToMixer() { outgoingInjectedAudioSequenceNumber++; } - _currentSendPosition += bytesToCopy; + _currentSendOffset += bytesToCopy; // send two packets before the first sleep so the mixer can start playback right away - if (_currentSendPosition != bytesToCopy && _currentSendPosition < _audioData.size()) { + if (_currentSendOffset != bytesToCopy && _currentSendOffset < _audioData.size()) { // process events in case we have been told to stop and be deleted QCoreApplication::processEvents(); @@ -268,8 +268,8 @@ void AudioInjector::injectToMixer() { } } - if (shouldLoop && _currentSendPosition >= _audioData.size()) { - _currentSendPosition = 0; + if (shouldLoop && _currentSendOffset >= _audioData.size()) { + _currentSendOffset = 0; } } } diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index 88d3f1e151..d65925b865 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -31,7 +31,6 @@ class AbstractAudioInterface; class AudioInjector : public QObject { Q_OBJECT - Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: AudioInjector(QObject* parent); AudioInjector(Sound* sound, const AudioInjectorOptions& injectorOptions); @@ -39,7 +38,8 @@ public: bool isFinished() const { return _isFinished; } - int getCurrentSendPosition() const { return _currentSendPosition; } + int getCurrentSendOffset() const { return _currentSendOffset; } + void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; } AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; } bool isLocalOnly() const { return _options.localOnly; } @@ -58,9 +58,8 @@ public slots: void stopAndDeleteLater(); const AudioInjectorOptions& getOptions() const { return _options; } - void setOptions(const AudioInjectorOptions& options) { _options = options; } + void setOptions(const AudioInjectorOptions& options) { _options = options; } - void setCurrentSendPosition(int currentSendPosition) { _currentSendPosition = currentSendPosition; } float getLoudness() const { return _loudness; } bool isPlaying() const { return _isPlaying; } void restartPortionAfterFinished(); @@ -82,7 +81,7 @@ private: bool _isStarted = false; bool _isFinished = false; bool _shouldDeleteAfterFinish = false; - int _currentSendPosition = 0; + int _currentSendOffset = 0; AbstractAudioInterface* _localAudioInterface = NULL; AudioInjectorLocalBuffer* _localBuffer = NULL; }; diff --git a/libraries/auto-updater/CMakeLists.txt b/libraries/auto-updater/CMakeLists.txt index b3665af2cb..6960d8368d 100644 --- a/libraries/auto-updater/CMakeLists.txt +++ b/libraries/auto-updater/CMakeLists.txt @@ -1,3 +1,6 @@ set(TARGET_NAME auto-updater) + +setup_memory_debugger() + setup_hifi_library(Network) link_hifi_libraries(shared networking) diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index acc939b25c..b05c667c71 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME avatars) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Script) diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp index e7d94f0735..29544924b2 100644 --- a/libraries/avatars/src/Player.cpp +++ b/libraries/avatars/src/Player.cpp @@ -371,7 +371,7 @@ void Player::setAudioInjectorPosition() { int MSEC_PER_SEC = 1000; int FRAME_SIZE = sizeof(AudioConstants::AudioSample) * _recording->numberAudioChannel(); int currentAudioFrame = elapsed() * FRAME_SIZE * (AudioConstants::SAMPLE_RATE / MSEC_PER_SEC); - _injector->setCurrentSendPosition(currentAudioFrame); + _injector->setCurrentSendOffset(currentAudioFrame); } void Player::setPlayFromCurrentLocation(bool playFromCurrentLocation) { diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index 321b13f191..79b41fa957 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME display-plugins) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(OpenGL) @@ -31,4 +33,4 @@ if (WIN32) find_package(OpenVR REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) -endif() \ No newline at end of file +endif() diff --git a/libraries/embedded-webserver/CMakeLists.txt b/libraries/embedded-webserver/CMakeLists.txt index 955487e540..2d8915998b 100644 --- a/libraries/embedded-webserver/CMakeLists.txt +++ b/libraries/embedded-webserver/CMakeLists.txt @@ -1,4 +1,6 @@ set(TARGET_NAME embedded-webserver) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules -setup_hifi_library(Network) \ No newline at end of file +setup_hifi_library(Network) diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 19443e01da..72436fc55e 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include @@ -19,16 +20,19 @@ #include "EmbeddedWebserverLogging.h" #include "HTTPManager.h" +const int SOCKET_ERROR_EXIT_CODE = 2; +const int SOCKET_CHECK_INTERVAL_IN_MS = 30000; + HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) : QTcpServer(parent), _documentRoot(documentRoot), - _requestHandler(requestHandler) + _requestHandler(requestHandler), + _port(port) { - // start listening on the passed port - if (!listen(QHostAddress("0.0.0.0"), port)) { - qCDebug(embeddedwebserver) << "Failed to open HTTP server socket:" << errorString(); - return; - } + bindSocket(); + _isListeningTimer = new QTimer(this); + connect(_isListeningTimer, &QTimer::timeout, this, &HTTPManager::isTcpServerListening); + _isListeningTimer->start(SOCKET_CHECK_INTERVAL_IN_MS); } void HTTPManager::incomingConnection(qintptr socketDescriptor) { @@ -157,3 +161,19 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) { return _requestHandler && _requestHandler->handleHTTPRequest(connection, url); } + +void HTTPManager::isTcpServerListening() { + if (!isListening()) { + qCWarning(embeddedwebserver) << "Socket on port " << QString::number(_port) << " is no longer listening"; + bindSocket(); + } +} + +bool HTTPManager::bindSocket() { + qCDebug(embeddedwebserver) << "Attempting to bind TCP socket on port " << QString::number(_port); + if (!listen(QHostAddress::Any, _port)) { + qCritical() << "Failed to open HTTP server socket:" << errorString() << " can't continue"; + QCoreApplication::exit(SOCKET_ERROR_EXIT_CODE); + } + return true; +} \ No newline at end of file diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index 83c4103c15..6375b10205 100644 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -17,6 +17,7 @@ #define hifi_HTTPManager_h #include +#include class HTTPConnection; class HTTPSConnection; @@ -35,14 +36,22 @@ public: HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); + +private slots: + void isTcpServerListening(); + +private: + bool bindSocket(); protected: /// Accepts all pending connections virtual void incomingConnection(qintptr socketDescriptor); virtual bool requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url); -protected: + QString _documentRoot; HTTPRequestHandler* _requestHandler; + QTimer* _isListeningTimer; + const quint16 _port; }; #endif // hifi_HTTPManager_h diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index c4dddb8971..3387715348 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -26,4 +26,6 @@ find_package(PolyVox REQUIRED) target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) +setup_memory_debugger() + link_hifi_libraries(shared gpu script-engine render render-utils) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index bb0a35f7b0..930a684617 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -38,7 +38,7 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) { _model = getModel(); _needsInitialSimulation = true; - _model->setURL(getCompoundShapeURL(), QUrl(), true, true); + _model->setURL(getCompoundShapeURL()); } if (oldPosition != getPosition() || oldRotation != getRotation() || @@ -85,7 +85,7 @@ void RenderableZoneEntityItem::initialSimulation() { void RenderableZoneEntityItem::updateGeometry() { if (_model && !_model->isActive() && hasCompoundShapeURL()) { // Since we have a delayload, we need to update the geometry if it has been downloaded - _model->setURL(getCompoundShapeURL(), QUrl(), true); + _model->setURL(getCompoundShapeURL()); } if (_model && _model->isActive() && _needsInitialSimulation) { initialSimulation(); diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index f7936ff125..368257661e 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME entities) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network Script) diff --git a/libraries/environment/CMakeLists.txt b/libraries/environment/CMakeLists.txt index a2ee9e3f55..fbdc614d26 100644 --- a/libraries/environment/CMakeLists.txt +++ b/libraries/environment/CMakeLists.txt @@ -7,4 +7,6 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared networking) \ No newline at end of file +setup_memory_debugger() + +link_hifi_libraries(shared networking) diff --git a/libraries/fbx/CMakeLists.txt b/libraries/fbx/CMakeLists.txt index 1ce1c74922..c06bb0efc1 100644 --- a/libraries/fbx/CMakeLists.txt +++ b/libraries/fbx/CMakeLists.txt @@ -7,4 +7,6 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared gpu model networking octree) \ No newline at end of file +setup_memory_debugger() + +link_hifi_libraries(shared gpu model networking octree) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 2db5f5fa51..f0d13f8792 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1373,12 +1373,12 @@ FBXLight extractLight(const FBXNode& object) { #if USE_MODEL_MESH -void buildModelMesh(ExtractedMesh& extracted) { +void buildModelMesh(ExtractedMesh& extracted, const QString& url) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); if (extracted.mesh.vertices.size() == 0) { extracted.mesh._mesh = model::Mesh(); - qCDebug(modelformat) << "buildModelMesh failed -- no vertices"; + qCDebug(modelformat) << "buildModelMesh failed -- no vertices, url = " << url; return; } FBXMesh& fbxMesh = extracted.mesh; @@ -1465,7 +1465,7 @@ void buildModelMesh(ExtractedMesh& extracted) { if (! totalIndices) { extracted.mesh._mesh = model::Mesh(); - qCDebug(modelformat) << "buildModelMesh failed -- no indices"; + qCDebug(modelformat) << "buildModelMesh failed -- no indices, url = " << url; return; } @@ -1505,7 +1505,7 @@ void buildModelMesh(ExtractedMesh& extracted) { mesh.setPartBuffer(pbv); } else { extracted.mesh._mesh = model::Mesh(); - qCDebug(modelformat) << "buildModelMesh failed -- no parts"; + qCDebug(modelformat) << "buildModelMesh failed -- no parts, url = " << url; return; } @@ -1530,7 +1530,7 @@ QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) { return filename; } -FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { QHash meshes; QHash modelIDsToNames; QHash meshIDsToMeshIndices; @@ -1615,7 +1615,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, #if defined(DEBUG_FBXREADER) int unknown = 0; #endif - FBXGeometry geometry; + FBXGeometry* geometryPtr = new FBXGeometry; + FBXGeometry& geometry = *geometryPtr; + float unitScaleFactor = 1.0f; glm::vec3 ambientColor; QString hifiGlobalNodeID; @@ -2680,7 +2682,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex); # if USE_MODEL_MESH - buildModelMesh(extracted); + buildModelMesh(extracted, url); # endif if (extracted.mesh.isEye) { @@ -2761,15 +2763,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } } - return geometry; + return geometryPtr; } -FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel); } -FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { return extractFBXGeometry(parseFBX(device), mapping, url, loadLightmaps, lightmapLevel); } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index b8a22b0b80..471a9c1777 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -272,10 +272,10 @@ Q_DECLARE_METATYPE(FBXGeometry) /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); #endif // hifi_FBXReader_h diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index b7ae948490..841fdcfad9 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -399,15 +399,16 @@ done: } -FBXGeometry OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) { +FBXGeometry* OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); return readOBJ(&buffer, mapping, nullptr); } -FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) { - FBXGeometry geometry; +FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) { + FBXGeometry* geometryPtr = new FBXGeometry(); + FBXGeometry& geometry = *geometryPtr; OBJTokenizer tokenizer(device); float scaleGuess = 1.0f; @@ -545,7 +546,7 @@ FBXGeometry OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, Q qCDebug(modelformat) << "OBJ reader fail: " << e.what(); } - return geometry; + return geometryPtr; } diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index 2e7b050b0a..df4c88553e 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -71,8 +71,8 @@ public: QHash materials; QNetworkReply* request(QUrl& url, bool isTest); - FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping); - FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url); + FBXGeometry* readOBJ(const QByteArray& model, const QVariantHash& mapping); + FBXGeometry* readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url); private: QUrl* _url = nullptr; diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 7a88580f7f..84320297eb 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME gpu) +setup_memory_debugger() + AUTOSCRIBE_SHADER_LIB(gpu) # use setup_hifi_library macro to setup our project and link appropriate Qt modules diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index c3ded6c587..4428327deb 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -33,6 +33,8 @@ endif() #target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) #target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) +setup_memory_debugger() + # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) @@ -69,4 +71,4 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\") endif () endif () -endforeach() \ No newline at end of file +endforeach() diff --git a/libraries/model/CMakeLists.txt b/libraries/model/CMakeLists.txt index 563f347952..2099f83fec 100755 --- a/libraries/model/CMakeLists.txt +++ b/libraries/model/CMakeLists.txt @@ -1,7 +1,9 @@ set(TARGET_NAME model) - + AUTOSCRIBE_SHADER_LIB(gpu model) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library() diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index d79e6bde58..d0e0b850c7 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME networking) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Network) @@ -29,4 +31,4 @@ target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES} ${TBB_LIBRARIES}) # append tbb includes to our list of includes to bubble target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) -include_application_version() \ No newline at end of file +include_application_version() diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index e127380630..75028abe93 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -320,7 +320,6 @@ void Resource::attemptRequest() { void Resource::finishedLoading(bool success) { if (success) { _loaded = true; - emit loaded(); } else { _failedToLoad = true; } @@ -333,91 +332,26 @@ void Resource::reinsert() { static const int REPLY_TIMEOUT_MS = 5000; void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (!_reply->isFinished()) { - _bytesReceived = bytesReceived; - _bytesTotal = bytesTotal; - _replyTimer->start(REPLY_TIMEOUT_MS); - return; - } - _reply->disconnect(this); - _replyTimer->disconnect(this); - QNetworkReply* reply = _reply; - _reply = nullptr; - _replyTimer->deleteLater(); - _replyTimer = nullptr; - ResourceCache::requestCompleted(this); - - downloadFinished(reply); + _bytesReceived = bytesReceived; + _bytesTotal = bytesTotal; + _replyTimer->start(REPLY_TIMEOUT_MS); } void Resource::handleReplyError() { - handleReplyError(_reply->error(), qDebug() << _reply->errorString()); + handleReplyErrorInternal(_reply->error()); } void Resource::handleReplyTimeout() { - handleReplyError(QNetworkReply::TimeoutError, qDebug() << "Timed out loading" << _reply->url() << - "received" << _bytesReceived << "total" << _bytesTotal); -} - -void Resource::maybeRefresh() { - if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) { - QNetworkReply* reply = qobject_cast(sender()); - QVariant variant = reply->header(QNetworkRequest::LastModifiedHeader); - QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url); - if (variant.isValid() && variant.canConvert() && metaData.isValid()) { - QDateTime lastModified = variant.value(); - QDateTime lastModifiedOld = metaData.lastModified(); - if (lastModified.isValid() && lastModifiedOld.isValid() && - lastModifiedOld >= lastModified) { // With >=, cache won't thrash in eventually-consistent cdn. - qCDebug(networking) << "Using cached version of" << _url.fileName(); - // We don't need to update, return - return; - } - } else if (!variant.isValid() || !variant.canConvert() || - !variant.value().isValid() || variant.value().isNull()) { - qCDebug(networking) << "Cannot determine when" << _url.fileName() << "was modified last, cached version might be outdated"; - return; - } - qCDebug(networking) << "Loaded" << _url.fileName() << "from the disk cache but the network version is newer, refreshing."; - refresh(); - } + handleReplyErrorInternal(QNetworkReply::TimeoutError); } void Resource::makeRequest() { _reply = NetworkAccessManager::getInstance().get(_request); - + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished())); - - if (_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) { - // If the file as been updated since it was cached, refresh it - QNetworkRequest request(_request); - request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); - request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); - QNetworkReply* reply = NetworkAccessManager::getInstance().head(request); - connect(reply, &QNetworkReply::finished, this, &Resource::maybeRefresh); - } else { - if (Q_LIKELY(NetworkAccessManager::getInstance().cache())) { - QNetworkCacheMetaData metaData = NetworkAccessManager::getInstance().cache()->metaData(_url); - bool needUpdate = false; - if (metaData.expirationDate().isNull() || metaData.expirationDate() <= QDateTime::currentDateTime()) { - // If the expiration date is NULL or in the past, - // put one far enough away that it won't be an issue. - metaData.setExpirationDate(QDateTime::currentDateTime().addYears(100)); - needUpdate = true; - } - if (metaData.lastModified().isNull()) { - // If the lastModified date is NULL, set it to now. - metaData.setLastModified(QDateTime::currentDateTime()); - needUpdate = true; - } - if (needUpdate) { - NetworkAccessManager::getInstance().cache()->updateMetaData(metaData); - } - } - } - + _replyTimer = new QTimer(this); connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout())); _replyTimer->setSingleShot(true); @@ -425,7 +359,8 @@ void Resource::makeRequest() { _bytesReceived = _bytesTotal = 0; } -void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) { +void Resource::handleReplyErrorInternal(QNetworkReply::NetworkError error) { + _reply->disconnect(this); _replyTimer->disconnect(this); _reply->deleteLater(); @@ -433,7 +368,7 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) _replyTimer->deleteLater(); _replyTimer = nullptr; ResourceCache::requestCompleted(this); - + // retry for certain types of failures switch (error) { case QNetworkReply::RemoteHostClosedError: @@ -444,26 +379,46 @@ void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) case QNetworkReply::UnknownNetworkError: case QNetworkReply::UnknownProxyError: case QNetworkReply::UnknownContentError: - case QNetworkReply::ProtocolFailure: { + case QNetworkReply::ProtocolFailure: { // retry with increasing delays const int MAX_ATTEMPTS = 8; const int BASE_DELAY_MS = 1000; if (++_attempts < MAX_ATTEMPTS) { QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest())); - debug << "-- retrying..."; + qCWarning(networking) << "error downloading url =" << _url.toDisplayString() << ", error =" << error << ", retrying (" << _attempts << "/" << MAX_ATTEMPTS << ")"; return; } // fall through to final failure - } + } default: + qCCritical(networking) << "error downloading, url =" << _url.toDisplayString() << ", error =" << error; + emit failed(error); finishedLoading(false); break; } } void Resource::handleReplyFinished() { - qCDebug(networking) << "Got finished without download progress/error?" << _url; - handleDownloadProgress(0, 0); + + bool fromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); + qCDebug(networking) << "success downloading url =" << _url.toDisplayString() << (fromCache ? "from cache" : ""); + + _reply->disconnect(this); + _replyTimer->disconnect(this); + QNetworkReply* reply = _reply; + _reply = nullptr; + _replyTimer->deleteLater(); + _replyTimer = nullptr; + ResourceCache::requestCompleted(this); + + finishedLoading(true); + emit loaded(*reply); + downloadFinished(reply); +} + + +void Resource::downloadFinished(QNetworkReply* reply) { + ; } uint qHash(const QPointer& value, uint seed) { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 93ddfe77be..9a88c434e1 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -150,7 +150,7 @@ public: float getLoadPriority(); /// Checks whether the resource has loaded. - bool isLoaded() const { return _loaded; } + virtual bool isLoaded() const { return _loaded; } /// For loading resources, returns the number of bytes received. qint64 getBytesReceived() const { return _bytesReceived; } @@ -174,21 +174,22 @@ public: signals: /// Fired when the resource has been loaded. - void loaded(); + void loaded(QNetworkReply& request); + + /// Fired when resource failed to load. + void failed(QNetworkReply::NetworkError error); + + /// Fired when resource is refreshed. void onRefresh(); protected slots: void attemptRequest(); - - /// Refreshes the resource if the last modified date on the network - /// is greater than the last modified date in the cache. - void maybeRefresh(); protected: virtual void init(); /// Called when the download has finished. The recipient should delete the reply when done with it. - virtual void downloadFinished(QNetworkReply* reply) = 0; + virtual void downloadFinished(QNetworkReply* reply); /// Should be called by subclasses when all the loading that will be done has been done. Q_INVOKABLE void finishedLoading(bool success); @@ -216,7 +217,7 @@ private: void makeRequest(); - void handleReplyError(QNetworkReply::NetworkError error, QDebug debug); + void handleReplyErrorInternal(QNetworkReply::NetworkError error); friend class ResourceCache; diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index cc36aead15..8b9ff6bda2 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME octree) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library() diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index b1f9fbb79c..802665b948 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME physics) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library() diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index 28b136ccf4..98fd5fdc93 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(shared) add_dependency_external_projects(glm) find_package(GLM REQUIRED) + +setup_memory_debugger() + target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) - - diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 0ea71e54e3..ceb1a192ab 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -40,4 +40,6 @@ add_dependency_external_projects(oglplus) find_package(OGLPLUS REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) +setup_memory_debugger() + link_hifi_libraries(animation fbx shared gpu model render environment) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 31b030f75a..f48ceb9b62 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include @@ -50,6 +49,13 @@ GeometryCache::~GeometryCache() { #endif //def WANT_DEBUG } +QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra) { + // NetworkGeometry is no longer a subclass of Resource, but requires this method because, it is pure virtual. + assert(false); + return QSharedPointer(); +} + const int NUM_VERTICES_PER_TRIANGLE = 3; const int NUM_TRIANGLES_PER_QUAD = 2; const int NUM_VERTICES_PER_TRIANGULATED_QUAD = NUM_VERTICES_PER_TRIANGLE * NUM_TRIANGLES_PER_QUAD; @@ -1643,19 +1649,6 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm batch.draw(gpu::LINES, 2, 0); } - -QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) { - return getResource(url, fallback, delayLoad, NULL).staticCast(); -} - -QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, - bool delayLoad, const void* extra) { - QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad), - &Resource::allReferencesCleared); - geometry->setLODParent(geometry); - return geometry.staticCast(); -} - void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { if (!_standardDrawPipeline) { auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert))); @@ -1685,33 +1678,82 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { } } -const float NetworkGeometry::NO_HYSTERESIS = -1.0f; +GeometryReader::GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping) : + _url(url), + _reply(reply), + _mapping(mapping) { +} -NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, - const QVariantHash& mapping, const QUrl& textureBase) : - Resource(url, delayLoad), - _mapping(mapping), - _textureBase(textureBase.isValid() ? textureBase : url), - _fallback(fallback) -{ - - if (url.isEmpty()) { - // make the minimal amount of dummy geometry to satisfy Model - FBXJoint joint = { false, QVector(), -1, 0.0f, 0.0f, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), - glm::quat(), glm::mat4(), glm::mat4(), glm::vec3(), glm::vec3(), glm::quat(), glm::quat(), - glm::mat4(), QString(""), false}; - _geometry.joints.append(joint); - _geometry.leftEyeJointIndex = -1; - _geometry.rightEyeJointIndex = -1; - _geometry.neckJointIndex = -1; - _geometry.rootJointIndex = -1; - _geometry.leanJointIndex = -1; - _geometry.headJointIndex = -1; - _geometry.leftHandJointIndex = -1; - _geometry.rightHandJointIndex = -1; +void GeometryReader::run() { + try { + if (!_reply) { + throw QString("Reply is NULL ?!"); + } + QString urlname = _url.path().toLower(); + bool urlValid = true; + urlValid &= !urlname.isEmpty(); + urlValid &= !_url.path().isEmpty(); + urlValid &= _url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".obj"); + + if (urlValid) { + // Let's read the binaries from the network + FBXGeometry* fbxgeo = nullptr; + if (_url.path().toLower().endsWith(".fbx")) { + const bool grabLightmaps = true; + const float lightmapLevel = 1.0f; + fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel); + } else if (_url.path().toLower().endsWith(".obj")) { + fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url); + } else { + QString errorStr("usupported format"); + emit onError(NetworkGeometry::ModelParseError, errorStr); + } + emit onSuccess(fbxgeo); + } else { + throw QString("url is invalid"); + } + + } catch (const QString& error) { + qCDebug(renderutils) << "Error reading " << _url << ": " << error; + emit onError(NetworkGeometry::ModelParseError, error); } - - connect(this, &Resource::loaded, this, &NetworkGeometry::replaceTexturesWithPendingChanges); + _reply->deleteLater(); +} + +NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : + _url(url), + _mapping(mapping), + _textureBaseUrl(textureBaseUrl) { + + if (delayLoad) { + _state = DelayState; + } else { + attemptRequestInternal(); + } +} + +NetworkGeometry::~NetworkGeometry() { + if (_resource) { + _resource->deleteLater(); + } +} + +void NetworkGeometry::attemptRequest() { + if (_state == DelayState) { + attemptRequestInternal(); + } +} + +void NetworkGeometry::attemptRequestInternal() { + if (_url.path().toLower().endsWith(".fst")) { + requestMapping(_url); + } else { + requestModel(_url); + } +} + +bool NetworkGeometry::isLoaded() const { + return _state == SuccessState; } bool NetworkGeometry::isLoadedWithTextures() const { @@ -1719,12 +1761,12 @@ bool NetworkGeometry::isLoadedWithTextures() const { return false; } if (!_isLoadedWithTextures) { - foreach (const NetworkMesh& mesh, _meshes) { - foreach (const NetworkMeshPart& part, mesh.parts) { - if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) || - (part.normalTexture && !part.normalTexture->isLoaded()) || - (part.specularTexture && !part.specularTexture->isLoaded()) || - (part.emissiveTexture && !part.emissiveTexture->isLoaded())) { + for (auto&& mesh : _meshes) { + for (auto && part : mesh->_parts) { + if ((part->diffuseTexture && !part->diffuseTexture->isLoaded()) || + (part->normalTexture && !part->normalTexture->isLoaded()) || + (part->specularTexture && !part->specularTexture->isLoaded()) || + (part->emissiveTexture && !part->emissiveTexture->isLoaded())) { return false; } } @@ -1734,183 +1776,38 @@ bool NetworkGeometry::isLoadedWithTextures() const { return true; } -QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const { - if (_lodParent.data() != this) { - return _lodParent.data()->getLODOrFallback(distance, hysteresis, delayLoad); - } - if (_failedToLoad && _fallback) { - return _fallback; - } - QSharedPointer lod = _lodParent; - float lodDistance = 0.0f; - QMap >::const_iterator it = _lods.upperBound(distance); - if (it != _lods.constBegin()) { - it = it - 1; - lod = it.value(); - lodDistance = it.key(); - } - if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) { - // if we previously selected a different distance, make sure we've moved far enough to justify switching - const float HYSTERESIS_PROPORTION = 0.1f; - if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) { - lod = _lodParent; - lodDistance = 0.0f; - it = _lods.upperBound(hysteresis); - if (it != _lods.constBegin()) { - it = it - 1; - lod = it.value(); - lodDistance = it.key(); - } - } - } - if (lod && lod->isLoaded()) { - hysteresis = lodDistance; - return lod; - } - // if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one - if (!delayLoad) { - lod->ensureLoading(); - } - float closestDistance = FLT_MAX; - if (isLoaded()) { - lod = _lodParent; - closestDistance = distance; - } - for (it = _lods.constBegin(); it != _lods.constEnd(); it++) { - float distanceToLOD = glm::abs(distance - it.key()); - if (it.value()->isLoaded() && distanceToLOD < closestDistance) { - lod = it.value(); - closestDistance = distanceToLOD; - } - } - hysteresis = NO_HYSTERESIS; - return lod; -} - -uint qHash(const QWeakPointer& animation, uint seed = 0) { - return qHash(animation.data(), seed); -} - -QVector NetworkGeometry::getJointMappings(const AnimationPointer& animation) { - QVector mappings = _jointMappings.value(animation); - if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) { - const FBXGeometry& animationGeometry = animation->getGeometry(); - for (int i = 0; i < animationGeometry.joints.size(); i++) { - mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1); - } - _jointMappings.insert(animation, mappings); - } - return mappings; -} - -void NetworkGeometry::setLoadPriority(const QPointer& owner, float priority) { - Resource::setLoadPriority(owner, priority); - - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - if (part.diffuseTexture) { - part.diffuseTexture->setLoadPriority(owner, priority); - } - if (part.normalTexture) { - part.normalTexture->setLoadPriority(owner, priority); - } - if (part.specularTexture) { - part.specularTexture->setLoadPriority(owner, priority); - } - if (part.emissiveTexture) { - part.emissiveTexture->setLoadPriority(owner, priority); - } - } - } -} - -void NetworkGeometry::setLoadPriorities(const QHash, float>& priorities) { - Resource::setLoadPriorities(priorities); - - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - if (part.diffuseTexture) { - part.diffuseTexture->setLoadPriorities(priorities); - } - if (part.normalTexture) { - part.normalTexture->setLoadPriorities(priorities); - } - if (part.specularTexture) { - part.specularTexture->setLoadPriorities(priorities); - } - if (part.emissiveTexture) { - part.emissiveTexture->setLoadPriorities(priorities); - } - } - } -} - -void NetworkGeometry::clearLoadPriority(const QPointer& owner) { - Resource::clearLoadPriority(owner); - - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - if (part.diffuseTexture) { - part.diffuseTexture->clearLoadPriority(owner); - } - if (part.normalTexture) { - part.normalTexture->clearLoadPriority(owner); - } - if (part.specularTexture) { - part.specularTexture->clearLoadPriority(owner); - } - if (part.emissiveTexture) { - part.emissiveTexture->clearLoadPriority(owner); - } - } - } -} - void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) { if (_meshes.size() > 0) { auto textureCache = DependencyManager::get(); - for (int i = 0; i < _meshes.size(); i++) { - NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - NetworkMeshPart& part = mesh.parts[j]; - + for (size_t i = 0; i < _meshes.size(); i++) { + NetworkMesh& mesh = *(_meshes[i].get()); + for (size_t j = 0; j < mesh._parts.size(); j++) { + NetworkMeshPart& part = *(mesh._parts[j].get()); QSharedPointer matchingTexture = QSharedPointer(); if (part.diffuseTextureName == name) { - part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry.meshes[i].isEye); - part.diffuseTexture->setLoadPriorities(_loadPriorities); + part.diffuseTexture = textureCache->getTexture(url, DEFAULT_TEXTURE, _geometry->meshes[i].isEye); } else if (part.normalTextureName == name) { part.normalTexture = textureCache->getTexture(url); - part.normalTexture->setLoadPriorities(_loadPriorities); } else if (part.specularTextureName == name) { part.specularTexture = textureCache->getTexture(url); - part.specularTexture->setLoadPriorities(_loadPriorities); } else if (part.emissiveTextureName == name) { part.emissiveTexture = textureCache->getTexture(url); - part.emissiveTexture->setLoadPriorities(_loadPriorities); } } } } else { - qCDebug(renderutils) << "Adding a name url pair to pending" << name << url; - // we don't have meshes downloaded yet, so hold this texture as pending - _pendingTextureChanges.insert(name, url); + qCWarning(renderutils) << "Ignoring setTextureWirthNameToURL() geometry not ready." << name << url; } _isLoadedWithTextures = false; } QStringList NetworkGeometry::getTextureNames() const { QStringList result; - for (int i = 0; i < _meshes.size(); i++) { - const NetworkMesh& mesh = _meshes[i]; - for (int j = 0; j < mesh.parts.size(); j++) { - const NetworkMeshPart& part = mesh.parts[j]; - + for (size_t i = 0; i < _meshes.size(); i++) { + const NetworkMesh& mesh = *(_meshes[i].get()); + for (size_t j = 0; j < mesh._parts.size(); j++) { + const NetworkMeshPart& part = *(mesh._parts[j].get()); + if (!part.diffuseTextureName.isEmpty() && part.diffuseTexture) { QString textureURL = part.diffuseTexture->getURL().toString(); result << part.diffuseTextureName + ":" + textureURL; @@ -1935,320 +1832,259 @@ QStringList NetworkGeometry::getTextureNames() const { return result; } -void NetworkGeometry::replaceTexturesWithPendingChanges() { - QHash::Iterator it = _pendingTextureChanges.begin(); - - while (it != _pendingTextureChanges.end()) { - setTextureWithNameToURL(it.key(), it.value()); - it = _pendingTextureChanges.erase(it); +void NetworkGeometry::requestMapping(const QUrl& url) { + _state = RequestMappingState; + if (_resource) { + _resource->deleteLater(); } + _resource = new Resource(url, false); + connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(mappingRequestDone(QNetworkReply&))); + connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(mappingRequestError(QNetworkReply::NetworkError))); } -/// Reads geometry in a worker thread. -class GeometryReader : public QRunnable { -public: - - GeometryReader(const QWeakPointer& geometry, const QUrl& url, - QNetworkReply* reply, const QVariantHash& mapping); - - virtual void run(); - -private: - - QWeakPointer _geometry; - QUrl _url; - QNetworkReply* _reply; - QVariantHash _mapping; -}; - -GeometryReader::GeometryReader(const QWeakPointer& geometry, const QUrl& url, - QNetworkReply* reply, const QVariantHash& mapping) : - _geometry(geometry), - _url(url), - _reply(reply), - _mapping(mapping) { +void NetworkGeometry::requestModel(const QUrl& url) { + _state = RequestModelState; + if (_resource) { + _resource->deleteLater(); + } + _resource = new Resource(url, false); + connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(modelRequestDone(QNetworkReply&))); + connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(modelRequestError(QNetworkReply::NetworkError))); } -void GeometryReader::run() { - QSharedPointer geometry = _geometry.toStrongRef(); - if (geometry.isNull()) { - _reply->deleteLater(); - return; - } - try { - if (!_reply) { - throw QString("Reply is NULL ?!"); - } - QString urlname = _url.path().toLower(); - bool urlValid = true; - urlValid &= !urlname.isEmpty(); - urlValid &= !_url.path().isEmpty(); - urlValid &= _url.path().toLower().endsWith(".fbx") - || _url.path().toLower().endsWith(".obj") - || _url.path().toLower().endsWith(".svo"); +void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) { + assert(_state == RequestMappingState); - if (urlValid) { - // Let's read the binaries from the network - FBXGeometry fbxgeo; - if (_url.path().toLower().endsWith(".fbx")) { - bool grabLightmaps = true; - float lightmapLevel = 1.0f; - // HACK: For monday 12/01/2014 we need to kill lighmaps loading in starchamber... - if (_url.path().toLower().endsWith("loungev4_11-18.fbx")) { - grabLightmaps = false; - } else if (_url.path().toLower().endsWith("apt8_reboot.fbx")) { - lightmapLevel = 4.0f; - } else if (_url.path().toLower().endsWith("palaceoforinthilian4.fbx")) { - lightmapLevel = 3.5f; - } - fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel); - } else if (_url.path().toLower().endsWith(".obj")) { - fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url); + // parse the mapping file + _mapping = FSTReader::readMapping(reply.readAll()); + + QUrl replyUrl = reply.url(); + QString modelUrlStr = _mapping.value("filename").toString(); + if (modelUrlStr.isNull()) { + qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry"; + emit onFailure(*this, MissingFilenameInMapping); + } else { + // read _textureBase from mapping file, if present + QString texdir = _mapping.value("texdir").toString(); + if (!texdir.isNull()) { + if (!texdir.endsWith('/')) { + texdir += '/'; } - QMetaObject::invokeMethod(geometry.data(), "setGeometry", Q_ARG(const FBXGeometry&, fbxgeo)); - } else { - throw QString("url is invalid"); + _textureBaseUrl = replyUrl.resolved(texdir); } - } catch (const QString& error) { - qCDebug(renderutils) << "Error reading " << _url << ": " << error; - QMetaObject::invokeMethod(geometry.data(), "finishedLoading", Q_ARG(bool, false)); - } - _reply->deleteLater(); -} - -void NetworkGeometry::init() { - _mapping = QVariantHash(); - _geometry = FBXGeometry(); - _meshes.clear(); - _lods.clear(); - _pendingTextureChanges.clear(); - _request.setUrl(_url); - Resource::init(); -} - -void NetworkGeometry::downloadFinished(QNetworkReply* reply) { - QUrl url = reply->url(); - if (url.path().toLower().endsWith(".fst")) { - // it's a mapping file; parse it and get the mesh filename - _mapping = FSTReader::readMapping(reply->readAll()); - reply->deleteLater(); - QString filename = _mapping.value("filename").toString(); - if (filename.isNull()) { - qCDebug(renderutils) << "Mapping file " << url << " has no filename."; - finishedLoading(false); - - } else { - QString texdir = _mapping.value("texdir").toString(); - if (!texdir.isNull()) { - if (!texdir.endsWith('/')) { - texdir += '/'; - } - _textureBase = url.resolved(texdir); - } - QVariantHash lods = _mapping.value("lod").toHash(); - for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) { - auto geometry = QSharedPointer::create(url.resolved(it.key()), - QSharedPointer(), true, _mapping, _textureBase); - geometry->setSelf(geometry.staticCast()); - geometry->setLODParent(_lodParent); - _lods.insert(it.value().toFloat(), geometry); - } - _request.setUrl(url.resolved(filename)); - - // make the request immediately only if we have no LODs to switch between - _startedLoading = false; - if (_lods.isEmpty()) { - attemptRequest(); - } - } - return; - } - - // send the reader off to the thread pool - QThreadPool::globalInstance()->start(new GeometryReader(_self, url, reply, _mapping)); -} - -void NetworkGeometry::reinsert() { - Resource::reinsert(); - - _lodParent = qWeakPointerCast(_self); - foreach (const QSharedPointer& lod, _lods) { - lod->setLODParent(_lodParent); + QUrl modelUrl = replyUrl.resolved(modelUrlStr); + requestModel(modelUrl); } } -void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { - _geometry = geometry; +void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) { + assert(_state == RequestMappingState); + _state = ErrorState; + emit onFailure(*this, MappingRequestError); +} +void NetworkGeometry::modelRequestDone(QNetworkReply& reply) { + assert(_state == RequestModelState); + + _state = ParsingModelState; + + // asynchronously parse the model file. + GeometryReader* geometryReader = new GeometryReader(reply.url(), &reply, _mapping); + connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*))); + connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString))); + + QThreadPool::globalInstance()->start(geometryReader); +} + +void NetworkGeometry::modelRequestError(QNetworkReply::NetworkError error) { + assert(_state == RequestModelState); + _state = ErrorState; + emit onFailure(*this, ModelRequestError); +} + +static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBaseUrl) { auto textureCache = DependencyManager::get(); - - foreach (const FBXMesh& mesh, _geometry.meshes) { - NetworkMesh networkMesh; - - int totalIndices = 0; - bool checkForTexcoordLightmap = false; - foreach (const FBXMeshPart& part, mesh.parts) { - NetworkMeshPart networkPart; - if (!part.diffuseTexture.filename.isEmpty()) { - networkPart.diffuseTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE, - mesh.isEye, part.diffuseTexture.content); - networkPart.diffuseTextureName = part.diffuseTexture.name; - networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); - } - if (!part.normalTexture.filename.isEmpty()) { - networkPart.normalTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE, - false, part.normalTexture.content); - networkPart.normalTextureName = part.normalTexture.name; - networkPart.normalTexture->setLoadPriorities(_loadPriorities); - } - if (!part.specularTexture.filename.isEmpty()) { - networkPart.specularTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE, - false, part.specularTexture.content); - networkPart.specularTextureName = part.specularTexture.name; - networkPart.specularTexture->setLoadPriorities(_loadPriorities); - } - if (!part.emissiveTexture.filename.isEmpty()) { - networkPart.emissiveTexture = textureCache->getTexture( - _textureBase.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE, - false, part.emissiveTexture.content); - networkPart.emissiveTextureName = part.emissiveTexture.name; - networkPart.emissiveTexture->setLoadPriorities(_loadPriorities); - checkForTexcoordLightmap = true; - } - networkMesh.parts.append(networkPart); - - totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); + NetworkMesh* networkMesh = new NetworkMesh(); + + int totalIndices = 0; + bool checkForTexcoordLightmap = false; + + // process network parts + foreach (const FBXMeshPart& part, mesh.parts) { + NetworkMeshPart* networkPart = new NetworkMeshPart(); + + if (!part.diffuseTexture.filename.isEmpty()) { + networkPart->diffuseTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.diffuseTexture.filename)), DEFAULT_TEXTURE, + mesh.isEye, part.diffuseTexture.content); + networkPart->diffuseTextureName = part.diffuseTexture.name; } - - { - networkMesh._indexBuffer = std::make_shared(); - networkMesh._indexBuffer->resize(totalIndices * sizeof(int)); - int offset = 0; - foreach(const FBXMeshPart& part, mesh.parts) { - networkMesh._indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int), - (gpu::Byte*) part.quadIndices.constData()); - offset += part.quadIndices.size() * sizeof(int); - networkMesh._indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int), - (gpu::Byte*) part.triangleIndices.constData()); - offset += part.triangleIndices.size() * sizeof(int); - } + if (!part.normalTexture.filename.isEmpty()) { + networkPart->normalTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.normalTexture.filename)), NORMAL_TEXTURE, + false, part.normalTexture.content); + networkPart->normalTextureName = part.normalTexture.name; } - - { - networkMesh._vertexBuffer = std::make_shared(); - // if we don't need to do any blending, the positions/normals can be static - if (mesh.blendshapes.isEmpty()) { - int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3); - int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3); - int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3); - int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); - int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); - int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2); - int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); - - networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); - - networkMesh._vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); - networkMesh._vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); - networkMesh._vertexBuffer->setSubData(tangentsOffset, - mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); - networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); - networkMesh._vertexBuffer->setSubData(texCoordsOffset, - mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); - networkMesh._vertexBuffer->setSubData(texCoords1Offset, - mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData()); - networkMesh._vertexBuffer->setSubData(clusterIndicesOffset, - mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); - networkMesh._vertexBuffer->setSubData(clusterWeightsOffset, - mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); - - // otherwise, at least the cluster indices/weights can be static - networkMesh._vertexStream = std::make_shared(); - networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3)); - if (mesh.normals.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, normalsOffset, sizeof(glm::vec3)); - if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, tangentsOffset, sizeof(glm::vec3)); - if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3)); - if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); - if (mesh.texCoords1.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoords1Offset, sizeof(glm::vec2)); - if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); - if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); - - int channelNum = 0; - networkMesh._vertexFormat = std::make_shared(); - networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); - if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); - if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - if (mesh.texCoords1.size()) { - networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - } else if (checkForTexcoordLightmap && mesh.texCoords.size()) { - // need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel - networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - } - if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - } - else { - int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); - int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); - int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); - int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); - - networkMesh._vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); - networkMesh._vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); - networkMesh._vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); - networkMesh._vertexBuffer->setSubData(texCoordsOffset, - mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); - networkMesh._vertexBuffer->setSubData(clusterIndicesOffset, - mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); - networkMesh._vertexBuffer->setSubData(clusterWeightsOffset, - mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); - - networkMesh._vertexStream = std::make_shared(); - if (mesh.tangents.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, 0, sizeof(glm::vec3)); - if (mesh.colors.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, colorsOffset, sizeof(glm::vec3)); - if (mesh.texCoords.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); - if (mesh.clusterIndices.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); - if (mesh.clusterWeights.size()) networkMesh._vertexStream->addBuffer(networkMesh._vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); - - int channelNum = 0; - networkMesh._vertexFormat = std::make_shared(); - networkMesh._vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.normals.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.tangents.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - if (mesh.colors.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); - if (mesh.texCoords.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - if (mesh.clusterIndices.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - if (mesh.clusterWeights.size()) networkMesh._vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - - } + if (!part.specularTexture.filename.isEmpty()) { + networkPart->specularTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.specularTexture.filename)), SPECULAR_TEXTURE, + false, part.specularTexture.content); + networkPart->specularTextureName = part.specularTexture.name; } - - _meshes.append(networkMesh); + if (!part.emissiveTexture.filename.isEmpty()) { + networkPart->emissiveTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(part.emissiveTexture.filename)), EMISSIVE_TEXTURE, + false, part.emissiveTexture.content); + networkPart->emissiveTextureName = part.emissiveTexture.name; + checkForTexcoordLightmap = true; + } + networkMesh->_parts.emplace_back(networkPart); + totalIndices += (part.quadIndices.size() + part.triangleIndices.size()); } - - finishedLoading(true); + + // initialize index buffer + { + networkMesh->_indexBuffer = std::make_shared(); + networkMesh->_indexBuffer->resize(totalIndices * sizeof(int)); + int offset = 0; + foreach(const FBXMeshPart& part, mesh.parts) { + networkMesh->_indexBuffer->setSubData(offset, part.quadIndices.size() * sizeof(int), + (gpu::Byte*) part.quadIndices.constData()); + offset += part.quadIndices.size() * sizeof(int); + networkMesh->_indexBuffer->setSubData(offset, part.triangleIndices.size() * sizeof(int), + (gpu::Byte*) part.triangleIndices.constData()); + offset += part.triangleIndices.size() * sizeof(int); + } + } + + // initialize vertex buffer + { + networkMesh->_vertexBuffer = std::make_shared(); + // if we don't need to do any blending, the positions/normals can be static + if (mesh.blendshapes.isEmpty()) { + int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3); + int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3); + int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3); + int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); + int texCoords1Offset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); + int clusterIndicesOffset = texCoords1Offset + mesh.texCoords1.size() * sizeof(glm::vec2); + int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); + + networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); + + networkMesh->_vertexBuffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); + networkMesh->_vertexBuffer->setSubData(normalsOffset, mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); + networkMesh->_vertexBuffer->setSubData(tangentsOffset, + mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); + networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); + networkMesh->_vertexBuffer->setSubData(texCoordsOffset, + mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); + networkMesh->_vertexBuffer->setSubData(texCoords1Offset, + mesh.texCoords1.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords1.constData()); + networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset, + mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); + networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset, + mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); + + // otherwise, at least the cluster indices/weights can be static + networkMesh->_vertexStream = std::make_shared(); + networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3)); + if (mesh.normals.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, normalsOffset, sizeof(glm::vec3)); + if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, tangentsOffset, sizeof(glm::vec3)); + if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3)); + if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); + if (mesh.texCoords1.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoords1Offset, sizeof(glm::vec2)); + if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); + if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); + + int channelNum = 0; + networkMesh->_vertexFormat = std::make_shared(); + networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); + if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + if (mesh.texCoords1.size()) { + networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + } else if (checkForTexcoordLightmap && mesh.texCoords.size()) { + // need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel + networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + } + if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + } + else { + int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); + int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3); + int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2); + int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4); + + networkMesh->_vertexBuffer->resize(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4)); + networkMesh->_vertexBuffer->setSubData(0, mesh.tangents.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.tangents.constData()); + networkMesh->_vertexBuffer->setSubData(colorsOffset, mesh.colors.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.colors.constData()); + networkMesh->_vertexBuffer->setSubData(texCoordsOffset, + mesh.texCoords.size() * sizeof(glm::vec2), (gpu::Byte*) mesh.texCoords.constData()); + networkMesh->_vertexBuffer->setSubData(clusterIndicesOffset, + mesh.clusterIndices.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterIndices.constData()); + networkMesh->_vertexBuffer->setSubData(clusterWeightsOffset, + mesh.clusterWeights.size() * sizeof(glm::vec4), (gpu::Byte*) mesh.clusterWeights.constData()); + + networkMesh->_vertexStream = std::make_shared(); + if (mesh.tangents.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, 0, sizeof(glm::vec3)); + if (mesh.colors.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, colorsOffset, sizeof(glm::vec3)); + if (mesh.texCoords.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, texCoordsOffset, sizeof(glm::vec2)); + if (mesh.clusterIndices.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterIndicesOffset, sizeof(glm::vec4)); + if (mesh.clusterWeights.size()) networkMesh->_vertexStream->addBuffer(networkMesh->_vertexBuffer, clusterWeightsOffset, sizeof(glm::vec4)); + + int channelNum = 0; + networkMesh->_vertexFormat = std::make_shared(); + networkMesh->_vertexFormat->setAttribute(gpu::Stream::POSITION, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.normals.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::NORMAL, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); + if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); + if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + } + } + + return networkMesh; +} + +void NetworkGeometry::modelParseSuccess(FBXGeometry* geometry) { + // assume owner ship of geometry pointer + _geometry.reset(geometry); + + foreach(const FBXMesh& mesh, _geometry->meshes) { + _meshes.emplace_back(buildNetworkMesh(mesh, _textureBaseUrl)); + } + + _state = SuccessState; + emit onSuccess(*this, *_geometry.get()); + + delete _resource; + _resource = nullptr; +} + +void NetworkGeometry::modelParseError(int error, QString str) { + _state = ErrorState; + emit onFailure(*this, (NetworkGeometry::Error)error); + + delete _resource; + _resource = nullptr; } bool NetworkMeshPart::isTranslucent() const { return diffuseTexture && diffuseTexture->isTranslucent(); } - bool NetworkMesh::isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const { assert(partIndex >= 0); - assert(partIndex < parts.size()); - return (parts.at(partIndex).isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f); + assert((size_t)partIndex < _parts.size()); + return (_parts.at(partIndex)->isTranslucent() || fbxMesh.parts.at(partIndex).opacity != 1.0f); } int NetworkMesh::getTranslucentPartCount(const FBXMesh& fbxMesh) const { int count = 0; - for (int i = 0; i < parts.size(); i++) { + + for (size_t i = 0; i < _parts.size(); i++) { if (isPartTranslucent(fbxMesh, i)) { count++; } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index f70eae6380..774df1561c 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -13,6 +13,7 @@ #define hifi_GeometryCache_h #include +#include #include #include @@ -129,6 +130,9 @@ public: int allocateID() { return _nextID++; } static const int UNKNOWN_ID; + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra); + void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec3& color, bool solid = true, int id = UNKNOWN_ID) { renderSphere(batch, radius, slices, stacks, glm::vec4(color, 1.0f), solid, id); } @@ -208,11 +212,6 @@ public: /// Set a batch to the simple pipeline, returning the previous pipeline void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false); -protected: - - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); - private: GeometryCache(); virtual ~GeometryCache(); @@ -305,70 +304,104 @@ private: QHash > _networkGeometry; }; -/// Geometry loaded from the network. -class NetworkGeometry : public Resource { +class NetworkGeometry : public QObject { Q_OBJECT public: - - /// A hysteresis value indicating that we have no state memory. - static const float NO_HYSTERESIS; - - NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, - const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); + // mapping is only used if url is a .fbx or .obj file, it is essentially the content of an fst file. + // if delayLoad is true, the url will not be immediately downloaded. + // use the attemptRequest method to initiate the download. + NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl = QUrl()); + ~NetworkGeometry(); - /// Checks whether the geometry and its textures are loaded. + const QUrl& getURL() const { return _url; } + + void attemptRequest(); + + // true when the geometry is loaded (but maybe not it's associated textures) + bool isLoaded() const; + + // true when the requested geometry and its textures are loaded. bool isLoadedWithTextures() const; - /// Returns a pointer to the geometry appropriate for the specified distance. - /// \param hysteresis a hysteresis parameter that prevents rapid model switching - QSharedPointer getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const; + // WARNING: only valid when isLoaded returns true. + const FBXGeometry& getFBXGeometry() const { return *_geometry; } + const std::vector>& getMeshes() const { return _meshes; } - const FBXGeometry& getFBXGeometry() const { return _geometry; } - const QVector& getMeshes() const { return _meshes; } - - QVector getJointMappings(const AnimationPointer& animation); - - virtual void setLoadPriority(const QPointer& owner, float priority); - virtual void setLoadPriorities(const QHash, float>& priorities); - virtual void clearLoadPriority(const QPointer& owner); - void setTextureWithNameToURL(const QString& name, const QUrl& url); QStringList getTextureNames() const; - + + enum Error { + MissingFilenameInMapping = 0, + MappingRequestError, + ModelRequestError, + ModelParseError + }; + +signals: + // Fired when everything has downloaded and parsed successfully. + void onSuccess(NetworkGeometry& networkGeometry, FBXGeometry& fbxGeometry); + + // Fired when something went wrong. + void onFailure(NetworkGeometry& networkGeometry, Error error); + +protected slots: + void mappingRequestDone(QNetworkReply& reply); + void mappingRequestError(QNetworkReply::NetworkError error); + + void modelRequestDone(QNetworkReply& reply); + void modelRequestError(QNetworkReply::NetworkError error); + + void modelParseSuccess(FBXGeometry* geometry); + void modelParseError(int error, QString str); + protected: + void attemptRequestInternal(); + void requestMapping(const QUrl& url); + void requestModel(const QUrl& url); - virtual void init(); - virtual void downloadFinished(QNetworkReply* reply); - virtual void reinsert(); - - Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); - -private slots: - void replaceTexturesWithPendingChanges(); -private: - - friend class GeometryCache; - - void setLODParent(const QWeakPointer& lodParent) { _lodParent = lodParent; } - + enum State { DelayState, + RequestMappingState, + RequestModelState, + ParsingModelState, + SuccessState, + ErrorState }; + State _state; + + QUrl _url; QVariantHash _mapping; - QUrl _textureBase; - QSharedPointer _fallback; - - QMap > _lods; - FBXGeometry _geometry; - QVector _meshes; - - QWeakPointer _lodParent; - - QHash, QVector > _jointMappings; - - QHash _pendingTextureChanges; + QUrl _textureBaseUrl; + Resource* _resource = nullptr; + std::unique_ptr _geometry; + std::vector> _meshes; + + // cache for isLoadedWithTextures() mutable bool _isLoadedWithTextures = false; }; +/// Reads geometry in a worker thread. +class GeometryReader : public QObject, public QRunnable { + Q_OBJECT + +public: + + GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping); + + virtual void run(); + +signals: + void onSuccess(FBXGeometry* geometry); + void onError(int error, QString str); + +private: + + QWeakPointer _geometry; + QUrl _url; + QNetworkReply* _reply; + QVariantHash _mapping; +}; + /// The state associated with a single mesh part. class NetworkMeshPart { public: @@ -394,9 +427,9 @@ public: gpu::BufferStreamPointer _vertexStream; gpu::Stream::FormatPointer _vertexFormat; - - QVector parts; - + + std::vector> _parts; + int getTranslucentPartCount(const FBXMesh& fbxMesh) const; bool isPartTranslucent(const FBXMesh& fbxMesh, int partIndex) const; }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 7452c32ed2..c2d723a323 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -48,6 +48,8 @@ #include "model_lightmap_specular_map_frag.h" #include "model_translucent_frag.h" +#include "RenderUtilsLogging.h" + using namespace std; static int modelPointerTypeId = qRegisterMetaType >(); @@ -66,7 +68,6 @@ Model::Model(RigPointer rig, QObject* parent) : _snappedToRegistrationPoint(false), _showTrueJointTransforms(true), _cauterizeBones(false), - _lodDistance(0.0f), _pupilDilation(0.0f), _url(HTTP_INVALID_COM), _urlAsString(HTTP_INVALID_COM), @@ -234,7 +235,7 @@ QVector Model::createJointStates(const FBXGeometry& geometry) { }; void Model::initJointTransforms() { - if (!_geometry) { + if (!_geometry || !_geometry->isLoaded()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -368,8 +369,10 @@ void Model::init() { } void Model::reset() { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - _rig->reset(geometry.joints); + if (_geometry && _geometry->isLoaded()) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + _rig->reset(geometry.joints); + } _meshGroupsKnown = false; _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid @@ -378,68 +381,23 @@ void Model::reset() { bool Model::updateGeometry() { PROFILE_RANGE(__FUNCTION__); bool needFullUpdate = false; - bool needToRebuild = false; - if (_nextGeometry) { - _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); - _nextGeometry->setLoadPriority(this, -_lodDistance); - _nextGeometry->ensureLoading(); - if (_nextGeometry->isLoaded()) { - applyNextGeometry(); - needToRebuild = true; - } - } - if (!_geometry) { + + if (!_geometry || !_geometry->isLoaded()) { // geometry is not ready return false; } - QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); - if (_geometry != geometry) { + _needsReload = false; - // NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above. - // Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry. - - const FBXGeometry& newGeometry = geometry->getFBXGeometry(); - QVector newJointStates = createJointStates(newGeometry); - - if (! _rig->jointStatesEmpty()) { - // copy the existing joint states - const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); - for (QHash::const_iterator it = oldGeometry.jointIndices.constBegin(); - it != oldGeometry.jointIndices.constEnd(); it++) { - int oldIndex = it.value() - 1; - int newIndex = newGeometry.getJointIndex(it.key()); - if (newIndex != -1) { - newJointStates[newIndex].copyState(_rig->getJointState(oldIndex)); - } - } - } - - deleteGeometry(); - _dilatedTextures.clear(); - if (!geometry) { - std::cout << "WARNING: no geometry in Model::updateGeometry\n"; - } - setGeometry(geometry); - - _meshGroupsKnown = false; - _readyWhenAdded = false; // in case any of our users are using scenes - invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid - initJointStates(newJointStates); - needToRebuild = true; - } else if (_rig->jointStatesEmpty()) { + QSharedPointer geometry = _geometry; + if (_rig->jointStatesEmpty()) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); if (fbxGeometry.joints.size() > 0) { initJointStates(createJointStates(fbxGeometry)); needToRebuild = true; } - } else if (!geometry->isLoaded()) { - deleteGeometry(); - _dilatedTextures.clear(); } - _geometry->setLoadPriority(this, -_lodDistance); - _geometry->ensureLoading(); if (needToRebuild) { const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); @@ -454,7 +412,7 @@ bool Model::updateGeometry() { buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); + mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); } _blendedVertexBuffers.push_back(buffer); } @@ -1069,53 +1027,36 @@ int Model::getLastFreeJointIndex(int jointIndex) const { return (isActive() && jointIndex != -1) ? _geometry->getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; } -void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bool delayLoad) { +void Model::setURL(const QUrl& url) { + // don't recreate the geometry if it's the same URL if (_url == url && _geometry && _geometry->getURL() == url) { return; } - _readyWhenAdded = false; // reset out render items. - _needsReload = true; - invalidCalculatedMeshBoxes(); - _url = url; _urlAsString = _url.toString(); + { + render::PendingChanges pendingChanges; + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + removeFromScene(scene, pendingChanges); + scene->enqueuePendingChanges(pendingChanges); + } + + _needsReload = true; + _meshGroupsKnown = false; + invalidCalculatedMeshBoxes(); + deleteGeometry(); + + _geometry.reset(new NetworkGeometry(url, false, QVariantHash())); onInvalidate(); - - // if so instructed, keep the current geometry until the new one is loaded - _nextGeometry = DependencyManager::get()->getGeometry(url, fallback, delayLoad); - _nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS; - if (!retainCurrent || !isActive() || (_nextGeometry && _nextGeometry->isLoaded())) { - applyNextGeometry(); - } } -void Model::geometryRefreshed() { - QObject* sender = QObject::sender(); - - if (sender == _geometry) { - _readyWhenAdded = false; // reset out render items. - _needsReload = true; - invalidCalculatedMeshBoxes(); - - onInvalidate(); - - // if so instructed, keep the current geometry until the new one is loaded - _nextGeometry = DependencyManager::get()->getGeometry(_url); - _nextLODHysteresis = NetworkGeometry::NO_HYSTERESIS; - applyNextGeometry(); - } else { - sender->disconnect(this, SLOT(geometryRefreshed())); - } -} - - const QSharedPointer Model::getCollisionGeometry(bool delayLoad) { if (_collisionGeometry.isNull() && !_collisionUrl.isEmpty()) { - _collisionGeometry = DependencyManager::get()->getGeometry(_collisionUrl, QUrl(), delayLoad); + _collisionGeometry.reset(new NetworkGeometry(_collisionUrl, delayLoad, QVariantHash())); } if (_collisionGeometry && _collisionGeometry->isLoaded()) { @@ -1130,7 +1071,7 @@ void Model::setCollisionModelURL(const QUrl& url) { return; } _collisionUrl = url; - _collisionGeometry = DependencyManager::get()->getGeometry(url, QUrl(), true); + _collisionGeometry.reset(new NetworkGeometry(url, false, QVariantHash())); } bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { @@ -1461,46 +1402,23 @@ void Model::setGeometry(const QSharedPointer& newGeometry) { if (_geometry == newGeometry) { return; } - - if (_geometry) { - _geometry->disconnect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed); - } _geometry = newGeometry; - QObject::connect(_geometry.data(), &Resource::onRefresh, this, &Model::geometryRefreshed); -} - -void Model::applyNextGeometry() { - // delete our local geometry and custom textures - deleteGeometry(); - _dilatedTextures.clear(); - _lodHysteresis = _nextLODHysteresis; - - // we retain a reference to the base geometry so that its reference count doesn't fall to zero - setGeometry(_nextGeometry); - - _meshGroupsKnown = false; - _readyWhenAdded = false; // in case any of our users are using scenes - _needsReload = false; // we are loaded now! - invalidCalculatedMeshBoxes(); - _nextGeometry.reset(); } void Model::deleteGeometry() { _blendedVertexBuffers.clear(); _rig->clearJointStates(); _meshStates.clear(); - _rig->deleteAnimations(); - - if (_geometry) { - _geometry->clearLoadPriority(this); - } - _blendedBlendshapeCoefficients.clear(); } AABox Model::getPartBounds(int meshIndex, int partIndex) { + if (!_geometry || !_geometry->isLoaded()) { + return AABox(); + } + if (meshIndex < _meshStates.size()) { const MeshState& state = _meshStates.at(meshIndex); bool isSkinned = state.clusterMatrices.size() > 1; @@ -1531,7 +1449,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { } void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) { - // PROFILE_RANGE(__FUNCTION__); +// PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("Model::renderPart"); if (!_readyWhenAdded) { return; // bail asap @@ -1557,14 +1475,14 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran auto alphaThreshold = args->_alphaThreshold; //translucent ? TRANSPARENT_ALPHA_THRESHOLD : OPAQUE_ALPHA_THRESHOLD; // FIX ME const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const QVector& networkMeshes = _geometry->getMeshes(); + const std::vector>& networkMeshes = _geometry->getMeshes(); // guard against partially loaded meshes - if (meshIndex >= networkMeshes.size() || meshIndex >= geometry.meshes.size() || meshIndex >= _meshStates.size() ) { + if (meshIndex >= (int)networkMeshes.size() || meshIndex >= (int)geometry.meshes.size() || meshIndex >= (int)_meshStates.size() ) { return; } - const NetworkMesh& networkMesh = networkMeshes.at(meshIndex); + const NetworkMesh& networkMesh = *(networkMeshes.at(meshIndex).get()); const FBXMesh& mesh = geometry.meshes.at(meshIndex); const MeshState& state = _meshStates.at(meshIndex); @@ -1614,8 +1532,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. - - if (meshIndex < 0 || meshIndex >= networkMeshes.size() || meshIndex > geometry.meshes.size()) { + if (meshIndex < 0 || meshIndex >= (int)networkMeshes.size() || meshIndex > geometry.meshes.size()) { _meshGroupsKnown = false; // regenerate these lists next time around. _readyWhenAdded = false; // in case any of our users are using scenes invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid @@ -1669,11 +1586,11 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran } // guard against partially loaded meshes - if (partIndex >= networkMesh.parts.size() || partIndex >= mesh.parts.size()) { + if (partIndex >= (int)networkMesh._parts.size() || partIndex >= mesh.parts.size()) { return; } - const NetworkMeshPart& networkPart = networkMesh.parts.at(partIndex); + const NetworkMeshPart& networkPart = *(networkMesh._parts.at(partIndex).get()); const FBXMeshPart& part = mesh.parts.at(partIndex); model::MaterialPointer material = part._material; @@ -1790,10 +1707,10 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran void Model::segregateMeshGroups() { const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const QVector& networkMeshes = _geometry->getMeshes(); + const std::vector>& networkMeshes = _geometry->getMeshes(); // all of our mesh vectors must match in size - if (networkMeshes.size() != geometry.meshes.size() || + if ((int)networkMeshes.size() != geometry.meshes.size() || geometry.meshes.size() != _meshStates.size()) { qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; return; @@ -1803,12 +1720,12 @@ void Model::segregateMeshGroups() { _opaqueRenderItems.clear(); // Run through all of the meshes, and place them into their segregated, but unsorted buckets - for (int i = 0; i < networkMeshes.size(); i++) { - const NetworkMesh& networkMesh = networkMeshes.at(i); + for (int i = 0; i < (int)networkMeshes.size(); i++) { + const NetworkMesh& networkMesh = *(networkMeshes.at(i).get()); const FBXMesh& mesh = geometry.meshes.at(i); const MeshState& state = _meshStates.at(i); - bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size(); + bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == (int)networkMesh._parts.size(); bool hasTangents = !mesh.tangents.isEmpty(); bool hasSpecular = mesh.hasSpecularTexture(); bool hasLightmap = mesh.hasEmissiveTexture(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index c9b63b598e..e55bff6aca 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -68,11 +68,7 @@ public: /// Sets the URL of the model to render. - /// \param fallback the URL of a fallback model to render if the requested model fails to load - /// \param retainCurrent if true, keep rendering the current model until the new one is loaded - /// \param delayLoad if true, don't load the model immediately; wait until actually requested - Q_INVOKABLE void setURL(const QUrl& url, const QUrl& fallback = QUrl(), - bool retainCurrent = false, bool delayLoad = false); + Q_INVOKABLE void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } const QString& getURLAsString() const { return _urlAsString; } @@ -89,7 +85,7 @@ public: render::Item::Status::Getters& statusGetters); void removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); void renderSetup(RenderArgs* args); - bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); } + bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().empty()); } bool isVisible() const { return _isVisible; } @@ -141,14 +137,11 @@ public: const QUrl& getCollisionURL() const { return _collisionUrl; } /// Returns a reference to the shared collision geometry. - const QSharedPointer getCollisionGeometry(bool delayLoad = true); + const QSharedPointer getCollisionGeometry(bool delayLoad = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } - /// Sets the distance parameter used for LOD computations. - void setLODDistance(float distance) { _lodDistance = distance; } - void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false); bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled @@ -309,20 +302,12 @@ protected: // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; - void geometryRefreshed(); - private: - void applyNextGeometry(); void deleteGeometry(); QVector createJointStates(const FBXGeometry& geometry); void initJointTransforms(); - QSharedPointer _nextGeometry; - float _lodDistance; - float _lodHysteresis; - float _nextLODHysteresis; - QSharedPointer _collisionGeometry; float _pupilDilation; diff --git a/libraries/render/CMakeLists.txt b/libraries/render/CMakeLists.txt index 4d2be949e6..1f73d93519 100644 --- a/libraries/render/CMakeLists.txt +++ b/libraries/render/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME render) +setup_memory_debugger() + AUTOSCRIBE_SHADER_LIB(gpu model) # use setup_hifi_library macro to setup our project and link appropriate Qt modules @@ -21,4 +23,4 @@ if (WIN32) target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") endif () endif() -endif (WIN32) \ No newline at end of file +endif (WIN32) diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 139b99e426..670a13f081 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME script-engine) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules setup_hifi_library(Gui Network Script WebSockets Widgets) diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 92bc5d31da..0d16b26fdf 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -21,13 +21,15 @@ class ScriptAudioInjector : public QObject { Q_PROPERTY(bool isPlaying READ isPlaying) Q_PROPERTY(float loudness READ getLoudness) + Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: ScriptAudioInjector(AudioInjector* injector); ~ScriptAudioInjector(); public slots: void restart() { _injector->restart(); } void stop() { _injector->stop(); } - + + const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); } float getLoudness() const { return _injector->getLoudness(); } @@ -49,4 +51,4 @@ Q_DECLARE_METATYPE(ScriptAudioInjector*) QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in); void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& out); -#endif // hifi_ScriptAudioInjector_h \ No newline at end of file +#endif // hifi_ScriptAudioInjector_h diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 00a80619bc..a80f4194ef 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -1,5 +1,7 @@ set(TARGET_NAME shared) +setup_memory_debugger() + # use setup_hifi_library macro to setup our project and link appropriate Qt modules # TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp) setup_hifi_library(Gui Network Script Widgets) diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index 1aefc99c78..68caa940c1 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -7,6 +7,7 @@ link_hifi_libraries(render-utils shared) add_dependency_external_projects(glm) find_package(GLM REQUIRED) + +setup_memory_debugger() + target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) - - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1593b649a0..c2dc30c4bb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -35,3 +35,5 @@ set_target_properties("all-tests" PROPERTIES FOLDER "hidden/test-targets") set_target_properties("all-tests" PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD TRUE EXCLUDE_FROM_ALL TRUE) + +setup_memory_debugger() diff --git a/tests/animation/CMakeLists.txt b/tests/animation/CMakeLists.txt index 2e9dbc9424..bc1e93a94f 100644 --- a/tests/animation/CMakeLists.txt +++ b/tests/animation/CMakeLists.txt @@ -6,4 +6,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () +setup_memory_debugger() + setup_hifi_testcase() diff --git a/tests/audio/CMakeLists.txt b/tests/audio/CMakeLists.txt index 8e894e929e..c56ef049bd 100644 --- a/tests/audio/CMakeLists.txt +++ b/tests/audio/CMakeLists.txt @@ -6,4 +6,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/entities/CMakeLists.txt b/tests/entities/CMakeLists.txt index 0077549100..f83efe7f64 100644 --- a/tests/entities/CMakeLists.txt +++ b/tests/entities/CMakeLists.txt @@ -9,4 +9,6 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries(entities avatars shared octree gpu model fbx networking animation environment) -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/tests/jitter/CMakeLists.txt b/tests/jitter/CMakeLists.txt index 7b636aa87f..ba46582b02 100644 --- a/tests/jitter/CMakeLists.txt +++ b/tests/jitter/CMakeLists.txt @@ -7,4 +7,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro() -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/networking/CMakeLists.txt b/tests/networking/CMakeLists.txt index 3be2fff027..fcf32d89c8 100644 --- a/tests/networking/CMakeLists.txt +++ b/tests/networking/CMakeLists.txt @@ -7,4 +7,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/networking/src/ResourceTests.cpp b/tests/networking/src/ResourceTests.cpp new file mode 100644 index 0000000000..f18f676398 --- /dev/null +++ b/tests/networking/src/ResourceTests.cpp @@ -0,0 +1,95 @@ +// +// ResoruceTests.cpp +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ResourceCache.h" +#include "NetworkAccessManager.h" +#include "DependencyManager.h" + +#include "ResourceTests.h" + +QTEST_MAIN(ResourceTests) + +void ResourceTests::initTestCase() { + + auto resourceCacheSharedItems = DependencyManager::set(); + + const qint64 MAXIMUM_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB + + // set up the file cache + //QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + QString cachePath = "./resourceTestCache"; + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkDiskCache* cache = new QNetworkDiskCache(); + cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); + cache->setCacheDirectory(cachePath); + cache->clear(); // clear the cache + networkAccessManager.setCache(cache); +} + +static Resource* resource = nullptr; + + +static bool waitForSignal(QObject *sender, const char *signal, int timeout = 1000) { + QEventLoop loop; + QTimer timer; + timer.setInterval(timeout); + timer.setSingleShot(true); + + loop.connect(sender, signal, SLOT(quit())); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + timer.start(); + loop.exec(); + + return timer.isActive(); +} + +void ResourceTests::downloadFirst() { + + // download the Mery fst file + QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst"); + resource = new Resource(meryUrl, false); + + const int timeout = 1000; + QEventLoop loop; + QTimer timer; + timer.setInterval(timeout); + timer.setSingleShot(true); + + loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit())); + loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit())); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + timer.start(); + loop.exec(); + + QVERIFY(resource->isLoaded()); +} + +void ResourceTests::downloadAgain() { + + // download the Mery fst file + QUrl meryUrl = QUrl("http://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst"); + resource = new Resource(meryUrl, false); + + const int timeout = 1000; + QEventLoop loop; + QTimer timer; + timer.setInterval(timeout); + timer.setSingleShot(true); + + loop.connect(resource, SIGNAL(loaded(QNetworkReply&)), SLOT(quit())); + loop.connect(resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(quit())); + loop.connect(&timer, SIGNAL(timeout()), SLOT(quit())); + timer.start(); + loop.exec(); + + QVERIFY(resource->isLoaded()); + +} diff --git a/tests/networking/src/ResourceTests.h b/tests/networking/src/ResourceTests.h new file mode 100644 index 0000000000..32fc151982 --- /dev/null +++ b/tests/networking/src/ResourceTests.h @@ -0,0 +1,23 @@ +// +// ResourceTests.h +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ResourceTests_h +#define hifi_ResourceTests_h + +#include + +class ResourceTests : public QObject { + Q_OBJECT +private slots: + void initTestCase(); + void downloadFirst(); + void downloadAgain(); +}; + +#endif // hifi_ResourceTests_h diff --git a/tests/octree/CMakeLists.txt b/tests/octree/CMakeLists.txt index a605a4088b..77511c682a 100644 --- a/tests/octree/CMakeLists.txt +++ b/tests/octree/CMakeLists.txt @@ -7,4 +7,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase(Script Network) \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase(Script Network) diff --git a/tests/physics/CMakeLists.txt b/tests/physics/CMakeLists.txt index 36cf21c681..1a6f49430b 100644 --- a/tests/physics/CMakeLists.txt +++ b/tests/physics/CMakeLists.txt @@ -21,4 +21,6 @@ macro (SETUP_TESTCASE_DEPENDENCIES) copy_dlls_beside_windows_executable() endmacro () +setup_memory_debugger() + setup_hifi_testcase(Script) diff --git a/tests/render-utils/CMakeLists.txt b/tests/render-utils/CMakeLists.txt index 97d3214744..1b47f85099 100644 --- a/tests/render-utils/CMakeLists.txt +++ b/tests/render-utils/CMakeLists.txt @@ -9,4 +9,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(render-utils gpu shared) message(${PROJECT_BINARY_DIR}) + +setup_memory_debugger() + copy_dlls_beside_windows_executable() diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt index eefdf2aa3a..3ee9f4ae9f 100644 --- a/tests/shaders/CMakeLists.txt +++ b/tests/shaders/CMakeLists.txt @@ -18,4 +18,7 @@ include_directories("${PROJECT_BINARY_DIR}/../../libraries/entities-renderer/") include_directories("${PROJECT_BINARY_DIR}/../../libraries/model/") message(${PROJECT_BINARY_DIR}) + +setup_memory_debugger() + copy_dlls_beside_windows_executable() diff --git a/tests/shared/CMakeLists.txt b/tests/shared/CMakeLists.txt index bc6eab0212..c3d6cfd810 100644 --- a/tests/shared/CMakeLists.txt +++ b/tests/shared/CMakeLists.txt @@ -8,4 +8,6 @@ macro (setup_testcase_dependencies) copy_dlls_beside_windows_executable() endmacro () -setup_hifi_testcase() \ No newline at end of file +setup_memory_debugger() + +setup_hifi_testcase() diff --git a/tests/ui/CMakeLists.txt b/tests/ui/CMakeLists.txt index ad1009d925..c0f20a280f 100644 --- a/tests/ui/CMakeLists.txt +++ b/tests/ui/CMakeLists.txt @@ -13,4 +13,6 @@ endif() # link in the shared libraries link_hifi_libraries(ui render-utils gpu shared) -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 55994f3d89..4d8618b37c 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,3 +1,5 @@ +setup_memory_debugger() + # add the tool directories add_subdirectory(mtc) set_target_properties(mtc PROPERTIES FOLDER "Tools") @@ -7,4 +9,3 @@ set_target_properties(scribe PROPERTIES FOLDER "Tools") add_subdirectory(vhacd-util) set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") - diff --git a/tools/mtc/CMakeLists.txt b/tools/mtc/CMakeLists.txt index 5c598eaf0b..fe5f1920bc 100644 --- a/tools/mtc/CMakeLists.txt +++ b/tools/mtc/CMakeLists.txt @@ -1,4 +1,6 @@ set(TARGET_NAME mtc) setup_hifi_project() -copy_dlls_beside_windows_executable() \ No newline at end of file +setup_memory_debugger() + +copy_dlls_beside_windows_executable() diff --git a/tools/scribe/CMakeLists.txt b/tools/scribe/CMakeLists.txt index b71a287e46..0abf70f727 100755 --- a/tools/scribe/CMakeLists.txt +++ b/tools/scribe/CMakeLists.txt @@ -1,2 +1,5 @@ set(TARGET_NAME scribe) -setup_hifi_project() \ No newline at end of file + +setup_memory_debugger() + +setup_hifi_project() diff --git a/tools/vhacd-util/CMakeLists.txt b/tools/vhacd-util/CMakeLists.txt index c94b2ad083..b79a6a1893 100644 --- a/tools/vhacd-util/CMakeLists.txt +++ b/tools/vhacd-util/CMakeLists.txt @@ -8,6 +8,8 @@ find_package(VHACD REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${VHACD_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${VHACD_LIBRARIES}) +setup_memory_debugger() + if (UNIX AND NOT APPLE) include(FindOpenMP) if(OPENMP_FOUND) diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index 3c02c956e4..f39bea9cf9 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -36,15 +36,16 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { std::cout << "Reading FBX.....\n"; QByteArray fbxContents = fbx.readAll(); - + FBXGeometry* geom; if (filename.toLower().endsWith(".obj")) { - result = OBJReader().readOBJ(fbxContents, QVariantHash()); + geom = OBJReader().readOBJ(fbxContents, QVariantHash()); } else if (filename.toLower().endsWith(".fbx")) { - result = readFBX(fbxContents, QVariantHash(), filename); + geom = readFBX(fbxContents, QVariantHash(), filename); } else { qDebug() << "unknown file extension"; return false; } + result = *geom; reSortFBXGeometryMeshes(result);