diff --git a/BUILD.md b/BUILD.md index c86e34823c..9bd5da7c14 100644 --- a/BUILD.md +++ b/BUILD.md @@ -6,12 +6,12 @@ * [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1g * IMPORTANT: OpenSSL 1.0.1g is critical to avoid a security vulnerability. * [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3 -* [Bullet Physics Engine](http://bulletphysics.org) ~> 2.82 +* [Bullet Physics Engine](https://code.google.com/p/bullet/downloads/list) ~> 2.82 ### OS Specific Build Guides +* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows. * [BUILD_OSX.md](BUILD_OSX.md) - additional instructions for OS X. * [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux. -* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows. ###CMake Hifi uses CMake to generate build files and project files for your platform. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 3ccc4881c1..5045201f62 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -1,15 +1,10 @@ Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Windows specific instructions are found in this file. -###Windows Dependencies +###Windows Specific Dependencies * [GLEW](http://glew.sourceforge.net/) ~> 1.10.0 * [freeglut MSVC](http://www.transmissionzero.co.uk/software/freeglut-devel/) ~> 2.8.1 * [zLib](http://www.zlib.net/) ~> 1.2.8 - -###Visual Studio - -Currently building on Windows has been tested using the following compilers: -* Visual Studio 2013 -* Visual Studio 2013 Express +* (remember that you need all other dependencies listed in [BUILD.md](BUILD.md)) ####Visual Studio 2013 @@ -30,14 +25,15 @@ You can use the online installer or the offline installer. If you use the offlin NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit version of libraries for interface.exe to run. The 32-bit version of the static library is the one linked by our CMake find modules. -* Download the online installer [here](http://qt-project.org/downloads) +* [Download the online installer](http://qt-project.org/downloads) * When it asks you to select components, ONLY select the following: * Qt > Qt 5.3.2 > **msvc2013 32-bit OpenGL** -* Download the offline installer [here](http://download.qt-project.org/official_releases/qt/5.3/5.3.2/qt-opensource-windows-x86-msvc2013_opengl-5.3.2.exe) +* [Download the offline installer](http://download.qt-project.org/official_releases/qt/5.3/5.3.2/qt-opensource-windows-x86-msvc2013_opengl-5.3.2.exe) Once Qt is installed, you need to manually configure the following: * Make sure the Qt runtime DLLs are loadable. You must do this before you attempt to build because some tools for the build depend on Qt. E.g., add to the PATH: `Qt\5.3.2\msvc2013_opengl\bin\`. +* Go to Control Panel > System > Advanced System Settings > Environment Variables > New ... * Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.3.2\msvc2013_opengl` directory. ###External Libraries @@ -47,6 +43,9 @@ CMake will need to know where the headers and libraries for required external de The recommended route for CMake to find the external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure: root_lib_dir + -> bullet + -> include + -> lib -> freeglut -> bin -> include @@ -92,7 +91,7 @@ To prevent these problems, install OpenSSL yourself. Download the following bina * Visual C++ 2008 Redistributables * Win32 OpenSSL v1.0.1h -Install OpenSSL into the Windows system directory, to make sure that QT uses the version that you've just installed, and not some other version. +Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version. ###Intel Threading Building Blocks (TBB) @@ -111,8 +110,10 @@ Add the following environment variables (remember to substitute your own directo Add to the PATH: `%HIFI_LIB_DIR%\zlib` -Important! This should be added at the beginning of the path, not the end. That's because your -system likely has many copies of zlib1.dll, and you want High Fidelity to use the correct version. If High Fidelity picks up the wrong zlib1.dll then it might be unable to use it, and that would cause it to fail to start, showing only the cryptic error "The application was unable to start correctly: 0xc0000022". +(The PATH environment variable is where Windows looks for its DLL's and executables. There's a great tool for editing these variables with ease, [Rapid Environment Editor](http://www.rapidee.com/en/download)) + +Important! This should be added at the beginning of the path, not the end (your +system likely has many copies of zlib1.dll, and you want High Fidelity to use the correct version). If High Fidelity picks up the wrong zlib1.dll then it might be unable to use it, and that would cause it to fail to start, showing only the cryptic error "The application was unable to start correctly: 0xc0000022". ###freeglut @@ -134,10 +135,10 @@ Be careful with glm. For the folder other libraries would normally call 'include ###Bullet -Bullet 2.82 source can be downloaded [here](https://code.google.com/p/bullet/downloads/detail?name=bullet-2.82-r2704.zip). Bullet does not come with prebuilt libraries, you need to make those yourself. +Bullet 2.82 source can be [downloaded here](https://code.google.com/p/bullet/downloads/detail?name=bullet-2.82-r2704.zip). Bullet does not come with prebuilt libraries, you need to make those yourself. * Download the zip file and extract into a temporary folder -* Create a directory named cmakebuild. Bullet comes with a build\ directory by default, however, that directory is intended for use with premake, and considering premake doesn't support VS2013, I prefer to run the cmake build on its own directory. +* Create a directory named cmakebuild. Bullet comes with a build\ directory by default, however, that directory is intended for use with premake, and considering premake doesn't support VS2013, we prefer to run the cmake build on its own directory. * Make the following modifications to Bullet's source: 1. In file: Extras\HACD\hacdICHull.cpp --- in line: 17 --- insert: #include <algorithm> 2. In file: src\MiniCL\cl_MiniCL_Defs.h --- comment lines 364 to 372 @@ -162,7 +163,7 @@ You now have Bullet libraries compiled, now you need to put them in the right pl _Note that the INSTALL target should handle the copying of files into an install directory automatically, however, without modifications to Cmake, the install target didn't work right for me, please update this instructions if you get that working right - Leo <leo@highfidelity.io>_ ###Build High Fidelity using Visual Studio -Follow the same build steps from the CMake section, but pass a different generator to CMake. +Follow the same build steps from the CMake section of [BUILD.md](BUILD.md), but pass a different generator to CMake. cmake .. -DZLIB_LIBRARY=%ZLIB_LIBRARY% -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% -G "Visual Studio 12" diff --git a/README.md b/README.md index 90f1ca0dc7..a4d193324d 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal win This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option. - ./assignment-client -n 6 + ./assignment-client -n 4 To test things out you'll want to run the Interface client. diff --git a/examples/realsenseHands.js b/examples/controllers/RealSense/realsenseHands.js similarity index 100% rename from examples/realsenseHands.js rename to examples/controllers/RealSense/realsenseHands.js diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index 0c0740e12b..f3c1e14001 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -486,7 +486,8 @@ function mousePressEvent(event) { if (clickedOverlay == offButton) { Script.stop(); } else if (clickedOverlay == platformButton) { - makePlatform(-9.8, 1.0, 5); + var platformSize = 3; + makePlatform(-9.8, 1.0, platformSize); } else if (clickedOverlay == gridButton) { makeGrid("Box", 1.0, 3); } diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js index 0c6e135739..202a226f1e 100644 --- a/examples/controllers/hydra/hydraGrab.js +++ b/examples/controllers/hydra/hydraGrab.js @@ -14,13 +14,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/entityPropertyDialogBox.js"); +Script.include("../../libraries/entityPropertyDialogBox.js"); var entityPropertyDialogBox = EntityPropertyDialogBox; -var LASER_WIDTH = 4; -var LASER_COLOR = { red: 255, green: 0, blue: 0 }; -var LASER_LENGTH_FACTOR = 500; - var MIN_ANGULAR_SIZE = 2; var MAX_ANGULAR_SIZE = 45; var allowLargeModels = false; @@ -32,7 +28,44 @@ var RIGHT = 1; var jointList = MyAvatar.getJointNames(); -var mode = 0; +var STICKS = 0; +var MAPPED = 1; +var mode = STICKS; + +var LASER_WIDTH = 4; +var LASER_COLOR = [{ red: 200, green: 150, blue: 50 }, // STICKS + { red: 50, green: 150, blue: 200 }]; // MAPPED +var LASER_LENGTH_FACTOR = 500; + +var lastAccurateIntersection = null; +var accurateIntersections = 0; +var totalIntersections = 0; +var inaccurateInARow = 0; +var maxInaccurateInARow = 0; +function getRayIntersection(pickRay) { // pickRay : { origin : {xyz}, direction : {xyz} } + if (lastAccurateIntersection === null) { + lastAccurateIntersection = Entities.findRayIntersectionBlocking(pickRay); + } else { + var intersection = Entities.findRayIntersection(pickRay); + if (intersection.accurate) { + lastAccurateIntersection = intersection; + accurateIntersections++; + maxInaccurateInARow = (maxInaccurateInARow > inaccurateInARow) ? maxInaccurateInARow : inaccurateInARow; + inaccurateInARow = 0; + } else { + inaccurateInARow++; + } + totalIntersections++; + } + return lastAccurateIntersection; +} + +function printIntersectionsStats() { + var ratio = accurateIntersections / totalIntersections; + print("Out of " + totalIntersections + " intersections, " + accurateIntersections + " where accurate. (" + ratio * 100 +"%)"); + print("Worst case was " + maxInaccurateInARow + " inaccurate intersections in a row."); +} + function controller(wichSide) { this.side = wichSide; @@ -42,10 +75,10 @@ function controller(wichSide) { this.bumper = 6 * wichSide + 5; this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm); - this.palmPosition = Controller.getSpatialControlPosition(this.palm); + this.palmPosition = this.oldPalmPosition; this.oldTipPosition = Controller.getSpatialControlPosition(this.tip); - this.tipPosition = Controller.getSpatialControlPosition(this.tip); + this.tipPosition = this.oldTipPosition; this.oldUp = Controller.getSpatialControlNormal(this.palm); this.up = this.oldUp; @@ -75,13 +108,13 @@ function controller(wichSide) { this.positionAtGrab; this.rotationAtGrab; this.modelPositionAtGrab; - this.rotationAtGrab; + this.modelRotationAtGrab; this.jointsIntersectingFromStart = []; this.laser = Overlays.addOverlay("line3d", { start: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, - color: LASER_COLOR, + color: LASER_COLOR[mode], alpha: 1, visible: false, lineWidth: LASER_WIDTH, @@ -132,7 +165,7 @@ function controller(wichSide) { this.positionAtGrab = this.palmPosition; this.rotationAtGrab = this.rotation; this.modelPositionAtGrab = properties.position; - this.rotationAtGrab = properties.rotation; + this.modelRotationAtGrab = properties.rotation; this.jointsIntersectingFromStart = []; for (var i = 0; i < jointList.length; i++) { var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); @@ -245,9 +278,9 @@ function controller(wichSide) { var inverseRotation = Quat.inverse(MyAvatar.orientation); var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); + startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); - var distance = Vec3.length(direction); - direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / distance); + direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); var endPosition = Vec3.sum(startPosition, direction); Overlays.editOverlay(this.laser, { @@ -267,17 +300,16 @@ function controller(wichSide) { start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)) }); - this.showLaser(!this.grabbing || mode == 0); + this.showLaser(!this.grabbing || mode == STICKS); if (this.glowedIntersectingModel.isKnownID) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); this.glowedIntersectingModel.isKnownID = false; } if (!this.grabbing) { - var intersection = Entities.findRayIntersectionBlocking({ - origin: this.palmPosition, - direction: this.front - }); + var intersection = getRayIntersection({ origin: this.palmPosition, + direction: this.front + }); var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0; @@ -304,17 +336,16 @@ function controller(wichSide) { if (this.grabbing) { if (!this.entityID.isKnownID) { print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); - this.entityID = Entities.findRayIntersectionBlocking({ - origin: this.palmPosition, - direction: this.front - }).entityID; + this.entityID = getRayIntersection({ origin: this.palmPosition, + direction: this.front + }).entityID; print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); } var newPosition; var newRotation; switch (mode) { - case 0: + case STICKS: newPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, this.x)); newPosition = Vec3.sum(newPosition, @@ -328,7 +359,7 @@ function controller(wichSide) { newRotation = Quat.multiply(newRotation, this.oldModelRotation); break; - case 1: + case MAPPED: var forward = Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 }); var d = Vec3.dot(forward, MyAvatar.position); @@ -350,8 +381,9 @@ function controller(wichSide) { newRotation = Quat.multiply(this.rotation, Quat.inverse(this.rotationAtGrab)); + newRotation = Quat.multiply(newRotation, newRotation); newRotation = Quat.multiply(newRotation, - this.rotationAtGrab); + this.modelRotationAtGrab); break; } Entities.editEntity(this.entityID, { @@ -397,15 +429,13 @@ function controller(wichSide) { var bumperValue = Controller.isButtonPressed(this.bumper); if (bumperValue && !this.bumperValue) { - if (mode == 0) { - mode = 1; - Overlays.editOverlay(leftController.laser, { color: { red: 0, green: 0, blue: 255 } }); - Overlays.editOverlay(rightController.laser, { color: { red: 0, green: 0, blue: 255 } }); - } else { - mode = 0; - Overlays.editOverlay(leftController.laser, { color: { red: 255, green: 0, blue: 0 } }); - Overlays.editOverlay(rightController.laser, { color: { red: 255, green: 0, blue: 0 } }); + if (mode === STICKS) { + mode = MAPPED; + } else if (mode === MAPPED) { + mode = STICKS; } + Overlays.editOverlay(leftController.laser, { color: LASER_COLOR[mode] }); + Overlays.editOverlay(rightController.laser, { color: LASER_COLOR[mode] }); } this.bumperValue = bumperValue; @@ -475,10 +505,10 @@ function controller(wichSide) { Vec3.print("Looking at: ", this.palmPosition); var pickRay = { origin: this.palmPosition, direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; - var foundIntersection = Entities.findRayIntersectionBlocking(pickRay); + var foundIntersection = getRayIntersection(pickRay); - if(!foundIntersection.accurate) { - print("No accurate intersection"); + if(!foundIntersection.intersects) { + print("No intersection"); return; } newModel = foundIntersection.entityID; @@ -526,7 +556,7 @@ function moveEntities() { switch (mode) { - case 0: + case STICKS: var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); @@ -545,7 +575,7 @@ function moveEntities() { newPosition = Vec3.sum(middle, Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); break; - case 1: + case MAPPED: var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition)); var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition)); @@ -566,11 +596,11 @@ function moveEntities() { leftController.positionAtGrab = leftController.palmPosition; leftController.rotationAtGrab = leftController.rotation; leftController.modelPositionAtGrab = leftController.oldModelPosition; - leftController.rotationAtGrab = rotation; + leftController.modelRotationAtGrab = rotation; rightController.positionAtGrab = rightController.palmPosition; rightController.rotationAtGrab = rightController.rotation; rightController.modelPositionAtGrab = rightController.oldModelPosition; - rightController.rotationAtGrab = rotation; + rightController.modelRotationAtGrab = rotation; break; } Entities.editEntity(leftController.entityID, { @@ -628,43 +658,56 @@ var glowedEntityID = { id: -1, isKnownID: false }; // In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already // exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that // added it. +var ROOT_MENU = "Edit"; +var ITEM_BEFORE = "Physics"; +var MENU_SEPARATOR = "Models"; +var EDIT_PROPERTIES = "Edit Properties..."; +var INTERSECTION_STATS = "Print Intersection Stats"; +var DELETE = "Delete"; +var LARGE_MODELS = "Allow Selecting of Large Models"; +var SMALL_MODELS = "Allow Selecting of Small Models"; + var LIGHTS = "Allow Selecting of Lights"; + var modelMenuAddedDelete = false; var originalLightsArePickable = Entities.getLightsArePickable(); function setupModelMenus() { print("setupModelMenus()"); // adj our menuitems - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...", - shortcutKeyEvent: { text: "`" }, afterItem: "Models" }); - if (!Menu.menuItemExists("Edit", "Delete")) { + Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: MENU_SEPARATOR, isSeparator: true, beforeItem: ITEM_BEFORE }); + Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: EDIT_PROPERTIES, + shortcutKeyEvent: { text: "`" }, afterItem: MENU_SEPARATOR }); + Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: INTERSECTION_STATS, afterItem: MENU_SEPARATOR }); + if (!Menu.menuItemExists(ROOT_MENU, DELETE)) { print("no delete... adding ours"); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", - shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" }); + Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: DELETE, + shortcutKeyEvent: { text: "backspace" }, afterItem: MENU_SEPARATOR }); modelMenuAddedDelete = true; } else { print("delete exists... don't add ours"); } - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", - afterItem: "Paste Models", isCheckable: true }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S", - afterItem: "Allow Selecting of Large Models", isCheckable: true }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L", - afterItem: "Allow Selecting of Small Models", isCheckable: true }); + Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LARGE_MODELS, shortcutKey: "CTRL+META+L", + afterItem: DELETE, isCheckable: true }); + Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: SMALL_MODELS, shortcutKey: "CTRL+META+S", + afterItem: LARGE_MODELS, isCheckable: true }); + Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LIGHTS, shortcutKey: "CTRL+SHIFT+META+L", + afterItem: SMALL_MODELS, isCheckable: true }); Entities.setLightsArePickable(false); } function cleanupModelMenus() { - Menu.removeMenuItem("Edit", "Edit Properties..."); + Menu.removeSeparator(ROOT_MENU, MENU_SEPARATOR); + Menu.removeMenuItem(ROOT_MENU, EDIT_PROPERTIES); + Menu.removeMenuItem(ROOT_MENU, INTERSECTION_STATS); if (modelMenuAddedDelete) { // delete our menuitems - Menu.removeMenuItem("Edit", "Delete"); + Menu.removeMenuItem(ROOT_MENU, DELETE); } - Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); - Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); - Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); + Menu.removeMenuItem(ROOT_MENU, LARGE_MODELS); + Menu.removeMenuItem(ROOT_MENU, SMALL_MODELS); + Menu.removeMenuItem(ROOT_MENU, LIGHTS); } @@ -688,13 +731,13 @@ function showPropertiesForm(editModelID) { Menu.menuItemEvent.connect(function (menuItem) { print("menuItemEvent() in JS... menuItem=" + menuItem); - if (menuItem == "Allow Selecting of Small Models") { - allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); - } else if (menuItem == "Allow Selecting of Large Models") { - allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models"); - } else if (menuItem == "Allow Selecting of Lights") { - Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); - } else if (menuItem == "Delete") { + if (menuItem == SMALL_MODELS) { + allowSmallModels = Menu.isOptionChecked(SMALL_MODELS); + } else if (menuItem == LARGE_MODELS) { + allowLargeModels = Menu.isOptionChecked(LARGE_MODELS); + } else if (menuItem == LIGHTS) { + Entities.setLightsArePickable(Menu.isOptionChecked(LIGHTS)); + } else if (menuItem == DELETE) { if (leftController.grabbing) { print(" Delete Entity.... leftController.entityID="+ leftController.entityID); Entities.deleteEntity(leftController.entityID); @@ -712,7 +755,7 @@ Menu.menuItemEvent.connect(function (menuItem) { } else { print(" Delete Entity.... not holding..."); } - } else if (menuItem == "Edit Properties...") { + } else if (menuItem == EDIT_PROPERTIES) { editModelID = -1; if (leftController.grabbing) { print(" Edit Properties.... leftController.entityID="+ leftController.entityID); @@ -727,16 +770,18 @@ Menu.menuItemEvent.connect(function (menuItem) { print(" Edit Properties.... about to edit properties..."); showPropertiesForm(editModelID); } + } else if (menuItem == INTERSECTION_STATS) { + printIntersectionsStats(); } }); Controller.keyReleaseEvent.connect(function (event) { // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items if (event.text == "`") { - handeMenuEvent("Edit Properties..."); + handeMenuEvent(EDIT_PROPERTIES); } if (event.text == "BACKSPACE") { - handeMenuEvent("Delete"); + handeMenuEvent(DELETE); } }); diff --git a/examples/controllers/hydra/squeezeHands.js b/examples/controllers/hydra/squeezeHands.js index 2a4756f017..b1e9274905 100644 --- a/examples/controllers/hydra/squeezeHands.js +++ b/examples/controllers/hydra/squeezeHands.js @@ -60,11 +60,9 @@ Script.update.connect(function(deltaTime) { } if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){ - MyAvatar.stopAnimation(leftHandAnimation); MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); } if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { - MyAvatar.stopAnimation(rightHandAnimation); MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); } diff --git a/examples/billiards.js b/examples/example/games/billiards.js similarity index 100% rename from examples/billiards.js rename to examples/example/games/billiards.js diff --git a/examples/inspect.js b/examples/inspect.js index ff0925db97..d730a7e53f 100644 --- a/examples/inspect.js +++ b/examples/inspect.js @@ -158,6 +158,11 @@ function handleModes() { avatarOrientation.w != MyAvatar.orientation.w)) { newMode = noMode; } + + if (mode == noMode && newMode != noMode && Camera.mode == "independent") { + newMode = noMode; + } + // if leaving noMode if (mode == noMode && newMode != noMode) { saveCameraState(); diff --git a/examples/libraries/entityCameraTool.js b/examples/libraries/entityCameraTool.js index b9170dc25d..f5095bb149 100644 --- a/examples/libraries/entityCameraTool.js +++ b/examples/libraries/entityCameraTool.js @@ -80,7 +80,8 @@ CameraManager = function() { that.lastMousePosition = { x: 0, y: 0 }; that.enable = function() { - if (that.enabled) return; + if (Camera.mode == "independent" || that.enabled) return; + that.enabled = true; that.mode = MODE_INACTIVE; diff --git a/examples/utilities/record/recorder.js b/examples/utilities/record/recorder.js index f3f46adf1a..495a862db1 100644 --- a/examples/utilities/record/recorder.js +++ b/examples/utilities/record/recorder.js @@ -10,7 +10,7 @@ // HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; -Script.include("libraries/toolBars.js"); +Script.include("../../libraries/toolBars.js"); var recordingFile = "recording.rec"; diff --git a/examples/utilities/tools/developerMenuItems.js b/examples/utilities/tools/developerMenuItems.js index 3a274c7083..419285eeb9 100644 --- a/examples/utilities/tools/developerMenuItems.js +++ b/examples/utilities/tools/developerMenuItems.js @@ -18,6 +18,10 @@ function setupMenus() { } if (!Menu.menuExists("Developer > Entities")) { Menu.addMenu("Developer > Entities"); + + // NOTE: these menu items aren't currently working. I've temporarily removed them. Will add them back once we + // rewire these to work + /* Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Bounds", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Triangles", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Element Bounds", isCheckable: true, isChecked: false }); @@ -26,9 +30,26 @@ function setupMenus() { Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Attempt Render Entities as Scene", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false }); Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Disable Light Entities", isCheckable: true, isChecked: false }); + */ + Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't send collision updates to server", isCheckable: true, isChecked: false }); } } +Menu.menuItemEvent.connect(function (menuItem) { + print("menuItemEvent() in JS... menuItem=" + menuItem); + + if (menuItem == "Don't send collision updates to server") { + var dontSendUpdates = Menu.isOptionChecked("Don't send collision updates to server"); + print(" dontSendUpdates... checked=" + dontSendUpdates); + Entities.setSendPhysicsUpdates(!dontSendUpdates); + } +}); + +setupMenus(); + +// register our scriptEnding callback +Script.scriptEnding.connect(scriptEnding); + function scriptEnding() { Menu.removeMenu("Developer > Entities"); } diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 4648656e87..77deb6125b 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -173,23 +173,12 @@ void IceServer::clearInactivePeers() { bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { // // We need an HTTP handler in order to monitor the health of the ice server - // The correct functioning of the ICE server will first be determined by its HTTP availability, - // and then by the existence of a minimum number of peers in the list, matching the minimum number of - // domains in production by High Fidelity. + // The correct functioning of the ICE server will be determined by its HTTP availability, // - int MINIMUM_PEERS = 3; - bool IS_HEALTHY = false; - - IS_HEALTHY = _activePeers.size() >= MINIMUM_PEERS ? true : false; - if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (url.path() == "/status") { - if (IS_HEALTHY) { - connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size())); - } else { - connection->respond(HTTPConnection::StatusCode404, QByteArray::number(_activePeers.size())); - } + connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size())); } } return true; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 86af8b0e91..4537b3eb91 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -153,6 +153,16 @@ bool setupEssentials(int& argc, char** argv) { listenPort = atoi(portStr); } + // read the ApplicationInfo.ini file for Name/Version/Domain information + QSettings::setDefaultFormat(QSettings::IniFormat); + QSettings applicationInfo(PathUtils::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); + // set the associated application properties + applicationInfo.beginGroup("INFO"); + QApplication::setApplicationName(applicationInfo.value("name").toString()); + QApplication::setApplicationVersion(BUILD_VERSION); + QApplication::setOrganizationName(applicationInfo.value("organizationName").toString()); + QApplication::setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); + DependencyManager::registerInheritance(); // Set dependencies @@ -223,35 +233,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _isVSyncOn(true), _aboutToQuit(false) { - auto glCanvas = DependencyManager::get(); - auto nodeList = DependencyManager::get(); + _logger = new FileLogger(this); // After setting organization name in order to get correct directory + qInstallMessageHandler(messageHandler); + + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); + _window->setWindowTitle("Interface"); + Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us - - // read the ApplicationInfo.ini file for Name/Version/Domain information - QSettings applicationInfo(PathUtils::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); - - // set the associated application properties - applicationInfo.beginGroup("INFO"); - - setApplicationName(applicationInfo.value("name").toString()); - setApplicationVersion(BUILD_VERSION); - setOrganizationName(applicationInfo.value("organizationName").toString()); - setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); - - _logger = new FileLogger(this); // After setting organization name in order to get correct directory - - QSettings::setDefaultFormat(QSettings::IniFormat); + auto glCanvas = DependencyManager::get(); + auto nodeList = DependencyManager::get(); _myAvatar = _avatarManager.getMyAvatar(); _applicationStartupTime = startup_time; - QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); - _window->setWindowTitle("Interface"); - - qInstallMessageHandler(messageHandler); - qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); _bookmarks = new Bookmarks(); // Before setting up the menu diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index ce50e093e4..c7ba02700f 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -118,7 +118,9 @@ void Audio::audioMixerKilled() { QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) { QAudioDeviceInfo result; -#ifdef WIN32 +// Temporarily enable audio device selection in Windows again to test how it behaves now +//#ifdef WIN32 +#if FALSE // NOTE // this is a workaround for a windows only QtBug https://bugreports.qt-project.org/browse/QTBUG-16117 // static QAudioDeviceInfo objects get deallocated when QList objects go out of scope diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 0ea0d1b725..b477f606d7 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -171,10 +171,6 @@ void Hand::renderHandTargets(bool isMine) { glm::vec3 tip = palm.getTipPosition(); glm::vec3 root = palm.getPosition(); - //Scale the positions based on avatar scale - myAvatar->scaleVectorRelativeToPosition(tip); - myAvatar->scaleVectorRelativeToPosition(root); - Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS); // Render sphere at palm/finger root glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS; diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 1df82a42a0..4cb6414966 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -822,6 +822,12 @@ HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QStrin _radius->setSingleStep(0.01); _radius->setMaximum(FLT_MAX); _radius->setValue(5.0); + + _form->addRow("Granularity:", _granularity = new QDoubleSpinBox()); + _granularity->setMinimum(-FLT_MAX); + _granularity->setMaximum(FLT_MAX); + _granularity->setPrefix("2^"); + _granularity->setValue(8.0); } bool HeightfieldBrushTool::appliesTo(const AttributePointer& attribute) const { @@ -851,7 +857,7 @@ bool HeightfieldBrushTool::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::Wheel) { float angle = static_cast(event)->angleDelta().y(); const float ANGLE_SCALE = 1.0f / 1000.0f; - _radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE)); + _radius->setValue(_radius->value() * pow(2.0f, angle * ANGLE_SCALE)); return true; } else if (event->type() == QEvent::MouseButtonPress && _positionValid) { @@ -881,7 +887,7 @@ QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) { const int ERASE_MODE_INDEX = 2; return QVariant::fromValue(PaintHeightfieldHeightEdit(_position, _radius->value(), alternate ? -_height->value() : _height->value(), _mode->currentIndex() == SET_MODE_INDEX, - _mode->currentIndex() == ERASE_MODE_INDEX)); + _mode->currentIndex() == ERASE_MODE_INDEX, pow(2.0f, _granularity->value()))); } MaterialControl::MaterialControl(QWidget* widget, QFormLayout* form, bool clearable) : @@ -956,10 +962,11 @@ QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) { sphere->setScale(_radius->value()); if (alternate) { return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), - SharedObjectPointer(), QColor(0, 0, 0, 0), true)); + SharedObjectPointer(), QColor(0, 0, 0, 0), true, false, pow(2.0f, _granularity->value()))); } else { return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), - _materialControl->getMaterial(), _materialControl->getColor(), true)); + _materialControl->getMaterial(), _materialControl->getColor(), + true, false, pow(2.0f, _granularity->value()))); } } @@ -974,10 +981,11 @@ QVariant HeightfieldSculptBrushTool::createEdit(bool alternate) { sphere->setScale(_radius->value()); if (alternate) { return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), - SharedObjectPointer(), QColor(0, 0, 0, 0))); + SharedObjectPointer(), QColor(0, 0, 0, 0), false, false, pow(2.0f, _granularity->value()))); } else { return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), - _materialControl->getMaterial(), _materialControl->getColor())); + _materialControl->getMaterial(), _materialControl->getColor(), + false, false, pow(2.0f, _granularity->value()))); } } @@ -992,13 +1000,14 @@ HeightfieldFillBrushTool::HeightfieldFillBrushTool(MetavoxelEditor* editor) : QVariant HeightfieldFillBrushTool::createEdit(bool alternate) { const int FILL_MODE_INDEX = 0; if (_mode->currentIndex() == FILL_MODE_INDEX) { - return QVariant::fromValue(FillHeightfieldHeightEdit(_position, _radius->value())); + return QVariant::fromValue(FillHeightfieldHeightEdit(_position, _radius->value(), + pow(2.0f, _granularity->value()))); } Sphere* sphere = new Sphere(); sphere->setTranslation(_position); sphere->setScale(_radius->value()); return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere), - SharedObjectPointer(), QColor(), false, true)); + SharedObjectPointer(), QColor(), false, true, pow(2.0f, _granularity->value()))); } HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor) : @@ -1017,6 +1026,12 @@ HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor) _snapToGrid->setChecked(true); _materialControl = new MaterialControl(this, form, true); + + form->addRow("Granularity:", _granularity = new QDoubleSpinBox()); + _granularity->setMinimum(-FLT_MAX); + _granularity->setMaximum(FLT_MAX); + _granularity->setPrefix("2^"); + _granularity->setValue(8.0); } bool HeightfieldMaterialBoxTool::appliesTo(const AttributePointer& attribute) const { @@ -1039,7 +1054,7 @@ void HeightfieldMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm: cuboid->setAspectY(vector.y / vector.x); cuboid->setAspectZ(vector.z / vector.x); MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(cuboid), - _materialControl->getMaterial(), _materialControl->getColor())) }; + _materialControl->getMaterial(), _materialControl->getColor(), false, false, pow(2.0f, _granularity->value()))) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } @@ -1056,6 +1071,12 @@ HeightfieldMaterialSpannerTool::HeightfieldMaterialSpannerTool(MetavoxelEditor* _materialControl = new MaterialControl(this, form, true); + form->addRow("Granularity:", _granularity = new QDoubleSpinBox()); + _granularity->setMinimum(-FLT_MAX); + _granularity->setMaximum(FLT_MAX); + _granularity->setPrefix("2^"); + _granularity->setValue(8.0); + QPushButton* place = new QPushButton("Set"); layout()->addWidget(place); connect(place, &QPushButton::clicked, this, &HeightfieldMaterialSpannerTool::place); @@ -1076,7 +1097,7 @@ QColor HeightfieldMaterialSpannerTool::getColor() { void HeightfieldMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { static_cast(spanner.data())->setWillBeVoxelized(true); MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(spanner, - _materialControl->getMaterial(), _materialControl->getColor())) }; + _materialControl->getMaterial(), _materialControl->getColor(), false, false, pow(2.0f, _granularity->value()))) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 23feb940c9..a816b9ebe7 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -326,6 +326,7 @@ protected: QFormLayout* _form; QDoubleSpinBox* _radius; + QDoubleSpinBox* _granularity; glm::vec3 _position; bool _positionValid; @@ -448,6 +449,7 @@ private: QCheckBox* _snapToGrid; MaterialControl* _materialControl; + QDoubleSpinBox* _granularity; }; /// Allows setting heightfield materials by placing a spanner. @@ -470,6 +472,7 @@ private: SharedObjectEditor* _spannerEditor; MaterialControl* _materialControl; + QDoubleSpinBox* _granularity; }; #endif // hifi_MetavoxelEditor_h diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index e18f99072f..0c5d9a7737 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -92,7 +92,13 @@ void ImageOverlay::render(RenderArgs* args) { QRect fromImage; if (_wantClipFromImage) { - fromImage = _fromImage; + float scaleX = imageWidth / _texture->getOriginalWidth(); + float scaleY = imageHeight / _texture->getOriginalHeight(); + + fromImage.setX(scaleX * _fromImage.x()); + fromImage.setY(scaleY * _fromImage.y()); + fromImage.setWidth(scaleX * _fromImage.width()); + fromImage.setHeight(scaleY * _fromImage.height()); } else { fromImage.setX(0); fromImage.setY(0); diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index b691bcc8ef..5d850d06d0 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -27,7 +27,7 @@ HandData::HandData(AvatarData* owningAvatar) : } glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const { - return glm::inverse(getBaseOrientation()) * worldVector; + return glm::inverse(getBaseOrientation()) * worldVector / getBaseScale(); } PalmData& HandData::addNewPalm() { @@ -108,15 +108,19 @@ glm::quat HandData::getBaseOrientation() const { glm::vec3 HandData::getBasePosition() const { return _owningAvatarData->getPosition(); } + +float HandData::getBaseScale() const { + return _owningAvatarData->getTargetScale(); +} glm::vec3 PalmData::getFingerDirection() const { const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f); - return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION); + return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION)); } glm::vec3 PalmData::getNormal() const { const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f); - return _owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION); + return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION)); } diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 8e44658faf..534ea67726 100644 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -36,11 +36,11 @@ public: // position conversion glm::vec3 localToWorldPosition(const glm::vec3& localPosition) { - return getBasePosition() + getBaseOrientation() * localPosition; + return getBasePosition() + getBaseOrientation() * localPosition * getBaseScale(); } glm::vec3 localToWorldDirection(const glm::vec3& localVector) { - return getBaseOrientation() * localVector; + return getBaseOrientation() * localVector * getBaseScale(); } glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const; @@ -71,6 +71,7 @@ protected: glm::quat getBaseOrientation() const; glm::vec3 getBasePosition() const; + float getBaseScale() const; private: // privatize copy ctor and assignment operator so copies of this object cannot be made diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp index 88e1a4b6b1..25ef2e6aaf 100644 --- a/libraries/entities/src/BoxEntityItem.cpp +++ b/libraries/entities/src/BoxEntityItem.cpp @@ -101,3 +101,12 @@ void BoxEntityItem::computeShapeInfo(ShapeInfo& info) const { info.setBox(halfExtents); } +void BoxEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qDebug() << " BOX EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; + qDebug() << " position:" << debugTreeVector(_position); + qDebug() << " dimensions:" << debugTreeVector(_dimensions); + qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now); +} + diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h index 3ce67369ee..8d68a13158 100644 --- a/libraries/entities/src/BoxEntityItem.h +++ b/libraries/entities/src/BoxEntityItem.h @@ -53,6 +53,8 @@ public: void computeShapeInfo(ShapeInfo& info) const; + virtual void debugDump() const; + protected: rgbColor _color; }; diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index f2588d0493..a04e07ebb3 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -36,6 +36,11 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemI int sizeOut = 0; if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) { + #ifdef WANT_DEBUG + qDebug() << "calling queueOctreeEditMessage()..."; + qDebug() << " id:" << modelID; + qDebug() << " properties:" << properties; + #endif queueOctreeEditMessage(type, bufferOut, sizeOut); } } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 91aef39eea..64cbf8ab04 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -21,6 +21,8 @@ #include "EntityItem.h" #include "EntityTree.h" +bool EntityItem::_sendPhysicsUpdates = true; + void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _id = entityItemID.id; _creatorTokenID = entityItemID.creatorTokenID; @@ -165,13 +167,12 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet quint64 lastEdited = getLastEdited(); - const bool wantDebug = false; - if (wantDebug) { + #ifdef WANT_DEBUG float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); qDebug() << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited << " ago=" << editedAgo << "seconds - " << agoAsString; - } + #endif bool successIDFits = false; bool successTypeFits = false; @@ -225,11 +226,6 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, getPosition()); APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, getDimensions()); // NOTE: PROP_RADIUS obsolete - - if (wantDebug) { - qDebug() << " APPEND_ENTITY_PROPERTY() PROP_DIMENSIONS:" << getDimensions(); - } - APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity()); @@ -308,7 +304,6 @@ int EntityItem::expectedBytes() { int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { - bool wantDebug = false; if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) { @@ -373,17 +368,21 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef _created = createdFromBuffer; } - if (wantDebug) { + #ifdef WANT_DEBUG quint64 lastEdited = getLastEdited(); float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); QString ageAsString = formatSecondsElapsed(getAge()); + qDebug() << "------------------------------------------"; qDebug() << "Loading entity " << getEntityItemID() << " from buffer..."; + qDebug() << "------------------------------------------"; + debugDump(); + qDebug() << "------------------------------------------"; qDebug() << " _created =" << _created; qDebug() << " age=" << getAge() << "seconds - " << ageAsString; qDebug() << " lastEdited =" << lastEdited; qDebug() << " ago=" << editedAgo << "seconds - " << agoAsString; - } + #endif quint64 lastEditedFromBuffer = 0; quint64 lastEditedFromBufferAdjusted = 0; @@ -400,20 +399,18 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << "data from server **************** "; - qDebug() << " entityItemID=" << getEntityItemID(); - qDebug() << " now=" << now; - qDebug() << " getLastEdited()=" << getLastEdited(); - qDebug() << " lastEditedFromBuffer=" << lastEditedFromBuffer << " (BEFORE clockskew adjust)"; - qDebug() << " clockSkew=" << clockSkew; - qDebug() << " lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted << " (AFTER clockskew adjust)"; - qDebug() << " _lastEditedFromRemote=" << _lastEditedFromRemote - << " (our local time the last server edit we accepted)"; - qDebug() << " _lastEditedFromRemoteInRemoteTime=" << _lastEditedFromRemoteInRemoteTime - << " (remote time the last server edit we accepted)"; - qDebug() << " fromSameServerEdit=" << fromSameServerEdit; - } + qDebug() << " entityItemID:" << getEntityItemID(); + qDebug() << " now:" << now; + qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now); + qDebug() << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); + qDebug() << " clockSkew:" << debugTimeOnly(clockSkew); + qDebug() << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + qDebug() << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); + qDebug() << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); + qDebug() << " fromSameServerEdit:" << fromSameServerEdit; + #endif bool ignoreServerPacket = false; // assume we'll use this server packet @@ -436,14 +433,16 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef if (ignoreServerPacket) { overwriteLocalData = false; - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << "IGNORING old data from server!!! ****************"; - } + debugDump(); + #endif } else { - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << "USING NEW data from server!!! ****************"; - } + debugDump(); + #endif // don't allow _lastEdited to be in the future _lastEdited = lastEditedFromBufferAdjusted; @@ -461,11 +460,11 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef quint64 updateDelta = updateDeltaCoder; if (overwriteLocalData) { _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that - if (wantDebug) { - qDebug() << "_lastUpdated =" << _lastUpdated; - qDebug() << "_lastEdited=" << _lastEdited; - qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted; - } + #ifdef WANT_DEBUG + qDebug() << " _lastUpdated:" << debugTime(_lastUpdated, now); + qDebug() << " _lastEdited:" << debugTime(_lastEdited, now); + qDebug() << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + #endif } encodedUpdateDelta = updateDeltaCoder; // determine true length dataAt += encodedUpdateDelta.size(); @@ -479,16 +478,26 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef quint64 simulatedDelta = simulatedDeltaCoder; if (overwriteLocalData) { _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that - if (wantDebug) { - qDebug() << "_lastSimulated =" << _lastSimulated; - qDebug() << "_lastEdited=" << _lastEdited; - qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted; - } + #ifdef WANT_DEBUG + qDebug() << " _lastSimulated:" << debugTime(_lastSimulated, now); + qDebug() << " _lastEdited:" << debugTime(_lastEdited, now); + qDebug() << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + #endif } encodedSimulatedDelta = simulatedDeltaCoder; // determine true length dataAt += encodedSimulatedDelta.size(); bytesRead += encodedSimulatedDelta.size(); - } + } + + #ifdef WANT_DEBUG + if (overwriteLocalData) { + qDebug() << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); + qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now); + qDebug() << " getLastSimulated:" << debugTime(getLastSimulated(), now); + qDebug() << " getLastUpdated:" << debugTime(getLastUpdated(), now); + } + #endif + // Property Flags QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size @@ -508,23 +517,11 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef if (overwriteLocalData) { setRadius(fromBuffer); } - - if (wantDebug) { - qDebug() << " readEntityDataFromBuffer() OLD FORMAT... found PROP_RADIUS"; - } - } } else { READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions); - if (wantDebug) { - qDebug() << " readEntityDataFromBuffer() NEW FORMAT... look for PROP_DIMENSIONS"; - } } - if (wantDebug) { - qDebug() << " readEntityDataFromBuffer() _dimensions:" << getDimensionsInMeters() << " in meters"; - } - READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation); READ_ENTITY_PROPERTY_SETTER(PROP_DENSITY, float, updateDensity); READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity); @@ -541,23 +538,22 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked); READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA,setUserData); - if (wantDebug) { - qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint; - qDebug() << " readEntityDataFromBuffer() _visible:" << _visible; - qDebug() << " readEntityDataFromBuffer() _ignoreForCollisions:" << _ignoreForCollisions; - qDebug() << " readEntityDataFromBuffer() _collisionsWillMove:" << _collisionsWillMove; - } - bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); recalculateCollisionShape(); if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) { - // TODO: Andrew & Brad to discuss -- this probably should not be "now" but instead should be the last - // simulated time from server. The logic should maybe be: the position changed from the server, so the - // position we just set can be thought of as the position at the time it was last simulated by the - // server (clock skew adjusted). By setting it to "now" we are saying that the last position is to be - // considered to be the correct position for "now" which is likely in the future from when it actually - // was at that last known positition. + // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more + // closely match where the entities should be if they'd stepped forward in time to "now". The server + // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore + // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and + // use our simulation helper routine to get a best estimate of where the entity should be. + float skipTimeForward = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND); + if (skipTimeForward > 0.0f) { + #ifdef WANT_DEBUG + qDebug() << "skipTimeForward:" << skipTimeForward; + #endif + simulateKinematicMotion(skipTimeForward); + } _lastSimulated = now; } } @@ -583,13 +579,12 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime)); quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew; memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime)); - const bool wantDebug = false; - if (wantDebug) { + #ifdef WANT_DEBUG qDebug("EntityItem::adjustEditPacketForClockSkew()..."); qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime; qDebug() << " clockSkew: " << clockSkew; qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime; - } + #endif } float EntityItem::computeMass() const { @@ -644,15 +639,13 @@ bool EntityItem::isRestingOnSurface() const { } void EntityItem::simulate(const quint64& now) { - bool wantDebug = false; - if (_lastSimulated == 0) { _lastSimulated = now; } float timeElapsed = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND); - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << "********** EntityItem::simulate()"; qDebug() << " entity ID=" << getEntityItemID(); qDebug() << " now=" << now; @@ -687,27 +680,23 @@ void EntityItem::simulate(const quint64& now) { qDebug() << " getAge()=" << getAge(); qDebug() << " getLifetime()=" << getLifetime(); } - } - - if (wantDebug) { qDebug() << " ********** EntityItem::simulate() .... SETTING _lastSimulated=" << _lastSimulated; - } + #endif simulateKinematicMotion(timeElapsed); _lastSimulated = now; } void EntityItem::simulateKinematicMotion(float timeElapsed) { - bool wantDebug = false; if (hasAngularVelocity()) { // angular damping glm::vec3 angularVelocity = getAngularVelocity(); if (_angularDamping > 0.0f) { angularVelocity *= powf(1.0f - _angularDamping, timeElapsed); - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << " angularDamping :" << _angularDamping; qDebug() << " newAngularVelocity:" << angularVelocity; - } + #endif setAngularVelocity(angularVelocity); } @@ -735,19 +724,19 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { glm::vec3 velocity = getVelocity(); if (_damping > 0.0f) { velocity *= powf(1.0f - _damping, timeElapsed); - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << " damping:" << _damping; qDebug() << " velocity AFTER dampingResistance:" << velocity; qDebug() << " glm::length(velocity):" << glm::length(velocity); qDebug() << " velocityEspilon :" << ENTITY_ITEM_EPSILON_VELOCITY_LENGTH; - } + #endif } // integrate position forward glm::vec3 position = getPosition(); glm::vec3 newPosition = position + (velocity * timeElapsed); - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << " EntityItem::simulate()...."; qDebug() << " timeElapsed:" << timeElapsed; qDebug() << " old AACube:" << getMaximumAACube(); @@ -757,7 +746,7 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { qDebug() << " getDistanceToBottomOfEntity():" << getDistanceToBottomOfEntity() * (float)TREE_SCALE << " in meters"; qDebug() << " newPosition:" << newPosition; qDebug() << " glm::distance(newPosition, position):" << glm::distance(newPosition, position); - } + #endif position = newPosition; @@ -780,12 +769,12 @@ void EntityItem::simulateKinematicMotion(float timeElapsed) { setVelocity(velocity); } - if (wantDebug) { + #ifdef WANT_DEBUG qDebug() << " new position:" << position; qDebug() << " new velocity:" << velocity; qDebug() << " new AACube:" << getMaximumAACube(); qDebug() << " old getAABox:" << getAABox(); - } + #endif } } @@ -859,13 +848,12 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { if (somethingChanged) { somethingChangedNotification(); // notify derived classes that something has changed - bool wantDebug = false; uint64_t now = usecTimestampNow(); - if (wantDebug) { + #ifdef WANT_DEBUG int elapsed = now - getLastEdited(); qDebug() << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); - } + #endif if (_created != UNKNOWN_CREATED_TIME) { setLastEdited(now); } @@ -1007,15 +995,6 @@ void EntityItem::setRadius(float value) { float diameter = value * 2.0f; float maxDimension = sqrt((diameter * diameter) / 3.0f); _dimensions = glm::vec3(maxDimension, maxDimension, maxDimension); - - bool wantDebug = false; - if (wantDebug) { - qDebug() << "EntityItem::setRadius()..."; - qDebug() << " radius:" << value; - qDebug() << " diameter:" << diameter; - qDebug() << " maxDimension:" << maxDimension; - qDebug() << " _dimensions:" << _dimensions; - } } // TODO: get rid of all users of this function... diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index df619e2f69..58dc391458 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -36,13 +36,16 @@ class EntityTreeElementExtraEncodeData; #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { }; +#define debugTime(T, N) qPrintable(QString("%1 [ %2 ago]").arg(T, 16, 10).arg(formatUsecTime(N - T), 15)) +#define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) +#define debugTreeVector(V) V << "[" << (V * (float)TREE_SCALE) << " in meters ]" + /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. class EntityItem { friend class EntityTreeElement; - public: enum EntityDirtyFlags { DIRTY_POSITION = 0x0001, @@ -288,8 +291,15 @@ public: void setPhysicsInfo(void* data) { _physicsInfo = data; } EntityTreeElement* getElement() const { return _element; } + + static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; } + static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; } + + protected: + static bool _sendPhysicsUpdates; + virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init virtual void recalculateCollisionShape(); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 6226012052..c247da2a8c 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -14,6 +14,7 @@ #include "LightEntityItem.h" #include "ModelEntityItem.h" + EntityScriptingInterface::EntityScriptingInterface() : _nextCreatorTokenID(0), _entityTree(NULL) @@ -144,15 +145,26 @@ void EntityScriptingInterface::deleteEntity(EntityItemID entityID) { } } + bool shouldDelete = true; + // If we have a local entity tree set, then also update it. if (_entityTree) { _entityTree->lockForWrite(); - _entityTree->deleteEntity(entityID); + + EntityItem* entity = const_cast(_entityTree->findEntityByEntityItemID(actualID)); + if (entity) { + if (entity->getLocked()) { + shouldDelete = false; + } else { + _entityTree->deleteEntity(entityID); + } + } + _entityTree->unlock(); } - // if at this point, we know the id, send the update to the entity server - if (entityID.isKnownID) { + // if at this point, we know the id, and we should still delete the entity, send the update to the entity server + if (shouldDelete && entityID.isKnownID) { getEntityPacketSender()->queueEraseEntityMessage(entityID); } } @@ -234,6 +246,14 @@ bool EntityScriptingInterface::getLightsArePickable() const { return LightEntityItem::getLightsArePickable(); } +void EntityScriptingInterface::setSendPhysicsUpdates(bool value) { + EntityItem::setSendPhysicsUpdates(value); +} + +bool EntityScriptingInterface::getSendPhysicsUpdates() const { + return EntityItem::getSendPhysicsUpdates(); +} + RayToEntityIntersectionResult::RayToEntityIntersectionResult() : intersects(false), diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index e0fc721516..3585cce946 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -99,6 +99,9 @@ public slots: Q_INVOKABLE void setLightsArePickable(bool value); Q_INVOKABLE bool getLightsArePickable() const; + Q_INVOKABLE void setSendPhysicsUpdates(bool value); + Q_INVOKABLE bool getSendPhysicsUpdates() const; + Q_INVOKABLE void dumpTree() const; signals: diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 580fed8790..a0a8a33ca8 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -229,6 +229,23 @@ void EntityTree::setSimulation(EntitySimulation* simulation) { } void EntityTree::deleteEntity(const EntityItemID& entityID) { + EntityTreeElement* containingElement = getContainingElement(entityID); + if (!containingElement) { + qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntity() entityID doesn't exist!!! entityID=" << entityID; + return; + } + + EntityItem* existingEntity = containingElement->getEntityWithEntityItemID(entityID); + if (!existingEntity) { + qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntity() on entity items that don't exist. entityID=" << entityID; + return; + } + + if (existingEntity->getLocked()) { + qDebug() << "ERROR! EntityTree::deleteEntity() trying to delete locked entity. entityID=" << entityID; + return; + } + emit deletingEntity(entityID); // NOTE: callers must lock the tree before using this method @@ -242,14 +259,33 @@ void EntityTree::deleteEntities(QSet entityIDs) { // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(this); foreach(const EntityItemID& entityID, entityIDs) { + EntityTreeElement* containingElement = getContainingElement(entityID); + if (!containingElement) { + qDebug() << "UNEXPECTED!!!! EntityTree::deleteEntities() entityID doesn't exist!!! entityID=" << entityID; + continue; + } + + EntityItem* existingEntity = containingElement->getEntityWithEntityItemID(entityID); + if (!existingEntity) { + qDebug() << "UNEXPECTED!!!! don't call EntityTree::deleteEntities() on entity items that don't exist. entityID=" << entityID; + continue; + } + + if (existingEntity->getLocked()) { + qDebug() << "ERROR! EntityTree::deleteEntities() trying to delete locked entity. entityID=" << entityID; + continue; + } + // tell our delete operator about this entityID theOperator.addEntityIDToDeleteList(entityID); emit deletingEntity(entityID); } - recurseTreeWithOperator(&theOperator); - processRemovedEntities(theOperator); - _isDirty = true; + if (theOperator.getEntities().size() > 0) { + recurseTreeWithOperator(&theOperator); + processRemovedEntities(theOperator); + _isDirty = true; + } } void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) { diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 7f3d619e03..181e5851f6 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -132,3 +132,14 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons } return false; } + + +void SphereEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qDebug() << "SHPERE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qDebug() << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; + qDebug() << " position:" << debugTreeVector(_position); + qDebug() << " dimensions:" << debugTreeVector(_dimensions); + qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now); +} + diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index c81f80d9ab..f76c9f5600 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -63,6 +63,8 @@ public: bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, bool precisionPicking) const; + virtual void debugDump() const; + protected: virtual void recalculateCollisionShape(); diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index bae5768068..98da28cafa 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -149,12 +149,13 @@ void SetDataEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects } PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius, - float height, bool set, bool erase) : + float height, bool set, bool erase, float granularity) : position(position), radius(radius), height(height), set(set), - erase(erase) { + erase(erase), + granularity(granularity) { } void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { @@ -166,7 +167,8 @@ void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObje Box(position - extents, position + extents), results); foreach (const SharedObjectPointer& spanner, results) { - Spanner* newSpanner = static_cast(spanner.data())->paintHeight(position, radius, height, set, erase); + Spanner* newSpanner = static_cast(spanner.data())->paintHeight(position, radius, + height, set, erase, granularity); if (newSpanner != spanner) { data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner); } @@ -179,11 +181,12 @@ MaterialEdit::MaterialEdit(const SharedObjectPointer& material, const QColor& av } HeightfieldMaterialSpannerEdit::HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner, - const SharedObjectPointer& material, const QColor& averageColor, bool paint, bool voxelize) : + const SharedObjectPointer& material, const QColor& averageColor, bool paint, bool voxelize, float granularity) : MaterialEdit(material, averageColor), spanner(spanner), paint(paint), - voxelize(voxelize) { + voxelize(voxelize), + granularity(granularity) { } class SpannerProjectionFetchVisitor : public SpannerVisitor { @@ -250,16 +253,18 @@ void HeightfieldMaterialSpannerEdit::apply(MetavoxelData& data, const WeakShared } foreach (const SharedObjectPointer& result, results) { - Spanner* newResult = static_cast(result.data())->setMaterial(spanner, material, color, paint, voxelize); + Spanner* newResult = static_cast(result.data())->setMaterial(spanner, material, + color, paint, voxelize, granularity); if (newResult != result) { data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), result, newResult); } } } -FillHeightfieldHeightEdit::FillHeightfieldHeightEdit(const glm::vec3& position, float radius) : +FillHeightfieldHeightEdit::FillHeightfieldHeightEdit(const glm::vec3& position, float radius, float granularity) : position(position), - radius(radius) { + radius(radius), + granularity(granularity) { } void FillHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { @@ -269,7 +274,7 @@ void FillHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjec Box(position - extents, position + extents), results); foreach (const SharedObjectPointer& spanner, results) { - Spanner* newSpanner = static_cast(spanner.data())->fillHeight(position, radius); + Spanner* newSpanner = static_cast(spanner.data())->fillHeight(position, radius, granularity); if (newSpanner != spanner) { data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner); } diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 71013996c2..7fbe2b4243 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -205,9 +205,10 @@ public: STREAM float height; STREAM bool set; STREAM bool erase; + STREAM float granularity; PaintHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, - float height = 0.0f, bool set = false, bool erase = false); + float height = 0.0f, bool set = false, bool erase = false, float granularity = 0.0f); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; @@ -237,10 +238,11 @@ public: STREAM SharedObjectPointer spanner; STREAM bool paint; STREAM bool voxelize; + STREAM float granularity; HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(), const SharedObjectPointer& material = SharedObjectPointer(), - const QColor& averageColor = QColor(), bool paint = false, bool voxelize = false); + const QColor& averageColor = QColor(), bool paint = false, bool voxelize = false, float granularity = 0.0f); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; @@ -255,8 +257,9 @@ public: STREAM glm::vec3 position; STREAM float radius; + STREAM float granularity; - FillHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f); + FillHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, float granularity = 0.0f); virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; }; diff --git a/libraries/metavoxels/src/Spanner.cpp b/libraries/metavoxels/src/Spanner.cpp index c771b8fb4a..5d9cc24b92 100644 --- a/libraries/metavoxels/src/Spanner.cpp +++ b/libraries/metavoxels/src/Spanner.cpp @@ -111,16 +111,16 @@ bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& dire return _bounds.findRayIntersection(origin, direction, distance); } -Spanner* Spanner::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) { +Spanner* Spanner::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase, float granularity) { return this; } -Spanner* Spanner::fillHeight(const glm::vec3& position, float radius) { +Spanner* Spanner::fillHeight(const glm::vec3& position, float radius, float granularity) { return this; } Spanner* Spanner::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, - const QColor& color, bool paint, bool voxelize) { + const QColor& color, bool paint, bool voxelize, float granularity) { return this; } @@ -1786,7 +1786,7 @@ void HeightfieldNode::getRangeAfterHeightPaint(const glm::vec3& translation, con HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& position, float radius, float height, bool set, bool erase, - float normalizeScale, float normalizeOffset) { + float normalizeScale, float normalizeOffset, float granularity) { if (!_height) { return this; } @@ -1813,7 +1813,7 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons HeightfieldNode* newChild = _children[i]->paintHeight(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, - nextScale, position, radius, height, set, erase, normalizeScale, normalizeOffset); + nextScale, position, radius, height, set, erase, normalizeScale, normalizeOffset, granularity); if (_children[i] != newChild) { if (newNode == this) { newNode = new HeightfieldNode(*this); @@ -1846,6 +1846,13 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); } + // if the granularity is insufficient, we must subdivide + if (scale.x / innerHeightWidth > granularity || scale.z / innerHeightHeight > granularity) { + HeightfieldNodePointer newNode(subdivide(newHeightContents, newStackContents)); + return newNode->paintHeight(translation, rotation, scale, position, radius, height, set, + erase, 1.0f, 0.0f, granularity); + } + // now apply the actual change glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(), glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ)); @@ -1885,7 +1892,7 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons } HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, - const glm::vec3& position, float radius) { + const glm::vec3& position, float radius, float granularity) { if (!_height) { return this; } @@ -1911,7 +1918,7 @@ HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const HeightfieldNode* newChild = _children[i]->fillHeight(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, - nextScale, position, radius); + nextScale, position, radius, granularity); if (_children[i] != newChild) { if (newNode == this) { newNode = new HeightfieldNode(*this); @@ -1927,9 +1934,15 @@ HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const if (!_stack) { return this; } - QVector newHeightContents = _height->getContents(); + // if the granularity is insufficient, we must subdivide + QVector newHeightContents = _height->getContents(); QVector newStackContents = _stack->getContents(); + if (scale.x / innerHeightWidth > granularity || scale.z / innerHeightHeight > granularity) { + HeightfieldNodePointer newNode(subdivide(newHeightContents, newStackContents)); + return newNode->fillHeight(translation, rotation, scale, position, radius, granularity); + } + int stackWidth = _stack->getWidth(); int stackHeight = newStackContents.size() / stackWidth; QVector newStackMaterials = _stack->getMaterials(); @@ -2088,7 +2101,7 @@ void HeightfieldNode::getRangeAfterEdit(const glm::vec3& translation, const glm: HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize, - float normalizeScale, float normalizeOffset) { + float normalizeScale, float normalizeOffset, float granularity) { if (!_height) { return this; } @@ -2109,7 +2122,7 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons HeightfieldNode* newChild = _children[i]->setMaterial(translation + rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f, i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, spanner, - material, color, paint, voxelize, normalizeScale, normalizeOffset); + material, color, paint, voxelize, normalizeScale, normalizeOffset, granularity); if (_children[i] != newChild) { if (newNode == this) { newNode = new HeightfieldNode(*this); @@ -2149,6 +2162,14 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)), _color, _material, HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials))); } + + // if the granularity is insufficient, we must subdivide + if (scale.x / innerHeightWidth > granularity || scale.z / innerHeightHeight > granularity) { + HeightfieldNodePointer newNode(subdivide(newHeightContents, newStackContents)); + return newNode->setMaterial(translation, rotation, scale, spanner, material, color, + paint, voxelize, 1.0f, 0.0f, granularity); + } + QVector oldHeightContents = newHeightContents; QVector oldStackContents = newStackContents; @@ -3210,6 +3231,210 @@ bool HeightfieldNode::findHeightfieldRayIntersection(const glm::vec3& origin, co return false; } +static inline float mixHeights(float firstHeight, float secondHeight, float t) { + return (firstHeight == 0.0f) ? secondHeight : (secondHeight == 0.0f ? firstHeight : + glm::mix(firstHeight, secondHeight, t)); +} + +HeightfieldNode* HeightfieldNode::subdivide(const QVector& heightContents, + const QVector& stackContents) const { + HeightfieldNode* newNode = new HeightfieldNode(*this); + int heightWidth = _height->getWidth(); + int heightHeight = heightContents.size() / heightWidth; + newNode->setHeight(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, heightContents))); + int stackWidth = 0, stackHeight = 0; + QVector stackMaterials; + if (_stack) { + stackWidth = _stack->getWidth(); + stackHeight = stackContents.size() / stackWidth; + stackMaterials = _stack->getMaterials(); + newNode->setStack(HeightfieldStackPointer(new HeightfieldStack(stackWidth, stackContents, stackMaterials))); + } + int colorWidth = 0, colorHeight = 0; + if (_color) { + colorWidth = _color->getWidth(); + colorHeight = _color->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES); + } + int materialWidth = 0, materialHeight = 0; + QVector materialMaterials; + if (_material) { + materialWidth = _material->getWidth(); + materialHeight = _material->getContents().size() / materialWidth; + materialMaterials = _material->getMaterials(); + } + for (int i = 0; i < CHILD_COUNT; i++) { + QVector childHeightContents(heightWidth * heightHeight); + QByteArray childColorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF); + QByteArray childMaterialContents(materialWidth * materialHeight, 0); + QVector childStackContents(stackWidth * stackHeight); + + quint16* heightDest = childHeightContents.data(); + const quint16* heightSrc = heightContents.constData() + (i & Y_MAXIMUM_FLAG ? (heightHeight / 2) * heightWidth : 0) + + (i & X_MAXIMUM_FLAG ? heightWidth / 2 : 0); + for (int z = 0; z < heightHeight; z++) { + float srcZ = z * 0.5f + 0.5f; + float fractZ = glm::fract(srcZ); + const quint16* heightSrcZ = heightSrc + (int)srcZ * heightWidth; + for (int x = 0; x < heightWidth; x++) { + float srcX = x * 0.5f + 0.5f; + float fractX = glm::fract(srcX); + const quint16* heightSrcX = heightSrcZ + (int)srcX; + if (fractZ == 0.0f) { + if (fractX == 0.0f) { + *heightDest++ = heightSrcX[0]; + } else { + *heightDest++ = mixHeights(heightSrcX[0], heightSrcX[1], fractX); + } + } else { + if (fractX == 0.0f) { + *heightDest++ = mixHeights(heightSrcX[0], heightSrcX[heightWidth], fractZ); + } else { + *heightDest++ = mixHeights(mixHeights(heightSrcX[0], heightSrcX[1], fractX), + mixHeights(heightSrcX[heightWidth], heightSrcX[heightWidth + 1], fractX), fractZ); + } + } + } + } + + if (colorWidth != 0) { + char* colorDest = childColorContents.data(); + const uchar* colorSrc = (const uchar*)_color->getContents().constData() + + ((i & Y_MAXIMUM_FLAG ? (colorHeight / 2) * colorWidth : 0) + + (i & X_MAXIMUM_FLAG ? colorWidth / 2 : 0)) * DataBlock::COLOR_BYTES; + for (int z = 0; z < colorHeight; z++) { + float srcZ = z * 0.5f; + float fractZ = glm::fract(srcZ); + const uchar* colorSrcZ = colorSrc + (int)srcZ * colorWidth * DataBlock::COLOR_BYTES; + for (int x = 0; x < colorWidth; x++) { + float srcX = x * 0.5f; + float fractX = glm::fract(srcX); + const uchar* colorSrcX = colorSrcZ + (int)srcX * DataBlock::COLOR_BYTES; + const uchar* nextColorSrcX = colorSrcX + colorWidth * DataBlock::COLOR_BYTES; + if (fractZ == 0.0f) { + if (fractX == 0.0f) { + *colorDest++ = colorSrcX[0]; + *colorDest++ = colorSrcX[1]; + *colorDest++ = colorSrcX[2]; + } else { + *colorDest++ = glm::mix(colorSrcX[0], colorSrcX[3], fractX); + *colorDest++ = glm::mix(colorSrcX[1], colorSrcX[4], fractX); + *colorDest++ = glm::mix(colorSrcX[2], colorSrcX[5], fractX); + } + } else { + if (fractX == 0.0f) { + *colorDest++ = glm::mix(colorSrcX[0], nextColorSrcX[0], fractZ); + *colorDest++ = glm::mix(colorSrcX[1], nextColorSrcX[1], fractZ); + *colorDest++ = glm::mix(colorSrcX[2], nextColorSrcX[2], fractZ); + } else { + *colorDest++ = glm::mix(glm::mix(colorSrcX[0], colorSrcX[3], fractX), + glm::mix(nextColorSrcX[0], nextColorSrcX[3], fractX), fractZ); + *colorDest++ = glm::mix(glm::mix(colorSrcX[1], colorSrcX[4], fractX), + glm::mix(nextColorSrcX[1], nextColorSrcX[4], fractX), fractZ); + *colorDest++ = glm::mix(glm::mix(colorSrcX[2], colorSrcX[5], fractX), + glm::mix(nextColorSrcX[2], nextColorSrcX[5], fractX), fractZ); + } + } + } + } + } + + if (materialWidth != 0) { + char* materialDest = childMaterialContents.data(); + const char* materialSrc = _material->getContents().constData() + + (i & Y_MAXIMUM_FLAG ? (materialHeight / 2) * materialWidth : 0) + + (i & X_MAXIMUM_FLAG ? materialWidth / 2 : 0); + for (int z = 0; z < materialHeight; z++) { + float srcZ = z * 0.5f; + const char* materialSrcZ = materialSrc + (int)srcZ * materialWidth; + for (int x = 0; x < materialWidth; x++) { + float srcX = x * 0.5f; + const char* materialSrcX = materialSrcZ + (int)srcX; + *materialDest++ = *materialSrcX; + } + } + } + + if (stackWidth != 0) { + StackArray* stackDest = childStackContents.data(); + const StackArray* stackSrc = _stack->getContents().constData() + + (i & Y_MAXIMUM_FLAG ? (stackHeight / 2) * stackWidth : 0) + + (i & X_MAXIMUM_FLAG ? stackWidth / 2 : 0); + for (int z = 0; z < stackHeight; z++) { + float srcZ = z * 0.5f; + float fractZ = glm::fract(srcZ); + const StackArray* stackSrcZ = stackSrc + (int)srcZ * stackWidth; + for (int x = 0; x < stackWidth; x++) { + float srcX = x * 0.5f; + float fractX = glm::fract(srcX); + const StackArray* stackSrcX = stackSrcZ + (int)srcX; + if (stackSrcX->isEmpty()) { + stackDest++; + continue; + } + int minimumY = stackSrcX->getPosition() * 2; + int maximumY = (stackSrcX->getPosition() + stackSrcX->getEntryCount() - 1) * 2; + *stackDest = StackArray(maximumY - minimumY + 1); + stackDest->setPosition(minimumY); + for (int y = minimumY; y <= maximumY; y++) { + float srcY = y * 0.5f; + float fractY = glm::fract(srcY); + const StackArray::Entry& srcEntry = stackSrcX->getEntry((int)srcY); + StackArray::Entry& destEntry = stackDest->getEntry(y); + destEntry.color = srcEntry.color; + destEntry.material = srcEntry.material; + if (srcEntry.hermiteX != 0) { + glm::vec3 normal; + float distance = srcEntry.getHermiteX(normal); + if (distance < fractX) { + const StackArray::Entry& nextSrcEntryX = stackSrcX[1].getEntry((int)srcY); + destEntry.color = nextSrcEntryX.color; + destEntry.material = nextSrcEntryX.material; + + } else { + destEntry.setHermiteX(normal, (distance - fractX) / 0.5f); + } + } + if (srcEntry.hermiteY != 0) { + glm::vec3 normal; + float distance = srcEntry.getHermiteY(normal); + if (distance < fractY) { + const StackArray::Entry& nextSrcEntryY = stackSrcX->getEntry((int)srcY + 1); + destEntry.color = nextSrcEntryY.color; + destEntry.material = nextSrcEntryY.material; + + } else { + destEntry.setHermiteY(normal, (distance - fractY) / 0.5f); + } + } + if (srcEntry.hermiteZ != 0) { + glm::vec3 normal; + float distance = srcEntry.getHermiteZ(normal); + if (distance < fractZ) { + const StackArray::Entry& nextSrcEntryZ = stackSrcX[stackWidth].getEntry((int)srcY); + destEntry.color = nextSrcEntryZ.color; + destEntry.material = nextSrcEntryZ.material; + + } else { + destEntry.setHermiteZ(normal, (distance - fractZ) / 0.5f); + } + } + } + stackDest++; + } + } + } + + newNode->setChild(i, HeightfieldNodePointer(new HeightfieldNode( + HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, childHeightContents)), + HeightfieldColorPointer(colorWidth == 0 ? NULL : new HeightfieldColor(colorWidth, childColorContents)), + HeightfieldMaterialPointer(materialWidth == 0 ? NULL : + new HeightfieldMaterial(materialWidth, childMaterialContents, materialMaterials)), + HeightfieldStackPointer(stackWidth == 0 ? NULL : + new HeightfieldStack(stackWidth, childStackContents, stackMaterials))))); + } + return newNode; +} + AbstractHeightfieldNodeRenderer::~AbstractHeightfieldNodeRenderer() { } @@ -3310,7 +3535,8 @@ bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3& getScale() * _aspectZ), origin, direction, distance); } -Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase) { +Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float height, + bool set, bool erase, float granularity) { // first see if we're going to exceed the range limits float minimumValue = 1.0f, maximumValue = numeric_limits::max(); if (set) { @@ -3328,19 +3554,19 @@ Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float Heightfield* newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset); newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(newHeightfield->getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), position, radius, height, - set, erase, normalizeScale, normalizeOffset))); + set, erase, normalizeScale, normalizeOffset, granularity))); return newHeightfield; } -Spanner* Heightfield::fillHeight(const glm::vec3& position, float radius) { +Spanner* Heightfield::fillHeight(const glm::vec3& position, float radius, float granularity) { Heightfield* newHeightfield = static_cast(clone(true)); newHeightfield->setRoot(HeightfieldNodePointer(_root->fillHeight(getTranslation(), getRotation(), - glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), position, radius))); + glm::vec3(getScale(), getScale() * _aspectY, getScale() * _aspectZ), position, radius, granularity))); return newHeightfield; } Spanner* Heightfield::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, - const QColor& color, bool paint, bool voxelize) { + const QColor& color, bool paint, bool voxelize, float granularity) { // first see if we're going to exceed the range limits, normalizing if necessary Spanner* spannerData = static_cast(spanner.data()); float normalizeScale = 1.0f, normalizeOffset = 0.0f; @@ -3355,7 +3581,7 @@ Spanner* Heightfield::setMaterial(const SharedObjectPointer& spanner, const Shar } newHeightfield->setRoot(HeightfieldNodePointer(_root->setMaterial(newHeightfield->getTranslation(), getRotation(), glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), spannerData, - material, color, paint, voxelize, normalizeScale, normalizeOffset))); + material, color, paint, voxelize, normalizeScale, normalizeOffset, granularity))); return newHeightfield; } diff --git a/libraries/metavoxels/src/Spanner.h b/libraries/metavoxels/src/Spanner.h index 653893c84d..b60a104d68 100644 --- a/libraries/metavoxels/src/Spanner.h +++ b/libraries/metavoxels/src/Spanner.h @@ -77,16 +77,17 @@ public: /// \param set whether to set the height as opposed to raising/lowering it /// \param erase whether to erase height values /// \return the modified spanner, or this if no modification was performed - virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase); + virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, + bool set, bool erase, float granularity); /// Attempts to fill the spanner's height (adding removing volumetric information). /// \return the modified spanner, or this if no modification was performed - virtual Spanner* fillHeight(const glm::vec3& position, float radius); + virtual Spanner* fillHeight(const glm::vec3& position, float radius, float granularity); /// Attempts to "sculpt" or "paint," etc., with the supplied spanner. /// \return the modified spanner, or this if no modification was performed virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, - const QColor& color, bool paint, bool voxelize); + const QColor& color, bool paint, bool voxelize, float granularity); /// Checks whether this spanner has its own colors. virtual bool hasOwnColors() const; @@ -696,17 +697,17 @@ public: HeightfieldNode* paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& position, float radius, float height, bool set, bool erase, - float normalizeScale, float normalizeOffset); + float normalizeScale, float normalizeOffset, float granularity); HeightfieldNode* fillHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, - const glm::vec3& position, float radius); + const glm::vec3& position, float radius, float granularity); void getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const Box& editBounds, float& minimum, float& maximum) const; HeightfieldNode* setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize, - float normalizeScale, float normalizeOffset); + float normalizeScale, float normalizeOffset, float granularity); void read(HeightfieldStreamState& state); void write(HeightfieldStreamState& state) const; @@ -736,6 +737,8 @@ private: bool findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float boundsDistance, float& distance) const; + HeightfieldNode* subdivide(const QVector& heightContents, const QVector& stackContents) const; + HeightfieldHeightPointer _height; HeightfieldColorPointer _color; HeightfieldMaterialPointer _material; @@ -803,12 +806,13 @@ public: virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; - virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, bool set, bool erase); + virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height, + bool set, bool erase, float granularity); - virtual Spanner* fillHeight(const glm::vec3& position, float radius); + virtual Spanner* fillHeight(const glm::vec3& position, float radius, float granularity); virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, - const QColor& color, bool paint, bool voxelize); + const QColor& color, bool paint, bool voxelize, float granularity); virtual bool hasOwnColors() const; virtual bool hasOwnMaterials() const; diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 07228c8351..21625dc7d5 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -78,7 +78,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAudioStreamStats: return 1; case PacketTypeMetavoxelData: - return 12; + return 13; default: return 0; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 96897ff527..acff3ed64a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -97,8 +97,18 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { // DANGER! EntityItem stores angularVelocity in degrees/sec!!! _entity->setAngularVelocity(glm::degrees(v)); + _entity->setLastSimulated(usecTimestampNow()); + _outgoingPacketFlags = DIRTY_PHYSICS_FLAGS; EntityMotionState::enqueueOutgoingEntity(_entity); + + #ifdef WANT_DEBUG + quint64 now = usecTimestampNow(); + qDebug() << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID(); + qDebug() << " last edited:" << _entity->getLastEdited() << formatUsecTime(now - _entity->getLastEdited()) << "ago"; + qDebug() << " last simulated:" << _entity->getLastSimulated() << formatUsecTime(now - _entity->getLastSimulated()) << "ago"; + qDebug() << " last updated:" << _entity->getLastUpdated() << formatUsecTime(now - _entity->getLastUpdated()) << "ago"; + #endif } void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) { @@ -221,13 +231,30 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ quint64 lastSimulated = _entity->getLastSimulated(); _entity->setLastEdited(lastSimulated); properties.setLastEdited(lastSimulated); + + #ifdef WANT_DEBUG + quint64 now = usecTimestampNow(); + qDebug() << "EntityMotionState::sendUpdate()"; + qDebug() << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; + qDebug() << " lastSimulated:" << debugTime(lastSimulated, now); + #endif //def WANT_DEBUG + } else { properties.setLastEdited(_entity->getLastEdited()); } - EntityItemID id(_entity->getID()); - EntityEditPacketSender* entityPacketSender = static_cast(packetSender); - entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); + if (EntityItem::getSendPhysicsUpdates()) { + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + #ifdef WANT_DEBUG + qDebug() << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; + #endif + entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties); + } else { + #ifdef WANT_DEBUG + qDebug() << "EntityMotionState::sendUpdate()... NOT sending update as requested."; + #endif + } // The outgoing flags only itemized WHAT to send, not WHETHER to send, hence we always set them // to the full set. These flags may be momentarily cleared by incoming external changes. diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index dfa059d47f..7e9c6b76b6 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -111,6 +111,12 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { _sentFrame = simulationFrame; return false; } + + #ifdef WANT_DEBUG + glm::vec3 wasPosition = _sentPosition; + glm::quat wasRotation = _sentRotation; + glm::vec3 wasAngularVelocity = _sentAngularVelocity; + #endif float dt = (float)(simulationFrame - _sentFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP; _sentFrame = simulationFrame; @@ -147,11 +153,21 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); float dx2 = glm::distance2(position, _sentPosition); + const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m if (dx2 > MAX_POSITION_ERROR_SQUARED) { + + #ifdef WANT_DEBUG + qDebug() << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ...."; + qDebug() << "wasPosition:" << wasPosition; + qDebug() << "bullet position:" << position; + qDebug() << "_sentPosition:" << _sentPosition; + qDebug() << "dx2:" << dx2; + #endif + return true; } - + if (glm::length2(_sentAngularVelocity) > 0.0f) { // compute rotation error _sentAngularVelocity *= powf(1.0f - _angularDamping, dt); @@ -165,6 +181,23 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { } const float MIN_ROTATION_DOT = 0.98f; glm::quat actualRotation = bulletToGLM(worldTrans.getRotation()); + + #ifdef WANT_DEBUG + if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) { + qDebug() << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ...."; + + qDebug() << "wasAngularVelocity:" << wasAngularVelocity; + qDebug() << "_sentAngularVelocity:" << _sentAngularVelocity; + + qDebug() << "length wasAngularVelocity:" << glm::length(wasAngularVelocity); + qDebug() << "length _sentAngularVelocity:" << glm::length(_sentAngularVelocity); + + qDebug() << "wasRotation:" << wasRotation; + qDebug() << "bullet actualRotation:" << actualRotation; + qDebug() << "_sentRotation:" << _sentRotation; + } + #endif + return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT); } diff --git a/libraries/render-utils/src/AnimationHandle.cpp b/libraries/render-utils/src/AnimationHandle.cpp index 30edf97a33..64e2bf28b9 100644 --- a/libraries/render-utils/src/AnimationHandle.cpp +++ b/libraries/render-utils/src/AnimationHandle.cpp @@ -74,6 +74,7 @@ void AnimationHandle::setRunning(bool running) { } } else { _model->_runningAnimations.removeOne(_self); + restoreJoints(); replaceMatchingPriorities(0.0f); } emit runningChanged(isRunning()); @@ -173,3 +174,13 @@ void AnimationHandle::replaceMatchingPriorities(float newPriority) { } } +void AnimationHandle::restoreJoints() { + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + JointState& state = _model->_jointStates[mapping]; + state.restoreRotation(1.0f, state._animationPriority); + } + } +} + diff --git a/libraries/render-utils/src/AnimationHandle.h b/libraries/render-utils/src/AnimationHandle.h index 3956b01ebf..13a1b97dc1 100644 --- a/libraries/render-utils/src/AnimationHandle.h +++ b/libraries/render-utils/src/AnimationHandle.h @@ -92,6 +92,7 @@ private: void simulate(float deltaTime); void applyFrame(float frameIndex); void replaceMatchingPriorities(float newPriority); + void restoreJoints(); Model* _model; WeakAnimationHandlePointer _self; diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 8ac53cec2e..3644ded81c 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -454,6 +454,9 @@ void ImageReader::run() { _reply->deleteLater(); } QImage image = QImage::fromData(_content); + + int originalWidth = image.width(); + int originalHeight = image.height(); // enforce a fixed maximum const int MAXIMUM_SIZE = 1024; @@ -482,7 +485,7 @@ void ImageReader::run() { averageColor.setRgb(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea); } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false), - Q_ARG(const QColor&, averageColor)); + Q_ARG(const QColor&, averageColor), Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); return; } if (image.format() != QImage::Format_ARGB32) { @@ -514,7 +517,8 @@ void ImageReader::run() { } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, translucentPixels >= imageArea / 2), Q_ARG(const QColor&, QColor(redTotal / imageArea, - greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea))); + greenTotal / imageArea, blueTotal / imageArea, alphaTotal / imageArea)), + Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); } void NetworkTexture::downloadFinished(QNetworkReply* reply) { @@ -526,9 +530,12 @@ void NetworkTexture::loadContent(const QByteArray& content) { QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content)); } -void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor) { +void NetworkTexture::setImage(const QImage& image, bool translucent, const QColor& averageColor, int originalWidth, + int originalHeight) { _translucent = translucent; _averageColor = averageColor; + _originalWidth = originalWidth; + _originalHeight = originalHeight; _width = image.width(); _height = image.height(); diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index 5393a1c211..3ea46a4421 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -155,6 +155,8 @@ public: /// Returns the lazily-computed average texture color. const QColor& getAverageColor() const { return _averageColor; } + int getOriginalWidth() const { return _originalWidth; } + int getOriginalHeight() const { return _originalHeight; } int getWidth() const { return _width; } int getHeight() const { return _height; } @@ -163,7 +165,8 @@ protected: virtual void downloadFinished(QNetworkReply* reply); Q_INVOKABLE void loadContent(const QByteArray& content); - Q_INVOKABLE void setImage(const QImage& image, bool translucent, const QColor& averageColor); + Q_INVOKABLE void setImage(const QImage& image, bool translucent, const QColor& averageColor, int originalWidth, + int originalHeight); virtual void imageLoaded(const QImage& image); @@ -172,6 +175,8 @@ protected: private: bool _translucent; QColor _averageColor; + int _originalWidth; + int _originalHeight; int _width; int _height; };