mirror of
https://github.com/overte-org/overte.git
synced 2025-07-23 12:44:03 +02:00
Merge branch 'master' into 20276
This commit is contained in:
commit
2ee1570a40
57 changed files with 1327 additions and 741 deletions
4
BUILD.md
4
BUILD.md
|
@ -6,12 +6,12 @@
|
||||||
* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1g
|
* [OpenSSL](https://www.openssl.org/related/binaries.html) ~> 1.0.1g
|
||||||
* IMPORTANT: OpenSSL 1.0.1g is critical to avoid a security vulnerability.
|
* IMPORTANT: OpenSSL 1.0.1g is critical to avoid a security vulnerability.
|
||||||
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
|
* [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
|
### 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_OSX.md](BUILD_OSX.md) - additional instructions for OS X.
|
||||||
* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
|
* [BUILD_LINUX.md](BUILD_LINUX.md) - additional instructions for Linux.
|
||||||
* [BUILD_WIN.md](BUILD_WIN.md) - additional instructions for Windows.
|
|
||||||
|
|
||||||
###CMake
|
###CMake
|
||||||
Hifi uses CMake to generate build files and project files for your platform.
|
Hifi uses CMake to generate build files and project files for your platform.
|
||||||
|
|
31
BUILD_WIN.md
31
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.
|
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
|
* [GLEW](http://glew.sourceforge.net/) ~> 1.10.0
|
||||||
* [freeglut MSVC](http://www.transmissionzero.co.uk/software/freeglut-devel/) ~> 2.8.1
|
* [freeglut MSVC](http://www.transmissionzero.co.uk/software/freeglut-devel/) ~> 2.8.1
|
||||||
* [zLib](http://www.zlib.net/) ~> 1.2.8
|
* [zLib](http://www.zlib.net/) ~> 1.2.8
|
||||||
|
* (remember that you need all other dependencies listed in [BUILD.md](BUILD.md))
|
||||||
###Visual Studio
|
|
||||||
|
|
||||||
Currently building on Windows has been tested using the following compilers:
|
|
||||||
* Visual Studio 2013
|
|
||||||
* Visual Studio 2013 Express
|
|
||||||
|
|
||||||
####Visual Studio 2013
|
####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.
|
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:
|
* When it asks you to select components, ONLY select the following:
|
||||||
* Qt > Qt 5.3.2 > **msvc2013 32-bit OpenGL**
|
* 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:
|
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\`.
|
* 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.
|
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.3.2\msvc2013_opengl` directory.
|
||||||
|
|
||||||
###External Libraries
|
###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:
|
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
|
root_lib_dir
|
||||||
|
-> bullet
|
||||||
|
-> include
|
||||||
|
-> lib
|
||||||
-> freeglut
|
-> freeglut
|
||||||
-> bin
|
-> bin
|
||||||
-> include
|
-> include
|
||||||
|
@ -92,7 +91,7 @@ To prevent these problems, install OpenSSL yourself. Download the following bina
|
||||||
* Visual C++ 2008 Redistributables
|
* Visual C++ 2008 Redistributables
|
||||||
* Win32 OpenSSL v1.0.1h
|
* 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)
|
###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`
|
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
|
(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))
|
||||||
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".
|
|
||||||
|
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
|
###freeglut
|
||||||
|
|
||||||
|
@ -134,10 +135,10 @@ Be careful with glm. For the folder other libraries would normally call 'include
|
||||||
|
|
||||||
###Bullet
|
###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
|
* 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:
|
* Make the following modifications to Bullet's source:
|
||||||
1. In file: Extras\HACD\hacdICHull.cpp --- in line: 17 --- insert: #include <algorithm>
|
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
|
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>_
|
_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
|
###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"
|
cmake .. -DZLIB_LIBRARY=%ZLIB_LIBRARY% -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% -G "Visual Studio 12"
|
||||||
|
|
||||||
|
|
|
@ -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.
|
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.
|
To test things out you'll want to run the Interface client.
|
||||||
|
|
||||||
|
|
|
@ -486,7 +486,8 @@ function mousePressEvent(event) {
|
||||||
if (clickedOverlay == offButton) {
|
if (clickedOverlay == offButton) {
|
||||||
Script.stop();
|
Script.stop();
|
||||||
} else if (clickedOverlay == platformButton) {
|
} else if (clickedOverlay == platformButton) {
|
||||||
makePlatform(-9.8, 1.0, 5);
|
var platformSize = 3;
|
||||||
|
makePlatform(-9.8, 1.0, platformSize);
|
||||||
} else if (clickedOverlay == gridButton) {
|
} else if (clickedOverlay == gridButton) {
|
||||||
makeGrid("Box", 1.0, 3);
|
makeGrid("Box", 1.0, 3);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,9 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
Script.include("libraries/entityPropertyDialogBox.js");
|
Script.include("../../libraries/entityPropertyDialogBox.js");
|
||||||
var entityPropertyDialogBox = EntityPropertyDialogBox;
|
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 MIN_ANGULAR_SIZE = 2;
|
||||||
var MAX_ANGULAR_SIZE = 45;
|
var MAX_ANGULAR_SIZE = 45;
|
||||||
var allowLargeModels = false;
|
var allowLargeModels = false;
|
||||||
|
@ -32,7 +28,44 @@ var RIGHT = 1;
|
||||||
|
|
||||||
var jointList = MyAvatar.getJointNames();
|
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) {
|
function controller(wichSide) {
|
||||||
this.side = wichSide;
|
this.side = wichSide;
|
||||||
|
@ -42,10 +75,10 @@ function controller(wichSide) {
|
||||||
this.bumper = 6 * wichSide + 5;
|
this.bumper = 6 * wichSide + 5;
|
||||||
|
|
||||||
this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm);
|
this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm);
|
||||||
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
|
this.palmPosition = this.oldPalmPosition;
|
||||||
|
|
||||||
this.oldTipPosition = Controller.getSpatialControlPosition(this.tip);
|
this.oldTipPosition = Controller.getSpatialControlPosition(this.tip);
|
||||||
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
|
this.tipPosition = this.oldTipPosition;
|
||||||
|
|
||||||
this.oldUp = Controller.getSpatialControlNormal(this.palm);
|
this.oldUp = Controller.getSpatialControlNormal(this.palm);
|
||||||
this.up = this.oldUp;
|
this.up = this.oldUp;
|
||||||
|
@ -75,13 +108,13 @@ function controller(wichSide) {
|
||||||
this.positionAtGrab;
|
this.positionAtGrab;
|
||||||
this.rotationAtGrab;
|
this.rotationAtGrab;
|
||||||
this.modelPositionAtGrab;
|
this.modelPositionAtGrab;
|
||||||
this.rotationAtGrab;
|
this.modelRotationAtGrab;
|
||||||
this.jointsIntersectingFromStart = [];
|
this.jointsIntersectingFromStart = [];
|
||||||
|
|
||||||
this.laser = Overlays.addOverlay("line3d", {
|
this.laser = Overlays.addOverlay("line3d", {
|
||||||
start: { x: 0, y: 0, z: 0 },
|
start: { x: 0, y: 0, z: 0 },
|
||||||
end: { x: 0, y: 0, z: 0 },
|
end: { x: 0, y: 0, z: 0 },
|
||||||
color: LASER_COLOR,
|
color: LASER_COLOR[mode],
|
||||||
alpha: 1,
|
alpha: 1,
|
||||||
visible: false,
|
visible: false,
|
||||||
lineWidth: LASER_WIDTH,
|
lineWidth: LASER_WIDTH,
|
||||||
|
@ -132,7 +165,7 @@ function controller(wichSide) {
|
||||||
this.positionAtGrab = this.palmPosition;
|
this.positionAtGrab = this.palmPosition;
|
||||||
this.rotationAtGrab = this.rotation;
|
this.rotationAtGrab = this.rotation;
|
||||||
this.modelPositionAtGrab = properties.position;
|
this.modelPositionAtGrab = properties.position;
|
||||||
this.rotationAtGrab = properties.rotation;
|
this.modelRotationAtGrab = properties.rotation;
|
||||||
this.jointsIntersectingFromStart = [];
|
this.jointsIntersectingFromStart = [];
|
||||||
for (var i = 0; i < jointList.length; i++) {
|
for (var i = 0; i < jointList.length; i++) {
|
||||||
var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition);
|
var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition);
|
||||||
|
@ -245,9 +278,9 @@ function controller(wichSide) {
|
||||||
|
|
||||||
var inverseRotation = Quat.inverse(MyAvatar.orientation);
|
var inverseRotation = Quat.inverse(MyAvatar.orientation);
|
||||||
var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
|
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 direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
|
||||||
var distance = Vec3.length(direction);
|
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale));
|
||||||
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / distance);
|
|
||||||
var endPosition = Vec3.sum(startPosition, direction);
|
var endPosition = Vec3.sum(startPosition, direction);
|
||||||
|
|
||||||
Overlays.editOverlay(this.laser, {
|
Overlays.editOverlay(this.laser, {
|
||||||
|
@ -267,15 +300,14 @@ function controller(wichSide) {
|
||||||
start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)),
|
start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)),
|
||||||
end: 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) {
|
if (this.glowedIntersectingModel.isKnownID) {
|
||||||
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 });
|
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 });
|
||||||
this.glowedIntersectingModel.isKnownID = false;
|
this.glowedIntersectingModel.isKnownID = false;
|
||||||
}
|
}
|
||||||
if (!this.grabbing) {
|
if (!this.grabbing) {
|
||||||
var intersection = Entities.findRayIntersection({
|
var intersection = getRayIntersection({ origin: this.palmPosition,
|
||||||
origin: this.palmPosition,
|
|
||||||
direction: this.front
|
direction: this.front
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -304,8 +336,7 @@ function controller(wichSide) {
|
||||||
if (this.grabbing) {
|
if (this.grabbing) {
|
||||||
if (!this.entityID.isKnownID) {
|
if (!this.entityID.isKnownID) {
|
||||||
print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
|
print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
|
||||||
this.entityID = Entities.findRayIntersection({
|
this.entityID = getRayIntersection({ origin: this.palmPosition,
|
||||||
origin: this.palmPosition,
|
|
||||||
direction: this.front
|
direction: this.front
|
||||||
}).entityID;
|
}).entityID;
|
||||||
print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
|
print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
|
||||||
|
@ -314,7 +345,7 @@ function controller(wichSide) {
|
||||||
var newRotation;
|
var newRotation;
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 0:
|
case STICKS:
|
||||||
newPosition = Vec3.sum(this.palmPosition,
|
newPosition = Vec3.sum(this.palmPosition,
|
||||||
Vec3.multiply(this.front, this.x));
|
Vec3.multiply(this.front, this.x));
|
||||||
newPosition = Vec3.sum(newPosition,
|
newPosition = Vec3.sum(newPosition,
|
||||||
|
@ -328,7 +359,7 @@ function controller(wichSide) {
|
||||||
newRotation = Quat.multiply(newRotation,
|
newRotation = Quat.multiply(newRotation,
|
||||||
this.oldModelRotation);
|
this.oldModelRotation);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case MAPPED:
|
||||||
var forward = Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 });
|
var forward = Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 });
|
||||||
var d = Vec3.dot(forward, MyAvatar.position);
|
var d = Vec3.dot(forward, MyAvatar.position);
|
||||||
|
|
||||||
|
@ -350,8 +381,9 @@ function controller(wichSide) {
|
||||||
|
|
||||||
newRotation = Quat.multiply(this.rotation,
|
newRotation = Quat.multiply(this.rotation,
|
||||||
Quat.inverse(this.rotationAtGrab));
|
Quat.inverse(this.rotationAtGrab));
|
||||||
|
newRotation = Quat.multiply(newRotation, newRotation);
|
||||||
newRotation = Quat.multiply(newRotation,
|
newRotation = Quat.multiply(newRotation,
|
||||||
this.rotationAtGrab);
|
this.modelRotationAtGrab);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Entities.editEntity(this.entityID, {
|
Entities.editEntity(this.entityID, {
|
||||||
|
@ -397,15 +429,13 @@ function controller(wichSide) {
|
||||||
|
|
||||||
var bumperValue = Controller.isButtonPressed(this.bumper);
|
var bumperValue = Controller.isButtonPressed(this.bumper);
|
||||||
if (bumperValue && !this.bumperValue) {
|
if (bumperValue && !this.bumperValue) {
|
||||||
if (mode == 0) {
|
if (mode === STICKS) {
|
||||||
mode = 1;
|
mode = MAPPED;
|
||||||
Overlays.editOverlay(leftController.laser, { color: { red: 0, green: 0, blue: 255 } });
|
} else if (mode === MAPPED) {
|
||||||
Overlays.editOverlay(rightController.laser, { color: { red: 0, green: 0, blue: 255 } });
|
mode = STICKS;
|
||||||
} 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 } });
|
|
||||||
}
|
}
|
||||||
|
Overlays.editOverlay(leftController.laser, { color: LASER_COLOR[mode] });
|
||||||
|
Overlays.editOverlay(rightController.laser, { color: LASER_COLOR[mode] });
|
||||||
}
|
}
|
||||||
this.bumperValue = bumperValue;
|
this.bumperValue = bumperValue;
|
||||||
|
|
||||||
|
@ -475,10 +505,10 @@ function controller(wichSide) {
|
||||||
Vec3.print("Looking at: ", this.palmPosition);
|
Vec3.print("Looking at: ", this.palmPosition);
|
||||||
var pickRay = { origin: this.palmPosition,
|
var pickRay = { origin: this.palmPosition,
|
||||||
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) };
|
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) };
|
||||||
var foundIntersection = Entities.findRayIntersection(pickRay);
|
var foundIntersection = getRayIntersection(pickRay);
|
||||||
|
|
||||||
if(!foundIntersection.accurate) {
|
if(!foundIntersection.intersects) {
|
||||||
print("No accurate intersection");
|
print("No intersection");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newModel = foundIntersection.entityID;
|
newModel = foundIntersection.entityID;
|
||||||
|
@ -526,7 +556,7 @@ function moveEntities() {
|
||||||
|
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 0:
|
case STICKS:
|
||||||
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
|
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
|
||||||
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
|
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
|
||||||
|
|
||||||
|
@ -545,7 +575,7 @@ function moveEntities() {
|
||||||
newPosition = Vec3.sum(middle,
|
newPosition = Vec3.sum(middle,
|
||||||
Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio));
|
Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case MAPPED:
|
||||||
var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition));
|
var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition));
|
||||||
var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition));
|
var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition));
|
||||||
|
|
||||||
|
@ -566,11 +596,11 @@ function moveEntities() {
|
||||||
leftController.positionAtGrab = leftController.palmPosition;
|
leftController.positionAtGrab = leftController.palmPosition;
|
||||||
leftController.rotationAtGrab = leftController.rotation;
|
leftController.rotationAtGrab = leftController.rotation;
|
||||||
leftController.modelPositionAtGrab = leftController.oldModelPosition;
|
leftController.modelPositionAtGrab = leftController.oldModelPosition;
|
||||||
leftController.rotationAtGrab = rotation;
|
leftController.modelRotationAtGrab = rotation;
|
||||||
rightController.positionAtGrab = rightController.palmPosition;
|
rightController.positionAtGrab = rightController.palmPosition;
|
||||||
rightController.rotationAtGrab = rightController.rotation;
|
rightController.rotationAtGrab = rightController.rotation;
|
||||||
rightController.modelPositionAtGrab = rightController.oldModelPosition;
|
rightController.modelPositionAtGrab = rightController.oldModelPosition;
|
||||||
rightController.rotationAtGrab = rotation;
|
rightController.modelRotationAtGrab = rotation;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Entities.editEntity(leftController.entityID, {
|
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
|
// 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
|
// 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.
|
// 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 modelMenuAddedDelete = false;
|
||||||
var originalLightsArePickable = Entities.getLightsArePickable();
|
var originalLightsArePickable = Entities.getLightsArePickable();
|
||||||
function setupModelMenus() {
|
function setupModelMenus() {
|
||||||
print("setupModelMenus()");
|
print("setupModelMenus()");
|
||||||
// adj our menuitems
|
// adj our menuitems
|
||||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" });
|
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: MENU_SEPARATOR, isSeparator: true, beforeItem: ITEM_BEFORE });
|
||||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...",
|
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: EDIT_PROPERTIES,
|
||||||
shortcutKeyEvent: { text: "`" }, afterItem: "Models" });
|
shortcutKeyEvent: { text: "`" }, afterItem: MENU_SEPARATOR });
|
||||||
if (!Menu.menuItemExists("Edit", "Delete")) {
|
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: INTERSECTION_STATS, afterItem: MENU_SEPARATOR });
|
||||||
|
if (!Menu.menuItemExists(ROOT_MENU, DELETE)) {
|
||||||
print("no delete... adding ours");
|
print("no delete... adding ours");
|
||||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete",
|
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: DELETE,
|
||||||
shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" });
|
shortcutKeyEvent: { text: "backspace" }, afterItem: MENU_SEPARATOR });
|
||||||
modelMenuAddedDelete = true;
|
modelMenuAddedDelete = true;
|
||||||
} else {
|
} else {
|
||||||
print("delete exists... don't add ours");
|
print("delete exists... don't add ours");
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
|
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LARGE_MODELS, shortcutKey: "CTRL+META+L",
|
||||||
afterItem: "Paste Models", isCheckable: true });
|
afterItem: DELETE, isCheckable: true });
|
||||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
|
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: SMALL_MODELS, shortcutKey: "CTRL+META+S",
|
||||||
afterItem: "Allow Selecting of Large Models", isCheckable: true });
|
afterItem: LARGE_MODELS, isCheckable: true });
|
||||||
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L",
|
Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LIGHTS, shortcutKey: "CTRL+SHIFT+META+L",
|
||||||
afterItem: "Allow Selecting of Small Models", isCheckable: true });
|
afterItem: SMALL_MODELS, isCheckable: true });
|
||||||
|
|
||||||
Entities.setLightsArePickable(false);
|
Entities.setLightsArePickable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupModelMenus() {
|
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) {
|
if (modelMenuAddedDelete) {
|
||||||
// delete our menuitems
|
// delete our menuitems
|
||||||
Menu.removeMenuItem("Edit", "Delete");
|
Menu.removeMenuItem(ROOT_MENU, DELETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
|
Menu.removeMenuItem(ROOT_MENU, LARGE_MODELS);
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
|
Menu.removeMenuItem(ROOT_MENU, SMALL_MODELS);
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Lights");
|
Menu.removeMenuItem(ROOT_MENU, LIGHTS);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -688,13 +731,13 @@ function showPropertiesForm(editModelID) {
|
||||||
|
|
||||||
Menu.menuItemEvent.connect(function (menuItem) {
|
Menu.menuItemEvent.connect(function (menuItem) {
|
||||||
print("menuItemEvent() in JS... menuItem=" + menuItem);
|
print("menuItemEvent() in JS... menuItem=" + menuItem);
|
||||||
if (menuItem == "Allow Selecting of Small Models") {
|
if (menuItem == SMALL_MODELS) {
|
||||||
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
|
allowSmallModels = Menu.isOptionChecked(SMALL_MODELS);
|
||||||
} else if (menuItem == "Allow Selecting of Large Models") {
|
} else if (menuItem == LARGE_MODELS) {
|
||||||
allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models");
|
allowLargeModels = Menu.isOptionChecked(LARGE_MODELS);
|
||||||
} else if (menuItem == "Allow Selecting of Lights") {
|
} else if (menuItem == LIGHTS) {
|
||||||
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
|
Entities.setLightsArePickable(Menu.isOptionChecked(LIGHTS));
|
||||||
} else if (menuItem == "Delete") {
|
} else if (menuItem == DELETE) {
|
||||||
if (leftController.grabbing) {
|
if (leftController.grabbing) {
|
||||||
print(" Delete Entity.... leftController.entityID="+ leftController.entityID);
|
print(" Delete Entity.... leftController.entityID="+ leftController.entityID);
|
||||||
Entities.deleteEntity(leftController.entityID);
|
Entities.deleteEntity(leftController.entityID);
|
||||||
|
@ -712,7 +755,7 @@ Menu.menuItemEvent.connect(function (menuItem) {
|
||||||
} else {
|
} else {
|
||||||
print(" Delete Entity.... not holding...");
|
print(" Delete Entity.... not holding...");
|
||||||
}
|
}
|
||||||
} else if (menuItem == "Edit Properties...") {
|
} else if (menuItem == EDIT_PROPERTIES) {
|
||||||
editModelID = -1;
|
editModelID = -1;
|
||||||
if (leftController.grabbing) {
|
if (leftController.grabbing) {
|
||||||
print(" Edit Properties.... leftController.entityID="+ leftController.entityID);
|
print(" Edit Properties.... leftController.entityID="+ leftController.entityID);
|
||||||
|
@ -727,16 +770,18 @@ Menu.menuItemEvent.connect(function (menuItem) {
|
||||||
print(" Edit Properties.... about to edit properties...");
|
print(" Edit Properties.... about to edit properties...");
|
||||||
showPropertiesForm(editModelID);
|
showPropertiesForm(editModelID);
|
||||||
}
|
}
|
||||||
|
} else if (menuItem == INTERSECTION_STATS) {
|
||||||
|
printIntersectionsStats();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Controller.keyReleaseEvent.connect(function (event) {
|
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
|
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
|
||||||
if (event.text == "`") {
|
if (event.text == "`") {
|
||||||
handeMenuEvent("Edit Properties...");
|
handeMenuEvent(EDIT_PROPERTIES);
|
||||||
}
|
}
|
||||||
if (event.text == "BACKSPACE") {
|
if (event.text == "BACKSPACE") {
|
||||||
handeMenuEvent("Delete");
|
handeMenuEvent(DELETE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
172
examples/controllers/hydra/paddleBall.js
Normal file
172
examples/controllers/hydra/paddleBall.js
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// PaddleBall.js
|
||||||
|
//
|
||||||
|
// Created by Philip Rosedale on January 21, 2015
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Move your hand with the hydra controller, and hit the ball with the paddle.
|
||||||
|
// Click 'X' button to turn off this script.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
var BALL_SIZE = 0.08;
|
||||||
|
var PADDLE_SIZE = 0.20;
|
||||||
|
var PADDLE_THICKNESS = 0.06;
|
||||||
|
var PADDLE_COLOR = { red: 184, green: 134, blue: 11 };
|
||||||
|
var BALL_COLOR = { red: 255, green: 0, blue: 0 };
|
||||||
|
var LINE_COLOR = { red: 255, green: 255, blue: 0 };
|
||||||
|
var PADDLE_OFFSET = { x: 0.05, y: 0.0, z: 0.0 };
|
||||||
|
var GRAVITY = 0.0;
|
||||||
|
var SPRING_FORCE = 15.0;
|
||||||
|
var lastSoundTime = 0;
|
||||||
|
var gameOn = false;
|
||||||
|
var leftHanded = false;
|
||||||
|
var controllerID;
|
||||||
|
|
||||||
|
if (leftHanded) {
|
||||||
|
controllerID = 1;
|
||||||
|
} else {
|
||||||
|
controllerID = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||||
|
hitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav");
|
||||||
|
|
||||||
|
var screenSize = Controller.getViewportDimensions();
|
||||||
|
var offButton = Overlays.addOverlay("image", {
|
||||||
|
x: screenSize.x - 48,
|
||||||
|
y: 96,
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
imageURL: HIFI_PUBLIC_BUCKET + "images/close.png",
|
||||||
|
color: { red: 255, green: 255, blue: 255},
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
var ball, paddle, paddleModel, line;
|
||||||
|
|
||||||
|
function createEntities() {
|
||||||
|
ball = Entities.addEntity(
|
||||||
|
{ type: "Sphere",
|
||||||
|
position: Controller.getSpatialControlPosition(controllerID),
|
||||||
|
dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE },
|
||||||
|
color: BALL_COLOR,
|
||||||
|
gravity: { x: 0, y: GRAVITY, z: 0 },
|
||||||
|
ignoreCollisions: false,
|
||||||
|
damping: 0.50,
|
||||||
|
collisionsWillMove: true });
|
||||||
|
|
||||||
|
paddle = Entities.addEntity(
|
||||||
|
{ type: "Box",
|
||||||
|
position: Controller.getSpatialControlPosition(controllerID),
|
||||||
|
dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 0.80 },
|
||||||
|
color: PADDLE_COLOR,
|
||||||
|
gravity: { x: 0, y: 0, z: 0 },
|
||||||
|
ignoreCollisions: false,
|
||||||
|
damping: 0.10,
|
||||||
|
visible: false,
|
||||||
|
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
|
||||||
|
collisionsWillMove: false });
|
||||||
|
|
||||||
|
modelURL = "http://public.highfidelity.io/models/attachments/pong_paddle.fbx";
|
||||||
|
paddleModel = Entities.addEntity(
|
||||||
|
{ type: "Model",
|
||||||
|
position: Vec3.sum(Controller.getSpatialControlPosition(controllerID), PADDLE_OFFSET),
|
||||||
|
dimensions: { x: PADDLE_SIZE * 1.5, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 1.25 },
|
||||||
|
color: PADDLE_COLOR,
|
||||||
|
gravity: { x: 0, y: 0, z: 0 },
|
||||||
|
ignoreCollisions: true,
|
||||||
|
modelURL: modelURL,
|
||||||
|
damping: 0.10,
|
||||||
|
rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)),
|
||||||
|
collisionsWillMove: false });
|
||||||
|
|
||||||
|
line = Overlays.addOverlay("line3d", {
|
||||||
|
start: { x: 0, y: 0, z: 0 },
|
||||||
|
end: { x: 0, y: 0, z: 0 },
|
||||||
|
color: LINE_COLOR,
|
||||||
|
alpha: 1,
|
||||||
|
visible: true,
|
||||||
|
lineWidth: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteEntities() {
|
||||||
|
Entities.deleteEntity(ball);
|
||||||
|
Entities.deleteEntity(paddle);
|
||||||
|
Entities.deleteEntity(paddleModel);
|
||||||
|
Overlays.deleteOverlay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(deltaTime) {
|
||||||
|
var palmPosition = Controller.getSpatialControlPosition(controllerID);
|
||||||
|
var controllerActive = (Vec3.length(palmPosition) > 0);
|
||||||
|
|
||||||
|
if (!gameOn && controllerActive) {
|
||||||
|
createEntities();
|
||||||
|
gameOn = true;
|
||||||
|
} else if (gameOn && !controllerActive) {
|
||||||
|
deleteEntities();
|
||||||
|
gameOn = false;
|
||||||
|
}
|
||||||
|
if (!gameOn || !controllerActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paddle.isKnownID) {
|
||||||
|
paddle = Entities.identifyEntity(paddle);
|
||||||
|
}
|
||||||
|
if (!ball.isKnownID) {
|
||||||
|
ball = Entities.identifyEntity(ball);
|
||||||
|
} else {
|
||||||
|
var props = Entities.getEntityProperties(ball);
|
||||||
|
var spring = Vec3.subtract(palmPosition, props.position);
|
||||||
|
var paddleWorldOrientation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID));
|
||||||
|
var springLength = Vec3.length(spring);
|
||||||
|
spring = Vec3.normalize(spring);
|
||||||
|
var ballVelocity = Vec3.sum(props.velocity, Vec3.multiply(springLength * SPRING_FORCE * deltaTime, spring));
|
||||||
|
Entities.editEntity(ball, { velocity: ballVelocity });
|
||||||
|
Overlays.editOverlay(line, { start: props.position, end: palmPosition });
|
||||||
|
Entities.editEntity(paddle, { position: palmPosition,
|
||||||
|
velocity: Controller.getSpatialControlVelocity(controllerID),
|
||||||
|
rotation: paddleWorldOrientation });
|
||||||
|
Entities.editEntity(paddleModel, { position: Vec3.sum(palmPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_OFFSET)),
|
||||||
|
velocity: Controller.getSpatialControlVelocity(controllerID),
|
||||||
|
rotation: paddleWorldOrientation });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function entityCollisionWithEntity(entity1, entity2, collision) {
|
||||||
|
if ((entity1.id == ball.id) || (entity2.id ==ball.id)) {
|
||||||
|
var props1 = Entities.getEntityProperties(entity1);
|
||||||
|
var props2 = Entities.getEntityProperties(entity2);
|
||||||
|
var dVel = Vec3.length(Vec3.subtract(props1.velocity, props2.velocity));
|
||||||
|
var currentTime = new Date().getTime();
|
||||||
|
var MIN_MSECS_BETWEEN_BOUNCE_SOUNDS = 100;
|
||||||
|
var MIN_VELOCITY_FOR_SOUND_IMPACT = 0.25;
|
||||||
|
if ((dVel > MIN_VELOCITY_FOR_SOUND_IMPACT) && (currentTime - lastSoundTime) > MIN_MSECS_BETWEEN_BOUNCE_SOUNDS) {
|
||||||
|
Audio.playSound(hitSound, { position: props1.position, volume: Math.min(dVel, 1.0) });
|
||||||
|
lastSoundTime = new Date().getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mousePressEvent(event) {
|
||||||
|
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||||
|
if (clickedOverlay == offButton) {
|
||||||
|
Script.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scriptEnding() {
|
||||||
|
if (gameOn) {
|
||||||
|
deleteEntities();
|
||||||
|
}
|
||||||
|
Overlays.deleteOverlay(offButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity);
|
||||||
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
|
Script.scriptEnding.connect(scriptEnding);
|
||||||
|
Script.update.connect(update);
|
|
@ -60,11 +60,9 @@ Script.update.connect(function(deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){
|
if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){
|
||||||
MyAvatar.stopAnimation(leftHandAnimation);
|
|
||||||
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
|
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame);
|
||||||
}
|
}
|
||||||
if ((rightFrame != lastRightFrame) && rightHandAnimation.length) {
|
if ((rightFrame != lastRightFrame) && rightHandAnimation.length) {
|
||||||
MyAvatar.stopAnimation(rightHandAnimation);
|
|
||||||
MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame);
|
MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,6 +158,11 @@ function handleModes() {
|
||||||
avatarOrientation.w != MyAvatar.orientation.w)) {
|
avatarOrientation.w != MyAvatar.orientation.w)) {
|
||||||
newMode = noMode;
|
newMode = noMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode == noMode && newMode != noMode && Camera.mode == "independent") {
|
||||||
|
newMode = noMode;
|
||||||
|
}
|
||||||
|
|
||||||
// if leaving noMode
|
// if leaving noMode
|
||||||
if (mode == noMode && newMode != noMode) {
|
if (mode == noMode && newMode != noMode) {
|
||||||
saveCameraState();
|
saveCameraState();
|
||||||
|
|
|
@ -80,7 +80,8 @@ CameraManager = function() {
|
||||||
that.lastMousePosition = { x: 0, y: 0 };
|
that.lastMousePosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
that.enable = function() {
|
that.enable = function() {
|
||||||
if (that.enabled) return;
|
if (Camera.mode == "independent" || that.enabled) return;
|
||||||
|
|
||||||
that.enabled = true;
|
that.enabled = true;
|
||||||
that.mode = MODE_INACTIVE;
|
that.mode = MODE_INACTIVE;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||||
Script.include("libraries/toolBars.js");
|
Script.include("../../libraries/toolBars.js");
|
||||||
|
|
||||||
var recordingFile = "recording.rec";
|
var recordingFile = "recording.rec";
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ function setupMenus() {
|
||||||
}
|
}
|
||||||
if (!Menu.menuExists("Developer > Entities")) {
|
if (!Menu.menuExists("Developer > Entities")) {
|
||||||
Menu.addMenu("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 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 Triangles", isCheckable: true, isChecked: false });
|
||||||
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Display Model Element Bounds", 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 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: "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: "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() {
|
function scriptEnding() {
|
||||||
Menu.removeMenu("Developer > Entities");
|
Menu.removeMenu("Developer > Entities");
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,23 +173,12 @@ void IceServer::clearInactivePeers() {
|
||||||
bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
|
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
|
// 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,
|
// The correct functioning of the ICE server will 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.
|
|
||||||
//
|
//
|
||||||
|
|
||||||
int MINIMUM_PEERS = 3;
|
|
||||||
bool IS_HEALTHY = false;
|
|
||||||
|
|
||||||
IS_HEALTHY = _activePeers.size() >= MINIMUM_PEERS ? true : false;
|
|
||||||
|
|
||||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
|
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
|
||||||
if (url.path() == "/status") {
|
if (url.path() == "/status") {
|
||||||
if (IS_HEALTHY) {
|
|
||||||
connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size()));
|
connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size()));
|
||||||
} else {
|
|
||||||
connection->respond(HTTPConnection::StatusCode404, QByteArray::number(_activePeers.size()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -153,6 +153,16 @@ bool setupEssentials(int& argc, char** argv) {
|
||||||
listenPort = atoi(portStr);
|
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<LimitedNodeList, NodeList>();
|
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||||
|
|
||||||
// Set dependencies
|
// Set dependencies
|
||||||
|
@ -223,34 +233,20 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
_isVSyncOn(true),
|
_isVSyncOn(true),
|
||||||
_aboutToQuit(false)
|
_aboutToQuit(false)
|
||||||
{
|
{
|
||||||
auto glCanvas = DependencyManager::get<GLCanvas>();
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
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
|
_logger = new FileLogger(this); // After setting organization name in order to get correct directory
|
||||||
|
qInstallMessageHandler(messageHandler);
|
||||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
|
||||||
|
|
||||||
_myAvatar = _avatarManager.getMyAvatar();
|
|
||||||
|
|
||||||
_applicationStartupTime = startup_time;
|
|
||||||
|
|
||||||
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf");
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf");
|
||||||
_window->setWindowTitle("Interface");
|
_window->setWindowTitle("Interface");
|
||||||
|
|
||||||
qInstallMessageHandler(messageHandler);
|
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
|
||||||
|
|
||||||
|
auto glCanvas = DependencyManager::get<GLCanvas>();
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
_myAvatar = _avatarManager.getMyAvatar();
|
||||||
|
|
||||||
|
_applicationStartupTime = startup_time;
|
||||||
|
|
||||||
qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion());
|
qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion());
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,9 @@ void Audio::audioMixerKilled() {
|
||||||
|
|
||||||
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
|
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
|
||||||
QAudioDeviceInfo result;
|
QAudioDeviceInfo result;
|
||||||
#ifdef WIN32
|
// Temporarily enable audio device selection in Windows again to test how it behaves now
|
||||||
|
//#ifdef WIN32
|
||||||
|
#if FALSE
|
||||||
// NOTE
|
// NOTE
|
||||||
// this is a workaround for a windows only QtBug https://bugreports.qt-project.org/browse/QTBUG-16117
|
// this is a workaround for a windows only QtBug https://bugreports.qt-project.org/browse/QTBUG-16117
|
||||||
// static QAudioDeviceInfo objects get deallocated when QList<QAudioDevieInfo> objects go out of scope
|
// static QAudioDeviceInfo objects get deallocated when QList<QAudioDevieInfo> objects go out of scope
|
||||||
|
|
|
@ -1427,6 +1427,8 @@ public:
|
||||||
void setColorMaterial(const StackArray::Entry& entry) { color = entry.color; material = entry.material; }
|
void setColorMaterial(const StackArray::Entry& entry) { color = entry.color; material = entry.material; }
|
||||||
|
|
||||||
void mix(const EdgeCrossing& first, const EdgeCrossing& second, float t);
|
void mix(const EdgeCrossing& first, const EdgeCrossing& second, float t);
|
||||||
|
|
||||||
|
VoxelPoint createPoint(int clampedX, int clampedZ, float step) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, float t) {
|
void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, float t) {
|
||||||
|
@ -1437,6 +1439,16 @@ void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, fl
|
||||||
material = (t < 0.5f) ? first.material : second.material;
|
material = (t < 0.5f) ? first.material : second.material;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VoxelPoint EdgeCrossing::createPoint(int clampedX, int clampedZ, float step) const {
|
||||||
|
VoxelPoint voxelPoint = { glm::vec3(clampedX + point.x, point.y, clampedZ + point.z) * step,
|
||||||
|
{ (quint8)qRed(color), (quint8)qGreen(color), (quint8)qBlue(color) },
|
||||||
|
{ (char)(normal.x * numeric_limits<qint8>::max()), (char)(normal.y * numeric_limits<qint8>::max()),
|
||||||
|
(char)(normal.z * numeric_limits<qint8>::max()) },
|
||||||
|
{ (quint8)material, 0, 0, 0 },
|
||||||
|
{ numeric_limits<quint8>::max(), 0, 0, 0 } };
|
||||||
|
return voxelPoint;
|
||||||
|
}
|
||||||
|
|
||||||
const int MAX_NORMALS_PER_VERTEX = 4;
|
const int MAX_NORMALS_PER_VERTEX = 4;
|
||||||
|
|
||||||
class NormalIndex {
|
class NormalIndex {
|
||||||
|
@ -1498,6 +1510,84 @@ const NormalIndex& IndexVector::get(int y) const {
|
||||||
return (relative >= 0 && relative < size()) ? at(relative) : invalidIndex;
|
return (relative >= 0 && relative < size()) ? at(relative) : invalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline glm::vec3 getNormal(const QVector<VoxelPoint>& vertices, const NormalIndex& i0,
|
||||||
|
const NormalIndex& i1, const NormalIndex& i2, const NormalIndex& i3) {
|
||||||
|
// check both triangles in case one is degenerate
|
||||||
|
const glm::vec3& v0 = vertices.at(i0.indices[0]).vertex;
|
||||||
|
glm::vec3 normal = glm::cross(vertices.at(i1.indices[0]).vertex - v0, vertices.at(i2.indices[0]).vertex - v0);
|
||||||
|
if (glm::length(normal) > EPSILON) {
|
||||||
|
return normal;
|
||||||
|
}
|
||||||
|
return glm::cross(vertices.at(i2.indices[0]).vertex - v0, vertices.at(i3.indices[0]).vertex - v0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void appendTriangle(const EdgeCrossing& e0, const EdgeCrossing& e1, const EdgeCrossing& e2,
|
||||||
|
int clampedX, int clampedZ, float step, QVector<VoxelPoint>& vertices, QVector<int>& indices,
|
||||||
|
QMultiHash<VoxelCoord, int>& quadIndices) {
|
||||||
|
int firstIndex = vertices.size();
|
||||||
|
vertices.append(e0.createPoint(clampedX, clampedZ, step));
|
||||||
|
vertices.append(e1.createPoint(clampedX, clampedZ, step));
|
||||||
|
vertices.append(e2.createPoint(clampedX, clampedZ, step));
|
||||||
|
indices.append(firstIndex);
|
||||||
|
indices.append(firstIndex + 1);
|
||||||
|
indices.append(firstIndex + 2);
|
||||||
|
indices.append(firstIndex + 2);
|
||||||
|
|
||||||
|
int minimumY = qMin((int)e0.point.y, qMin((int)e1.point.y, (int)e2.point.y));
|
||||||
|
int maximumY = qMax((int)e0.point.y, qMax((int)e1.point.y, (int)e2.point.y));
|
||||||
|
for (int y = minimumY; y <= maximumY; y++) {
|
||||||
|
quadIndices.insert(qRgb(clampedX, y, clampedZ), firstIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int CORNER_COUNT = 4;
|
||||||
|
|
||||||
|
static StackArray::Entry getEntry(const StackArray* lineSrc, int stackWidth, int y, float heightfieldHeight,
|
||||||
|
EdgeCrossing cornerCrossings[CORNER_COUNT], int cornerIndex) {
|
||||||
|
int offsetX = (cornerIndex & X_MAXIMUM_FLAG) ? 1 : 0;
|
||||||
|
int offsetZ = (cornerIndex & Y_MAXIMUM_FLAG) ? 1 : 0;
|
||||||
|
const StackArray& src = lineSrc[offsetZ * stackWidth + offsetX];
|
||||||
|
int count = src.getEntryCount();
|
||||||
|
if (count > 0) {
|
||||||
|
int relative = y - src.getPosition();
|
||||||
|
if (relative < count && (relative >= 0 || heightfieldHeight == 0.0f)) {
|
||||||
|
return src.getEntry(y, heightfieldHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const EdgeCrossing& cornerCrossing = cornerCrossings[cornerIndex];
|
||||||
|
if (cornerCrossing.point.y == 0.0f) {
|
||||||
|
return src.getEntry(y, heightfieldHeight);
|
||||||
|
}
|
||||||
|
StackArray::Entry entry;
|
||||||
|
bool set = false;
|
||||||
|
if (cornerCrossing.point.y >= y) {
|
||||||
|
entry.color = cornerCrossing.color;
|
||||||
|
entry.material = cornerCrossing.material;
|
||||||
|
set = true;
|
||||||
|
entry.setHermiteY(cornerCrossing.normal, glm::clamp(cornerCrossing.point.y - y, 0.0f, 1.0f));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
entry.material = entry.color = 0;
|
||||||
|
}
|
||||||
|
if (!(cornerIndex & X_MAXIMUM_FLAG)) {
|
||||||
|
const EdgeCrossing& nextCornerCrossingX = cornerCrossings[cornerIndex | X_MAXIMUM_FLAG];
|
||||||
|
if (nextCornerCrossingX.point.y != 0.0f && (nextCornerCrossingX.point.y >= y) != set) {
|
||||||
|
float t = glm::clamp((y - cornerCrossing.point.y) /
|
||||||
|
(nextCornerCrossingX.point.y - cornerCrossing.point.y), 0.0f, 1.0f);
|
||||||
|
entry.setHermiteX(glm::normalize(glm::mix(cornerCrossing.normal, nextCornerCrossingX.normal, t)), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(cornerIndex & Y_MAXIMUM_FLAG)) {
|
||||||
|
const EdgeCrossing& nextCornerCrossingZ = cornerCrossings[cornerIndex | Y_MAXIMUM_FLAG];
|
||||||
|
if (nextCornerCrossingZ.point.y != 0.0f && (nextCornerCrossingZ.point.y >= y) != set) {
|
||||||
|
float t = glm::clamp((y - cornerCrossing.point.y) /
|
||||||
|
(nextCornerCrossingZ.point.y - cornerCrossing.point.y), 0.0f, 1.0f);
|
||||||
|
entry.setHermiteZ(glm::normalize(glm::mix(cornerCrossing.normal, nextCornerCrossingZ.normal, t)), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const glm::vec3& translation,
|
void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const glm::vec3& translation,
|
||||||
const glm::quat& rotation, const glm::vec3& scale, bool cursor) {
|
const glm::quat& rotation, const glm::vec3& scale, bool cursor) {
|
||||||
if (!node->getHeight()) {
|
if (!node->getHeight()) {
|
||||||
|
@ -1706,7 +1796,6 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
const int LOWER_RIGHT_CORNER = 8;
|
const int LOWER_RIGHT_CORNER = 8;
|
||||||
const int NO_CORNERS = 0;
|
const int NO_CORNERS = 0;
|
||||||
const int ALL_CORNERS = UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER;
|
const int ALL_CORNERS = UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER;
|
||||||
const int CORNER_COUNT = 4;
|
|
||||||
const int NEXT_CORNERS[] = { 1, 3, 0, 2 };
|
const int NEXT_CORNERS[] = { 1, 3, 0, 2 };
|
||||||
int corners = NO_CORNERS;
|
int corners = NO_CORNERS;
|
||||||
if (heightfieldHeight != 0.0f) {
|
if (heightfieldHeight != 0.0f) {
|
||||||
|
@ -1772,6 +1861,15 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
}
|
}
|
||||||
minimumY = qMin(minimumY, cornerMinimumY);
|
minimumY = qMin(minimumY, cornerMinimumY);
|
||||||
maximumY = qMax(maximumY, cornerMaximumY);
|
maximumY = qMax(maximumY, cornerMaximumY);
|
||||||
|
|
||||||
|
if (corners == (LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER)) {
|
||||||
|
appendTriangle(cornerCrossings[1], cornerCrossings[0], cornerCrossings[2],
|
||||||
|
clampedX, clampedZ, step, vertices, indices, quadIndices);
|
||||||
|
|
||||||
|
} else if (corners == (UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER)) {
|
||||||
|
appendTriangle(cornerCrossings[2], cornerCrossings[3], cornerCrossings[1],
|
||||||
|
clampedX, clampedZ, step, vertices, indices, quadIndices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int position = minimumY;
|
int position = minimumY;
|
||||||
int count = maximumY - minimumY + 1;
|
int count = maximumY - minimumY + 1;
|
||||||
|
@ -1781,7 +1879,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
indicesZ[x].position = position;
|
indicesZ[x].position = position;
|
||||||
indicesZ[x].resize(count);
|
indicesZ[x].resize(count);
|
||||||
for (int y = position, end = position + count; y < end; y++) {
|
for (int y = position, end = position + count; y < end; y++) {
|
||||||
const StackArray::Entry& entry = lineSrc->getEntry(y, heightfieldHeight);
|
StackArray::Entry entry = getEntry(lineSrc, stackWidth, y, heightfieldHeight, cornerCrossings, 0);
|
||||||
if (displayHermite && x != 0 && z != 0 && !lineSrc->isEmpty() && y >= lineSrc->getPosition()) {
|
if (displayHermite && x != 0 && z != 0 && !lineSrc->isEmpty() && y >= lineSrc->getPosition()) {
|
||||||
glm::vec3 normal;
|
glm::vec3 normal;
|
||||||
if (entry.hermiteX != 0) {
|
if (entry.hermiteX != 0) {
|
||||||
|
@ -1846,18 +1944,15 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
if (!(corners & (1 << i))) {
|
if (!(corners & (1 << i))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
|
const EdgeCrossing& cornerCrossing = cornerCrossings[i];
|
||||||
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
|
if (cornerCrossing.point.y >= y && cornerCrossing.point.y < y + 1) {
|
||||||
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
|
|
||||||
float heightValue = *height * voxelScale;
|
|
||||||
if (heightValue >= y && heightValue < y + 1) {
|
|
||||||
crossedCorners |= (1 << i);
|
crossedCorners |= (1 << i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (crossedCorners) {
|
switch (crossedCorners) {
|
||||||
case UPPER_LEFT_CORNER:
|
case UPPER_LEFT_CORNER:
|
||||||
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
|
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
|
||||||
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
|
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
|
||||||
case UPPER_LEFT_CORNER | LOWER_RIGHT_CORNER:
|
case UPPER_LEFT_CORNER | LOWER_RIGHT_CORNER:
|
||||||
crossings[crossingCount++] = cornerCrossings[0];
|
crossings[crossingCount++] = cornerCrossings[0];
|
||||||
crossings[crossingCount - 1].point.y -= y;
|
crossings[crossingCount - 1].point.y -= y;
|
||||||
|
@ -1865,22 +1960,22 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
|
|
||||||
case UPPER_RIGHT_CORNER:
|
case UPPER_RIGHT_CORNER:
|
||||||
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
|
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
|
||||||
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
|
|
||||||
case UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER:
|
case UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER:
|
||||||
|
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
|
||||||
crossings[crossingCount++] = cornerCrossings[1];
|
crossings[crossingCount++] = cornerCrossings[1];
|
||||||
crossings[crossingCount - 1].point.y -= y;
|
crossings[crossingCount - 1].point.y -= y;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOWER_LEFT_CORNER:
|
case LOWER_LEFT_CORNER:
|
||||||
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
|
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
|
||||||
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
|
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
|
||||||
crossings[crossingCount++] = cornerCrossings[2];
|
crossings[crossingCount++] = cornerCrossings[2];
|
||||||
crossings[crossingCount - 1].point.y -= y;
|
crossings[crossingCount - 1].point.y -= y;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LOWER_RIGHT_CORNER:
|
case LOWER_RIGHT_CORNER:
|
||||||
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
|
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
|
||||||
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
|
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
|
||||||
crossings[crossingCount++] = cornerCrossings[3];
|
crossings[crossingCount++] = cornerCrossings[3];
|
||||||
crossings[crossingCount - 1].point.y -= y;
|
crossings[crossingCount - 1].point.y -= y;
|
||||||
break;
|
break;
|
||||||
|
@ -1890,30 +1985,24 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
if (!(corners & (1 << i))) {
|
if (!(corners & (1 << i))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
|
|
||||||
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
|
|
||||||
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
|
|
||||||
float heightValue = *height * voxelScale;
|
|
||||||
int nextIndex = NEXT_CORNERS[i];
|
int nextIndex = NEXT_CORNERS[i];
|
||||||
if (!(corners & (1 << nextIndex))) {
|
if (!(corners & (1 << nextIndex))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int nextOffsetX = (nextIndex & X_MAXIMUM_FLAG) ? 1 : 0;
|
const EdgeCrossing& cornerCrossing = cornerCrossings[i];
|
||||||
int nextOffsetZ = (nextIndex & Y_MAXIMUM_FLAG) ? 1 : 0;
|
const EdgeCrossing& nextCornerCrossing = cornerCrossings[nextIndex];
|
||||||
const quint16* nextHeight = heightLineSrc + nextOffsetZ * width + nextOffsetX;
|
float divisor = (nextCornerCrossing.point.y - cornerCrossing.point.y);
|
||||||
float nextHeightValue = *nextHeight * voxelScale;
|
|
||||||
float divisor = (nextHeightValue - heightValue);
|
|
||||||
if (divisor == 0.0f) {
|
if (divisor == 0.0f) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float t1 = (y - heightValue) / divisor;
|
float t1 = (y - cornerCrossing.point.y) / divisor;
|
||||||
float t2 = (y + 1 - heightValue) / divisor;
|
float t2 = (y + 1 - cornerCrossing.point.y) / divisor;
|
||||||
if (t1 >= 0.0f && t1 <= 1.0f) {
|
if (t1 >= 0.0f && t1 <= 1.0f) {
|
||||||
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t1);
|
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t1);
|
||||||
crossings[crossingCount - 1].point.y -= y;
|
crossings[crossingCount - 1].point.y -= y;
|
||||||
}
|
}
|
||||||
if (t2 >= 0.0f && t2 <= 1.0f) {
|
if (t2 >= 0.0f && t2 <= 1.0f) {
|
||||||
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t2);
|
crossings[crossingCount++].mix(cornerCrossing, nextCornerCrossing, t2);
|
||||||
crossings[crossingCount - 1].point.y -= y;
|
crossings[crossingCount - 1].point.y -= y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1924,10 +2013,13 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
// the terrifying conditional code that follows checks each cube edge for a crossing, gathering
|
// the terrifying conditional code that follows checks each cube edge for a crossing, gathering
|
||||||
// its properties (color, material, normal) if one is present; as before, boundary edges are excluded
|
// its properties (color, material, normal) if one is present; as before, boundary edges are excluded
|
||||||
if (crossingCount == 0) {
|
if (crossingCount == 0) {
|
||||||
const StackArray::Entry& nextEntryY = lineSrc->getEntry(y + 1, heightfieldHeight);
|
StackArray::Entry nextEntryY = getEntry(lineSrc, stackWidth, y + 1,
|
||||||
|
heightfieldHeight, cornerCrossings, 0);
|
||||||
if (middleX) {
|
if (middleX) {
|
||||||
const StackArray::Entry& nextEntryX = lineSrc[1].getEntry(y, nextHeightfieldHeightX);
|
StackArray::Entry nextEntryX = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightX,
|
||||||
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
|
cornerCrossings, 1);
|
||||||
|
StackArray::Entry nextEntryXY = getEntry(lineSrc, stackWidth, y + 1, nextHeightfieldHeightX,
|
||||||
|
cornerCrossings, 1);
|
||||||
if (alpha0 != alpha1) {
|
if (alpha0 != alpha1) {
|
||||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||||
crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f);
|
crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f);
|
||||||
|
@ -1944,12 +2036,12 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
|
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
|
||||||
}
|
}
|
||||||
if (middleZ) {
|
if (middleZ) {
|
||||||
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y,
|
StackArray::Entry nextEntryZ = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightZ,
|
||||||
nextHeightfieldHeightZ);
|
cornerCrossings, 2);
|
||||||
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
|
StackArray::Entry nextEntryXZ = getEntry(lineSrc, stackWidth, y, nextHeightfieldHeightXZ,
|
||||||
y, nextHeightfieldHeightXZ);
|
cornerCrossings, 3);
|
||||||
const StackArray::Entry& nextEntryXYZ = lineSrc[stackWidth + 1].getEntry(
|
StackArray::Entry nextEntryXYZ = getEntry(lineSrc, stackWidth, y + 1,
|
||||||
y + 1, nextHeightfieldHeightXZ);
|
nextHeightfieldHeightXZ, cornerCrossings, 3);
|
||||||
if (alpha1 != alpha5) {
|
if (alpha1 != alpha5) {
|
||||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||||
crossing.point = glm::vec3(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal));
|
crossing.point = glm::vec3(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal));
|
||||||
|
@ -1957,8 +2049,8 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
}
|
}
|
||||||
if (alpha3 != alpha7) {
|
if (alpha3 != alpha7) {
|
||||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||||
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1,
|
StackArray::Entry nextEntryXY = getEntry(lineSrc, stackWidth, y + 1,
|
||||||
nextHeightfieldHeightX);
|
nextHeightfieldHeightX, cornerCrossings, 1);
|
||||||
crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal));
|
crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal));
|
||||||
crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY);
|
crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY);
|
||||||
}
|
}
|
||||||
|
@ -1969,15 +2061,15 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
}
|
}
|
||||||
if (alpha5 != alpha7) {
|
if (alpha5 != alpha7) {
|
||||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||||
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
|
StackArray::Entry nextEntryXZ = getEntry(lineSrc, stackWidth, y,
|
||||||
y, nextHeightfieldHeightXZ);
|
nextHeightfieldHeightXZ, cornerCrossings, 3);
|
||||||
crossing.point = glm::vec3(1.0f, nextEntryXZ.getHermiteY(crossing.normal), 1.0f);
|
crossing.point = glm::vec3(1.0f, nextEntryXZ.getHermiteY(crossing.normal), 1.0f);
|
||||||
crossing.setColorMaterial(alpha5 == 0 ? nextEntryXYZ : nextEntryXZ);
|
crossing.setColorMaterial(alpha5 == 0 ? nextEntryXYZ : nextEntryXZ);
|
||||||
}
|
}
|
||||||
if (alpha6 != alpha7) {
|
if (alpha6 != alpha7) {
|
||||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||||
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(
|
StackArray::Entry nextEntryYZ = getEntry(lineSrc, stackWidth, y + 1,
|
||||||
y + 1, nextHeightfieldHeightZ);
|
nextHeightfieldHeightZ, cornerCrossings, 2);
|
||||||
crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f);
|
crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f);
|
||||||
crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ);
|
crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ);
|
||||||
}
|
}
|
||||||
|
@ -1989,9 +2081,10 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry);
|
crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry);
|
||||||
}
|
}
|
||||||
if (middleZ) {
|
if (middleZ) {
|
||||||
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y, nextHeightfieldHeightZ);
|
StackArray::Entry nextEntryZ = getEntry(lineSrc, stackWidth, y,
|
||||||
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(y + 1,
|
nextHeightfieldHeightZ, cornerCrossings, 2);
|
||||||
nextHeightfieldHeightZ);
|
StackArray::Entry nextEntryYZ = getEntry(lineSrc, stackWidth, y + 1,
|
||||||
|
nextHeightfieldHeightZ, cornerCrossings, 2);
|
||||||
if (alpha0 != alpha4) {
|
if (alpha0 != alpha4) {
|
||||||
EdgeCrossing& crossing = crossings[crossingCount++];
|
EdgeCrossing& crossing = crossings[crossingCount++];
|
||||||
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
|
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
|
||||||
|
@ -2174,10 +2267,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ - 1), indices.size());
|
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ - 1), indices.size());
|
||||||
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
|
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
|
||||||
}
|
}
|
||||||
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
|
glm::vec3 normal = getNormal(vertices, index, index1, index2, index3);
|
||||||
glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first,
|
|
||||||
vertices.at(index3.indices[0]).vertex - first);
|
|
||||||
|
|
||||||
if (alpha0 == 0) { // quad faces negative x
|
if (alpha0 == 0) { // quad faces negative x
|
||||||
indices.append(index3.getClosestIndex(normal = -normal, vertices));
|
indices.append(index3.getClosestIndex(normal = -normal, vertices));
|
||||||
indices.append(index2.getClosestIndex(normal, vertices));
|
indices.append(index2.getClosestIndex(normal, vertices));
|
||||||
|
@ -2206,10 +2296,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
if (reclampedZ > 0) {
|
if (reclampedZ > 0) {
|
||||||
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
|
quadIndices.insert(qRgb(reclampedX, y, reclampedZ - 1), indices.size());
|
||||||
}
|
}
|
||||||
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
|
glm::vec3 normal = getNormal(vertices, index, index3, index2, index1);
|
||||||
glm::vec3 normal = glm::cross(vertices.at(index3.indices[0]).vertex - first,
|
|
||||||
vertices.at(index1.indices[0]).vertex - first);
|
|
||||||
|
|
||||||
if (alpha0 == 0) { // quad faces negative y
|
if (alpha0 == 0) { // quad faces negative y
|
||||||
indices.append(index3.getClosestIndex(normal, vertices));
|
indices.append(index3.getClosestIndex(normal, vertices));
|
||||||
indices.append(index2.getClosestIndex(normal, vertices));
|
indices.append(index2.getClosestIndex(normal, vertices));
|
||||||
|
@ -2235,10 +2322,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
|
||||||
}
|
}
|
||||||
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ), indices.size());
|
quadIndices.insert(qRgb(reclampedX, y - 1, reclampedZ), indices.size());
|
||||||
|
|
||||||
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
|
glm::vec3 normal = getNormal(vertices, index, index1, index2, index3);
|
||||||
glm::vec3 normal = glm::cross(vertices.at(index1.indices[0]).vertex - first,
|
|
||||||
vertices.at(index3.indices[0]).vertex - first);
|
|
||||||
|
|
||||||
if (alpha0 == 0) { // quad faces negative z
|
if (alpha0 == 0) { // quad faces negative z
|
||||||
indices.append(index1.getClosestIndex(normal, vertices));
|
indices.append(index1.getClosestIndex(normal, vertices));
|
||||||
indices.append(index2.getClosestIndex(normal, vertices));
|
indices.append(index2.getClosestIndex(normal, vertices));
|
||||||
|
|
|
@ -171,10 +171,6 @@ void Hand::renderHandTargets(bool isMine) {
|
||||||
glm::vec3 tip = palm.getTipPosition();
|
glm::vec3 tip = palm.getTipPosition();
|
||||||
glm::vec3 root = palm.getPosition();
|
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);
|
Avatar::renderJointConnectingCone(root, tip, PALM_FINGER_ROD_RADIUS, PALM_FINGER_ROD_RADIUS);
|
||||||
// Render sphere at palm/finger root
|
// Render sphere at palm/finger root
|
||||||
glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS;
|
glm::vec3 offsetFromPalm = root + palm.getNormal() * PALM_DISK_THICKNESS;
|
||||||
|
|
|
@ -822,6 +822,12 @@ HeightfieldBrushTool::HeightfieldBrushTool(MetavoxelEditor* editor, const QStrin
|
||||||
_radius->setSingleStep(0.01);
|
_radius->setSingleStep(0.01);
|
||||||
_radius->setMaximum(FLT_MAX);
|
_radius->setMaximum(FLT_MAX);
|
||||||
_radius->setValue(5.0);
|
_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 {
|
bool HeightfieldBrushTool::appliesTo(const AttributePointer& attribute) const {
|
||||||
|
@ -851,7 +857,7 @@ bool HeightfieldBrushTool::eventFilter(QObject* watched, QEvent* event) {
|
||||||
if (event->type() == QEvent::Wheel) {
|
if (event->type() == QEvent::Wheel) {
|
||||||
float angle = static_cast<QWheelEvent*>(event)->angleDelta().y();
|
float angle = static_cast<QWheelEvent*>(event)->angleDelta().y();
|
||||||
const float ANGLE_SCALE = 1.0f / 1000.0f;
|
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;
|
return true;
|
||||||
|
|
||||||
} else if (event->type() == QEvent::MouseButtonPress && _positionValid) {
|
} else if (event->type() == QEvent::MouseButtonPress && _positionValid) {
|
||||||
|
@ -881,7 +887,7 @@ QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) {
|
||||||
const int ERASE_MODE_INDEX = 2;
|
const int ERASE_MODE_INDEX = 2;
|
||||||
return QVariant::fromValue(PaintHeightfieldHeightEdit(_position, _radius->value(),
|
return QVariant::fromValue(PaintHeightfieldHeightEdit(_position, _radius->value(),
|
||||||
alternate ? -_height->value() : _height->value(), _mode->currentIndex() == SET_MODE_INDEX,
|
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) :
|
MaterialControl::MaterialControl(QWidget* widget, QFormLayout* form, bool clearable) :
|
||||||
|
@ -956,10 +962,11 @@ QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) {
|
||||||
sphere->setScale(_radius->value());
|
sphere->setScale(_radius->value());
|
||||||
if (alternate) {
|
if (alternate) {
|
||||||
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
|
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 {
|
} else {
|
||||||
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
|
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());
|
sphere->setScale(_radius->value());
|
||||||
if (alternate) {
|
if (alternate) {
|
||||||
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
|
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 {
|
} else {
|
||||||
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
|
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) {
|
QVariant HeightfieldFillBrushTool::createEdit(bool alternate) {
|
||||||
const int FILL_MODE_INDEX = 0;
|
const int FILL_MODE_INDEX = 0;
|
||||||
if (_mode->currentIndex() == FILL_MODE_INDEX) {
|
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* sphere = new Sphere();
|
||||||
sphere->setTranslation(_position);
|
sphere->setTranslation(_position);
|
||||||
sphere->setScale(_radius->value());
|
sphere->setScale(_radius->value());
|
||||||
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
|
return QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(sphere),
|
||||||
SharedObjectPointer(), QColor(), false, true));
|
SharedObjectPointer(), QColor(), false, true, pow(2.0f, _granularity->value())));
|
||||||
}
|
}
|
||||||
|
|
||||||
HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor) :
|
HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor) :
|
||||||
|
@ -1017,6 +1026,12 @@ HeightfieldMaterialBoxTool::HeightfieldMaterialBoxTool(MetavoxelEditor* editor)
|
||||||
_snapToGrid->setChecked(true);
|
_snapToGrid->setChecked(true);
|
||||||
|
|
||||||
_materialControl = new MaterialControl(this, form, 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 {
|
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->setAspectY(vector.y / vector.x);
|
||||||
cuboid->setAspectZ(vector.z / vector.x);
|
cuboid->setAspectZ(vector.z / vector.x);
|
||||||
MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(SharedObjectPointer(cuboid),
|
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);
|
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1056,6 +1071,12 @@ HeightfieldMaterialSpannerTool::HeightfieldMaterialSpannerTool(MetavoxelEditor*
|
||||||
|
|
||||||
_materialControl = new MaterialControl(this, form, 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);
|
||||||
|
|
||||||
QPushButton* place = new QPushButton("Set");
|
QPushButton* place = new QPushButton("Set");
|
||||||
layout()->addWidget(place);
|
layout()->addWidget(place);
|
||||||
connect(place, &QPushButton::clicked, this, &HeightfieldMaterialSpannerTool::place);
|
connect(place, &QPushButton::clicked, this, &HeightfieldMaterialSpannerTool::place);
|
||||||
|
@ -1076,7 +1097,7 @@ QColor HeightfieldMaterialSpannerTool::getColor() {
|
||||||
void HeightfieldMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) {
|
void HeightfieldMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) {
|
||||||
static_cast<Spanner*>(spanner.data())->setWillBeVoxelized(true);
|
static_cast<Spanner*>(spanner.data())->setWillBeVoxelized(true);
|
||||||
MetavoxelEditMessage message = { QVariant::fromValue(HeightfieldMaterialSpannerEdit(spanner,
|
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);
|
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -326,6 +326,7 @@ protected:
|
||||||
|
|
||||||
QFormLayout* _form;
|
QFormLayout* _form;
|
||||||
QDoubleSpinBox* _radius;
|
QDoubleSpinBox* _radius;
|
||||||
|
QDoubleSpinBox* _granularity;
|
||||||
|
|
||||||
glm::vec3 _position;
|
glm::vec3 _position;
|
||||||
bool _positionValid;
|
bool _positionValid;
|
||||||
|
@ -448,6 +449,7 @@ private:
|
||||||
|
|
||||||
QCheckBox* _snapToGrid;
|
QCheckBox* _snapToGrid;
|
||||||
MaterialControl* _materialControl;
|
MaterialControl* _materialControl;
|
||||||
|
QDoubleSpinBox* _granularity;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Allows setting heightfield materials by placing a spanner.
|
/// Allows setting heightfield materials by placing a spanner.
|
||||||
|
@ -470,6 +472,7 @@ private:
|
||||||
|
|
||||||
SharedObjectEditor* _spannerEditor;
|
SharedObjectEditor* _spannerEditor;
|
||||||
MaterialControl* _materialControl;
|
MaterialControl* _materialControl;
|
||||||
|
QDoubleSpinBox* _granularity;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_MetavoxelEditor_h
|
#endif // hifi_MetavoxelEditor_h
|
||||||
|
|
|
@ -92,7 +92,13 @@ void ImageOverlay::render(RenderArgs* args) {
|
||||||
|
|
||||||
QRect fromImage;
|
QRect fromImage;
|
||||||
if (_wantClipFromImage) {
|
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 {
|
} else {
|
||||||
fromImage.setX(0);
|
fromImage.setX(0);
|
||||||
fromImage.setY(0);
|
fromImage.setY(0);
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<family>Helvetica,Arial,sans-serif</family>
|
<family>Helvetica,Arial,sans-serif</family>
|
||||||
<pointsize>-1</pointsize>
|
<pointsize>16</pointsize>
|
||||||
<weight>75</weight>
|
<weight>75</weight>
|
||||||
<italic>false</italic>
|
<italic>false</italic>
|
||||||
<bold>true</bold>
|
<bold>true</bold>
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">color: #0e7077;
|
<string notr="true">color: #0e7077;
|
||||||
font: bold 16px;
|
font: bold 16pt;
|
||||||
</string>
|
</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -192,7 +192,7 @@ font: bold 16px;
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">font: 14px; color: #5f5f5f; margin: 2px;</string>
|
<string notr="true">font: 14pt; color: #5f5f5f; margin: 2px;</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>There are no scripts running.</string>
|
<string>There are no scripts running.</string>
|
||||||
|
@ -245,8 +245,8 @@ font: bold 16px;
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>334</width>
|
<width>328</width>
|
||||||
<height>20</height>
|
<height>18</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -259,7 +259,7 @@ font: bold 16px;
|
||||||
<enum>Qt::LeftToRight</enum>
|
<enum>Qt::LeftToRight</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">font-size: 14px;</string>
|
<string notr="true">font-size: 14pt;</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
|
@ -301,7 +301,7 @@ font: bold 16px;
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="tipLabel">
|
<widget class="QLabel" name="tipLabel">
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">font: 14px; color: #5f5f5f; margin: 2px;</string>
|
<string notr="true">font: 14pt; color: #5f5f5f; margin: 2px;</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Tip</string>
|
<string>Tip</string>
|
||||||
|
@ -370,7 +370,7 @@ font: bold 16px;
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="styleSheet">
|
<property name="styleSheet">
|
||||||
<string notr="true">color: #0e7077;
|
<string notr="true">color: #0e7077;
|
||||||
font: bold 16px;</string>
|
font: bold 16pt;</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Load Scripts</string>
|
<string>Load Scripts</string>
|
||||||
|
|
|
@ -27,7 +27,7 @@ HandData::HandData(AvatarData* owningAvatar) :
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const {
|
glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const {
|
||||||
return glm::inverse(getBaseOrientation()) * worldVector;
|
return glm::inverse(getBaseOrientation()) * worldVector / getBaseScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
PalmData& HandData::addNewPalm() {
|
PalmData& HandData::addNewPalm() {
|
||||||
|
@ -109,14 +109,18 @@ glm::vec3 HandData::getBasePosition() const {
|
||||||
return _owningAvatarData->getPosition();
|
return _owningAvatarData->getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float HandData::getBaseScale() const {
|
||||||
|
return _owningAvatarData->getTargetScale();
|
||||||
|
}
|
||||||
|
|
||||||
glm::vec3 PalmData::getFingerDirection() const {
|
glm::vec3 PalmData::getFingerDirection() const {
|
||||||
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f);
|
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 {
|
glm::vec3 PalmData::getNormal() const {
|
||||||
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f);
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,11 @@ public:
|
||||||
|
|
||||||
// position conversion
|
// position conversion
|
||||||
glm::vec3 localToWorldPosition(const glm::vec3& localPosition) {
|
glm::vec3 localToWorldPosition(const glm::vec3& localPosition) {
|
||||||
return getBasePosition() + getBaseOrientation() * localPosition;
|
return getBasePosition() + getBaseOrientation() * localPosition * getBaseScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 localToWorldDirection(const glm::vec3& localVector) {
|
glm::vec3 localToWorldDirection(const glm::vec3& localVector) {
|
||||||
return getBaseOrientation() * localVector;
|
return getBaseOrientation() * localVector * getBaseScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const;
|
glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const;
|
||||||
|
@ -71,6 +71,7 @@ protected:
|
||||||
|
|
||||||
glm::quat getBaseOrientation() const;
|
glm::quat getBaseOrientation() const;
|
||||||
glm::vec3 getBasePosition() const;
|
glm::vec3 getBasePosition() const;
|
||||||
|
float getBaseScale() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// privatize copy ctor and assignment operator so copies of this object cannot be made
|
// privatize copy ctor and assignment operator so copies of this object cannot be made
|
||||||
|
|
|
@ -101,3 +101,12 @@ void BoxEntityItem::computeShapeInfo(ShapeInfo& info) const {
|
||||||
info.setBox(halfExtents);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,8 @@ public:
|
||||||
|
|
||||||
void computeShapeInfo(ShapeInfo& info) const;
|
void computeShapeInfo(ShapeInfo& info) const;
|
||||||
|
|
||||||
|
virtual void debugDump() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
rgbColor _color;
|
rgbColor _color;
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,6 +36,11 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemI
|
||||||
int sizeOut = 0;
|
int sizeOut = 0;
|
||||||
|
|
||||||
if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) {
|
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);
|
queueOctreeEditMessage(type, bufferOut, sizeOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
#include "EntityItem.h"
|
#include "EntityItem.h"
|
||||||
#include "EntityTree.h"
|
#include "EntityTree.h"
|
||||||
|
|
||||||
|
bool EntityItem::_sendPhysicsUpdates = true;
|
||||||
|
|
||||||
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
|
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
|
||||||
_id = entityItemID.id;
|
_id = entityItemID.id;
|
||||||
_creatorTokenID = entityItemID.creatorTokenID;
|
_creatorTokenID = entityItemID.creatorTokenID;
|
||||||
|
@ -140,9 +142,17 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
||||||
ByteCountCoded<quint32> typeCoder = getType();
|
ByteCountCoded<quint32> typeCoder = getType();
|
||||||
QByteArray encodedType = typeCoder;
|
QByteArray encodedType = typeCoder;
|
||||||
|
|
||||||
quint64 updateDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited();
|
// last updated (animations, non-physics changes)
|
||||||
|
quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited();
|
||||||
ByteCountCoded<quint64> updateDeltaCoder = updateDelta;
|
ByteCountCoded<quint64> updateDeltaCoder = updateDelta;
|
||||||
QByteArray encodedUpdateDelta = updateDeltaCoder;
|
QByteArray encodedUpdateDelta = updateDeltaCoder;
|
||||||
|
|
||||||
|
// last simulated (velocity, angular velocity, physics changes)
|
||||||
|
quint64 simulatedDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited();
|
||||||
|
ByteCountCoded<quint64> simulatedDeltaCoder = simulatedDelta;
|
||||||
|
QByteArray encodedSimulatedDelta = simulatedDeltaCoder;
|
||||||
|
|
||||||
|
|
||||||
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
|
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
|
||||||
EntityPropertyFlags requestedProperties = getEntityProperties(params);
|
EntityPropertyFlags requestedProperties = getEntityProperties(params);
|
||||||
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
||||||
|
@ -157,19 +167,19 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
||||||
|
|
||||||
quint64 lastEdited = getLastEdited();
|
quint64 lastEdited = getLastEdited();
|
||||||
|
|
||||||
const bool wantDebug = false;
|
#ifdef WANT_DEBUG
|
||||||
if (wantDebug) {
|
|
||||||
float editedAgo = getEditedAgo();
|
float editedAgo = getEditedAgo();
|
||||||
QString agoAsString = formatSecondsElapsed(editedAgo);
|
QString agoAsString = formatSecondsElapsed(editedAgo);
|
||||||
qDebug() << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited
|
qDebug() << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited
|
||||||
<< " ago=" << editedAgo << "seconds - " << agoAsString;
|
<< " ago=" << editedAgo << "seconds - " << agoAsString;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
bool successIDFits = false;
|
bool successIDFits = false;
|
||||||
bool successTypeFits = false;
|
bool successTypeFits = false;
|
||||||
bool successCreatedFits = false;
|
bool successCreatedFits = false;
|
||||||
bool successLastEditedFits = false;
|
bool successLastEditedFits = false;
|
||||||
bool successLastUpdatedFits = false;
|
bool successLastUpdatedFits = false;
|
||||||
|
bool successLastSimulatedFits = false;
|
||||||
bool successPropertyFlagsFits = false;
|
bool successPropertyFlagsFits = false;
|
||||||
int propertyFlagsOffset = 0;
|
int propertyFlagsOffset = 0;
|
||||||
int oldPropertyFlagsLength = 0;
|
int oldPropertyFlagsLength = 0;
|
||||||
|
@ -189,8 +199,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
||||||
if (successLastEditedFits) {
|
if (successLastEditedFits) {
|
||||||
successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta);
|
successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (successLastUpdatedFits) {
|
if (successLastUpdatedFits) {
|
||||||
|
successLastSimulatedFits = packetData->appendValue(encodedSimulatedDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successLastSimulatedFits) {
|
||||||
propertyFlagsOffset = packetData->getUncompressedByteOffset();
|
propertyFlagsOffset = packetData->getUncompressedByteOffset();
|
||||||
encodedPropertyFlags = propertyFlags;
|
encodedPropertyFlags = propertyFlags;
|
||||||
oldPropertyFlagsLength = encodedPropertyFlags.length();
|
oldPropertyFlagsLength = encodedPropertyFlags.length();
|
||||||
|
@ -213,11 +226,6 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
||||||
|
|
||||||
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, getPosition());
|
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, getPosition());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, getDimensions()); // NOTE: PROP_RADIUS obsolete
|
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_ROTATION, appendValue, getRotation());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, getDensity());
|
APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, getDensity());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity());
|
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity());
|
||||||
|
@ -296,7 +304,6 @@ int EntityItem::expectedBytes() {
|
||||||
|
|
||||||
|
|
||||||
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
||||||
bool wantDebug = false;
|
|
||||||
|
|
||||||
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
|
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
|
||||||
|
|
||||||
|
@ -361,17 +368,21 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
_created = createdFromBuffer;
|
_created = createdFromBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
quint64 lastEdited = getLastEdited();
|
quint64 lastEdited = getLastEdited();
|
||||||
float editedAgo = getEditedAgo();
|
float editedAgo = getEditedAgo();
|
||||||
QString agoAsString = formatSecondsElapsed(editedAgo);
|
QString agoAsString = formatSecondsElapsed(editedAgo);
|
||||||
QString ageAsString = formatSecondsElapsed(getAge());
|
QString ageAsString = formatSecondsElapsed(getAge());
|
||||||
|
qDebug() << "------------------------------------------";
|
||||||
qDebug() << "Loading entity " << getEntityItemID() << " from buffer...";
|
qDebug() << "Loading entity " << getEntityItemID() << " from buffer...";
|
||||||
|
qDebug() << "------------------------------------------";
|
||||||
|
debugDump();
|
||||||
|
qDebug() << "------------------------------------------";
|
||||||
qDebug() << " _created =" << _created;
|
qDebug() << " _created =" << _created;
|
||||||
qDebug() << " age=" << getAge() << "seconds - " << ageAsString;
|
qDebug() << " age=" << getAge() << "seconds - " << ageAsString;
|
||||||
qDebug() << " lastEdited =" << lastEdited;
|
qDebug() << " lastEdited =" << lastEdited;
|
||||||
qDebug() << " ago=" << editedAgo << "seconds - " << agoAsString;
|
qDebug() << " ago=" << editedAgo << "seconds - " << agoAsString;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
quint64 lastEditedFromBuffer = 0;
|
quint64 lastEditedFromBuffer = 0;
|
||||||
quint64 lastEditedFromBufferAdjusted = 0;
|
quint64 lastEditedFromBufferAdjusted = 0;
|
||||||
|
@ -388,20 +399,18 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
|
|
||||||
bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);
|
bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);
|
||||||
|
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << "data from server **************** ";
|
qDebug() << "data from server **************** ";
|
||||||
qDebug() << " entityItemID=" << getEntityItemID();
|
qDebug() << " entityItemID:" << getEntityItemID();
|
||||||
qDebug() << " now=" << now;
|
qDebug() << " now:" << now;
|
||||||
qDebug() << " getLastEdited()=" << getLastEdited();
|
qDebug() << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||||
qDebug() << " lastEditedFromBuffer=" << lastEditedFromBuffer << " (BEFORE clockskew adjust)";
|
qDebug() << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now);
|
||||||
qDebug() << " clockSkew=" << clockSkew;
|
qDebug() << " clockSkew:" << debugTimeOnly(clockSkew);
|
||||||
qDebug() << " lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted << " (AFTER clockskew adjust)";
|
qDebug() << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now);
|
||||||
qDebug() << " _lastEditedFromRemote=" << _lastEditedFromRemote
|
qDebug() << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now);
|
||||||
<< " (our local time the last server edit we accepted)";
|
qDebug() << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now);
|
||||||
qDebug() << " _lastEditedFromRemoteInRemoteTime=" << _lastEditedFromRemoteInRemoteTime
|
qDebug() << " fromSameServerEdit:" << fromSameServerEdit;
|
||||||
<< " (remote time the last server edit we accepted)";
|
#endif
|
||||||
qDebug() << " fromSameServerEdit=" << fromSameServerEdit;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ignoreServerPacket = false; // assume we'll use this server packet
|
bool ignoreServerPacket = false; // assume we'll use this server packet
|
||||||
|
|
||||||
|
@ -424,14 +433,16 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
|
|
||||||
if (ignoreServerPacket) {
|
if (ignoreServerPacket) {
|
||||||
overwriteLocalData = false;
|
overwriteLocalData = false;
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << "IGNORING old data from server!!! ****************";
|
qDebug() << "IGNORING old data from server!!! ****************";
|
||||||
}
|
debugDump();
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << "USING NEW data from server!!! ****************";
|
qDebug() << "USING NEW data from server!!! ****************";
|
||||||
}
|
debugDump();
|
||||||
|
#endif
|
||||||
|
|
||||||
// don't allow _lastEdited to be in the future
|
// don't allow _lastEdited to be in the future
|
||||||
_lastEdited = lastEditedFromBufferAdjusted;
|
_lastEdited = lastEditedFromBufferAdjusted;
|
||||||
|
@ -449,16 +460,45 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
quint64 updateDelta = updateDeltaCoder;
|
quint64 updateDelta = updateDeltaCoder;
|
||||||
if (overwriteLocalData) {
|
if (overwriteLocalData) {
|
||||||
_lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that
|
_lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << "_lastUpdated =" << _lastUpdated;
|
qDebug() << " _lastUpdated:" << debugTime(_lastUpdated, now);
|
||||||
qDebug() << "_lastEdited=" << _lastEdited;
|
qDebug() << " _lastEdited:" << debugTime(_lastEdited, now);
|
||||||
qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted;
|
qDebug() << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now);
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
encodedUpdateDelta = updateDeltaCoder; // determine true length
|
encodedUpdateDelta = updateDeltaCoder; // determine true length
|
||||||
dataAt += encodedUpdateDelta.size();
|
dataAt += encodedUpdateDelta.size();
|
||||||
bytesRead += encodedUpdateDelta.size();
|
bytesRead += encodedUpdateDelta.size();
|
||||||
|
|
||||||
|
// Newer bitstreams will have a last simulated and a last updated value
|
||||||
|
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) {
|
||||||
|
// last simulated is stored as ByteCountCoded delta from lastEdited
|
||||||
|
QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size
|
||||||
|
ByteCountCoded<quint64> simulatedDeltaCoder = encodedSimulatedDelta;
|
||||||
|
quint64 simulatedDelta = simulatedDeltaCoder;
|
||||||
|
if (overwriteLocalData) {
|
||||||
|
_lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that
|
||||||
|
#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
|
// Property Flags
|
||||||
QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size
|
QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size
|
||||||
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
|
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
|
||||||
|
@ -477,21 +517,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
if (overwriteLocalData) {
|
if (overwriteLocalData) {
|
||||||
setRadius(fromBuffer);
|
setRadius(fromBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wantDebug) {
|
|
||||||
qDebug() << " readEntityDataFromBuffer() OLD FORMAT... found PROP_RADIUS";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions);
|
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_QUAT_SETTER(PROP_ROTATION, updateRotation);
|
||||||
|
@ -510,17 +538,22 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked);
|
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked);
|
||||||
READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA,setUserData);
|
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);
|
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
|
||||||
|
|
||||||
recalculateCollisionShape();
|
recalculateCollisionShape();
|
||||||
if (overwriteLocalData && (getDirtyFlags() & EntityItem::DIRTY_POSITION)) {
|
if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY))) {
|
||||||
|
// 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;
|
_lastSimulated = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -546,13 +579,12 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s
|
||||||
memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime));
|
memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime));
|
||||||
quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew;
|
quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew;
|
||||||
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
|
memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime));
|
||||||
const bool wantDebug = false;
|
#ifdef WANT_DEBUG
|
||||||
if (wantDebug) {
|
|
||||||
qDebug("EntityItem::adjustEditPacketForClockSkew()...");
|
qDebug("EntityItem::adjustEditPacketForClockSkew()...");
|
||||||
qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime;
|
qDebug() << " lastEditedInLocalTime: " << lastEditedInLocalTime;
|
||||||
qDebug() << " clockSkew: " << clockSkew;
|
qDebug() << " clockSkew: " << clockSkew;
|
||||||
qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime;
|
qDebug() << " lastEditedInServerTime: " << lastEditedInServerTime;
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
float EntityItem::computeMass() const {
|
float EntityItem::computeMass() const {
|
||||||
|
@ -607,20 +639,13 @@ bool EntityItem::isRestingOnSurface() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::simulate(const quint64& now) {
|
void EntityItem::simulate(const quint64& now) {
|
||||||
if (_physicsInfo) {
|
|
||||||
// we rely on bullet for simulation, so bail
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wantDebug = false;
|
|
||||||
|
|
||||||
if (_lastSimulated == 0) {
|
if (_lastSimulated == 0) {
|
||||||
_lastSimulated = now;
|
_lastSimulated = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
float timeElapsed = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND);
|
float timeElapsed = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND);
|
||||||
|
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << "********** EntityItem::simulate()";
|
qDebug() << "********** EntityItem::simulate()";
|
||||||
qDebug() << " entity ID=" << getEntityItemID();
|
qDebug() << " entity ID=" << getEntityItemID();
|
||||||
qDebug() << " now=" << now;
|
qDebug() << " now=" << now;
|
||||||
|
@ -655,23 +680,23 @@ void EntityItem::simulate(const quint64& now) {
|
||||||
qDebug() << " getAge()=" << getAge();
|
qDebug() << " getAge()=" << getAge();
|
||||||
qDebug() << " getLifetime()=" << getLifetime();
|
qDebug() << " getLifetime()=" << getLifetime();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (wantDebug) {
|
|
||||||
qDebug() << " ********** EntityItem::simulate() .... SETTING _lastSimulated=" << _lastSimulated;
|
qDebug() << " ********** EntityItem::simulate() .... SETTING _lastSimulated=" << _lastSimulated;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
|
simulateKinematicMotion(timeElapsed);
|
||||||
|
_lastSimulated = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityItem::simulateKinematicMotion(float timeElapsed) {
|
||||||
if (hasAngularVelocity()) {
|
if (hasAngularVelocity()) {
|
||||||
glm::quat rotation = getRotation();
|
|
||||||
|
|
||||||
// angular damping
|
// angular damping
|
||||||
glm::vec3 angularVelocity = getAngularVelocity();
|
glm::vec3 angularVelocity = getAngularVelocity();
|
||||||
if (_angularDamping > 0.0f) {
|
if (_angularDamping > 0.0f) {
|
||||||
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
|
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << " angularDamping :" << _angularDamping;
|
qDebug() << " angularDamping :" << _angularDamping;
|
||||||
qDebug() << " newAngularVelocity:" << angularVelocity;
|
qDebug() << " newAngularVelocity:" << angularVelocity;
|
||||||
}
|
#endif
|
||||||
setAngularVelocity(angularVelocity);
|
setAngularVelocity(angularVelocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,6 +704,9 @@ void EntityItem::simulate(const quint64& now) {
|
||||||
|
|
||||||
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
|
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
|
||||||
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
|
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
|
||||||
|
if (angularSpeed > 0.0f) {
|
||||||
|
_dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
|
||||||
|
}
|
||||||
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
|
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
|
||||||
} else {
|
} else {
|
||||||
// NOTE: angularSpeed is currently in degrees/sec!!!
|
// NOTE: angularSpeed is currently in degrees/sec!!!
|
||||||
|
@ -686,7 +714,7 @@ void EntityItem::simulate(const quint64& now) {
|
||||||
float angle = timeElapsed * glm::radians(angularSpeed);
|
float angle = timeElapsed * glm::radians(angularSpeed);
|
||||||
glm::vec3 axis = _angularVelocity / angularSpeed;
|
glm::vec3 axis = _angularVelocity / angularSpeed;
|
||||||
glm::quat dQ = glm::angleAxis(angle, axis);
|
glm::quat dQ = glm::angleAxis(angle, axis);
|
||||||
rotation = glm::normalize(dQ * rotation);
|
glm::quat rotation = glm::normalize(dQ * getRotation());
|
||||||
setRotation(rotation);
|
setRotation(rotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -696,19 +724,19 @@ void EntityItem::simulate(const quint64& now) {
|
||||||
glm::vec3 velocity = getVelocity();
|
glm::vec3 velocity = getVelocity();
|
||||||
if (_damping > 0.0f) {
|
if (_damping > 0.0f) {
|
||||||
velocity *= powf(1.0f - _damping, timeElapsed);
|
velocity *= powf(1.0f - _damping, timeElapsed);
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << " damping:" << _damping;
|
qDebug() << " damping:" << _damping;
|
||||||
qDebug() << " velocity AFTER dampingResistance:" << velocity;
|
qDebug() << " velocity AFTER dampingResistance:" << velocity;
|
||||||
qDebug() << " glm::length(velocity):" << glm::length(velocity);
|
qDebug() << " glm::length(velocity):" << glm::length(velocity);
|
||||||
qDebug() << " velocityEspilon :" << ENTITY_ITEM_EPSILON_VELOCITY_LENGTH;
|
qDebug() << " velocityEspilon :" << ENTITY_ITEM_EPSILON_VELOCITY_LENGTH;
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// integrate position forward
|
// integrate position forward
|
||||||
glm::vec3 position = getPosition();
|
glm::vec3 position = getPosition();
|
||||||
glm::vec3 newPosition = position + (velocity * timeElapsed);
|
glm::vec3 newPosition = position + (velocity * timeElapsed);
|
||||||
|
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
qDebug() << " EntityItem::simulate()....";
|
qDebug() << " EntityItem::simulate()....";
|
||||||
qDebug() << " timeElapsed:" << timeElapsed;
|
qDebug() << " timeElapsed:" << timeElapsed;
|
||||||
qDebug() << " old AACube:" << getMaximumAACube();
|
qDebug() << " old AACube:" << getMaximumAACube();
|
||||||
|
@ -718,91 +746,35 @@ void EntityItem::simulate(const quint64& now) {
|
||||||
qDebug() << " getDistanceToBottomOfEntity():" << getDistanceToBottomOfEntity() * (float)TREE_SCALE << " in meters";
|
qDebug() << " getDistanceToBottomOfEntity():" << getDistanceToBottomOfEntity() * (float)TREE_SCALE << " in meters";
|
||||||
qDebug() << " newPosition:" << newPosition;
|
qDebug() << " newPosition:" << newPosition;
|
||||||
qDebug() << " glm::distance(newPosition, position):" << glm::distance(newPosition, position);
|
qDebug() << " glm::distance(newPosition, position):" << glm::distance(newPosition, position);
|
||||||
}
|
#endif
|
||||||
|
|
||||||
position = newPosition;
|
position = newPosition;
|
||||||
|
|
||||||
// handle bounces off the ground... We bounce at the distance to the bottom of our entity
|
|
||||||
if (position.y <= getDistanceToBottomOfEntity()) {
|
|
||||||
velocity = velocity * glm::vec3(1,-1,1);
|
|
||||||
position.y = getDistanceToBottomOfEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply gravity
|
// apply gravity
|
||||||
if (hasGravity()) {
|
if (hasGravity()) {
|
||||||
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
|
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
|
||||||
// "ground" plane of the domain, but for now it's what we've got
|
// "ground" plane of the domain, but for now it's what we've got
|
||||||
if (isRestingOnSurface()) {
|
|
||||||
velocity.y = 0.0f;
|
|
||||||
position.y = getDistanceToBottomOfEntity();
|
|
||||||
} else {
|
|
||||||
velocity += getGravity() * timeElapsed;
|
velocity += getGravity() * timeElapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float speed = glm::length(velocity);
|
||||||
|
const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; // 1mm/sec
|
||||||
|
if (speed < EPSILON_LINEAR_VELOCITY_LENGTH) {
|
||||||
|
setVelocity(ENTITY_ITEM_ZERO_VEC3);
|
||||||
|
if (speed > 0.0f) {
|
||||||
|
_dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setPosition(position);
|
||||||
|
setVelocity(velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: we don't zero out very small velocities --> we rely on a remote Bullet simulation
|
#ifdef WANT_DEBUG
|
||||||
// to tell us when the entity has stopped.
|
|
||||||
|
|
||||||
// NOTE: the simulation should NOT set any DirtyFlags on this entity
|
|
||||||
setPosition(position); // this will automatically recalculate our collision shape
|
|
||||||
setVelocity(velocity);
|
|
||||||
|
|
||||||
if (wantDebug) {
|
|
||||||
qDebug() << " new position:" << position;
|
qDebug() << " new position:" << position;
|
||||||
qDebug() << " new velocity:" << velocity;
|
qDebug() << " new velocity:" << velocity;
|
||||||
qDebug() << " new AACube:" << getMaximumAACube();
|
qDebug() << " new AACube:" << getMaximumAACube();
|
||||||
qDebug() << " old getAABox:" << getAABox();
|
qDebug() << " old getAABox:" << getAABox();
|
||||||
}
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
_lastSimulated = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EntityItem::simulateSimpleKinematicMotion(float timeElapsed) {
|
|
||||||
if (hasAngularVelocity()) {
|
|
||||||
// angular damping
|
|
||||||
glm::vec3 angularVelocity = getAngularVelocity();
|
|
||||||
if (_angularDamping > 0.0f) {
|
|
||||||
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
|
|
||||||
setAngularVelocity(angularVelocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
float angularSpeed = glm::length(_angularVelocity);
|
|
||||||
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; //
|
|
||||||
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
|
|
||||||
setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
|
|
||||||
} else {
|
|
||||||
// NOTE: angularSpeed is currently in degrees/sec!!!
|
|
||||||
// TODO: Andrew to convert to radians/sec
|
|
||||||
float angle = timeElapsed * glm::radians(angularSpeed);
|
|
||||||
glm::vec3 axis = _angularVelocity / angularSpeed;
|
|
||||||
glm::quat dQ = glm::angleAxis(angle, axis);
|
|
||||||
glm::quat rotation = getRotation();
|
|
||||||
rotation = glm::normalize(dQ * rotation);
|
|
||||||
setRotation(rotation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasVelocity()) {
|
|
||||||
// linear damping
|
|
||||||
glm::vec3 velocity = getVelocity();
|
|
||||||
if (_damping > 0.0f) {
|
|
||||||
velocity *= powf(1.0f - _damping, timeElapsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// integrate position forward
|
|
||||||
glm::vec3 position = getPosition() + (velocity * timeElapsed);
|
|
||||||
|
|
||||||
// apply gravity
|
|
||||||
if (hasGravity()) {
|
|
||||||
// handle resting on surface case, this is definitely a bit of a hack, and it only works on the
|
|
||||||
// "ground" plane of the domain, but for now it's what we've got
|
|
||||||
velocity += getGravity() * timeElapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: the simulation should NOT set any DirtyFlags on this entity
|
|
||||||
setPosition(position); // this will automatically recalculate our collision shape
|
|
||||||
setVelocity(velocity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -876,17 +848,17 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
||||||
|
|
||||||
if (somethingChanged) {
|
if (somethingChanged) {
|
||||||
somethingChangedNotification(); // notify derived classes that something has changed
|
somethingChangedNotification(); // notify derived classes that something has changed
|
||||||
bool wantDebug = false;
|
|
||||||
uint64_t now = usecTimestampNow();
|
uint64_t now = usecTimestampNow();
|
||||||
if (wantDebug) {
|
#ifdef WANT_DEBUG
|
||||||
int elapsed = now - getLastEdited();
|
int elapsed = now - getLastEdited();
|
||||||
qDebug() << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
qDebug() << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||||
}
|
#endif
|
||||||
if (_created != UNKNOWN_CREATED_TIME) {
|
if (_created != UNKNOWN_CREATED_TIME) {
|
||||||
setLastEdited(now);
|
setLastEdited(now);
|
||||||
}
|
}
|
||||||
if (getDirtyFlags() & EntityItem::DIRTY_POSITION) {
|
if (getDirtyFlags() & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) {
|
||||||
|
// TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases.
|
||||||
_lastSimulated = now;
|
_lastSimulated = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1023,15 +995,6 @@ void EntityItem::setRadius(float value) {
|
||||||
float diameter = value * 2.0f;
|
float diameter = value * 2.0f;
|
||||||
float maxDimension = sqrt((diameter * diameter) / 3.0f);
|
float maxDimension = sqrt((diameter * diameter) / 3.0f);
|
||||||
_dimensions = glm::vec3(maxDimension, maxDimension, maxDimension);
|
_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...
|
// TODO: get rid of all users of this function...
|
||||||
|
|
|
@ -36,13 +36,16 @@ class EntityTreeElementExtraEncodeData;
|
||||||
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
|
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
|
||||||
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { };
|
#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
|
/// 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
|
/// 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.
|
/// one directly, instead you must only construct one of it's derived classes with additional features.
|
||||||
class EntityItem {
|
class EntityItem {
|
||||||
friend class EntityTreeElement;
|
friend class EntityTreeElement;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum EntityDirtyFlags {
|
enum EntityDirtyFlags {
|
||||||
DIRTY_POSITION = 0x0001,
|
DIRTY_POSITION = 0x0001,
|
||||||
|
@ -82,6 +85,7 @@ public:
|
||||||
|
|
||||||
void recordCreationTime(); // set _created to 'now'
|
void recordCreationTime(); // set _created to 'now'
|
||||||
quint64 getLastSimulated() const { return _lastSimulated; } /// Last simulated time of this entity universal usecs
|
quint64 getLastSimulated() const { return _lastSimulated; } /// Last simulated time of this entity universal usecs
|
||||||
|
void setLastSimulated(quint64 now) { _lastSimulated = now; }
|
||||||
|
|
||||||
/// Last edited time of this entity universal usecs
|
/// Last edited time of this entity universal usecs
|
||||||
quint64 getLastEdited() const { return _lastEdited; }
|
quint64 getLastEdited() const { return _lastEdited; }
|
||||||
|
@ -125,11 +129,11 @@ public:
|
||||||
|
|
||||||
// perform update
|
// perform update
|
||||||
virtual void update(const quint64& now) { _lastUpdated = now; }
|
virtual void update(const quint64& now) { _lastUpdated = now; }
|
||||||
|
quint64 getLastUpdated() const { return _lastUpdated; }
|
||||||
|
|
||||||
// perform linear extrapolation for SimpleEntitySimulation
|
// perform linear extrapolation for SimpleEntitySimulation
|
||||||
void simulate(const quint64& now);
|
void simulate(const quint64& now);
|
||||||
|
void simulateKinematicMotion(float timeElapsed);
|
||||||
void simulateSimpleKinematicMotion(float timeElapsed);
|
|
||||||
|
|
||||||
virtual bool needsToCallUpdate() const { return false; }
|
virtual bool needsToCallUpdate() const { return false; }
|
||||||
|
|
||||||
|
@ -287,8 +291,15 @@ public:
|
||||||
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
||||||
|
|
||||||
EntityTreeElement* getElement() const { return _element; }
|
EntityTreeElement* getElement() const { return _element; }
|
||||||
|
|
||||||
|
static void setSendPhysicsUpdates(bool value) { _sendPhysicsUpdates = value; }
|
||||||
|
static bool getSendPhysicsUpdates() { return _sendPhysicsUpdates; }
|
||||||
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
static bool _sendPhysicsUpdates;
|
||||||
|
|
||||||
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
|
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
|
||||||
virtual void recalculateCollisionShape();
|
virtual void recalculateCollisionShape();
|
||||||
|
|
||||||
|
@ -296,9 +307,10 @@ protected:
|
||||||
QUuid _id;
|
QUuid _id;
|
||||||
uint32_t _creatorTokenID;
|
uint32_t _creatorTokenID;
|
||||||
bool _newlyCreated;
|
bool _newlyCreated;
|
||||||
quint64 _lastSimulated; // last time this entity called simulate()
|
quint64 _lastSimulated; // last time this entity called simulate(), this includes velocity, angular velocity, and physics changes
|
||||||
quint64 _lastUpdated; // last time this entity called update()
|
quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes
|
||||||
quint64 _lastEdited; // last official local or remote edit time
|
quint64 _lastEdited; // last official local or remote edit time
|
||||||
|
|
||||||
quint64 _lastEditedFromRemote; // last time we received and edit from the server
|
quint64 _lastEditedFromRemote; // last time we received and edit from the server
|
||||||
quint64 _lastEditedFromRemoteInRemoteTime; // last time we received and edit from the server (in server-time-frame)
|
quint64 _lastEditedFromRemoteInRemoteTime; // last time we received and edit from the server (in server-time-frame)
|
||||||
quint64 _created;
|
quint64 _created;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "LightEntityItem.h"
|
#include "LightEntityItem.h"
|
||||||
#include "ModelEntityItem.h"
|
#include "ModelEntityItem.h"
|
||||||
|
|
||||||
|
|
||||||
EntityScriptingInterface::EntityScriptingInterface() :
|
EntityScriptingInterface::EntityScriptingInterface() :
|
||||||
_nextCreatorTokenID(0),
|
_nextCreatorTokenID(0),
|
||||||
_entityTree(NULL)
|
_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 we have a local entity tree set, then also update it.
|
||||||
if (_entityTree) {
|
if (_entityTree) {
|
||||||
_entityTree->lockForWrite();
|
_entityTree->lockForWrite();
|
||||||
|
|
||||||
|
EntityItem* entity = const_cast<EntityItem*>(_entityTree->findEntityByEntityItemID(actualID));
|
||||||
|
if (entity) {
|
||||||
|
if (entity->getLocked()) {
|
||||||
|
shouldDelete = false;
|
||||||
|
} else {
|
||||||
_entityTree->deleteEntity(entityID);
|
_entityTree->deleteEntity(entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_entityTree->unlock();
|
_entityTree->unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if at this point, we know the id, send the update to the entity server
|
// if at this point, we know the id, and we should still delete the entity, send the update to the entity server
|
||||||
if (entityID.isKnownID) {
|
if (shouldDelete && entityID.isKnownID) {
|
||||||
getEntityPacketSender()->queueEraseEntityMessage(entityID);
|
getEntityPacketSender()->queueEraseEntityMessage(entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,6 +246,14 @@ bool EntityScriptingInterface::getLightsArePickable() const {
|
||||||
return LightEntityItem::getLightsArePickable();
|
return LightEntityItem::getLightsArePickable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityScriptingInterface::setSendPhysicsUpdates(bool value) {
|
||||||
|
EntityItem::setSendPhysicsUpdates(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntityScriptingInterface::getSendPhysicsUpdates() const {
|
||||||
|
return EntityItem::getSendPhysicsUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
|
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
|
||||||
intersects(false),
|
intersects(false),
|
||||||
|
|
|
@ -99,6 +99,9 @@ public slots:
|
||||||
Q_INVOKABLE void setLightsArePickable(bool value);
|
Q_INVOKABLE void setLightsArePickable(bool value);
|
||||||
Q_INVOKABLE bool getLightsArePickable() const;
|
Q_INVOKABLE bool getLightsArePickable() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void setSendPhysicsUpdates(bool value);
|
||||||
|
Q_INVOKABLE bool getSendPhysicsUpdates() const;
|
||||||
|
|
||||||
Q_INVOKABLE void dumpTree() const;
|
Q_INVOKABLE void dumpTree() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
@ -229,6 +229,23 @@ void EntityTree::setSimulation(EntitySimulation* simulation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTree::deleteEntity(const EntityItemID& entityID) {
|
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);
|
emit deletingEntity(entityID);
|
||||||
|
|
||||||
// NOTE: callers must lock the tree before using this method
|
// NOTE: callers must lock the tree before using this method
|
||||||
|
@ -242,14 +259,33 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs) {
|
||||||
// NOTE: callers must lock the tree before using this method
|
// NOTE: callers must lock the tree before using this method
|
||||||
DeleteEntityOperator theOperator(this);
|
DeleteEntityOperator theOperator(this);
|
||||||
foreach(const EntityItemID& entityID, entityIDs) {
|
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
|
// tell our delete operator about this entityID
|
||||||
theOperator.addEntityIDToDeleteList(entityID);
|
theOperator.addEntityIDToDeleteList(entityID);
|
||||||
emit deletingEntity(entityID);
|
emit deletingEntity(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (theOperator.getEntities().size() > 0) {
|
||||||
recurseTreeWithOperator(&theOperator);
|
recurseTreeWithOperator(&theOperator);
|
||||||
processRemovedEntities(theOperator);
|
processRemovedEntities(theOperator);
|
||||||
_isDirty = true;
|
_isDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) {
|
void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) {
|
||||||
|
|
|
@ -60,7 +60,8 @@ public:
|
||||||
// own definition. Implement these to allow your octree based server to support editing
|
// own definition. Implement these to allow your octree based server to support editing
|
||||||
virtual bool getWantSVOfileVersions() const { return true; }
|
virtual bool getWantSVOfileVersions() const { return true; }
|
||||||
virtual PacketType expectedDataPacketType() const { return PacketTypeEntityData; }
|
virtual PacketType expectedDataPacketType() const { return PacketTypeEntityData; }
|
||||||
virtual bool canProcessVersion(PacketVersion thisVersion) const { return true; } // we support all versions
|
virtual bool canProcessVersion(PacketVersion thisVersion) const
|
||||||
|
{ return thisVersion >= VERSION_ENTITIES_SUPPORT_SPLIT_MTU; } // we support all versions with split mtu
|
||||||
virtual bool handlesEditPacketType(PacketType packetType) const;
|
virtual bool handlesEditPacketType(PacketType packetType) const;
|
||||||
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
|
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
|
||||||
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
|
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
|
||||||
|
|
|
@ -81,17 +81,6 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||||
return somethingChanged;
|
return somethingChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int ModelEntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
|
||||||
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
|
|
||||||
return oldVersionReadEntityDataFromBuffer(data, bytesLeftToRead, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// let our base class do most of the work... it will call us back for our porition...
|
|
||||||
return EntityItem::readEntityDataFromBuffer(data, bytesLeftToRead, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
ReadBitstreamToTreeParams& args,
|
ReadBitstreamToTreeParams& args,
|
||||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {
|
||||||
|
@ -131,122 +120,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
||||||
return bytesRead;
|
return bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ModelEntityItem::oldVersionReadEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
|
||||||
|
|
||||||
int bytesRead = 0;
|
|
||||||
if (bytesLeftToRead >= expectedBytes()) {
|
|
||||||
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
|
|
||||||
|
|
||||||
const unsigned char* dataAt = data;
|
|
||||||
|
|
||||||
// id
|
|
||||||
// this old bitstream format had 32bit IDs. They are obsolete and need to be replaced with our new UUID
|
|
||||||
// format. We can simply read and ignore the old ID since they should not be repeated. This code should only
|
|
||||||
// run on loading from an old file.
|
|
||||||
quint32 oldID;
|
|
||||||
memcpy(&oldID, dataAt, sizeof(oldID));
|
|
||||||
dataAt += sizeof(oldID);
|
|
||||||
bytesRead += sizeof(oldID);
|
|
||||||
_id = QUuid::createUuid();
|
|
||||||
|
|
||||||
// _lastUpdated
|
|
||||||
memcpy(&_lastUpdated, dataAt, sizeof(_lastUpdated));
|
|
||||||
dataAt += sizeof(_lastUpdated);
|
|
||||||
bytesRead += sizeof(_lastUpdated);
|
|
||||||
_lastUpdated -= clockSkew;
|
|
||||||
|
|
||||||
// _lastEdited
|
|
||||||
memcpy(&_lastEdited, dataAt, sizeof(_lastEdited));
|
|
||||||
dataAt += sizeof(_lastEdited);
|
|
||||||
bytesRead += sizeof(_lastEdited);
|
|
||||||
_lastEdited -= clockSkew;
|
|
||||||
_created = _lastEdited; // NOTE: old models didn't have age or created time, assume their last edit was a create
|
|
||||||
|
|
||||||
QString ageAsString = formatSecondsElapsed(getAge());
|
|
||||||
qDebug() << "Loading old model file, _created = _lastEdited =" << _created
|
|
||||||
<< " age=" << getAge() << "seconds - " << ageAsString
|
|
||||||
<< "old ID=" << oldID << "new ID=" << _id;
|
|
||||||
|
|
||||||
// radius
|
|
||||||
float radius;
|
|
||||||
memcpy(&radius, dataAt, sizeof(radius));
|
|
||||||
dataAt += sizeof(radius);
|
|
||||||
bytesRead += sizeof(radius);
|
|
||||||
setRadius(radius);
|
|
||||||
|
|
||||||
// position
|
|
||||||
memcpy(&_position, dataAt, sizeof(_position));
|
|
||||||
dataAt += sizeof(_position);
|
|
||||||
bytesRead += sizeof(_position);
|
|
||||||
|
|
||||||
// color
|
|
||||||
memcpy(&_color, dataAt, sizeof(_color));
|
|
||||||
dataAt += sizeof(_color);
|
|
||||||
bytesRead += sizeof(_color);
|
|
||||||
|
|
||||||
// TODO: how to handle this? Presumable, this would only ever be true if the model file was saved with
|
|
||||||
// a model being in a shouldBeDeleted state. Which seems unlikely. But if it happens, maybe we should delete the entity after loading?
|
|
||||||
// shouldBeDeleted
|
|
||||||
bool shouldBeDeleted = false;
|
|
||||||
memcpy(&shouldBeDeleted, dataAt, sizeof(shouldBeDeleted));
|
|
||||||
dataAt += sizeof(shouldBeDeleted);
|
|
||||||
bytesRead += sizeof(shouldBeDeleted);
|
|
||||||
if (shouldBeDeleted) {
|
|
||||||
qDebug() << "UNEXPECTED - read shouldBeDeleted=TRUE from an old format file";
|
|
||||||
}
|
|
||||||
|
|
||||||
// modelURL
|
|
||||||
uint16_t modelURLLength;
|
|
||||||
memcpy(&modelURLLength, dataAt, sizeof(modelURLLength));
|
|
||||||
dataAt += sizeof(modelURLLength);
|
|
||||||
bytesRead += sizeof(modelURLLength);
|
|
||||||
QString modelURLString((const char*)dataAt);
|
|
||||||
setModelURL(modelURLString);
|
|
||||||
dataAt += modelURLLength;
|
|
||||||
bytesRead += modelURLLength;
|
|
||||||
|
|
||||||
// rotation
|
|
||||||
int bytes = unpackOrientationQuatFromBytes(dataAt, _rotation);
|
|
||||||
dataAt += bytes;
|
|
||||||
bytesRead += bytes;
|
|
||||||
|
|
||||||
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ANIMATION) {
|
|
||||||
// animationURL
|
|
||||||
uint16_t animationURLLength;
|
|
||||||
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
|
|
||||||
dataAt += sizeof(animationURLLength);
|
|
||||||
bytesRead += sizeof(animationURLLength);
|
|
||||||
QString animationURLString((const char*)dataAt);
|
|
||||||
setAnimationURL(animationURLString);
|
|
||||||
dataAt += animationURLLength;
|
|
||||||
bytesRead += animationURLLength;
|
|
||||||
|
|
||||||
// animationIsPlaying
|
|
||||||
bool animationIsPlaying;
|
|
||||||
memcpy(&animationIsPlaying, dataAt, sizeof(animationIsPlaying));
|
|
||||||
dataAt += sizeof(animationIsPlaying);
|
|
||||||
bytesRead += sizeof(animationIsPlaying);
|
|
||||||
setAnimationIsPlaying(animationIsPlaying);
|
|
||||||
|
|
||||||
// animationFrameIndex
|
|
||||||
float animationFrameIndex;
|
|
||||||
memcpy(&animationFrameIndex, dataAt, sizeof(animationFrameIndex));
|
|
||||||
dataAt += sizeof(animationFrameIndex);
|
|
||||||
bytesRead += sizeof(animationFrameIndex);
|
|
||||||
setAnimationFrameIndex(animationFrameIndex);
|
|
||||||
|
|
||||||
// animationFPS
|
|
||||||
float animationFPS;
|
|
||||||
memcpy(&animationFPS, dataAt, sizeof(animationFPS));
|
|
||||||
dataAt += sizeof(animationFPS);
|
|
||||||
bytesRead += sizeof(animationFPS);
|
|
||||||
setAnimationFPS(animationFPS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||||
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||||
|
|
|
@ -40,7 +40,6 @@ public:
|
||||||
OctreeElement::AppendState& appendState) const;
|
OctreeElement::AppendState& appendState) const;
|
||||||
|
|
||||||
|
|
||||||
virtual int readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
|
|
||||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||||
ReadBitstreamToTreeParams& args,
|
ReadBitstreamToTreeParams& args,
|
||||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
|
EntityPropertyFlags& propertyFlags, bool overwriteLocalData);
|
||||||
|
@ -116,8 +115,6 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/// For reading models from pre V3 bitstreams
|
|
||||||
int oldVersionReadEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
|
|
||||||
bool isAnimatingSomething() const;
|
bool isAnimatingSomething() const;
|
||||||
|
|
||||||
rgbColor _color;
|
rgbColor _color;
|
||||||
|
|
|
@ -132,3 +132,14 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons
|
||||||
}
|
}
|
||||||
return false;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,8 @@ public:
|
||||||
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
|
bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face,
|
||||||
void** intersectedObject, bool precisionPicking) const;
|
void** intersectedObject, bool precisionPicking) const;
|
||||||
|
|
||||||
|
virtual void debugDump() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void recalculateCollisionShape();
|
virtual void recalculateCollisionShape();
|
||||||
|
|
||||||
|
|
|
@ -149,12 +149,13 @@ void SetDataEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects
|
||||||
}
|
}
|
||||||
|
|
||||||
PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius,
|
PaintHeightfieldHeightEdit::PaintHeightfieldHeightEdit(const glm::vec3& position, float radius,
|
||||||
float height, bool set, bool erase) :
|
float height, bool set, bool erase, float granularity) :
|
||||||
position(position),
|
position(position),
|
||||||
radius(radius),
|
radius(radius),
|
||||||
height(height),
|
height(height),
|
||||||
set(set),
|
set(set),
|
||||||
erase(erase) {
|
erase(erase),
|
||||||
|
granularity(granularity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
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);
|
Box(position - extents, position + extents), results);
|
||||||
|
|
||||||
foreach (const SharedObjectPointer& spanner, results) {
|
foreach (const SharedObjectPointer& spanner, results) {
|
||||||
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->paintHeight(position, radius, height, set, erase);
|
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->paintHeight(position, radius,
|
||||||
|
height, set, erase, granularity);
|
||||||
if (newSpanner != spanner) {
|
if (newSpanner != spanner) {
|
||||||
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
|
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
|
||||||
}
|
}
|
||||||
|
@ -179,11 +181,12 @@ MaterialEdit::MaterialEdit(const SharedObjectPointer& material, const QColor& av
|
||||||
}
|
}
|
||||||
|
|
||||||
HeightfieldMaterialSpannerEdit::HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner,
|
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),
|
MaterialEdit(material, averageColor),
|
||||||
spanner(spanner),
|
spanner(spanner),
|
||||||
paint(paint),
|
paint(paint),
|
||||||
voxelize(voxelize) {
|
voxelize(voxelize),
|
||||||
|
granularity(granularity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpannerProjectionFetchVisitor : public SpannerVisitor {
|
class SpannerProjectionFetchVisitor : public SpannerVisitor {
|
||||||
|
@ -250,16 +253,18 @@ void HeightfieldMaterialSpannerEdit::apply(MetavoxelData& data, const WeakShared
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (const SharedObjectPointer& result, results) {
|
foreach (const SharedObjectPointer& result, results) {
|
||||||
Spanner* newResult = static_cast<Spanner*>(result.data())->setMaterial(spanner, material, color, paint, voxelize);
|
Spanner* newResult = static_cast<Spanner*>(result.data())->setMaterial(spanner, material,
|
||||||
|
color, paint, voxelize, granularity);
|
||||||
if (newResult != result) {
|
if (newResult != result) {
|
||||||
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), result, newResult);
|
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),
|
position(position),
|
||||||
radius(radius) {
|
radius(radius),
|
||||||
|
granularity(granularity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FillHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
|
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);
|
Box(position - extents, position + extents), results);
|
||||||
|
|
||||||
foreach (const SharedObjectPointer& spanner, results) {
|
foreach (const SharedObjectPointer& spanner, results) {
|
||||||
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->fillHeight(position, radius);
|
Spanner* newSpanner = static_cast<Spanner*>(spanner.data())->fillHeight(position, radius, granularity);
|
||||||
if (newSpanner != spanner) {
|
if (newSpanner != spanner) {
|
||||||
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
|
data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), spanner, newSpanner);
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,9 +205,10 @@ public:
|
||||||
STREAM float height;
|
STREAM float height;
|
||||||
STREAM bool set;
|
STREAM bool set;
|
||||||
STREAM bool erase;
|
STREAM bool erase;
|
||||||
|
STREAM float granularity;
|
||||||
|
|
||||||
PaintHeightfieldHeightEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f,
|
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;
|
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||||
};
|
};
|
||||||
|
@ -237,10 +238,11 @@ public:
|
||||||
STREAM SharedObjectPointer spanner;
|
STREAM SharedObjectPointer spanner;
|
||||||
STREAM bool paint;
|
STREAM bool paint;
|
||||||
STREAM bool voxelize;
|
STREAM bool voxelize;
|
||||||
|
STREAM float granularity;
|
||||||
|
|
||||||
HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(),
|
HeightfieldMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(),
|
||||||
const SharedObjectPointer& material = 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;
|
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||||
};
|
};
|
||||||
|
@ -255,8 +257,9 @@ public:
|
||||||
|
|
||||||
STREAM glm::vec3 position;
|
STREAM glm::vec3 position;
|
||||||
STREAM float radius;
|
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;
|
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -111,16 +111,16 @@ bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& dire
|
||||||
return _bounds.findRayIntersection(origin, direction, distance);
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Spanner* Spanner::fillHeight(const glm::vec3& position, float radius) {
|
Spanner* Spanner::fillHeight(const glm::vec3& position, float radius, float granularity) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Spanner* Spanner::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
|
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;
|
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,
|
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,
|
const glm::vec3& position, float radius, float height, bool set, bool erase,
|
||||||
float normalizeScale, float normalizeOffset) {
|
float normalizeScale, float normalizeOffset, float granularity) {
|
||||||
if (!_height) {
|
if (!_height) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1813,7 +1813,7 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons
|
||||||
HeightfieldNode* newChild = _children[i]->paintHeight(translation +
|
HeightfieldNode* newChild = _children[i]->paintHeight(translation +
|
||||||
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
|
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
|
||||||
i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation,
|
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 (_children[i] != newChild) {
|
||||||
if (newNode == this) {
|
if (newNode == this) {
|
||||||
newNode = new HeightfieldNode(*this);
|
newNode = new HeightfieldNode(*this);
|
||||||
|
@ -1846,6 +1846,13 @@ HeightfieldNode* HeightfieldNode::paintHeight(const glm::vec3& translation, cons
|
||||||
new HeightfieldStack(stackWidth, newStackContents, newStackMaterials)));
|
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
|
// now apply the actual change
|
||||||
glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(),
|
glm::vec3 start = glm::clamp(glm::floor(center - extents), glm::vec3(),
|
||||||
glm::vec3((float)highestHeightX, 0.0f, (float)highestHeightZ));
|
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,
|
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) {
|
if (!_height) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1911,7 +1918,7 @@ HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const
|
||||||
HeightfieldNode* newChild = _children[i]->fillHeight(translation +
|
HeightfieldNode* newChild = _children[i]->fillHeight(translation +
|
||||||
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
|
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
|
||||||
i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation,
|
i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation,
|
||||||
nextScale, position, radius);
|
nextScale, position, radius, granularity);
|
||||||
if (_children[i] != newChild) {
|
if (_children[i] != newChild) {
|
||||||
if (newNode == this) {
|
if (newNode == this) {
|
||||||
newNode = new HeightfieldNode(*this);
|
newNode = new HeightfieldNode(*this);
|
||||||
|
@ -1927,9 +1934,15 @@ HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const
|
||||||
if (!_stack) {
|
if (!_stack) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
QVector<quint16> newHeightContents = _height->getContents();
|
|
||||||
|
|
||||||
|
// if the granularity is insufficient, we must subdivide
|
||||||
|
QVector<quint16> newHeightContents = _height->getContents();
|
||||||
QVector<StackArray> newStackContents = _stack->getContents();
|
QVector<StackArray> 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 stackWidth = _stack->getWidth();
|
||||||
int stackHeight = newStackContents.size() / stackWidth;
|
int stackHeight = newStackContents.size() / stackWidth;
|
||||||
QVector<SharedObjectPointer> newStackMaterials = _stack->getMaterials();
|
QVector<SharedObjectPointer> 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,
|
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,
|
Spanner* spanner, const SharedObjectPointer& material, const QColor& color, bool paint, bool voxelize,
|
||||||
float normalizeScale, float normalizeOffset) {
|
float normalizeScale, float normalizeOffset, float granularity) {
|
||||||
if (!_height) {
|
if (!_height) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -2109,7 +2122,7 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons
|
||||||
HeightfieldNode* newChild = _children[i]->setMaterial(translation +
|
HeightfieldNode* newChild = _children[i]->setMaterial(translation +
|
||||||
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
|
rotation * glm::vec3(i & X_MAXIMUM_FLAG ? nextScale.x : 0.0f, 0.0f,
|
||||||
i & Y_MAXIMUM_FLAG ? nextScale.z : 0.0f), rotation, nextScale, spanner,
|
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 (_children[i] != newChild) {
|
||||||
if (newNode == this) {
|
if (newNode == this) {
|
||||||
newNode = new HeightfieldNode(*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)),
|
return new HeightfieldNode(HeightfieldHeightPointer(new HeightfieldHeight(heightWidth, newHeightContents)),
|
||||||
_color, _material, HeightfieldStackPointer(new HeightfieldStack(stackWidth, newStackContents, newStackMaterials)));
|
_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<quint16> oldHeightContents = newHeightContents;
|
QVector<quint16> oldHeightContents = newHeightContents;
|
||||||
QVector<StackArray> oldStackContents = newStackContents;
|
QVector<StackArray> oldStackContents = newStackContents;
|
||||||
|
|
||||||
|
@ -3210,6 +3231,210 @@ bool HeightfieldNode::findHeightfieldRayIntersection(const glm::vec3& origin, co
|
||||||
return false;
|
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<quint16>& heightContents,
|
||||||
|
const QVector<StackArray>& 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<SharedObjectPointer> 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<SharedObjectPointer> materialMaterials;
|
||||||
|
if (_material) {
|
||||||
|
materialWidth = _material->getWidth();
|
||||||
|
materialHeight = _material->getContents().size() / materialWidth;
|
||||||
|
materialMaterials = _material->getMaterials();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||||
|
QVector<quint16> childHeightContents(heightWidth * heightHeight);
|
||||||
|
QByteArray childColorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF);
|
||||||
|
QByteArray childMaterialContents(materialWidth * materialHeight, 0);
|
||||||
|
QVector<StackArray> 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() {
|
AbstractHeightfieldNodeRenderer::~AbstractHeightfieldNodeRenderer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3310,7 +3535,8 @@ bool Heightfield::findRayIntersection(const glm::vec3& origin, const glm::vec3&
|
||||||
getScale() * _aspectZ), origin, direction, distance);
|
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
|
// first see if we're going to exceed the range limits
|
||||||
float minimumValue = 1.0f, maximumValue = numeric_limits<quint16>::max();
|
float minimumValue = 1.0f, maximumValue = numeric_limits<quint16>::max();
|
||||||
if (set) {
|
if (set) {
|
||||||
|
@ -3328,19 +3554,19 @@ Spanner* Heightfield::paintHeight(const glm::vec3& position, float radius, float
|
||||||
Heightfield* newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset);
|
Heightfield* newHeightfield = prepareEdit(minimumValue, maximumValue, normalizeScale, normalizeOffset);
|
||||||
newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(newHeightfield->getTranslation(), getRotation(),
|
newHeightfield->setRoot(HeightfieldNodePointer(_root->paintHeight(newHeightfield->getTranslation(), getRotation(),
|
||||||
glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), position, radius, height,
|
glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), position, radius, height,
|
||||||
set, erase, normalizeScale, normalizeOffset)));
|
set, erase, normalizeScale, normalizeOffset, granularity)));
|
||||||
return newHeightfield;
|
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<Heightfield*>(clone(true));
|
Heightfield* newHeightfield = static_cast<Heightfield*>(clone(true));
|
||||||
newHeightfield->setRoot(HeightfieldNodePointer(_root->fillHeight(getTranslation(), getRotation(),
|
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;
|
return newHeightfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
Spanner* Heightfield::setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
|
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
|
// first see if we're going to exceed the range limits, normalizing if necessary
|
||||||
Spanner* spannerData = static_cast<Spanner*>(spanner.data());
|
Spanner* spannerData = static_cast<Spanner*>(spanner.data());
|
||||||
float normalizeScale = 1.0f, normalizeOffset = 0.0f;
|
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(),
|
newHeightfield->setRoot(HeightfieldNodePointer(_root->setMaterial(newHeightfield->getTranslation(), getRotation(),
|
||||||
glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), spannerData,
|
glm::vec3(getScale(), getScale() * newHeightfield->getAspectY(), getScale() * _aspectZ), spannerData,
|
||||||
material, color, paint, voxelize, normalizeScale, normalizeOffset)));
|
material, color, paint, voxelize, normalizeScale, normalizeOffset, granularity)));
|
||||||
return newHeightfield;
|
return newHeightfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,16 +77,17 @@ public:
|
||||||
/// \param set whether to set the height as opposed to raising/lowering it
|
/// \param set whether to set the height as opposed to raising/lowering it
|
||||||
/// \param erase whether to erase height values
|
/// \param erase whether to erase height values
|
||||||
/// \return the modified spanner, or this if no modification was performed
|
/// \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).
|
/// Attempts to fill the spanner's height (adding removing volumetric information).
|
||||||
/// \return the modified spanner, or this if no modification was performed
|
/// \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.
|
/// Attempts to "sculpt" or "paint," etc., with the supplied spanner.
|
||||||
/// \return the modified spanner, or this if no modification was performed
|
/// \return the modified spanner, or this if no modification was performed
|
||||||
virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material,
|
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.
|
/// Checks whether this spanner has its own colors.
|
||||||
virtual bool hasOwnColors() const;
|
virtual bool hasOwnColors() const;
|
||||||
|
@ -696,17 +697,17 @@ public:
|
||||||
|
|
||||||
HeightfieldNode* paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
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,
|
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,
|
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,
|
void getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
||||||
const Box& editBounds, float& minimum, float& maximum) const;
|
const Box& editBounds, float& minimum, float& maximum) const;
|
||||||
|
|
||||||
HeightfieldNode* setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale,
|
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,
|
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 read(HeightfieldStreamState& state);
|
||||||
void write(HeightfieldStreamState& state) const;
|
void write(HeightfieldStreamState& state) const;
|
||||||
|
@ -736,6 +737,8 @@ private:
|
||||||
bool findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findHeightfieldRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
float boundsDistance, float& distance) const;
|
float boundsDistance, float& distance) const;
|
||||||
|
|
||||||
|
HeightfieldNode* subdivide(const QVector<quint16>& heightContents, const QVector<StackArray>& stackContents) const;
|
||||||
|
|
||||||
HeightfieldHeightPointer _height;
|
HeightfieldHeightPointer _height;
|
||||||
HeightfieldColorPointer _color;
|
HeightfieldColorPointer _color;
|
||||||
HeightfieldMaterialPointer _material;
|
HeightfieldMaterialPointer _material;
|
||||||
|
@ -803,12 +806,13 @@ public:
|
||||||
|
|
||||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
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,
|
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 hasOwnColors() const;
|
||||||
virtual bool hasOwnMaterials() const;
|
virtual bool hasOwnMaterials() const;
|
||||||
|
|
|
@ -72,13 +72,13 @@ PacketVersion versionForPacketType(PacketType type) {
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeEntityAddOrEdit:
|
case PacketTypeEntityAddOrEdit:
|
||||||
case PacketTypeEntityData:
|
case PacketTypeEntityData:
|
||||||
return VERSION_ENTITIES_HAVE_USER_DATA;
|
return VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME;
|
||||||
case PacketTypeEntityErase:
|
case PacketTypeEntityErase:
|
||||||
return 2;
|
return 2;
|
||||||
case PacketTypeAudioStreamStats:
|
case PacketTypeAudioStreamStats:
|
||||||
return 1;
|
return 1;
|
||||||
case PacketTypeMetavoxelData:
|
case PacketTypeMetavoxelData:
|
||||||
return 12;
|
return 13;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ const PacketVersion VERSION_ENTITIES_HAS_FILE_BREAKS = VERSION_ENTITIES_SUPPORT_
|
||||||
const PacketVersion VERSION_ENTITIES_SUPPORT_DIMENSIONS = 4;
|
const PacketVersion VERSION_ENTITIES_SUPPORT_DIMENSIONS = 4;
|
||||||
const PacketVersion VERSION_ENTITIES_MODELS_HAVE_ANIMATION_SETTINGS = 5;
|
const PacketVersion VERSION_ENTITIES_MODELS_HAVE_ANIMATION_SETTINGS = 5;
|
||||||
const PacketVersion VERSION_ENTITIES_HAVE_USER_DATA = 6;
|
const PacketVersion VERSION_ENTITIES_HAVE_USER_DATA = 6;
|
||||||
|
const PacketVersion VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME = 7;
|
||||||
const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1;
|
const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1;
|
||||||
|
|
||||||
#endif // hifi_PacketHeaders_h
|
#endif // hifi_PacketHeaders_h
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
#include "BulletUtil.h"
|
#include "BulletUtil.h"
|
||||||
#include "EntityMotionState.h"
|
#include "EntityMotionState.h"
|
||||||
#include "SimpleEntityKinematicController.h"
|
#include "PhysicsEngine.h"
|
||||||
|
|
||||||
|
|
||||||
QSet<EntityItem*>* _outgoingEntityList;
|
QSet<EntityItem*>* _outgoingEntityList;
|
||||||
|
@ -41,8 +41,6 @@ EntityMotionState::~EntityMotionState() {
|
||||||
assert(_entity);
|
assert(_entity);
|
||||||
_entity->setPhysicsInfo(NULL);
|
_entity->setPhysicsInfo(NULL);
|
||||||
_entity = NULL;
|
_entity = NULL;
|
||||||
delete _kinematicController;
|
|
||||||
_kinematicController = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionType EntityMotionState::computeMotionType() const {
|
MotionType EntityMotionState::computeMotionType() const {
|
||||||
|
@ -52,13 +50,16 @@ MotionType EntityMotionState::computeMotionType() const {
|
||||||
return _entity->isMoving() ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC;
|
return _entity->isMoving() ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityMotionState::addKinematicController() {
|
void EntityMotionState::updateKinematicState(uint32_t substep) {
|
||||||
if (!_kinematicController) {
|
setKinematic(_entity->isMoving(), substep);
|
||||||
_kinematicController = new SimpleEntityKinematicController(_entity);
|
}
|
||||||
_kinematicController->start();
|
|
||||||
} else {
|
void EntityMotionState::stepKinematicSimulation(quint64 now) {
|
||||||
_kinematicController->start();
|
assert(_isKinematic);
|
||||||
}
|
// NOTE: this is non-physical kinematic motion which steps to real run-time (now)
|
||||||
|
// which is different from physical kinematic motion (inside getWorldTransform())
|
||||||
|
// which steps in physics simulation time.
|
||||||
|
_entity->simulate(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This callback is invoked by the physics simulation in two cases:
|
// This callback is invoked by the physics simulation in two cases:
|
||||||
|
@ -67,8 +68,16 @@ void EntityMotionState::addKinematicController() {
|
||||||
// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's --
|
// (2) at the beginning of each simulation frame for KINEMATIC RigidBody's --
|
||||||
// it is an opportunity for outside code to update the object's simulation position
|
// it is an opportunity for outside code to update the object's simulation position
|
||||||
void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
|
void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
|
||||||
if (_kinematicController && _kinematicController->isRunning()) {
|
if (_isKinematic) {
|
||||||
_kinematicController->stepForward();
|
// This is physical kinematic motion which steps strictly by the subframe count
|
||||||
|
// of the physics simulation.
|
||||||
|
uint32_t substep = PhysicsEngine::getNumSubsteps();
|
||||||
|
float dt = (substep - _lastKinematicSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||||
|
_entity->simulateKinematicMotion(dt);
|
||||||
|
_entity->setLastSimulated(usecTimestampNow());
|
||||||
|
|
||||||
|
// bypass const-ness so we can remember the substep
|
||||||
|
const_cast<EntityMotionState*>(this)->_lastKinematicSubstep = substep;
|
||||||
}
|
}
|
||||||
worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset()));
|
worldTrans.setOrigin(glmToBullet(_entity->getPositionInMeters() - ObjectMotionState::getWorldOffset()));
|
||||||
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
|
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
|
||||||
|
@ -88,8 +97,18 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||||
// DANGER! EntityItem stores angularVelocity in degrees/sec!!!
|
// DANGER! EntityItem stores angularVelocity in degrees/sec!!!
|
||||||
_entity->setAngularVelocity(glm::degrees(v));
|
_entity->setAngularVelocity(glm::degrees(v));
|
||||||
|
|
||||||
|
_entity->setLastSimulated(usecTimestampNow());
|
||||||
|
|
||||||
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
|
_outgoingPacketFlags = DIRTY_PHYSICS_FLAGS;
|
||||||
EntityMotionState::enqueueOutgoingEntity(_entity);
|
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) {
|
void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) {
|
||||||
|
@ -208,16 +227,34 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
||||||
if (_numNonMovingUpdates <= 1) {
|
if (_numNonMovingUpdates <= 1) {
|
||||||
// we only update lastEdited when we're sending new physics data
|
// we only update lastEdited when we're sending new physics data
|
||||||
// (i.e. NOT when we just simulate the positions forward, nore when we resend non-moving data)
|
// (i.e. NOT when we just simulate the positions forward, nore when we resend non-moving data)
|
||||||
|
// NOTE: Andrew & Brad to discuss. Let's make sure we're using lastEdited, lastSimulated, and lastUpdated correctly
|
||||||
quint64 lastSimulated = _entity->getLastSimulated();
|
quint64 lastSimulated = _entity->getLastSimulated();
|
||||||
_entity->setLastEdited(lastSimulated);
|
_entity->setLastEdited(lastSimulated);
|
||||||
properties.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 {
|
} else {
|
||||||
properties.setLastEdited(_entity->getLastEdited());
|
properties.setLastEdited(_entity->getLastEdited());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (EntityItem::getSendPhysicsUpdates()) {
|
||||||
EntityItemID id(_entity->getID());
|
EntityItemID id(_entity->getID());
|
||||||
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
||||||
|
#ifdef WANT_DEBUG
|
||||||
|
qDebug() << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
|
||||||
|
#endif
|
||||||
entityPacketSender->queueEditEntityMessage(PacketTypeEntityAddOrEdit, id, properties);
|
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
|
// 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.
|
// to the full set. These flags may be momentarily cleared by incoming external changes.
|
||||||
|
@ -229,6 +266,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
||||||
uint32_t EntityMotionState::getIncomingDirtyFlags() const {
|
uint32_t EntityMotionState::getIncomingDirtyFlags() const {
|
||||||
uint32_t dirtyFlags = _entity->getDirtyFlags();
|
uint32_t dirtyFlags = _entity->getDirtyFlags();
|
||||||
|
|
||||||
|
if (_body) {
|
||||||
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
|
// we add DIRTY_MOTION_TYPE if the body's motion type disagrees with entity velocity settings
|
||||||
int bodyFlags = _body->getCollisionFlags();
|
int bodyFlags = _body->getCollisionFlags();
|
||||||
bool isMoving = _entity->isMoving();
|
bool isMoving = _entity->isMoving();
|
||||||
|
@ -236,5 +274,6 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() const {
|
||||||
(bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving)) {
|
(bodyFlags & btCollisionObject::CF_KINEMATIC_OBJECT && !isMoving)) {
|
||||||
dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
|
dirtyFlags |= EntityItem::DIRTY_MOTION_TYPE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return dirtyFlags;
|
return dirtyFlags;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
#include <AACube.h>
|
#include <AACube.h>
|
||||||
|
|
||||||
#include "KinematicController.h"
|
|
||||||
#include "ObjectMotionState.h"
|
#include "ObjectMotionState.h"
|
||||||
|
|
||||||
class EntityItem;
|
class EntityItem;
|
||||||
|
@ -39,8 +38,8 @@ public:
|
||||||
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
|
/// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem
|
||||||
MotionType computeMotionType() const;
|
MotionType computeMotionType() const;
|
||||||
|
|
||||||
// virtual override for ObjectMotionState
|
void updateKinematicState(uint32_t substep);
|
||||||
void addKinematicController();
|
void stepKinematicSimulation(quint64 now);
|
||||||
|
|
||||||
// this relays incoming position/rotation to the RigidBody
|
// this relays incoming position/rotation to the RigidBody
|
||||||
void getWorldTransform(btTransform& worldTrans) const;
|
void getWorldTransform(btTransform& worldTrans) const;
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
//
|
|
||||||
// KinematicController.cpp
|
|
||||||
// libraries/physcis/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows 2015.01.13
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "KinematicController.h"
|
|
||||||
#include "PhysicsEngine.h"
|
|
||||||
|
|
||||||
KinematicController::KinematicController() {
|
|
||||||
_lastSubstep = PhysicsEngine::getNumSubsteps();
|
|
||||||
}
|
|
||||||
|
|
||||||
void KinematicController::start() {
|
|
||||||
_enabled = true;
|
|
||||||
_lastSubstep = PhysicsEngine::getNumSubsteps();
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
//
|
|
||||||
// KinematicController.h
|
|
||||||
// libraries/physcis/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows 2015.01.13
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_KinematicController_h
|
|
||||||
#define hifi_KinematicController_h
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/// KinematicController defines an API for derived classes.
|
|
||||||
|
|
||||||
class KinematicController {
|
|
||||||
public:
|
|
||||||
KinematicController();
|
|
||||||
|
|
||||||
virtual ~KinematicController() {}
|
|
||||||
|
|
||||||
virtual void stepForward() = 0;
|
|
||||||
|
|
||||||
void start();
|
|
||||||
void stop() { _enabled = false; }
|
|
||||||
bool isRunning() const { return _enabled; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool _enabled = false;
|
|
||||||
uint32_t _lastSubstep;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_KinematicController_h
|
|
|
@ -12,7 +12,6 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "BulletUtil.h"
|
#include "BulletUtil.h"
|
||||||
#include "KinematicController.h"
|
|
||||||
#include "ObjectMotionState.h"
|
#include "ObjectMotionState.h"
|
||||||
#include "PhysicsEngine.h"
|
#include "PhysicsEngine.h"
|
||||||
|
|
||||||
|
@ -56,10 +55,6 @@ ObjectMotionState::ObjectMotionState() :
|
||||||
ObjectMotionState::~ObjectMotionState() {
|
ObjectMotionState::~ObjectMotionState() {
|
||||||
// NOTE: you MUST remove this MotionState from the world before you call the dtor.
|
// NOTE: you MUST remove this MotionState from the world before you call the dtor.
|
||||||
assert(_body == NULL);
|
assert(_body == NULL);
|
||||||
if (_kinematicController) {
|
|
||||||
delete _kinematicController;
|
|
||||||
_kinematicController = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectMotionState::setFriction(float friction) {
|
void ObjectMotionState::setFriction(float friction) {
|
||||||
|
@ -108,7 +103,6 @@ bool ObjectMotionState::doesNotNeedToSendUpdate() const {
|
||||||
|
|
||||||
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
||||||
assert(_body);
|
assert(_body);
|
||||||
|
|
||||||
// if we've never checked before, our _sentFrame will be 0, and we need to initialize our state
|
// if we've never checked before, our _sentFrame will be 0, and we need to initialize our state
|
||||||
if (_sentFrame == 0) {
|
if (_sentFrame == 0) {
|
||||||
_sentPosition = bulletToGLM(_body->getWorldTransform().getOrigin());
|
_sentPosition = bulletToGLM(_body->getWorldTransform().getOrigin());
|
||||||
|
@ -118,6 +112,12 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
||||||
return false;
|
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;
|
float dt = (float)(simulationFrame - _sentFrame) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||||
_sentFrame = simulationFrame;
|
_sentFrame = simulationFrame;
|
||||||
bool isActive = _body->isActive();
|
bool isActive = _body->isActive();
|
||||||
|
@ -153,8 +153,18 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
||||||
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
|
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
|
||||||
|
|
||||||
float dx2 = glm::distance2(position, _sentPosition);
|
float dx2 = glm::distance2(position, _sentPosition);
|
||||||
|
|
||||||
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
|
const float MAX_POSITION_ERROR_SQUARED = 0.001f; // 0.001 m^2 ~~> 0.03 m
|
||||||
if (dx2 > MAX_POSITION_ERROR_SQUARED) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,14 +181,24 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) {
|
||||||
}
|
}
|
||||||
const float MIN_ROTATION_DOT = 0.98f;
|
const float MIN_ROTATION_DOT = 0.98f;
|
||||||
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
|
glm::quat actualRotation = bulletToGLM(worldTrans.getRotation());
|
||||||
return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectMotionState::removeKinematicController() {
|
#ifdef WANT_DEBUG
|
||||||
if (_kinematicController) {
|
if ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) {
|
||||||
delete _kinematicController;
|
qDebug() << ".... ((fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT)) ....";
|
||||||
_kinematicController = NULL;
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
||||||
|
@ -193,3 +213,8 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectMotionState::setKinematic(bool kinematic, uint32_t substep) {
|
||||||
|
_isKinematic = kinematic;
|
||||||
|
_lastKinematicSubstep = substep;
|
||||||
|
}
|
||||||
|
|
|
@ -46,7 +46,6 @@ const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_POSITION | Entit
|
||||||
|
|
||||||
|
|
||||||
class OctreeEditPacketSender;
|
class OctreeEditPacketSender;
|
||||||
class KinematicController;
|
|
||||||
|
|
||||||
class ObjectMotionState : public btMotionState {
|
class ObjectMotionState : public btMotionState {
|
||||||
public:
|
public:
|
||||||
|
@ -93,11 +92,15 @@ public:
|
||||||
|
|
||||||
virtual MotionType computeMotionType() const = 0;
|
virtual MotionType computeMotionType() const = 0;
|
||||||
|
|
||||||
virtual void addKinematicController() = 0;
|
virtual void updateKinematicState(uint32_t substep) = 0;
|
||||||
virtual void removeKinematicController();
|
|
||||||
|
|
||||||
btRigidBody* getRigidBody() const { return _body; }
|
btRigidBody* getRigidBody() const { return _body; }
|
||||||
|
|
||||||
|
bool isKinematic() const { return _isKinematic; }
|
||||||
|
|
||||||
|
void setKinematic(bool kinematic, uint32_t substep);
|
||||||
|
virtual void stepKinematicSimulation(quint64 now) = 0;
|
||||||
|
|
||||||
friend class PhysicsEngine;
|
friend class PhysicsEngine;
|
||||||
protected:
|
protected:
|
||||||
void setRigidBody(btRigidBody* body);
|
void setRigidBody(btRigidBody* body);
|
||||||
|
@ -114,6 +117,9 @@ protected:
|
||||||
|
|
||||||
btRigidBody* _body;
|
btRigidBody* _body;
|
||||||
|
|
||||||
|
bool _isKinematic = false;
|
||||||
|
uint32_t _lastKinematicSubstep = 0;
|
||||||
|
|
||||||
bool _sentMoving; // true if last update was moving
|
bool _sentMoving; // true if last update was moving
|
||||||
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
|
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
|
||||||
|
|
||||||
|
@ -124,8 +130,6 @@ protected:
|
||||||
glm::vec3 _sentVelocity;
|
glm::vec3 _sentVelocity;
|
||||||
glm::vec3 _sentAngularVelocity; // radians per second
|
glm::vec3 _sentAngularVelocity; // radians per second
|
||||||
glm::vec3 _sentAcceleration;
|
glm::vec3 _sentAcceleration;
|
||||||
|
|
||||||
KinematicController* _kinematicController = NULL;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ObjectMotionState_h
|
#endif // hifi_ObjectMotionState_h
|
||||||
|
|
|
@ -62,7 +62,13 @@ void PhysicsEngine::addEntityInternal(EntityItem* entity) {
|
||||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||||
_entityMotionStates.insert(motionState);
|
_entityMotionStates.insert(motionState);
|
||||||
addObject(shapeInfo, shape, motionState);
|
addObject(shapeInfo, shape, motionState);
|
||||||
} else {
|
} else if (entity->isMoving()) {
|
||||||
|
EntityMotionState* motionState = new EntityMotionState(entity);
|
||||||
|
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||||
|
_entityMotionStates.insert(motionState);
|
||||||
|
|
||||||
|
motionState->setKinematic(true, _numSubsteps);
|
||||||
|
_nonPhysicalKinematicObjects.insert(motionState);
|
||||||
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
|
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
|
||||||
//qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
|
//qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
|
||||||
}
|
}
|
||||||
|
@ -74,10 +80,16 @@ void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
|
||||||
void* physicsInfo = entity->getPhysicsInfo();
|
void* physicsInfo = entity->getPhysicsInfo();
|
||||||
if (physicsInfo) {
|
if (physicsInfo) {
|
||||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
|
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
|
||||||
|
if (motionState->getRigidBody()) {
|
||||||
removeObject(motionState);
|
removeObject(motionState);
|
||||||
|
} else {
|
||||||
|
// only need to hunt in this list when there is no RigidBody
|
||||||
|
_nonPhysicalKinematicObjects.remove(motionState);
|
||||||
|
}
|
||||||
_entityMotionStates.remove(motionState);
|
_entityMotionStates.remove(motionState);
|
||||||
_incomingChanges.remove(motionState);
|
_incomingChanges.remove(motionState);
|
||||||
_outgoingPackets.remove(motionState);
|
_outgoingPackets.remove(motionState);
|
||||||
|
// NOTE: EntityMotionState dtor will remove its backpointer from EntityItem
|
||||||
delete motionState;
|
delete motionState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,6 +129,7 @@ void PhysicsEngine::clearEntitiesInternal() {
|
||||||
delete (*stateItr);
|
delete (*stateItr);
|
||||||
}
|
}
|
||||||
_entityMotionStates.clear();
|
_entityMotionStates.clear();
|
||||||
|
_nonPhysicalKinematicObjects.clear();
|
||||||
_incomingChanges.clear();
|
_incomingChanges.clear();
|
||||||
_outgoingPackets.clear();
|
_outgoingPackets.clear();
|
||||||
}
|
}
|
||||||
|
@ -127,19 +140,75 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
||||||
QSet<ObjectMotionState*>::iterator stateItr = _incomingChanges.begin();
|
QSet<ObjectMotionState*>::iterator stateItr = _incomingChanges.begin();
|
||||||
while (stateItr != _incomingChanges.end()) {
|
while (stateItr != _incomingChanges.end()) {
|
||||||
ObjectMotionState* motionState = *stateItr;
|
ObjectMotionState* motionState = *stateItr;
|
||||||
|
++stateItr;
|
||||||
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
uint32_t flags = motionState->getIncomingDirtyFlags() & DIRTY_PHYSICS_FLAGS;
|
||||||
|
|
||||||
|
bool removeMotionState = false;
|
||||||
btRigidBody* body = motionState->getRigidBody();
|
btRigidBody* body = motionState->getRigidBody();
|
||||||
if (body) {
|
if (body) {
|
||||||
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
if (flags & HARD_DIRTY_PHYSICS_FLAGS) {
|
||||||
// a HARD update requires the body be pulled out of physics engine, changed, then reinserted
|
// a HARD update requires the body be pulled out of physics engine, changed, then reinserted
|
||||||
// but it also handles all EASY changes
|
// but it also handles all EASY changes
|
||||||
updateObjectHard(body, motionState, flags);
|
bool success = updateObjectHard(body, motionState, flags);
|
||||||
|
if (!success) {
|
||||||
|
// NOTE: since updateObjectHard() failed we know that motionState has been removed
|
||||||
|
// from simulation and body has been deleted. Depending on what else has changed
|
||||||
|
// we might need to remove motionState altogether...
|
||||||
|
if (flags & EntityItem::DIRTY_VELOCITY) {
|
||||||
|
motionState->updateKinematicState(_numSubsteps);
|
||||||
|
if (motionState->isKinematic()) {
|
||||||
|
// all is NOT lost, we still need to move this object around kinematically
|
||||||
|
_nonPhysicalKinematicObjects.insert(motionState);
|
||||||
|
} else {
|
||||||
|
// no need to keep motionState around
|
||||||
|
removeMotionState = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no need to keep motionState around
|
||||||
|
removeMotionState = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (flags) {
|
} else if (flags) {
|
||||||
// an EASY update does NOT require that the body be pulled out of physics engine
|
// an EASY update does NOT require that the body be pulled out of physics engine
|
||||||
// hence the MotionState has all the knowledge and authority to perform the update.
|
// hence the MotionState has all the knowledge and authority to perform the update.
|
||||||
motionState->updateObjectEasy(flags, _numSubsteps);
|
motionState->updateObjectEasy(flags, _numSubsteps);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// the only way we should ever get here (motionState exists but no body) is when the object
|
||||||
|
// is undergoing non-physical kinematic motion.
|
||||||
|
assert(_nonPhysicalKinematicObjects.contains(motionState));
|
||||||
|
|
||||||
|
// it is possible that the changes are such that the object can now be added to the physical simulation
|
||||||
|
if (flags & EntityItem::DIRTY_SHAPE) {
|
||||||
|
ShapeInfo shapeInfo;
|
||||||
|
motionState->computeShapeInfo(shapeInfo);
|
||||||
|
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
|
||||||
|
if (shape) {
|
||||||
|
addObject(shapeInfo, shape, motionState);
|
||||||
|
_nonPhysicalKinematicObjects.remove(motionState);
|
||||||
|
} else if (flags & EntityItem::DIRTY_VELOCITY) {
|
||||||
|
// although we couldn't add the object to the simulation, might need to update kinematic motion...
|
||||||
|
motionState->updateKinematicState(_numSubsteps);
|
||||||
|
if (!motionState->isKinematic()) {
|
||||||
|
_nonPhysicalKinematicObjects.remove(motionState);
|
||||||
|
removeMotionState = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (flags & EntityItem::DIRTY_VELOCITY) {
|
||||||
|
// although we still can't add to physics simulation, might need to update kinematic motion...
|
||||||
|
motionState->updateKinematicState(_numSubsteps);
|
||||||
|
if (!motionState->isKinematic()) {
|
||||||
|
_nonPhysicalKinematicObjects.remove(motionState);
|
||||||
|
removeMotionState = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removeMotionState) {
|
||||||
|
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
|
||||||
|
_outgoingPackets.remove(motionState);
|
||||||
|
// NOTE: motionState will clean up its own backpointers in the Object
|
||||||
|
delete motionState;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: the grand order of operations is:
|
// NOTE: the grand order of operations is:
|
||||||
|
@ -152,7 +221,6 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
||||||
// outgoing changes at this point.
|
// outgoing changes at this point.
|
||||||
motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped
|
motionState->clearOutgoingPacketFlags(flags); // clear outgoing flags that were trumped
|
||||||
motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed
|
motionState->clearIncomingDirtyFlags(flags); // clear incoming flags that were processed
|
||||||
++stateItr;
|
|
||||||
}
|
}
|
||||||
_incomingChanges.clear();
|
_incomingChanges.clear();
|
||||||
}
|
}
|
||||||
|
@ -213,6 +281,7 @@ void PhysicsEngine::stepSimulation() {
|
||||||
// This is step (2).
|
// This is step (2).
|
||||||
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP);
|
||||||
_numSubsteps += (uint32_t)numSubsteps;
|
_numSubsteps += (uint32_t)numSubsteps;
|
||||||
|
stepNonPhysicalKinematics(usecTimestampNow());
|
||||||
unlock();
|
unlock();
|
||||||
|
|
||||||
if (numSubsteps > 0) {
|
if (numSubsteps > 0) {
|
||||||
|
@ -234,6 +303,17 @@ void PhysicsEngine::stepSimulation() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhysicsEngine::stepNonPhysicalKinematics(const quint64& now) {
|
||||||
|
QSet<ObjectMotionState*>::iterator stateItr = _nonPhysicalKinematicObjects.begin();
|
||||||
|
while (stateItr != _nonPhysicalKinematicObjects.end()) {
|
||||||
|
ObjectMotionState* motionState = *stateItr;
|
||||||
|
motionState->stepKinematicSimulation(now);
|
||||||
|
++stateItr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO?: need to occasionally scan for stopped non-physical kinematics objects
|
||||||
|
|
||||||
void PhysicsEngine::computeCollisionEvents() {
|
void PhysicsEngine::computeCollisionEvents() {
|
||||||
// update all contacts every frame
|
// update all contacts every frame
|
||||||
int numManifolds = _collisionDispatcher->getNumManifolds();
|
int numManifolds = _collisionDispatcher->getNumManifolds();
|
||||||
|
@ -332,7 +412,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
||||||
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||||
body->updateInertiaTensor();
|
body->updateInertiaTensor();
|
||||||
motionState->setRigidBody(body);
|
motionState->setRigidBody(body);
|
||||||
motionState->addKinematicController();
|
motionState->setKinematic(true, _numSubsteps);
|
||||||
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
|
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
|
||||||
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
|
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
|
||||||
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
|
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
|
||||||
|
@ -344,6 +424,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
||||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||||
body->updateInertiaTensor();
|
body->updateInertiaTensor();
|
||||||
motionState->setRigidBody(body);
|
motionState->setRigidBody(body);
|
||||||
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
motionState->updateObjectVelocities();
|
motionState->updateObjectVelocities();
|
||||||
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
|
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
|
||||||
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
|
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
|
||||||
|
@ -358,6 +439,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
||||||
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
|
||||||
body->updateInertiaTensor();
|
body->updateInertiaTensor();
|
||||||
motionState->setRigidBody(body);
|
motionState->setRigidBody(body);
|
||||||
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -368,7 +450,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
||||||
_dynamicsWorld->addRigidBody(body);
|
_dynamicsWorld->addRigidBody(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
void PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
||||||
assert(motionState);
|
assert(motionState);
|
||||||
btRigidBody* body = motionState->getRigidBody();
|
btRigidBody* body = motionState->getRigidBody();
|
||||||
if (body) {
|
if (body) {
|
||||||
|
@ -379,16 +461,14 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
||||||
_shapeManager.releaseShape(shapeInfo);
|
_shapeManager.releaseShape(shapeInfo);
|
||||||
delete body;
|
delete body;
|
||||||
motionState->setRigidBody(NULL);
|
motionState->setRigidBody(NULL);
|
||||||
motionState->removeKinematicController();
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
|
|
||||||
removeContacts(motionState);
|
removeContacts(motionState);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private
|
// private
|
||||||
void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
|
bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) {
|
||||||
MotionType newType = motionState->computeMotionType();
|
MotionType newType = motionState->computeMotionType();
|
||||||
|
|
||||||
// pull body out of physics engine
|
// pull body out of physics engine
|
||||||
|
@ -403,7 +483,16 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
||||||
ShapeInfo shapeInfo;
|
ShapeInfo shapeInfo;
|
||||||
motionState->computeShapeInfo(shapeInfo);
|
motionState->computeShapeInfo(shapeInfo);
|
||||||
btCollisionShape* newShape = _shapeManager.getShape(shapeInfo);
|
btCollisionShape* newShape = _shapeManager.getShape(shapeInfo);
|
||||||
if (newShape != oldShape) {
|
if (!newShape) {
|
||||||
|
// FAIL! we are unable to support these changes!
|
||||||
|
_shapeManager.releaseShape(oldShape);
|
||||||
|
|
||||||
|
delete body;
|
||||||
|
motionState->setRigidBody(NULL);
|
||||||
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
|
removeContacts(motionState);
|
||||||
|
return false;
|
||||||
|
} else if (newShape != oldShape) {
|
||||||
// BUG: if shape doesn't change but density does then we won't compute new mass properties
|
// BUG: if shape doesn't change but density does then we won't compute new mass properties
|
||||||
// TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly.
|
// TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly.
|
||||||
body->setCollisionShape(newShape);
|
body->setCollisionShape(newShape);
|
||||||
|
@ -436,7 +525,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
||||||
|
|
||||||
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
|
body->setMassProps(0.0f, btVector3(0.0f, 0.0f, 0.0f));
|
||||||
body->updateInertiaTensor();
|
body->updateInertiaTensor();
|
||||||
motionState->addKinematicController();
|
motionState->setKinematic(true, _numSubsteps);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MOTION_TYPE_DYNAMIC: {
|
case MOTION_TYPE_DYNAMIC: {
|
||||||
|
@ -453,7 +542,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
||||||
body->updateInertiaTensor();
|
body->updateInertiaTensor();
|
||||||
}
|
}
|
||||||
body->forceActivationState(ACTIVE_TAG);
|
body->forceActivationState(ACTIVE_TAG);
|
||||||
motionState->removeKinematicController();
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -468,7 +557,7 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
||||||
|
|
||||||
body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||||
body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f));
|
||||||
motionState->removeKinematicController();
|
motionState->setKinematic(false, _numSubsteps);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -477,4 +566,5 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
||||||
_dynamicsWorld->addRigidBody(body);
|
_dynamicsWorld->addRigidBody(body);
|
||||||
|
|
||||||
body->activate();
|
body->activate();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ public:
|
||||||
virtual void init(EntityEditPacketSender* packetSender);
|
virtual void init(EntityEditPacketSender* packetSender);
|
||||||
|
|
||||||
void stepSimulation();
|
void stepSimulation();
|
||||||
|
void stepNonPhysicalKinematics(const quint64& now);
|
||||||
|
|
||||||
void computeCollisionEvents();
|
void computeCollisionEvents();
|
||||||
|
|
||||||
|
@ -81,15 +82,16 @@ public:
|
||||||
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
|
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
|
||||||
|
|
||||||
/// \param motionState pointer to Object's MotionState
|
/// \param motionState pointer to Object's MotionState
|
||||||
/// \return true if Object removed
|
void removeObject(ObjectMotionState* motionState);
|
||||||
bool removeObject(ObjectMotionState* motionState);
|
|
||||||
|
|
||||||
/// process queue of changed from external sources
|
/// process queue of changed from external sources
|
||||||
void relayIncomingChangesToSimulation();
|
void relayIncomingChangesToSimulation();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void removeContacts(ObjectMotionState* motionState);
|
void removeContacts(ObjectMotionState* motionState);
|
||||||
void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
|
||||||
|
// return 'true' of update was successful
|
||||||
|
bool updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||||
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags);
|
||||||
|
|
||||||
btClock _clock;
|
btClock _clock;
|
||||||
|
@ -104,6 +106,7 @@ private:
|
||||||
|
|
||||||
// EntitySimulation stuff
|
// EntitySimulation stuff
|
||||||
QSet<EntityMotionState*> _entityMotionStates; // all entities that we track
|
QSet<EntityMotionState*> _entityMotionStates; // all entities that we track
|
||||||
|
QSet<ObjectMotionState*> _nonPhysicalKinematicObjects; // not in physics simulation, but still need kinematic simulation
|
||||||
QSet<ObjectMotionState*> _incomingChanges; // entities with pending physics changes by script or packet
|
QSet<ObjectMotionState*> _incomingChanges; // entities with pending physics changes by script or packet
|
||||||
QSet<ObjectMotionState*> _outgoingPackets; // MotionStates with pending changes that need to be sent over wire
|
QSet<ObjectMotionState*> _outgoingPackets; // MotionStates with pending changes that need to be sent over wire
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
//
|
|
||||||
// SimpleEntityKinematicController.cpp
|
|
||||||
// libraries/physcis/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows 2015.01.13
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "PhysicsEngine.h"
|
|
||||||
#include "SimpleEntityKinematicController.h"
|
|
||||||
|
|
||||||
void SimpleEntityKinematicController:: stepForward() {
|
|
||||||
uint32_t substep = PhysicsEngine::getNumSubsteps();
|
|
||||||
float dt = (substep - _lastSubstep) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
|
||||||
_entity->simulateSimpleKinematicMotion(dt);
|
|
||||||
_lastSubstep = substep;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
//
|
|
||||||
// SimpleEntityKinematicController.h
|
|
||||||
// libraries/physcis/src
|
|
||||||
//
|
|
||||||
// Created by Andrew Meadows 2015.01.13
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_SimpleEntityKinematicController_h
|
|
||||||
#define hifi_SimpleEntityKinematicController_h
|
|
||||||
|
|
||||||
/// SimpleKinematicConstroller performs simple exrapolation of velocities.
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
#include <EntityItem.h>
|
|
||||||
|
|
||||||
#include "KinematicController.h"
|
|
||||||
|
|
||||||
class SimpleEntityKinematicController : public KinematicController {
|
|
||||||
public:
|
|
||||||
SimpleEntityKinematicController() = delete; // prevent compiler from making a default ctor
|
|
||||||
|
|
||||||
SimpleEntityKinematicController(EntityItem* entity) : KinematicController(), _entity(entity) { assert(entity); }
|
|
||||||
|
|
||||||
~SimpleEntityKinematicController() { _entity = NULL; }
|
|
||||||
|
|
||||||
void stepForward();
|
|
||||||
|
|
||||||
private:
|
|
||||||
EntityItem* _entity;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_SimpleEntityKinematicController_h
|
|
|
@ -74,6 +74,7 @@ void AnimationHandle::setRunning(bool running) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_model->_runningAnimations.removeOne(_self);
|
_model->_runningAnimations.removeOne(_self);
|
||||||
|
restoreJoints();
|
||||||
replaceMatchingPriorities(0.0f);
|
replaceMatchingPriorities(0.0f);
|
||||||
}
|
}
|
||||||
emit runningChanged(isRunning());
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ private:
|
||||||
void simulate(float deltaTime);
|
void simulate(float deltaTime);
|
||||||
void applyFrame(float frameIndex);
|
void applyFrame(float frameIndex);
|
||||||
void replaceMatchingPriorities(float newPriority);
|
void replaceMatchingPriorities(float newPriority);
|
||||||
|
void restoreJoints();
|
||||||
|
|
||||||
Model* _model;
|
Model* _model;
|
||||||
WeakAnimationHandlePointer _self;
|
WeakAnimationHandlePointer _self;
|
||||||
|
|
|
@ -460,6 +460,9 @@ void ImageReader::run() {
|
||||||
}
|
}
|
||||||
QImage image = QImage::fromData(_content);
|
QImage image = QImage::fromData(_content);
|
||||||
|
|
||||||
|
int originalWidth = image.width();
|
||||||
|
int originalHeight = image.height();
|
||||||
|
|
||||||
// enforce a fixed maximum
|
// enforce a fixed maximum
|
||||||
const int MAXIMUM_SIZE = 1024;
|
const int MAXIMUM_SIZE = 1024;
|
||||||
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
|
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
|
||||||
|
@ -487,7 +490,7 @@ void ImageReader::run() {
|
||||||
averageColor.setRgb(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea);
|
averageColor.setRgb(redTotal / imageArea, greenTotal / imageArea, blueTotal / imageArea);
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), Q_ARG(bool, false),
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (image.format() != QImage::Format_ARGB32) {
|
if (image.format() != QImage::Format_ARGB32) {
|
||||||
|
@ -519,7 +522,8 @@ void ImageReader::run() {
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
|
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
|
||||||
Q_ARG(bool, translucentPixels >= imageArea / 2), Q_ARG(const QColor&, QColor(redTotal / imageArea,
|
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) {
|
void NetworkTexture::downloadFinished(QNetworkReply* reply) {
|
||||||
|
@ -531,9 +535,12 @@ void NetworkTexture::loadContent(const QByteArray& content) {
|
||||||
QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, 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;
|
_translucent = translucent;
|
||||||
_averageColor = averageColor;
|
_averageColor = averageColor;
|
||||||
|
_originalWidth = originalWidth;
|
||||||
|
_originalHeight = originalHeight;
|
||||||
_width = image.width();
|
_width = image.width();
|
||||||
_height = image.height();
|
_height = image.height();
|
||||||
|
|
||||||
|
|
|
@ -150,6 +150,8 @@ public:
|
||||||
/// Returns the lazily-computed average texture color.
|
/// Returns the lazily-computed average texture color.
|
||||||
const QColor& getAverageColor() const { return _averageColor; }
|
const QColor& getAverageColor() const { return _averageColor; }
|
||||||
|
|
||||||
|
int getOriginalWidth() const { return _originalWidth; }
|
||||||
|
int getOriginalHeight() const { return _originalHeight; }
|
||||||
int getWidth() const { return _width; }
|
int getWidth() const { return _width; }
|
||||||
int getHeight() const { return _height; }
|
int getHeight() const { return _height; }
|
||||||
|
|
||||||
|
@ -158,7 +160,8 @@ protected:
|
||||||
virtual void downloadFinished(QNetworkReply* reply);
|
virtual void downloadFinished(QNetworkReply* reply);
|
||||||
|
|
||||||
Q_INVOKABLE void loadContent(const QByteArray& content);
|
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);
|
virtual void imageLoaded(const QImage& image);
|
||||||
|
|
||||||
|
@ -166,6 +169,8 @@ private:
|
||||||
TextureType _type;
|
TextureType _type;
|
||||||
bool _translucent;
|
bool _translucent;
|
||||||
QColor _averageColor;
|
QColor _averageColor;
|
||||||
|
int _originalWidth;
|
||||||
|
int _originalHeight;
|
||||||
int _width;
|
int _width;
|
||||||
int _height;
|
int _height;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue