mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
merged from master
This commit is contained in:
commit
e7b54a9ef4
64 changed files with 2518 additions and 540 deletions
47
cmake/modules/MacOSXBundleInfo.plist.in
Normal file
47
cmake/modules/MacOSXBundleInfo.plist.in
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleLongVersionString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSRequiresCarbon</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME} URL</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>hifi</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
279
examples/overlaysExample.js
Normal file
279
examples/overlaysExample.js
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
//
|
||||||
|
// overlaysExample.js
|
||||||
|
// hifi
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 2/14/14.
|
||||||
|
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// This is an example script that demonstrates use of the Overlays class
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// The "Swatches" example of this script will create 9 different image overlays, that use the color feature to
|
||||||
|
// display different colors as color swatches. The overlays can be clicked on, to change the "selectedSwatch" variable
|
||||||
|
// and update the image used for the overlay so that it appears to have a selected indicator.
|
||||||
|
// These are our colors...
|
||||||
|
var swatchColors = new Array();
|
||||||
|
swatchColors[0] = { red: 255, green: 0, blue: 0};
|
||||||
|
swatchColors[1] = { red: 0, green: 255, blue: 0};
|
||||||
|
swatchColors[2] = { red: 0, green: 0, blue: 255};
|
||||||
|
swatchColors[3] = { red: 255, green: 255, blue: 0};
|
||||||
|
swatchColors[4] = { red: 255, green: 0, blue: 255};
|
||||||
|
swatchColors[5] = { red: 0, green: 255, blue: 255};
|
||||||
|
swatchColors[6] = { red: 128, green: 128, blue: 128};
|
||||||
|
swatchColors[7] = { red: 128, green: 0, blue: 0};
|
||||||
|
swatchColors[8] = { red: 0, green: 240, blue: 240};
|
||||||
|
|
||||||
|
// The location of the placement of these overlays
|
||||||
|
var swatchesX = 100;
|
||||||
|
var swatchesY = 200;
|
||||||
|
|
||||||
|
// These will be our "overlay IDs"
|
||||||
|
var swatches = new Array();
|
||||||
|
var numberOfSwatches = 9;
|
||||||
|
var selectedSwatch = 0;
|
||||||
|
|
||||||
|
// create the overlays, position them in a row, set their colors, and for the selected one, use a different source image
|
||||||
|
// location so that it displays the "selected" marker
|
||||||
|
for (s = 0; s < numberOfSwatches; s++) {
|
||||||
|
var imageFromX = 12 + (s * 27);
|
||||||
|
var imageFromY = 0;
|
||||||
|
if (s == selectedSwatch) {
|
||||||
|
imageFromY = 55;
|
||||||
|
}
|
||||||
|
|
||||||
|
swatches[s] = Overlays.addOverlay("image", {
|
||||||
|
x: 100 + (30 * s),
|
||||||
|
y: 200,
|
||||||
|
width: 31,
|
||||||
|
height: 54,
|
||||||
|
subImage: { x: imageFromX, y: imageFromY, width: 30, height: 54 },
|
||||||
|
imageURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg",
|
||||||
|
color: swatchColors[s],
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will create a text overlay that when you click on it, the text will change
|
||||||
|
var text = Overlays.addOverlay("text", {
|
||||||
|
x: 200,
|
||||||
|
y: 100,
|
||||||
|
width: 150,
|
||||||
|
height: 50,
|
||||||
|
color: { red: 0, green: 0, blue: 0},
|
||||||
|
textColor: { red: 255, green: 0, blue: 0},
|
||||||
|
topMargin: 4,
|
||||||
|
leftMargin: 4,
|
||||||
|
text: "Here is some text.\nAnd a second line."
|
||||||
|
});
|
||||||
|
|
||||||
|
// This will create an image overlay, which starts out as invisible
|
||||||
|
var toolA = Overlays.addOverlay("image", {
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
width: 62,
|
||||||
|
height: 40,
|
||||||
|
subImage: { x: 0, y: 0, width: 62, height: 40 },
|
||||||
|
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg",
|
||||||
|
color: { red: 255, green: 255, blue: 255},
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// This will create a couple of image overlays that make a "slider", we will demonstrate how to trap mouse messages to
|
||||||
|
// move the slider
|
||||||
|
var slider = Overlays.addOverlay("image", {
|
||||||
|
// alternate form of expressing bounds
|
||||||
|
bounds: { x: 100, y: 300, width: 158, height: 35},
|
||||||
|
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png",
|
||||||
|
color: { red: 255, green: 255, blue: 255},
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// This is the thumb of our slider
|
||||||
|
var minThumbX = 130;
|
||||||
|
var maxThumbX = minThumbX + 65;
|
||||||
|
var thumbX = (minThumbX + maxThumbX) / 2;
|
||||||
|
var thumb = Overlays.addOverlay("image", {
|
||||||
|
x: thumbX,
|
||||||
|
y: 309,
|
||||||
|
width: 18,
|
||||||
|
height: 17,
|
||||||
|
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
|
||||||
|
color: { red: 255, green: 255, blue: 255},
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// We will also demonstrate some 3D overlays. We will create a couple of cubes, spheres, and lines
|
||||||
|
// our 3D cube that moves around...
|
||||||
|
var cubePosition = { x: 2, y: 0, z: 2 };
|
||||||
|
var cubeSize = 5;
|
||||||
|
var cubeMove = 0.1;
|
||||||
|
var minCubeX = 1;
|
||||||
|
var maxCubeX = 20;
|
||||||
|
|
||||||
|
var cube = Overlays.addOverlay("cube", {
|
||||||
|
position: cubePosition,
|
||||||
|
size: cubeSize,
|
||||||
|
color: { red: 255, green: 0, blue: 0},
|
||||||
|
alpha: 1,
|
||||||
|
solid: false
|
||||||
|
});
|
||||||
|
|
||||||
|
var solidCubePosition = { x: 0, y: 5, z: 0 };
|
||||||
|
var solidCubeSize = 2;
|
||||||
|
var minSolidCubeX = 0;
|
||||||
|
var maxSolidCubeX = 10;
|
||||||
|
var solidCubeMove = 0.05;
|
||||||
|
var solidCube = Overlays.addOverlay("cube", {
|
||||||
|
position: solidCubePosition,
|
||||||
|
size: solidCubeSize,
|
||||||
|
color: { red: 0, green: 255, blue: 0},
|
||||||
|
alpha: 1,
|
||||||
|
solid: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var spherePosition = { x: 5, y: 5, z: 5 };
|
||||||
|
var sphereSize = 1;
|
||||||
|
var minSphereSize = 0.5;
|
||||||
|
var maxSphereSize = 10;
|
||||||
|
var sphereSizeChange = 0.05;
|
||||||
|
|
||||||
|
var sphere = Overlays.addOverlay("sphere", {
|
||||||
|
position: spherePosition,
|
||||||
|
size: sphereSize,
|
||||||
|
color: { red: 0, green: 0, blue: 255},
|
||||||
|
alpha: 1,
|
||||||
|
solid: false
|
||||||
|
});
|
||||||
|
|
||||||
|
var line3d = Overlays.addOverlay("line3d", {
|
||||||
|
position: { x: 0, y: 0, z:0 },
|
||||||
|
end: { x: 10, y: 10, z:10 },
|
||||||
|
color: { red: 0, green: 255, blue: 255},
|
||||||
|
alpha: 1,
|
||||||
|
lineWidth: 5
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// When our script shuts down, we should clean up all of our overlays
|
||||||
|
function scriptEnding() {
|
||||||
|
Overlays.deleteOverlay(toolA);
|
||||||
|
for (s = 0; s < numberOfSwatches; s++) {
|
||||||
|
Overlays.deleteOverlay(swatches[s]);
|
||||||
|
}
|
||||||
|
Overlays.deleteOverlay(thumb);
|
||||||
|
Overlays.deleteOverlay(slider);
|
||||||
|
Overlays.deleteOverlay(text);
|
||||||
|
Overlays.deleteOverlay(cube);
|
||||||
|
Overlays.deleteOverlay(solidCube);
|
||||||
|
Overlays.deleteOverlay(sphere);
|
||||||
|
Overlays.deleteOverlay(line3d);
|
||||||
|
}
|
||||||
|
Script.scriptEnding.connect(scriptEnding);
|
||||||
|
|
||||||
|
|
||||||
|
var toolAVisible = false;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
|
||||||
|
function update() {
|
||||||
|
count++;
|
||||||
|
|
||||||
|
// every second or so, toggle the visibility our our blinking tool
|
||||||
|
if (count % 60 == 0) {
|
||||||
|
if (toolAVisible) {
|
||||||
|
toolAVisible = false;
|
||||||
|
} else {
|
||||||
|
toolAVisible = true;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(toolA, { visible: toolAVisible } );
|
||||||
|
}
|
||||||
|
|
||||||
|
// move our 3D cube
|
||||||
|
cubePosition.x += cubeMove;
|
||||||
|
cubePosition.z += cubeMove;
|
||||||
|
if (cubePosition.x > maxCubeX || cubePosition.x < minCubeX) {
|
||||||
|
cubeMove = cubeMove * -1;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(cube, { position: cubePosition } );
|
||||||
|
|
||||||
|
// move our solid 3D cube
|
||||||
|
solidCubePosition.x += solidCubeMove;
|
||||||
|
solidCubePosition.z += solidCubeMove;
|
||||||
|
if (solidCubePosition.x > maxSolidCubeX || solidCubePosition.x < minSolidCubeX) {
|
||||||
|
solidCubeMove = solidCubeMove * -1;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(solidCube, { position: solidCubePosition } );
|
||||||
|
|
||||||
|
// adjust our 3D sphere
|
||||||
|
sphereSize += sphereSizeChange;
|
||||||
|
if (sphereSize > maxSphereSize || sphereSize < minSphereSize) {
|
||||||
|
sphereSizeChange = sphereSizeChange * -1;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(sphere, { size: sphereSize, solid: (sphereSizeChange < 0) } );
|
||||||
|
|
||||||
|
|
||||||
|
// update our 3D line to go from origin to our avatar's position
|
||||||
|
Overlays.editOverlay(line3d, { end: MyAvatar.position } );
|
||||||
|
}
|
||||||
|
Script.willSendVisualDataCallback.connect(update);
|
||||||
|
|
||||||
|
|
||||||
|
// The slider is handled in the mouse event callbacks.
|
||||||
|
var movingSlider = false;
|
||||||
|
var thumbClickOffsetX = 0;
|
||||||
|
function mouseMoveEvent(event) {
|
||||||
|
if (movingSlider) {
|
||||||
|
newThumbX = event.x - thumbClickOffsetX;
|
||||||
|
if (newThumbX < minThumbX) {
|
||||||
|
newThumbX = minThumbX;
|
||||||
|
}
|
||||||
|
if (newThumbX > maxThumbX) {
|
||||||
|
newThumbX = maxThumbX;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(thumb, { x: newThumbX } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we also handle click detection in our mousePressEvent()
|
||||||
|
function mousePressEvent(event) {
|
||||||
|
var clickedText = false;
|
||||||
|
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||||
|
|
||||||
|
// If the user clicked on the thumb, handle the slider logic
|
||||||
|
if (clickedOverlay == thumb) {
|
||||||
|
movingSlider = true;
|
||||||
|
thumbClickOffsetX = event.x - thumbX;
|
||||||
|
|
||||||
|
} else if (clickedOverlay == text) { // if the user clicked on the text, update text with where you clicked
|
||||||
|
|
||||||
|
Overlays.editOverlay(text, { text: "you clicked here:\n " + event.x + "," + event.y } );
|
||||||
|
clickedText = true;
|
||||||
|
|
||||||
|
} else { // if the user clicked on one of the color swatches, update the selectedSwatch
|
||||||
|
|
||||||
|
for (s = 0; s < numberOfSwatches; s++) {
|
||||||
|
if (clickedOverlay == swatches[s]) {
|
||||||
|
Overlays.editOverlay(swatches[selectedSwatch], { subImage: { y: 0 } } );
|
||||||
|
Overlays.editOverlay(swatches[s], { subImage: { y: 55 } } );
|
||||||
|
selectedSwatch = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!clickedText) { // if you didn't click on the text, then update the text accordningly
|
||||||
|
Overlays.editOverlay(text, { text: "you didn't click here" } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseReleaseEvent(event) {
|
||||||
|
if (movingSlider) {
|
||||||
|
movingSlider = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||||
|
Controller.mousePressEvent.connect(mousePressEvent);
|
||||||
|
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||||
|
|
|
@ -90,7 +90,13 @@ qt5_wrap_ui(QT_UI_HEADERS ${QT_UI_FILES})
|
||||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${QT_UI_HEADERS})
|
set(INTERFACE_SRCS ${INTERFACE_SRCS} ${QT_UI_HEADERS})
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
|
||||||
|
# configure CMake to use a custom Info.plist
|
||||||
|
SET_TARGET_PROPERTIES( ${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in )
|
||||||
|
|
||||||
set(MACOSX_BUNDLE_BUNDLE_NAME Interface)
|
set(MACOSX_BUNDLE_BUNDLE_NAME Interface)
|
||||||
|
set(MACOSX_BUNDLE_GUI_IDENTIFIER io.highfidelity.Interface)
|
||||||
|
|
||||||
# set how the icon shows up in the Info.plist file
|
# set how the icon shows up in the Info.plist file
|
||||||
SET(MACOSX_BUNDLE_ICON_FILE interface.icns)
|
SET(MACOSX_BUNDLE_ICON_FILE interface.icns)
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 330 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
@ -1,3 +1,5 @@
|
||||||
|
filename=defaultAvatar/body.fbx
|
||||||
|
texdir=defaultAvatar
|
||||||
scale=130
|
scale=130
|
||||||
joint = jointRoot = jointRoot
|
joint = jointRoot = jointRoot
|
||||||
joint = jointLean = jointSpine
|
joint = jointLean = jointSpine
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# faceshift target mapping file
|
# faceshift target mapping file
|
||||||
name= defaultAvatar_head
|
name= defaultAvatar_head
|
||||||
filename=../../../Avatars/Jelly/jellyrob_blue.fbx
|
filename=defaultAvatar/head.fbx
|
||||||
texdir=../../../Avatars/Jelly
|
texdir=defaultAvatar
|
||||||
scale=80
|
scale=80
|
||||||
rx=0
|
rx=0
|
||||||
ry=0
|
ry=0
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
uniform sampler2D permutationNormalTexture;
|
uniform sampler2D permutationNormalTexture;
|
||||||
|
|
||||||
// the noise frequency
|
// the noise frequency
|
||||||
const float frequency = 1024.0;
|
const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128
|
||||||
|
|
||||||
// the noise amplitude
|
// the noise amplitude
|
||||||
const float amplitude = 0.1;
|
const float amplitude = 0.1;
|
||||||
|
|
|
@ -101,6 +101,8 @@ const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::D
|
||||||
|
|
||||||
const int STATS_PELS_PER_LINE = 20;
|
const int STATS_PELS_PER_LINE = 20;
|
||||||
|
|
||||||
|
const QString CUSTOM_URL_SCHEME = "hifi:";
|
||||||
|
|
||||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||||
if (message.size() > 0) {
|
if (message.size() > 0) {
|
||||||
QString messageWithNewLine = message + "\n";
|
QString messageWithNewLine = message + "\n";
|
||||||
|
@ -290,6 +292,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
|
||||||
|
|
||||||
checkVersion();
|
checkVersion();
|
||||||
|
|
||||||
|
_overlays.init(_glWidget); // do this before scripts load
|
||||||
|
|
||||||
|
|
||||||
// do this as late as possible so that all required subsystems are inialized
|
// do this as late as possible so that all required subsystems are inialized
|
||||||
loadScripts();
|
loadScripts();
|
||||||
}
|
}
|
||||||
|
@ -679,6 +684,38 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Application::event(QEvent* event) {
|
||||||
|
|
||||||
|
// handle custom URL
|
||||||
|
if (event->type() == QEvent::FileOpen) {
|
||||||
|
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
|
||||||
|
if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) {
|
||||||
|
QString destination = fileEvent->url().toLocalFile().remove(CUSTOM_URL_SCHEME);
|
||||||
|
QStringList urlParts = destination.split('/', QString::SkipEmptyParts);
|
||||||
|
|
||||||
|
if (urlParts.count() > 1) {
|
||||||
|
// if url has 2 or more parts, the first one is domain name
|
||||||
|
Menu::getInstance()->goToDomain(urlParts[0]);
|
||||||
|
|
||||||
|
// location coordinates
|
||||||
|
Menu::getInstance()->goToDestination(urlParts[1]);
|
||||||
|
if (urlParts.count() > 2) {
|
||||||
|
|
||||||
|
// location orientation
|
||||||
|
Menu::getInstance()->goToOrientation(urlParts[2]);
|
||||||
|
}
|
||||||
|
} else if (urlParts.count() == 1) {
|
||||||
|
|
||||||
|
// location coordinates
|
||||||
|
Menu::getInstance()->goToDestination(urlParts[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return QApplication::event(event);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::keyPressEvent(QKeyEvent* event) {
|
void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
|
|
||||||
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
|
_controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
|
||||||
|
@ -1875,6 +1912,8 @@ void Application::init() {
|
||||||
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
|
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
|
||||||
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
|
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
|
||||||
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
|
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::closeMirrorView() {
|
void Application::closeMirrorView() {
|
||||||
|
@ -2706,7 +2745,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||||
"Application::displaySide() ... voxels...");
|
"Application::displaySide() ... voxels...");
|
||||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) {
|
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontRenderVoxels)) {
|
||||||
_voxels.render(Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures));
|
_voxels.render();
|
||||||
|
|
||||||
|
// double check that our LOD doesn't need to be auto-adjusted
|
||||||
|
// only adjust if our option is set
|
||||||
|
if (Menu::getInstance()->isOptionChecked(MenuOption::AutoAdjustLOD)) {
|
||||||
|
Menu::getInstance()->autoAdjustLOD(_fps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2797,7 +2842,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
||||||
_mouseVoxel.s,
|
_mouseVoxel.s,
|
||||||
_mouseVoxel.s);
|
_mouseVoxel.s);
|
||||||
|
|
||||||
_sharedVoxelSystem.render(true);
|
_sharedVoxelSystem.render();
|
||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2837,6 +2882,9 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
||||||
|
|
||||||
// give external parties a change to hook in
|
// give external parties a change to hook in
|
||||||
emit renderingInWorldInterface();
|
emit renderingInWorldInterface();
|
||||||
|
|
||||||
|
// render JS/scriptable overlays
|
||||||
|
_overlays.render3D();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2983,6 +3031,8 @@ void Application::displayOverlay() {
|
||||||
_pieMenu.render();
|
_pieMenu.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_overlays.render2D();
|
||||||
|
|
||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4060,6 +4110,8 @@ void Application::loadScript(const QString& fileNameString) {
|
||||||
scriptEngine->registerGlobalObject("Camera", cameraScriptable);
|
scriptEngine->registerGlobalObject("Camera", cameraScriptable);
|
||||||
connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater()));
|
connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater()));
|
||||||
|
|
||||||
|
scriptEngine->registerGlobalObject("Overlays", &_overlays);
|
||||||
|
|
||||||
QThread* workerThread = new QThread(this);
|
QThread* workerThread = new QThread(this);
|
||||||
|
|
||||||
// when the worker thread is started, call our engine's run..
|
// when the worker thread is started, call our engine's run..
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
#include "FileLogger.h"
|
#include "FileLogger.h"
|
||||||
#include "ParticleTreeRenderer.h"
|
#include "ParticleTreeRenderer.h"
|
||||||
#include "ControllerScriptingInterface.h"
|
#include "ControllerScriptingInterface.h"
|
||||||
|
#include "ui/Overlays.h"
|
||||||
|
|
||||||
|
|
||||||
class QAction;
|
class QAction;
|
||||||
|
@ -127,6 +128,8 @@ public:
|
||||||
|
|
||||||
void wheelEvent(QWheelEvent* event);
|
void wheelEvent(QWheelEvent* event);
|
||||||
|
|
||||||
|
bool event(QEvent* event);
|
||||||
|
|
||||||
void makeVoxel(glm::vec3 position,
|
void makeVoxel(glm::vec3 position,
|
||||||
float scale,
|
float scale,
|
||||||
unsigned char red,
|
unsigned char red,
|
||||||
|
@ -488,6 +491,8 @@ private:
|
||||||
void takeSnapshot();
|
void takeSnapshot();
|
||||||
|
|
||||||
TouchEvent _lastTouchEvent;
|
TouchEvent _lastTouchEvent;
|
||||||
|
|
||||||
|
Overlays _overlays;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__interface__Application__) */
|
#endif /* defined(__interface__Application__) */
|
||||||
|
|
|
@ -70,7 +70,8 @@ Menu::Menu() :
|
||||||
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
|
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
|
||||||
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
|
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
|
||||||
_boundaryLevelAdjust(0),
|
_boundaryLevelAdjust(0),
|
||||||
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS)
|
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS),
|
||||||
|
_lastAdjust(usecTimestampNow())
|
||||||
{
|
{
|
||||||
Application *appInstance = Application::getInstance();
|
Application *appInstance = Application::getInstance();
|
||||||
|
|
||||||
|
@ -168,14 +169,7 @@ Menu::Menu() :
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
|
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ClickToFly);
|
||||||
|
|
||||||
QMenu* collisionsOptionsMenu = editMenu->addMenu("Collision Options");
|
addAvatarCollisionSubMenu(editMenu);
|
||||||
|
|
||||||
QObject* avatar = appInstance->getAvatar();
|
|
||||||
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithEnvironment, 0, false, avatar, SLOT(updateCollisionFlags()));
|
|
||||||
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithAvatars, 0, false, avatar, SLOT(updateCollisionFlags()));
|
|
||||||
addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithVoxels, 0, false, avatar, SLOT(updateCollisionFlags()));
|
|
||||||
// TODO: make this option work
|
|
||||||
//addCheckableActionToQMenuAndActionHash(collisionsOptionsMenu, MenuOption::CollideWithParticles, 0, false, avatar, SLOT(updateCollisionFlags()));
|
|
||||||
|
|
||||||
QMenu* toolsMenu = addMenu("Tools");
|
QMenu* toolsMenu = addMenu("Tools");
|
||||||
|
|
||||||
|
@ -318,6 +312,7 @@ Menu::Menu() :
|
||||||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
|
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures);
|
||||||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
|
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion);
|
||||||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
|
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
|
||||||
|
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AutoAdjustLOD);
|
||||||
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
|
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
|
||||||
|
|
||||||
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
|
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
|
||||||
|
@ -343,6 +338,8 @@ Menu::Menu() :
|
||||||
SLOT(setTCPEnabled(bool)));
|
SLOT(setTCPEnabled(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
|
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
|
||||||
|
|
||||||
|
addAvatarCollisionSubMenu(avatarOptionsMenu);
|
||||||
|
|
||||||
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
|
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
|
||||||
|
|
||||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu,
|
addCheckableActionToQMenuAndActionHash(handOptionsMenu,
|
||||||
|
@ -517,6 +514,11 @@ void Menu::loadSettings(QSettings* settings) {
|
||||||
Application::getInstance()->getProfile()->loadData(settings);
|
Application::getInstance()->getProfile()->loadData(settings);
|
||||||
Application::getInstance()->updateWindowTitle();
|
Application::getInstance()->updateWindowTitle();
|
||||||
NodeList::getInstance()->loadData(settings);
|
NodeList::getInstance()->loadData(settings);
|
||||||
|
|
||||||
|
// MyAvatar caches some menu options, so we have to update them whenever we load settings.
|
||||||
|
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
|
||||||
|
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||||
|
myAvatar->updateCollisionFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::saveSettings(QSettings* settings) {
|
void Menu::saveSettings(QSettings* settings) {
|
||||||
|
@ -896,6 +898,17 @@ void Menu::editPreferences() {
|
||||||
sendFakeEnterEvent();
|
sendFakeEnterEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::goToDomain(const QString newDomain) {
|
||||||
|
if (NodeList::getInstance()->getDomainHostname() != newDomain) {
|
||||||
|
|
||||||
|
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||||
|
Application::getInstance()->getAvatar()->sendKillAvatar();
|
||||||
|
|
||||||
|
// give our nodeList the new domain-server hostname
|
||||||
|
NodeList::getInstance()->setDomainHostname(newDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Menu::goToDomain() {
|
void Menu::goToDomain() {
|
||||||
|
|
||||||
QString currentDomainHostname = NodeList::getInstance()->getDomainHostname();
|
QString currentDomainHostname = NodeList::getInstance()->getDomainHostname();
|
||||||
|
@ -921,30 +934,44 @@ void Menu::goToDomain() {
|
||||||
newHostname = domainDialog.textValue();
|
newHostname = domainDialog.textValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
goToDomain(newHostname);
|
||||||
Application::getInstance()->getAvatar()->sendKillAvatar();
|
|
||||||
|
|
||||||
// give our nodeList the new domain-server hostname
|
|
||||||
NodeList::getInstance()->setDomainHostname(domainDialog.textValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFakeEnterEvent();
|
sendFakeEnterEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::goTo() {
|
void Menu::goToOrientation(QString orientation) {
|
||||||
|
|
||||||
QInputDialog gotoDialog(Application::getInstance()->getWindow());
|
if (orientation.isEmpty()) {
|
||||||
gotoDialog.setWindowTitle("Go to");
|
return;
|
||||||
gotoDialog.setLabelText("Destination:");
|
}
|
||||||
QString destination = Application::getInstance()->getProfile()->getUsername();
|
|
||||||
gotoDialog.setTextValue(destination);
|
|
||||||
gotoDialog.setWindowFlags(Qt::Sheet);
|
|
||||||
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
|
|
||||||
|
|
||||||
int dialogReturn = gotoDialog.exec();
|
QStringList orientationItems = orientation.split(QRegExp("_|,"), QString::SkipEmptyParts);
|
||||||
if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
|
|
||||||
|
|
||||||
destination = gotoDialog.textValue();
|
const int NUMBER_OF_ORIENTATION_ITEMS = 4;
|
||||||
|
const int W_ITEM = 0;
|
||||||
|
const int X_ITEM = 1;
|
||||||
|
const int Y_ITEM = 2;
|
||||||
|
const int Z_ITEM = 3;
|
||||||
|
|
||||||
|
if (orientationItems.size() == NUMBER_OF_ORIENTATION_ITEMS) {
|
||||||
|
|
||||||
|
double w = replaceLastOccurrence('-', '.', orientationItems[W_ITEM].trimmed()).toDouble();
|
||||||
|
double x = replaceLastOccurrence('-', '.', orientationItems[X_ITEM].trimmed()).toDouble();
|
||||||
|
double y = replaceLastOccurrence('-', '.', orientationItems[Y_ITEM].trimmed()).toDouble();
|
||||||
|
double z = replaceLastOccurrence('-', '.', orientationItems[Z_ITEM].trimmed()).toDouble();
|
||||||
|
|
||||||
|
glm::quat newAvatarOrientation(w, x, y, z);
|
||||||
|
|
||||||
|
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||||
|
glm::quat avatarOrientation = myAvatar->getOrientation();
|
||||||
|
if (newAvatarOrientation != avatarOrientation) {
|
||||||
|
myAvatar->setOrientation(newAvatarOrientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Menu::goToDestination(QString destination) {
|
||||||
|
|
||||||
QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
|
QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts);
|
||||||
|
|
||||||
|
@ -970,7 +997,30 @@ void Menu::goTo() {
|
||||||
myAvatar->setPosition(newAvatarPos);
|
myAvatar->setPosition(newAvatarPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no coordinates were parsed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::goTo() {
|
||||||
|
|
||||||
|
QInputDialog gotoDialog(Application::getInstance()->getWindow());
|
||||||
|
gotoDialog.setWindowTitle("Go to");
|
||||||
|
gotoDialog.setLabelText("Destination:");
|
||||||
|
QString destination = Application::getInstance()->getProfile()->getUsername();
|
||||||
|
gotoDialog.setTextValue(destination);
|
||||||
|
gotoDialog.setWindowFlags(Qt::Sheet);
|
||||||
|
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
|
||||||
|
|
||||||
|
int dialogReturn = gotoDialog.exec();
|
||||||
|
if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
|
||||||
|
|
||||||
|
destination = gotoDialog.textValue();
|
||||||
|
|
||||||
|
// go to coordinate destination or to Username
|
||||||
|
if (!goToDestination(destination)) {
|
||||||
// there's a username entered by the user, make a request to the data-server
|
// there's a username entered by the user, make a request to the data-server
|
||||||
DataServerClient::getValuesForKeysAndUserString(
|
DataServerClient::getValuesForKeysAndUserString(
|
||||||
QStringList()
|
QStringList()
|
||||||
|
@ -1001,29 +1051,7 @@ void Menu::goToLocation() {
|
||||||
|
|
||||||
int dialogReturn = coordinateDialog.exec();
|
int dialogReturn = coordinateDialog.exec();
|
||||||
if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) {
|
if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) {
|
||||||
QByteArray newCoordinates;
|
goToDestination(coordinateDialog.textValue());
|
||||||
|
|
||||||
QString delimiterPattern(",");
|
|
||||||
QStringList coordinateItems = coordinateDialog.textValue().split(delimiterPattern);
|
|
||||||
|
|
||||||
const int NUMBER_OF_COORDINATE_ITEMS = 3;
|
|
||||||
const int X_ITEM = 0;
|
|
||||||
const int Y_ITEM = 1;
|
|
||||||
const int Z_ITEM = 2;
|
|
||||||
if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
|
|
||||||
double x = coordinateItems[X_ITEM].toDouble();
|
|
||||||
double y = coordinateItems[Y_ITEM].toDouble();
|
|
||||||
double z = coordinateItems[Z_ITEM].toDouble();
|
|
||||||
glm::vec3 newAvatarPos(x, y, z);
|
|
||||||
|
|
||||||
if (newAvatarPos != avatarPos) {
|
|
||||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
|
||||||
MyAvatar::sendKillAvatar();
|
|
||||||
|
|
||||||
qDebug("Going To Location: %f, %f, %f...", x, y, z);
|
|
||||||
myAvatar->setPosition(newAvatarPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFakeEnterEvent();
|
sendFakeEnterEvent();
|
||||||
|
@ -1103,14 +1131,38 @@ void Menu::voxelStatsDetailsClosed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::autoAdjustLOD(float currentFPS) {
|
||||||
|
bool changed = false;
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
quint64 elapsed = now - _lastAdjust;
|
||||||
|
|
||||||
|
if (elapsed > ADJUST_LOD_DOWN_DELAY && currentFPS < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) {
|
||||||
|
_voxelSizeScale *= ADJUST_LOD_DOWN_BY;
|
||||||
|
changed = true;
|
||||||
|
_lastAdjust = now;
|
||||||
|
qDebug() << "adjusting LOD down... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsed > ADJUST_LOD_UP_DELAY && currentFPS > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) {
|
||||||
|
_voxelSizeScale *= ADJUST_LOD_UP_BY;
|
||||||
|
changed = true;
|
||||||
|
_lastAdjust = now;
|
||||||
|
qDebug() << "adjusting LOD up... currentFPS=" << currentFPS << "_voxelSizeScale=" << _voxelSizeScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
if (_lodToolsDialog) {
|
||||||
|
_lodToolsDialog->reloadSliders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Menu::setVoxelSizeScale(float sizeScale) {
|
void Menu::setVoxelSizeScale(float sizeScale) {
|
||||||
_voxelSizeScale = sizeScale;
|
_voxelSizeScale = sizeScale;
|
||||||
Application::getInstance()->getVoxels()->redrawInViewVoxels();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
|
void Menu::setBoundaryLevelAdjust(int boundaryLevelAdjust) {
|
||||||
_boundaryLevelAdjust = boundaryLevelAdjust;
|
_boundaryLevelAdjust = boundaryLevelAdjust;
|
||||||
Application::getInstance()->getVoxels()->redrawInViewVoxels();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::lodTools() {
|
void Menu::lodTools() {
|
||||||
|
@ -1192,6 +1244,22 @@ void Menu::updateFrustumRenderModeAction() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) {
|
||||||
|
// add avatar collisions subMenu to overMenu
|
||||||
|
QMenu* subMenu = overMenu->addMenu("Collision Options");
|
||||||
|
|
||||||
|
Application* appInstance = Application::getInstance();
|
||||||
|
QObject* avatar = appInstance->getAvatar();
|
||||||
|
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment,
|
||||||
|
0, false, avatar, SLOT(updateCollisionFlags()));
|
||||||
|
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars,
|
||||||
|
0, true, avatar, SLOT(updateCollisionFlags()));
|
||||||
|
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels,
|
||||||
|
0, false, avatar, SLOT(updateCollisionFlags()));
|
||||||
|
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles,
|
||||||
|
0, true, avatar, SLOT(updateCollisionFlags()));
|
||||||
|
}
|
||||||
|
|
||||||
QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) {
|
QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) {
|
||||||
int lastIndex;
|
int lastIndex;
|
||||||
lastIndex = string.lastIndexOf(search);
|
lastIndex = string.lastIndexOf(search);
|
||||||
|
@ -1202,4 +1270,3 @@ QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string)
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,18 @@
|
||||||
|
|
||||||
#include <AbstractMenuInterface.h>
|
#include <AbstractMenuInterface.h>
|
||||||
|
|
||||||
|
const float ADJUST_LOD_DOWN_FPS = 40.0;
|
||||||
|
const float ADJUST_LOD_UP_FPS = 55.0;
|
||||||
|
|
||||||
|
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5;
|
||||||
|
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
|
||||||
|
|
||||||
|
const float ADJUST_LOD_DOWN_BY = 0.9f;
|
||||||
|
const float ADJUST_LOD_UP_BY = 1.1f;
|
||||||
|
|
||||||
|
const float ADJUST_LOD_MIN_SIZE_SCALE = TREE_SCALE * 1.0f;
|
||||||
|
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
|
||||||
|
|
||||||
enum FrustumDrawMode {
|
enum FrustumDrawMode {
|
||||||
FRUSTUM_DRAW_MODE_ALL,
|
FRUSTUM_DRAW_MODE_ALL,
|
||||||
FRUSTUM_DRAW_MODE_VECTORS,
|
FRUSTUM_DRAW_MODE_VECTORS,
|
||||||
|
@ -68,6 +80,7 @@ public:
|
||||||
void handleViewFrustumOffsetKeyModifier(int key);
|
void handleViewFrustumOffsetKeyModifier(int key);
|
||||||
|
|
||||||
// User Tweakable LOD Items
|
// User Tweakable LOD Items
|
||||||
|
void autoAdjustLOD(float currentFPS);
|
||||||
void setVoxelSizeScale(float sizeScale);
|
void setVoxelSizeScale(float sizeScale);
|
||||||
float getVoxelSizeScale() const { return _voxelSizeScale; }
|
float getVoxelSizeScale() const { return _voxelSizeScale; }
|
||||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
||||||
|
@ -84,6 +97,9 @@ public:
|
||||||
const char* member = NULL,
|
const char* member = NULL,
|
||||||
QAction::MenuRole role = QAction::NoRole);
|
QAction::MenuRole role = QAction::NoRole);
|
||||||
virtual void removeAction(QMenu* menu, const QString& actionName);
|
virtual void removeAction(QMenu* menu, const QString& actionName);
|
||||||
|
bool goToDestination(QString destination);
|
||||||
|
void goToOrientation(QString orientation);
|
||||||
|
void goToDomain(const QString newDomain);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void bandwidthDetails();
|
void bandwidthDetails();
|
||||||
|
@ -136,6 +152,8 @@ private:
|
||||||
|
|
||||||
void updateFrustumRenderModeAction();
|
void updateFrustumRenderModeAction();
|
||||||
|
|
||||||
|
void addAvatarCollisionSubMenu(QMenu* overMenu);
|
||||||
|
|
||||||
QHash<QString, QAction*> _actionHash;
|
QHash<QString, QAction*> _actionHash;
|
||||||
int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
|
int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
|
||||||
BandwidthDialog* _bandwidthDialog;
|
BandwidthDialog* _bandwidthDialog;
|
||||||
|
@ -154,6 +172,7 @@ private:
|
||||||
int _maxVoxelPacketsPerSecond;
|
int _maxVoxelPacketsPerSecond;
|
||||||
QMenu* _activeScriptsMenu;
|
QMenu* _activeScriptsMenu;
|
||||||
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
|
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
|
||||||
|
quint64 _lastAdjust;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace MenuOption {
|
namespace MenuOption {
|
||||||
|
@ -161,6 +180,7 @@ namespace MenuOption {
|
||||||
const QString AmbientOcclusion = "Ambient Occlusion";
|
const QString AmbientOcclusion = "Ambient Occlusion";
|
||||||
const QString Avatars = "Avatars";
|
const QString Avatars = "Avatars";
|
||||||
const QString Atmosphere = "Atmosphere";
|
const QString Atmosphere = "Atmosphere";
|
||||||
|
const QString AutoAdjustLOD = "Automatically Adjust LOD";
|
||||||
const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats";
|
const QString AutomaticallyAuditTree = "Automatically Audit Tree Stats";
|
||||||
const QString Bandwidth = "Bandwidth Display";
|
const QString Bandwidth = "Bandwidth Display";
|
||||||
const QString BandwidthDetails = "Bandwidth Details";
|
const QString BandwidthDetails = "Bandwidth Details";
|
||||||
|
|
|
@ -19,15 +19,6 @@
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
// the standard sans serif font family
|
|
||||||
#define SANS_FONT_FAMILY "Helvetica"
|
|
||||||
|
|
||||||
// the standard mono font family
|
|
||||||
#define MONO_FONT_FAMILY "Courier"
|
|
||||||
|
|
||||||
// the Inconsolata font family
|
|
||||||
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
|
|
||||||
|
|
||||||
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up);
|
void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up);
|
||||||
|
|
||||||
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos);
|
float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos);
|
||||||
|
|
|
@ -59,7 +59,10 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
|
||||||
: NodeData(),
|
: NodeData(),
|
||||||
_treeScale(treeScale),
|
_treeScale(treeScale),
|
||||||
_maxVoxels(maxVoxels),
|
_maxVoxels(maxVoxels),
|
||||||
_initialized(false) {
|
_initialized(false),
|
||||||
|
_writeArraysLock(QReadWriteLock::Recursive),
|
||||||
|
_readArraysLock(QReadWriteLock::Recursive)
|
||||||
|
{
|
||||||
|
|
||||||
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
|
_voxelsInReadArrays = _voxelsInWriteArrays = _voxelsUpdated = 0;
|
||||||
_writeRenderFullVBO = true;
|
_writeRenderFullVBO = true;
|
||||||
|
@ -99,6 +102,9 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels)
|
||||||
|
|
||||||
_culledOnce = false;
|
_culledOnce = false;
|
||||||
_inhideOutOfView = false;
|
_inhideOutOfView = false;
|
||||||
|
|
||||||
|
_lastKnownVoxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE;
|
||||||
|
_lastKnownBoundaryLevelAdjust = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelSystem::elementDeleted(OctreeElement* element) {
|
void VoxelSystem::elementDeleted(OctreeElement* element) {
|
||||||
|
@ -121,6 +127,7 @@ void VoxelSystem::setDisableFastVoxelPipeline(bool disableFastVoxelPipeline) {
|
||||||
|
|
||||||
void VoxelSystem::elementUpdated(OctreeElement* element) {
|
void VoxelSystem::elementUpdated(OctreeElement* element) {
|
||||||
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||||
|
|
||||||
// If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail..
|
// If we're in SetupNewVoxelsForDrawing() or _writeRenderFullVBO then bail..
|
||||||
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
|
if (!_useFastVoxelPipeline || _inSetupNewVoxelsForDrawing || _writeRenderFullVBO) {
|
||||||
return;
|
return;
|
||||||
|
@ -249,6 +256,9 @@ VoxelSystem::~VoxelSystem() {
|
||||||
delete _tree;
|
delete _tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This is called by the main application thread on both the initialization of the application and when
|
||||||
|
// the preferences dialog box is called/saved
|
||||||
void VoxelSystem::setMaxVoxels(int maxVoxels) {
|
void VoxelSystem::setMaxVoxels(int maxVoxels) {
|
||||||
if (maxVoxels == _maxVoxels) {
|
if (maxVoxels == _maxVoxels) {
|
||||||
return;
|
return;
|
||||||
|
@ -267,6 +277,8 @@ void VoxelSystem::setMaxVoxels(int maxVoxels) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is called by the main application thread on both the initialization of the application and when
|
||||||
|
// the use voxel shader menu item is chosen
|
||||||
void VoxelSystem::setUseVoxelShader(bool useVoxelShader) {
|
void VoxelSystem::setUseVoxelShader(bool useVoxelShader) {
|
||||||
if (_useVoxelShader == useVoxelShader) {
|
if (_useVoxelShader == useVoxelShader) {
|
||||||
return;
|
return;
|
||||||
|
@ -330,7 +342,7 @@ void VoxelSystem::setVoxelsAsPoints(bool voxelsAsPoints) {
|
||||||
|
|
||||||
void VoxelSystem::cleanupVoxelMemory() {
|
void VoxelSystem::cleanupVoxelMemory() {
|
||||||
if (_initialized) {
|
if (_initialized) {
|
||||||
_bufferWriteLock.lock();
|
_readArraysLock.lockForWrite();
|
||||||
_initialized = false; // no longer initialized
|
_initialized = false; // no longer initialized
|
||||||
if (_useVoxelShader) {
|
if (_useVoxelShader) {
|
||||||
// these are used when in VoxelShader mode.
|
// these are used when in VoxelShader mode.
|
||||||
|
@ -368,7 +380,7 @@ void VoxelSystem::cleanupVoxelMemory() {
|
||||||
delete[] _writeVoxelDirtyArray;
|
delete[] _writeVoxelDirtyArray;
|
||||||
delete[] _readVoxelDirtyArray;
|
delete[] _readVoxelDirtyArray;
|
||||||
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
|
_writeVoxelDirtyArray = _readVoxelDirtyArray = NULL;
|
||||||
_bufferWriteLock.unlock();
|
_readArraysLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +413,8 @@ void VoxelSystem::setupFaceIndices(GLuint& faceVBOID, GLubyte faceIdentityIndice
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelSystem::initVoxelMemory() {
|
void VoxelSystem::initVoxelMemory() {
|
||||||
_bufferWriteLock.lock();
|
_readArraysLock.lockForWrite();
|
||||||
|
_writeArraysLock.lockForWrite();
|
||||||
|
|
||||||
_memoryUsageRAM = 0;
|
_memoryUsageRAM = 0;
|
||||||
_memoryUsageVBO = 0; // our VBO allocations as we know them
|
_memoryUsageVBO = 0; // our VBO allocations as we know them
|
||||||
|
@ -516,7 +529,8 @@ void VoxelSystem::initVoxelMemory() {
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
|
|
||||||
_bufferWriteLock.unlock();
|
_writeArraysLock.unlock();
|
||||||
|
_readArraysLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const {
|
void VoxelSystem::writeToSVOFile(const char* filename, VoxelTreeElement* element) const {
|
||||||
|
@ -673,7 +687,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
|
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
|
||||||
_bufferWriteLock.lock();
|
_readArraysLock.lockForWrite();
|
||||||
|
|
||||||
if (_voxelsUpdated) {
|
if (_voxelsUpdated) {
|
||||||
_voxelsDirty=true;
|
_voxelsDirty=true;
|
||||||
|
@ -682,7 +696,7 @@ void VoxelSystem::setupNewVoxelsForDrawing() {
|
||||||
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
|
// copy the newly written data to the arrays designated for reading, only does something if _voxelsDirty && _voxelsUpdated
|
||||||
copyWrittenDataToReadArrays(didWriteFullVBO);
|
copyWrittenDataToReadArrays(didWriteFullVBO);
|
||||||
|
|
||||||
_bufferWriteLock.unlock();
|
_readArraysLock.unlock();
|
||||||
|
|
||||||
quint64 end = usecTimestampNow();
|
quint64 end = usecTimestampNow();
|
||||||
int elapsedmsec = (end - start) / 1000;
|
int elapsedmsec = (end - start) / 1000;
|
||||||
|
@ -713,8 +727,8 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
|
||||||
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
|
// lock on the buffer write lock so we can't modify the data when the GPU is reading it
|
||||||
{
|
{
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||||
"setupNewVoxelsForDrawingSingleNode()... _bufferWriteLock.lock();" );
|
"setupNewVoxelsForDrawingSingleNode()... _readArraysLock.lockForWrite();" );
|
||||||
_bufferWriteLock.lock();
|
_readArraysLock.lockForWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
|
_voxelsDirty = true; // if we got this far, then we can assume some voxels are dirty
|
||||||
|
@ -725,7 +739,7 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
|
||||||
// after...
|
// after...
|
||||||
_voxelsUpdated = 0;
|
_voxelsUpdated = 0;
|
||||||
|
|
||||||
_bufferWriteLock.unlock();
|
_readArraysLock.unlock();
|
||||||
|
|
||||||
quint64 end = usecTimestampNow();
|
quint64 end = usecTimestampNow();
|
||||||
int elapsedmsec = (end - start) / 1000;
|
int elapsedmsec = (end - start) / 1000;
|
||||||
|
@ -733,8 +747,73 @@ void VoxelSystem::setupNewVoxelsForDrawingSingleNode(bool allowBailEarly) {
|
||||||
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
|
_setupNewVoxelsForDrawingLastElapsed = elapsedmsec;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelSystem::checkForCulling() {
|
|
||||||
|
|
||||||
|
|
||||||
|
class recreateVoxelGeometryInViewArgs {
|
||||||
|
public:
|
||||||
|
VoxelSystem* thisVoxelSystem;
|
||||||
|
ViewFrustum thisViewFrustum;
|
||||||
|
unsigned long nodesScanned;
|
||||||
|
float voxelSizeScale;
|
||||||
|
int boundaryLevelAdjust;
|
||||||
|
|
||||||
|
recreateVoxelGeometryInViewArgs(VoxelSystem* voxelSystem) :
|
||||||
|
thisVoxelSystem(voxelSystem),
|
||||||
|
thisViewFrustum(*voxelSystem->getViewFrustum()),
|
||||||
|
nodesScanned(0),
|
||||||
|
voxelSizeScale(Menu::getInstance()->getVoxelSizeScale()),
|
||||||
|
boundaryLevelAdjust(Menu::getInstance()->getBoundaryLevelAdjust())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The goal of this operation is to remove any old references to old geometry, and if the voxel
|
||||||
|
// should be visible, create new geometry for it.
|
||||||
|
bool VoxelSystem::recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData) {
|
||||||
|
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||||
|
recreateVoxelGeometryInViewArgs* args = (recreateVoxelGeometryInViewArgs*)extraData;
|
||||||
|
|
||||||
|
args->nodesScanned++;
|
||||||
|
|
||||||
|
// reset the old geometry...
|
||||||
|
// note: this doesn't "mark the voxel as changed", so it only releases the old buffer index thereby forgetting the
|
||||||
|
// old geometry
|
||||||
|
voxel->setBufferIndex(GLBUFFER_INDEX_UNKNOWN);
|
||||||
|
|
||||||
|
bool shouldRender = voxel->calculateShouldRender(&args->thisViewFrustum, args->voxelSizeScale, args->boundaryLevelAdjust);
|
||||||
|
bool inView = voxel->isInView(args->thisViewFrustum);
|
||||||
|
voxel->setShouldRender(inView && shouldRender);
|
||||||
|
if (shouldRender && inView) {
|
||||||
|
// recreate the geometry
|
||||||
|
args->thisVoxelSystem->updateNodeInArrays(voxel, false, true); // DONT_REUSE_INDEX, FORCE_REDRAW
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // keep recursing!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: does cleanupRemovedVoxels() ever get called?
|
||||||
|
// TODO: other than cleanupRemovedVoxels() is there anyplace we attempt to detect too many abandoned slots???
|
||||||
|
void VoxelSystem::recreateVoxelGeometryInView() {
|
||||||
|
|
||||||
|
qDebug() << "recreateVoxelGeometryInView()...";
|
||||||
|
|
||||||
|
recreateVoxelGeometryInViewArgs args(this);
|
||||||
|
_writeArraysLock.lockForWrite(); // don't let anyone read or write our write arrays until we're done
|
||||||
|
_tree->lockForRead(); // don't let anyone change our tree structure until we're run
|
||||||
|
|
||||||
|
// reset our write arrays bookkeeping to think we've got no voxels in it
|
||||||
|
clearFreeBufferIndexes();
|
||||||
|
|
||||||
|
// do we need to reset out _writeVoxelDirtyArray arrays??
|
||||||
|
memset(_writeVoxelDirtyArray, false, _maxVoxels * sizeof(bool));
|
||||||
|
|
||||||
|
_tree->recurseTreeWithOperation(recreateVoxelGeometryInViewOperation,(void*)&args);
|
||||||
|
_tree->unlock();
|
||||||
|
_writeArraysLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoxelSystem::checkForCulling() {
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "checkForCulling()");
|
||||||
quint64 start = usecTimestampNow();
|
quint64 start = usecTimestampNow();
|
||||||
|
|
||||||
|
@ -762,7 +841,20 @@ void VoxelSystem::checkForCulling() {
|
||||||
_hasRecentlyChanged = false;
|
_hasRecentlyChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This would be a good place to do a special processing pass, for example, switching the LOD of the scene
|
||||||
|
bool fullRedraw = (_lastKnownVoxelSizeScale != Menu::getInstance()->getVoxelSizeScale() ||
|
||||||
|
_lastKnownBoundaryLevelAdjust != Menu::getInstance()->getBoundaryLevelAdjust());
|
||||||
|
|
||||||
|
// track that these values
|
||||||
|
_lastKnownVoxelSizeScale = Menu::getInstance()->getVoxelSizeScale();
|
||||||
|
_lastKnownBoundaryLevelAdjust = Menu::getInstance()->getBoundaryLevelAdjust();
|
||||||
|
|
||||||
|
if (fullRedraw) {
|
||||||
|
// this will remove all old geometry and recreate the correct geometry for all in view voxels
|
||||||
|
recreateVoxelGeometryInView();
|
||||||
|
} else {
|
||||||
hideOutOfView(forceFullFrustum);
|
hideOutOfView(forceFullFrustum);
|
||||||
|
}
|
||||||
|
|
||||||
if (forceFullFrustum) {
|
if (forceFullFrustum) {
|
||||||
quint64 endViewCulling = usecTimestampNow();
|
quint64 endViewCulling = usecTimestampNow();
|
||||||
|
@ -880,6 +972,12 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||||
"copyWrittenDataToReadArrays()");
|
"copyWrittenDataToReadArrays()");
|
||||||
|
|
||||||
|
// attempt to get the writeArraysLock for reading and the readArraysLock for writing
|
||||||
|
// so we can copy from the write to the read... if we fail, that's ok, we'll get it the next
|
||||||
|
// time around, the only side effect is the VBOs won't be updated this frame
|
||||||
|
const int WAIT_FOR_LOCK_IN_MS = 5;
|
||||||
|
if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) {
|
||||||
|
if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
|
||||||
if (_voxelsDirty && _voxelsUpdated) {
|
if (_voxelsDirty && _voxelsUpdated) {
|
||||||
if (fullVBOs) {
|
if (fullVBOs) {
|
||||||
copyWrittenDataToReadArraysFullVBOs();
|
copyWrittenDataToReadArraysFullVBOs();
|
||||||
|
@ -887,6 +985,14 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) {
|
||||||
copyWrittenDataToReadArraysPartialVBOs();
|
copyWrittenDataToReadArraysPartialVBOs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_writeArraysLock.unlock();
|
||||||
|
} else {
|
||||||
|
qDebug() << "couldn't get _writeArraysLock.LockForRead()...";
|
||||||
|
}
|
||||||
|
_readArraysLock.unlock();
|
||||||
|
} else {
|
||||||
|
qDebug() << "couldn't get _readArraysLock.LockForWrite()...";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int VoxelSystem::newTreeToArrays(VoxelTreeElement* voxel) {
|
int VoxelSystem::newTreeToArrays(VoxelTreeElement* voxel) {
|
||||||
|
@ -1141,6 +1247,11 @@ void VoxelSystem::updateVBOs() {
|
||||||
// would like to include _callsToTreesToArrays
|
// would like to include _callsToTreesToArrays
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), buffer);
|
||||||
if (_voxelsDirty) {
|
if (_voxelsDirty) {
|
||||||
|
|
||||||
|
// attempt to lock the read arrays, to for copying from them to the actual GPU VBOs.
|
||||||
|
// if we fail to get the lock, that's ok, our VBOs will update on the next frame...
|
||||||
|
const int WAIT_FOR_LOCK_IN_MS = 5;
|
||||||
|
if (_readArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) {
|
||||||
if (_readRenderFullVBO) {
|
if (_readRenderFullVBO) {
|
||||||
updateFullVBOs();
|
updateFullVBOs();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1148,10 +1259,15 @@ void VoxelSystem::updateVBOs() {
|
||||||
}
|
}
|
||||||
_voxelsDirty = false;
|
_voxelsDirty = false;
|
||||||
_readRenderFullVBO = false;
|
_readRenderFullVBO = false;
|
||||||
|
_readArraysLock.unlock();
|
||||||
|
} else {
|
||||||
|
qDebug() << "updateVBOs().... couldn't get _readArraysLock.tryLockForRead()";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_callsToTreesToArrays = 0; // clear it
|
_callsToTreesToArrays = 0; // clear it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should only be called on the main application thread during render
|
||||||
void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
|
void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex segmentEnd) {
|
||||||
bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarning = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
PerformanceWarning warn(showWarning, "updateVBOSegment()");
|
PerformanceWarning warn(showWarning, "updateVBOSegment()");
|
||||||
|
@ -1197,7 +1313,8 @@ void VoxelSystem::updateVBOSegment(glBufferIndex segmentStart, glBufferIndex seg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelSystem::render(bool texture) {
|
void VoxelSystem::render() {
|
||||||
|
bool texture = Menu::getInstance()->isOptionChecked(MenuOption::VoxelTextures);
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
PerformanceWarning warn(showWarnings, "render()");
|
PerformanceWarning warn(showWarnings, "render()");
|
||||||
|
|
||||||
|
@ -1404,11 +1521,7 @@ void VoxelSystem::killLocalVoxels() {
|
||||||
setupNewVoxelsForDrawing();
|
setupNewVoxelsForDrawing();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelSystem::redrawInViewVoxels() {
|
// only called on main thread
|
||||||
hideOutOfView(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) {
|
bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void* extraData) {
|
||||||
_nodeCount++;
|
_nodeCount++;
|
||||||
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
VoxelTreeElement* voxel = (VoxelTreeElement*)element;
|
||||||
|
@ -1416,12 +1529,15 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only called on main thread, and also always followed by a call to cleanupVoxelMemory()
|
||||||
|
// you shouldn't be calling this on any other thread or without also cleaning up voxel memory
|
||||||
void VoxelSystem::clearAllNodesBufferIndex() {
|
void VoxelSystem::clearAllNodesBufferIndex() {
|
||||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||||
"VoxelSystem::clearAllNodesBufferIndex()");
|
"VoxelSystem::clearAllNodesBufferIndex()");
|
||||||
_nodeCount = 0;
|
_nodeCount = 0;
|
||||||
_tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read
|
_tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read
|
||||||
_tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation);
|
_tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation);
|
||||||
|
clearFreeBufferIndexes(); // this should be called too
|
||||||
_tree->unlock();
|
_tree->unlock();
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) {
|
||||||
qDebug("clearing buffer index of %d nodes", _nodeCount);
|
qDebug("clearing buffer index of %d nodes", _nodeCount);
|
||||||
|
|
|
@ -53,7 +53,7 @@ public:
|
||||||
|
|
||||||
virtual void init();
|
virtual void init();
|
||||||
void simulate(float deltaTime) { }
|
void simulate(float deltaTime) { }
|
||||||
void render(bool texture);
|
void render();
|
||||||
|
|
||||||
void changeTree(VoxelTree* newTree);
|
void changeTree(VoxelTree* newTree);
|
||||||
VoxelTree* getTree() const { return _tree; }
|
VoxelTree* getTree() const { return _tree; }
|
||||||
|
@ -79,7 +79,6 @@ public:
|
||||||
unsigned long getVoxelMemoryUsageGPU();
|
unsigned long getVoxelMemoryUsageGPU();
|
||||||
|
|
||||||
void killLocalVoxels();
|
void killLocalVoxels();
|
||||||
void redrawInViewVoxels();
|
|
||||||
|
|
||||||
virtual void removeOutOfView();
|
virtual void removeOutOfView();
|
||||||
virtual void hideOutOfView(bool forceFullFrustum = false);
|
virtual void hideOutOfView(bool forceFullFrustum = false);
|
||||||
|
@ -151,6 +150,7 @@ protected:
|
||||||
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
|
static const bool DONT_BAIL_EARLY; // by default we will bail early, if you want to force not bailing, then use this
|
||||||
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
|
void setupNewVoxelsForDrawingSingleNode(bool allowBailEarly = true);
|
||||||
void checkForCulling();
|
void checkForCulling();
|
||||||
|
void recreateVoxelGeometryInView();
|
||||||
|
|
||||||
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
|
glm::vec3 computeVoxelVertex(const glm::vec3& startVertex, float voxelScale, int index) const;
|
||||||
|
|
||||||
|
@ -194,6 +194,7 @@ private:
|
||||||
static bool showAllSubTreeOperation(OctreeElement* element, void* extraData);
|
static bool showAllSubTreeOperation(OctreeElement* element, void* extraData);
|
||||||
static bool showAllLocalVoxelsOperation(OctreeElement* element, void* extraData);
|
static bool showAllLocalVoxelsOperation(OctreeElement* element, void* extraData);
|
||||||
static bool getVoxelEnclosingOperation(OctreeElement* element, void* extraData);
|
static bool getVoxelEnclosingOperation(OctreeElement* element, void* extraData);
|
||||||
|
static bool recreateVoxelGeometryInViewOperation(OctreeElement* element, void* extraData);
|
||||||
|
|
||||||
int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw);
|
int updateNodeInArrays(VoxelTreeElement* node, bool reuseIndex, bool forceDraw);
|
||||||
int forceRemoveNodeFromArrays(VoxelTreeElement* node);
|
int forceRemoveNodeFromArrays(VoxelTreeElement* node);
|
||||||
|
@ -211,6 +212,11 @@ private:
|
||||||
|
|
||||||
GLfloat* _readVerticesArray;
|
GLfloat* _readVerticesArray;
|
||||||
GLubyte* _readColorsArray;
|
GLubyte* _readColorsArray;
|
||||||
|
|
||||||
|
QReadWriteLock _writeArraysLock;
|
||||||
|
QReadWriteLock _readArraysLock;
|
||||||
|
|
||||||
|
|
||||||
GLfloat* _writeVerticesArray;
|
GLfloat* _writeVerticesArray;
|
||||||
GLubyte* _writeColorsArray;
|
GLubyte* _writeColorsArray;
|
||||||
bool* _writeVoxelDirtyArray;
|
bool* _writeVoxelDirtyArray;
|
||||||
|
@ -253,9 +259,6 @@ private:
|
||||||
GLuint _vboIndicesFront;
|
GLuint _vboIndicesFront;
|
||||||
GLuint _vboIndicesBack;
|
GLuint _vboIndicesBack;
|
||||||
|
|
||||||
QMutex _bufferWriteLock;
|
|
||||||
QMutex _treeLock;
|
|
||||||
|
|
||||||
ViewFrustum _lastKnownViewFrustum;
|
ViewFrustum _lastKnownViewFrustum;
|
||||||
ViewFrustum _lastStableViewFrustum;
|
ViewFrustum _lastStableViewFrustum;
|
||||||
ViewFrustum* _viewFrustum;
|
ViewFrustum* _viewFrustum;
|
||||||
|
@ -299,6 +302,9 @@ private:
|
||||||
bool _useFastVoxelPipeline;
|
bool _useFastVoxelPipeline;
|
||||||
|
|
||||||
bool _inhideOutOfView;
|
bool _inhideOutOfView;
|
||||||
|
|
||||||
|
float _lastKnownVoxelSizeScale;
|
||||||
|
int _lastKnownBoundaryLevelAdjust;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -176,6 +176,7 @@ static TextRenderer* textRenderer(TextRendererType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::render(bool forceRenderHead) {
|
void Avatar::render(bool forceRenderHead) {
|
||||||
|
|
||||||
{
|
{
|
||||||
// glow when moving in the distance
|
// glow when moving in the distance
|
||||||
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
|
glm::vec3 toTarget = _position - Application::getInstance()->getAvatar()->getPosition();
|
||||||
|
@ -392,27 +393,19 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||||
ModelCollisionList& collisions, int skeletonSkipIndex) {
|
CollisionList& collisions, int skeletonSkipIndex) {
|
||||||
bool didPenetrate = false;
|
// Temporarily disabling collisions against the skeleton because the collision proxies up
|
||||||
glm::vec3 skeletonPenetration;
|
// near the neck are bad and prevent the hand from hitting the face.
|
||||||
ModelCollisionInfo collisionInfo;
|
//return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, 1.0f, skeletonSkipIndex);
|
||||||
/* Temporarily disabling collisions against the skeleton because the collision proxies up
|
return _head.getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
|
||||||
* near the neck are bad and prevent the hand from hitting the face.
|
|
||||||
if (_skeletonModel.findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo, 1.0f, skeletonSkipIndex)) {
|
|
||||||
collisionInfo._model = &_skeletonModel;
|
|
||||||
collisions.push_back(collisionInfo);
|
|
||||||
didPenetrate = true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (_head.getFaceModel().findSphereCollision(penetratorCenter, penetratorRadius, collisionInfo)) {
|
|
||||||
collisionInfo._model = &(_head.getFaceModel());
|
|
||||||
collisions.push_back(collisionInfo);
|
|
||||||
didPenetrate = true;
|
|
||||||
}
|
|
||||||
return didPenetrate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
|
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
|
||||||
|
if (_collisionFlags & COLLISION_GROUP_PARTICLES) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool collided = false;
|
||||||
|
// first do the hand collisions
|
||||||
const HandData* handData = getHandData();
|
const HandData* handData = getHandData();
|
||||||
if (handData) {
|
if (handData) {
|
||||||
for (int i = 0; i < NUM_HANDS; i++) {
|
for (int i = 0; i < NUM_HANDS; i++) {
|
||||||
|
@ -430,51 +423,65 @@ bool Avatar::findSphereCollisionWithHands(const glm::vec3& sphereCenter, float s
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int jointIndex = -1;
|
||||||
glm::vec3 handPosition;
|
glm::vec3 handPosition;
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
_skeletonModel.getLeftHandPosition(handPosition);
|
_skeletonModel.getLeftHandPosition(handPosition);
|
||||||
|
jointIndex = _skeletonModel.getLeftHandJointIndex();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_skeletonModel.getRightHandPosition(handPosition);
|
_skeletonModel.getRightHandPosition(handPosition);
|
||||||
|
jointIndex = _skeletonModel.getRightHandJointIndex();
|
||||||
}
|
}
|
||||||
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
|
glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
|
||||||
glm::vec3 diskNormal = palm->getNormal();
|
glm::vec3 diskNormal = palm->getNormal();
|
||||||
float diskThickness = 0.08f;
|
const float DISK_THICKNESS = 0.08f;
|
||||||
|
|
||||||
// collide against the disk
|
// collide against the disk
|
||||||
if (findSphereDiskPenetration(sphereCenter, sphereRadius,
|
glm::vec3 penetration;
|
||||||
diskCenter, HAND_PADDLE_RADIUS, diskThickness, diskNormal,
|
if (findSphereDiskPenetration(particleCenter, particleRadius,
|
||||||
collision._penetration)) {
|
diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
|
||||||
collision._addedVelocity = palm->getVelocity();
|
penetration)) {
|
||||||
return true;
|
CollisionInfo* collision = collisions.getNewCollision();
|
||||||
|
if (collision) {
|
||||||
|
collision->_type = PADDLE_HAND_COLLISION;
|
||||||
|
collision->_flags = jointIndex;
|
||||||
|
collision->_penetration = penetration;
|
||||||
|
collision->_addedVelocity = palm->getVelocity();
|
||||||
|
collided = true;
|
||||||
|
} else {
|
||||||
|
// collisions are full, so we might as well bail now
|
||||||
|
return collided;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
// then collide against the models
|
||||||
/* adebug TODO: make this work again
|
int preNumCollisions = collisions.size();
|
||||||
bool Avatar::findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
|
if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
|
||||||
int jointIndex = _skeletonModel.findSphereCollision(sphereCenter, sphereRadius, collision._penetration);
|
// the Model doesn't have velocity info, so we have to set it for each new collision
|
||||||
if (jointIndex != -1) {
|
int postNumCollisions = collisions.size();
|
||||||
collision._penetration /= (float)(TREE_SCALE);
|
for (int i = preNumCollisions; i < postNumCollisions; ++i) {
|
||||||
collision._addedVelocity = getVelocity();
|
CollisionInfo* collision = collisions.getCollision(i);
|
||||||
return true;
|
collision->_penetration /= (float)(TREE_SCALE);
|
||||||
|
collision->_addedVelocity = getVelocity();
|
||||||
}
|
}
|
||||||
return false;
|
collided = true;
|
||||||
|
}
|
||||||
|
return collided;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
void Avatar::setFaceModelURL(const QUrl &faceModelURL) {
|
||||||
AvatarData::setFaceModelURL(faceModelURL);
|
AvatarData::setFaceModelURL(faceModelURL);
|
||||||
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fbx");
|
const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_head.fst");
|
||||||
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
|
_head.getFaceModel().setURL(_faceModelURL, DEFAULT_FACE_MODEL_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
|
void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) {
|
||||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||||
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fbx");
|
const QUrl DEFAULT_SKELETON_MODEL_URL = QUrl::fromLocalFile("resources/meshes/defaultAvatar_body.fst");
|
||||||
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
|
_skeletonModel.setURL(_skeletonModelURL, DEFAULT_SKELETON_MODEL_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,9 +565,9 @@ void Avatar::updateCollisionFlags() {
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
|
||||||
_collisionFlags |= COLLISION_GROUP_VOXELS;
|
_collisionFlags |= COLLISION_GROUP_VOXELS;
|
||||||
}
|
}
|
||||||
//if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
|
||||||
// _collisionFlags |= COLLISION_GROUP_PARTICLES;
|
_collisionFlags |= COLLISION_GROUP_PARTICLES;
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::setScale(float scale) {
|
void Avatar::setScale(float scale) {
|
||||||
|
@ -582,34 +589,34 @@ float Avatar::getHeadHeight() const {
|
||||||
return extents.maximum.y - extents.minimum.y;
|
return extents.maximum.y - extents.minimum.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Avatar::collisionWouldMoveAvatar(ModelCollisionInfo& collision) const {
|
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
|
||||||
// ATM only the Skeleton is pokeable
|
if (!collision._data || collision._type != MODEL_COLLISION) {
|
||||||
// TODO: make poke affect head
|
|
||||||
if (!collision._model) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
|
Model* model = static_cast<Model*>(collision._data);
|
||||||
|
int jointIndex = collision._flags;
|
||||||
|
|
||||||
|
if (model == &(_skeletonModel) && jointIndex != -1) {
|
||||||
// collision response of skeleton is temporarily disabled
|
// collision response of skeleton is temporarily disabled
|
||||||
return false;
|
return false;
|
||||||
//return _skeletonModel.collisionHitsMoveableJoint(collision);
|
//return _skeletonModel.collisionHitsMoveableJoint(collision);
|
||||||
}
|
}
|
||||||
if (collision._model == &(_head.getFaceModel())) {
|
if (model == &(_head.getFaceModel())) {
|
||||||
|
// ATM we always handle MODEL_COLLISIONS against the face.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::applyCollision(ModelCollisionInfo& collision) {
|
void Avatar::applyCollision(CollisionInfo& collision) {
|
||||||
if (!collision._model) {
|
if (!collision._data || collision._type != MODEL_COLLISION) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (collision._model == &(_head.getFaceModel())) {
|
// TODO: make skeleton also respond to collisions
|
||||||
|
Model* model = static_cast<Model*>(collision._data);
|
||||||
|
if (model == &(_head.getFaceModel())) {
|
||||||
_head.applyCollision(collision);
|
_head.applyCollision(collision);
|
||||||
}
|
}
|
||||||
// TODO: make skeleton respond to collisions
|
|
||||||
//if (collision._model == &_skeletonModel && collision._jointIndex != -1) {
|
|
||||||
// _skeletonModel.applyCollision(collision);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float Avatar::getPelvisFloatingHeight() const {
|
float Avatar::getPelvisFloatingHeight() const {
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
#include "SkeletonModel.h"
|
#include "SkeletonModel.h"
|
||||||
#include "world.h"
|
#include "world.h"
|
||||||
|
|
||||||
|
|
||||||
static const float SCALING_RATIO = .05f;
|
static const float SCALING_RATIO = .05f;
|
||||||
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
|
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
|
||||||
static const float RESCALING_TOLERANCE = .02f;
|
static const float RESCALING_TOLERANCE = .02f;
|
||||||
|
@ -58,8 +57,6 @@ enum ScreenTintLayer {
|
||||||
NUM_SCREEN_TINT_LAYERS
|
NUM_SCREEN_TINT_LAYERS
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef QVector<ModelCollisionInfo> ModelCollisionList;
|
|
||||||
|
|
||||||
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
|
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found)
|
||||||
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
|
// this is basically in the center of the ground plane. Slightly adjusted. This was asked for by
|
||||||
// Grayson as he's building a street around here for demo dinner 2
|
// Grayson as he's building a street around here for demo dinner 2
|
||||||
|
@ -98,25 +95,18 @@ public:
|
||||||
/// Checks for penetration between the described sphere and the avatar.
|
/// Checks for penetration between the described sphere and the avatar.
|
||||||
/// \param penetratorCenter the center of the penetration test sphere
|
/// \param penetratorCenter the center of the penetration test sphere
|
||||||
/// \param penetratorRadius the radius of the penetration test sphere
|
/// \param penetratorRadius the radius of the penetration test sphere
|
||||||
/// \param collisions[out] a list of collisions
|
/// \param collisions[out] a list to which collisions get appended
|
||||||
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
|
/// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
|
||||||
/// \return whether or not the sphere penetrated
|
/// \return whether or not the sphere penetrated
|
||||||
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||||
ModelCollisionList& collisions, int skeletonSkipIndex = -1);
|
CollisionList& collisions, int skeletonSkipIndex = -1);
|
||||||
|
|
||||||
/// Checks for collision between the a sphere and the avatar's (paddle) hands.
|
/// Checks for collision between the a spherical particle and the avatar (including paddle hands)
|
||||||
/// \param collisionCenter the center of the penetration test sphere
|
/// \param collisionCenter the center of particle's bounding sphere
|
||||||
/// \param collisionRadius the radius of the penetration test sphere
|
/// \param collisionRadius the radius of particle's bounding sphere
|
||||||
/// \param collision[out] the details of the collision point
|
/// \param collisions[out] a list to which collisions get appended
|
||||||
/// \return whether or not the sphere collided
|
/// \return whether or not the particle collided
|
||||||
bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
|
bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions);
|
||||||
|
|
||||||
/// Checks for collision between the a sphere and the avatar's skeleton (including hand capsules).
|
|
||||||
/// \param collisionCenter the center of the penetration test sphere
|
|
||||||
/// \param collisionRadius the radius of the penetration test sphere
|
|
||||||
/// \param collision[out] the details of the collision point
|
|
||||||
/// \return whether or not the sphere collided
|
|
||||||
//bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision);
|
|
||||||
|
|
||||||
virtual bool isMyAvatar() { return false; }
|
virtual bool isMyAvatar() { return false; }
|
||||||
|
|
||||||
|
@ -130,15 +120,15 @@ public:
|
||||||
|
|
||||||
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
|
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
|
||||||
|
|
||||||
float getSkeletonHeight() const;
|
|
||||||
|
|
||||||
float getHeadHeight() const;
|
|
||||||
|
|
||||||
/// \return true if we expect the avatar would move as a result of the collision
|
/// \return true if we expect the avatar would move as a result of the collision
|
||||||
bool collisionWouldMoveAvatar(ModelCollisionInfo& collision) const;
|
bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
|
||||||
|
|
||||||
/// \param collision a data structure for storing info about collisions against Models
|
/// \param collision a data structure for storing info about collisions against Models
|
||||||
void applyCollision(ModelCollisionInfo& collision);
|
void applyCollision(CollisionInfo& collision);
|
||||||
|
|
||||||
|
float getBoundingRadius() const { return 0.5f * getHeight(); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateCollisionFlags();
|
void updateCollisionFlags();
|
||||||
|
@ -170,12 +160,11 @@ protected:
|
||||||
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
||||||
void setScale(float scale);
|
void setScale(float scale);
|
||||||
|
|
||||||
|
float getSkeletonHeight() const;
|
||||||
|
float getHeadHeight() const;
|
||||||
float getPelvisFloatingHeight() const;
|
float getPelvisFloatingHeight() const;
|
||||||
float getPelvisToHeadLength() const;
|
float getPelvisToHeadLength() const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool _initialized;
|
bool _initialized;
|
||||||
|
|
|
@ -20,6 +20,7 @@ FaceModel::FaceModel(Head* owningHead) :
|
||||||
|
|
||||||
void FaceModel::simulate(float deltaTime) {
|
void FaceModel::simulate(float deltaTime) {
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
|
Model::simulate(deltaTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
|
Avatar* owningAvatar = static_cast<Avatar*>(_owningHead->_owningAvatar);
|
||||||
|
|
|
@ -125,6 +125,10 @@ void Hand::simulate(float deltaTime, bool isMine) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We create a static CollisionList that is recycled for each collision test.
|
||||||
|
const float MAX_COLLISIONS_PER_AVATAR = 32;
|
||||||
|
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
|
||||||
|
|
||||||
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
||||||
if (!avatar || avatar == _owningAvatar) {
|
if (!avatar || avatar == _owningAvatar) {
|
||||||
// don't collide with our own hands (that is done elsewhere)
|
// don't collide with our own hands (that is done elsewhere)
|
||||||
|
@ -137,7 +141,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
glm::vec3 totalPenetration;
|
glm::vec3 totalPenetration;
|
||||||
ModelCollisionList collisions;
|
|
||||||
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
|
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
|
||||||
// Check for palm collisions
|
// Check for palm collisions
|
||||||
glm::vec3 myPalmPosition = palm.getPosition();
|
glm::vec3 myPalmPosition = palm.getPosition();
|
||||||
|
@ -171,20 +174,22 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions)) {
|
handCollisions.clear();
|
||||||
for (int j = 0; j < collisions.size(); ++j) {
|
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) {
|
||||||
|
for (int j = 0; j < handCollisions.size(); ++j) {
|
||||||
|
CollisionInfo* collision = handCollisions.getCollision(j);
|
||||||
if (isMyHand) {
|
if (isMyHand) {
|
||||||
if (!avatar->collisionWouldMoveAvatar(collisions[j])) {
|
if (!avatar->collisionWouldMoveAvatar(*collision)) {
|
||||||
// we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
|
// we resolve the hand from collision when it belongs to MyAvatar AND the other Avatar is
|
||||||
// not expected to respond to the collision (hand hit unmovable part of their Avatar)
|
// not expected to respond to the collision (hand hit unmovable part of their Avatar)
|
||||||
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
|
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// when !isMyHand then avatar is MyAvatar and we apply the collision
|
// when !isMyHand then avatar is MyAvatar and we apply the collision
|
||||||
// which might not do anything (hand hit unmovable part of MyAvatar) however
|
// which might not do anything (hand hit unmovable part of MyAvatar) however
|
||||||
// we don't resolve the hand's penetration because we expect the remote
|
// we don't resolve the hand's penetration because we expect the remote
|
||||||
// simulation to do the right thing.
|
// simulation to do the right thing.
|
||||||
avatar->applyCollision(collisions[j]);
|
avatar->applyCollision(*collision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +205,6 @@ void Hand::collideAgainstOurself() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelCollisionList collisions;
|
|
||||||
int leftPalmIndex, rightPalmIndex;
|
int leftPalmIndex, rightPalmIndex;
|
||||||
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
||||||
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
||||||
|
@ -210,16 +214,18 @@ void Hand::collideAgainstOurself() {
|
||||||
if (!palm.isActive()) {
|
if (!palm.isActive()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
glm::vec3 totalPenetration;
|
|
||||||
// and the current avatar (ignoring everything below the parent of the parent of the last free joint)
|
|
||||||
collisions.clear();
|
|
||||||
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
|
const Model& skeletonModel = _owningAvatar->getSkeletonModel();
|
||||||
|
// ignoring everything below the parent of the parent of the last free joint
|
||||||
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
|
int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
|
||||||
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
|
skeletonModel.getLastFreeJointIndex((i == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
|
||||||
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
|
(i == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
|
||||||
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, collisions, skipIndex)) {
|
|
||||||
for (int j = 0; j < collisions.size(); ++j) {
|
handCollisions.clear();
|
||||||
totalPenetration = addPenetrations(totalPenetration, collisions[j]._penetration);
|
glm::vec3 totalPenetration;
|
||||||
|
if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
|
||||||
|
for (int j = 0; j < handCollisions.size(); ++j) {
|
||||||
|
CollisionInfo* collision = handCollisions.getCollision(j);
|
||||||
|
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// resolve penetration
|
// resolve penetration
|
||||||
|
|
|
@ -219,7 +219,7 @@ float Head::getTweakedRoll() const {
|
||||||
return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
|
return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
|
void Head::applyCollision(CollisionInfo& collision) {
|
||||||
// HACK: the collision proxies for the FaceModel are bad. As a temporary workaround
|
// HACK: the collision proxies for the FaceModel are bad. As a temporary workaround
|
||||||
// we collide against a hard coded collision proxy.
|
// we collide against a hard coded collision proxy.
|
||||||
// TODO: get a better collision proxy here.
|
// TODO: get a better collision proxy here.
|
||||||
|
@ -229,7 +229,7 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
|
||||||
// collide the contactPoint against the collision proxy to obtain a new penetration
|
// collide the contactPoint against the collision proxy to obtain a new penetration
|
||||||
// NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere)
|
// NOTE: that penetration is in opposite direction (points the way out for the point, not the sphere)
|
||||||
glm::vec3 penetration;
|
glm::vec3 penetration;
|
||||||
if (findPointSpherePenetration(collisionInfo._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
|
if (findPointSpherePenetration(collision._contactPoint, HEAD_CENTER, HEAD_RADIUS, penetration)) {
|
||||||
// compute lean angles
|
// compute lean angles
|
||||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
|
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
|
||||||
glm::quat bodyRotation = owningAvatar->getOrientation();
|
glm::quat bodyRotation = owningAvatar->getOrientation();
|
||||||
|
@ -239,8 +239,8 @@ void Head::applyCollision(ModelCollisionInfo& collisionInfo) {
|
||||||
glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f);
|
glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f);
|
||||||
float neckLength = glm::length(_position - neckPosition);
|
float neckLength = glm::length(_position - neckPosition);
|
||||||
if (neckLength > 0.f) {
|
if (neckLength > 0.f) {
|
||||||
float forward = glm::dot(collisionInfo._penetration, zAxis) / neckLength;
|
float forward = glm::dot(collision._penetration, zAxis) / neckLength;
|
||||||
float sideways = - glm::dot(collisionInfo._penetration, xAxis) / neckLength;
|
float sideways = - glm::dot(collision._penetration, xAxis) / neckLength;
|
||||||
addLean(sideways, forward);
|
addLean(sideways, forward);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ public:
|
||||||
float getTweakedYaw() const;
|
float getTweakedYaw() const;
|
||||||
float getTweakedRoll() const;
|
float getTweakedRoll() const;
|
||||||
|
|
||||||
void applyCollision(ModelCollisionInfo& collisionInfo);
|
void applyCollision(CollisionInfo& collisionInfo);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// disallow copies of the Head, copy of owning Avatar is disallowed too
|
// disallow copies of the Head, copy of owning Avatar is disallowed too
|
||||||
|
|
|
@ -961,7 +961,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
||||||
// no need to compute a bunch of stuff if we have one or fewer avatars
|
// no need to compute a bunch of stuff if we have one or fewer avatars
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float myBoundingRadius = 0.5f * getSkeletonHeight();
|
float myBoundingRadius = getBoundingRadius();
|
||||||
|
|
||||||
// HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
|
// HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis
|
||||||
// TODO: make the collision work without assuming avatar orientation
|
// TODO: make the collision work without assuming avatar orientation
|
||||||
|
@ -981,7 +981,7 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
|
||||||
if (_distanceToNearestAvatar > distance) {
|
if (_distanceToNearestAvatar > distance) {
|
||||||
_distanceToNearestAvatar = distance;
|
_distanceToNearestAvatar = distance;
|
||||||
}
|
}
|
||||||
float theirBoundingRadius = 0.5f * avatar->getSkeletonHeight();
|
float theirBoundingRadius = avatar->getBoundingRadius();
|
||||||
if (distance < myBoundingRadius + theirBoundingRadius) {
|
if (distance < myBoundingRadius + theirBoundingRadius) {
|
||||||
Extents theirStaticExtents = _skeletonModel.getStaticExtents();
|
Extents theirStaticExtents = _skeletonModel.getStaticExtents();
|
||||||
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;
|
glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum;
|
||||||
|
|
|
@ -21,9 +21,9 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar) :
|
||||||
|
|
||||||
void SkeletonModel::simulate(float deltaTime) {
|
void SkeletonModel::simulate(float deltaTime) {
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
|
Model::simulate(deltaTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTranslation(_owningAvatar->getPosition());
|
setTranslation(_owningAvatar->getPosition());
|
||||||
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f));
|
setRotation(_owningAvatar->getOrientation() * glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f));
|
||||||
const float MODEL_SCALE = 0.0006f;
|
const float MODEL_SCALE = 0.0006f;
|
||||||
|
|
|
@ -1577,14 +1577,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping) {
|
QVariantHash readMapping(const QByteArray& data) {
|
||||||
QBuffer modelBuffer(const_cast<QByteArray*>(&model));
|
QBuffer buffer(const_cast<QByteArray*>(&data));
|
||||||
modelBuffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
|
return parseMapping(&buffer);
|
||||||
|
}
|
||||||
|
|
||||||
QBuffer mappingBuffer(const_cast<QByteArray*>(&mapping));
|
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
|
||||||
mappingBuffer.open(QIODevice::ReadOnly);
|
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||||
|
buffer.open(QIODevice::ReadOnly);
|
||||||
return extractFBXGeometry(parseFBX(&modelBuffer), parseMapping(&mappingBuffer));
|
return extractFBXGeometry(parseFBX(&buffer), mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
|
bool addMeshVoxelsOperation(OctreeElement* element, void* extraData) {
|
||||||
|
|
|
@ -164,9 +164,12 @@ public:
|
||||||
QVector<FBXAttachment> attachments;
|
QVector<FBXAttachment> attachments;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Reads an FST mapping from the supplied data.
|
||||||
|
QVariantHash readMapping(const QByteArray& data);
|
||||||
|
|
||||||
/// Reads FBX geometry from the supplied model and mapping data.
|
/// Reads FBX geometry from the supplied model and mapping data.
|
||||||
/// \exception QString if an error occurs in parsing
|
/// \exception QString if an error occurs in parsing
|
||||||
FBXGeometry readFBX(const QByteArray& model, const QByteArray& mapping);
|
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
|
||||||
|
|
||||||
/// Reads SVO geometry from the supplied model data.
|
/// Reads SVO geometry from the supplied model data.
|
||||||
FBXGeometry readSVO(const QByteArray& model);
|
FBXGeometry readSVO(const QByteArray& model);
|
||||||
|
|
|
@ -7,11 +7,7 @@
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
|
|
||||||
#include "InterfaceConfig.h"
|
|
||||||
|
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QOpenGLBuffer>
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
@ -298,46 +294,86 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
|
||||||
if (geometry.isNull()) {
|
if (geometry.isNull()) {
|
||||||
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
|
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
|
||||||
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
|
getGeometry(fallback) : QSharedPointer<NetworkGeometry>()));
|
||||||
|
geometry->setLODParent(geometry);
|
||||||
_networkGeometry.insert(url, geometry);
|
_networkGeometry.insert(url, geometry);
|
||||||
}
|
}
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback) :
|
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
|
||||||
_modelRequest(url),
|
|
||||||
_modelReply(NULL),
|
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
|
||||||
_mappingReply(NULL),
|
const QVariantHash& mapping, const QUrl& textureBase) :
|
||||||
|
_request(url),
|
||||||
|
_reply(NULL),
|
||||||
|
_mapping(mapping),
|
||||||
|
_textureBase(textureBase.isValid() ? textureBase : url),
|
||||||
_fallback(fallback),
|
_fallback(fallback),
|
||||||
_attempts(0)
|
_startedLoading(false),
|
||||||
{
|
_failedToLoad(false),
|
||||||
|
_attempts(0) {
|
||||||
|
|
||||||
if (!url.isValid()) {
|
if (!url.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_modelRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||||
makeModelRequest();
|
|
||||||
|
|
||||||
QUrl mappingURL = url;
|
// if we already have a mapping (because we're an LOD), hold off on loading until we're requested
|
||||||
QString path = url.path();
|
if (mapping.isEmpty()) {
|
||||||
mappingURL.setPath(path.left(path.lastIndexOf('.')) + ".fst");
|
makeRequest();
|
||||||
QNetworkRequest mappingRequest(mappingURL);
|
}
|
||||||
mappingRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
|
||||||
_mappingReply = Application::getInstance()->getNetworkAccessManager()->get(mappingRequest);
|
|
||||||
|
|
||||||
connect(_mappingReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
|
|
||||||
connect(_mappingReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleMappingReplyError()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkGeometry::~NetworkGeometry() {
|
NetworkGeometry::~NetworkGeometry() {
|
||||||
if (_modelReply != NULL) {
|
if (_reply != NULL) {
|
||||||
delete _modelReply;
|
delete _reply;
|
||||||
}
|
}
|
||||||
if (_mappingReply != NULL) {
|
|
||||||
delete _mappingReply;
|
|
||||||
}
|
}
|
||||||
foreach (const NetworkMesh& mesh, _meshes) {
|
|
||||||
glDeleteBuffers(1, &mesh.indexBufferID);
|
QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis) const {
|
||||||
glDeleteBuffers(1, &mesh.vertexBufferID);
|
if (_lodParent.data() != this) {
|
||||||
|
return _lodParent.data()->getLODOrFallback(distance, hysteresis);
|
||||||
}
|
}
|
||||||
|
if (_failedToLoad && _fallback) {
|
||||||
|
return _fallback;
|
||||||
|
}
|
||||||
|
QSharedPointer<NetworkGeometry> lod = _lodParent;
|
||||||
|
float lodDistance = 0.0f;
|
||||||
|
QMap<float, QSharedPointer<NetworkGeometry> >::const_iterator it = _lods.upperBound(distance);
|
||||||
|
if (it != _lods.constBegin()) {
|
||||||
|
it = it - 1;
|
||||||
|
lod = it.value();
|
||||||
|
lodDistance = it.key();
|
||||||
|
}
|
||||||
|
if (hysteresis != NO_HYSTERESIS && hysteresis != lodDistance) {
|
||||||
|
// if we previously selected a different distance, make sure we've moved far enough to justify switching
|
||||||
|
const float HYSTERESIS_PROPORTION = 0.1f;
|
||||||
|
if (glm::abs(distance - qMax(hysteresis, lodDistance)) / fabsf(hysteresis - lodDistance) < HYSTERESIS_PROPORTION) {
|
||||||
|
return getLODOrFallback(hysteresis, hysteresis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lod->isLoaded()) {
|
||||||
|
hysteresis = lodDistance;
|
||||||
|
return lod;
|
||||||
|
}
|
||||||
|
// if the ideal LOD isn't loaded, we need to make sure it's started to load, and possibly return the closest loaded one
|
||||||
|
if (!lod->_startedLoading) {
|
||||||
|
lod->makeRequest();
|
||||||
|
}
|
||||||
|
float closestDistance = FLT_MAX;
|
||||||
|
if (isLoaded()) {
|
||||||
|
lod = _lodParent;
|
||||||
|
closestDistance = distance;
|
||||||
|
}
|
||||||
|
for (it = _lods.constBegin(); it != _lods.constEnd(); it++) {
|
||||||
|
float distanceToLOD = glm::abs(distance - it.key());
|
||||||
|
if (it.value()->isLoaded() && distanceToLOD < closestDistance) {
|
||||||
|
lod = it.value();
|
||||||
|
closestDistance = distanceToLOD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hysteresis = NO_HYSTERESIS;
|
||||||
|
return lod;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec4 NetworkGeometry::computeAverageColor() const {
|
glm::vec4 NetworkGeometry::computeAverageColor() const {
|
||||||
|
@ -364,20 +400,167 @@ glm::vec4 NetworkGeometry::computeAverageColor() const {
|
||||||
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
|
return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::makeModelRequest() {
|
void NetworkGeometry::makeRequest() {
|
||||||
_modelReply = Application::getInstance()->getNetworkAccessManager()->get(_modelRequest);
|
_startedLoading = true;
|
||||||
|
_reply = Application::getInstance()->getNetworkAccessManager()->get(_request);
|
||||||
|
|
||||||
connect(_modelReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(maybeReadModelWithMapping()));
|
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
|
||||||
connect(_modelReply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleModelReplyError()));
|
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::handleModelReplyError() {
|
void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
|
||||||
QDebug debug = qDebug() << _modelReply->errorString();
|
if (!_reply->isFinished()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QNetworkReply::NetworkError error = _modelReply->error();
|
QUrl url = _reply->url();
|
||||||
_modelReply->disconnect(this);
|
QByteArray data = _reply->readAll();
|
||||||
_modelReply->deleteLater();
|
_reply->disconnect(this);
|
||||||
_modelReply = NULL;
|
_reply->deleteLater();
|
||||||
|
_reply = NULL;
|
||||||
|
|
||||||
|
if (url.path().toLower().endsWith(".fst")) {
|
||||||
|
// it's a mapping file; parse it and get the mesh filename
|
||||||
|
_mapping = readMapping(data);
|
||||||
|
QString filename = _mapping.value("filename").toString();
|
||||||
|
if (filename.isNull()) {
|
||||||
|
qDebug() << "Mapping file " << url << " has no filename.";
|
||||||
|
_failedToLoad = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
QString texdir = _mapping.value("texdir").toString();
|
||||||
|
if (!texdir.isNull()) {
|
||||||
|
if (!texdir.endsWith('/')) {
|
||||||
|
texdir += '/';
|
||||||
|
}
|
||||||
|
_textureBase = url.resolved(texdir);
|
||||||
|
}
|
||||||
|
QVariantHash lods = _mapping.value("lod").toHash();
|
||||||
|
for (QVariantHash::const_iterator it = lods.begin(); it != lods.end(); it++) {
|
||||||
|
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url.resolved(it.key()),
|
||||||
|
QSharedPointer<NetworkGeometry>(), _mapping, _textureBase));
|
||||||
|
geometry->setLODParent(_lodParent);
|
||||||
|
_lods.insert(it.value().toFloat(), geometry);
|
||||||
|
}
|
||||||
|
_request.setUrl(url.resolved(filename));
|
||||||
|
|
||||||
|
// make the request immediately only if we have no LODs to switch between
|
||||||
|
_startedLoading = false;
|
||||||
|
if (_lods.isEmpty()) {
|
||||||
|
makeRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(data) : readFBX(data, _mapping);
|
||||||
|
|
||||||
|
} catch (const QString& error) {
|
||||||
|
qDebug() << "Error reading " << url << ": " << error;
|
||||||
|
_failedToLoad = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
||||||
|
NetworkMesh networkMesh = { QOpenGLBuffer(QOpenGLBuffer::IndexBuffer), QOpenGLBuffer(QOpenGLBuffer::VertexBuffer) };
|
||||||
|
|
||||||
|
int totalIndices = 0;
|
||||||
|
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||||
|
NetworkMeshPart networkPart;
|
||||||
|
if (!part.diffuseFilename.isEmpty()) {
|
||||||
|
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||||
|
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
|
||||||
|
}
|
||||||
|
if (!part.normalFilename.isEmpty()) {
|
||||||
|
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||||
|
_textureBase.resolved(QUrl(part.normalFilename)), true);
|
||||||
|
}
|
||||||
|
networkMesh.parts.append(networkPart);
|
||||||
|
|
||||||
|
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
networkMesh.indexBuffer.create();
|
||||||
|
networkMesh.indexBuffer.bind();
|
||||||
|
networkMesh.indexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||||
|
networkMesh.indexBuffer.allocate(totalIndices * sizeof(int));
|
||||||
|
int offset = 0;
|
||||||
|
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||||
|
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
|
||||||
|
part.quadIndices.constData());
|
||||||
|
offset += part.quadIndices.size() * sizeof(int);
|
||||||
|
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
|
||||||
|
part.triangleIndices.constData());
|
||||||
|
offset += part.triangleIndices.size() * sizeof(int);
|
||||||
|
}
|
||||||
|
networkMesh.indexBuffer.release();
|
||||||
|
|
||||||
|
networkMesh.vertexBuffer.create();
|
||||||
|
networkMesh.vertexBuffer.bind();
|
||||||
|
networkMesh.vertexBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||||
|
|
||||||
|
// if we don't need to do any blending or springing, then the positions/normals can be static
|
||||||
|
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
||||||
|
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
||||||
|
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
||||||
|
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||||
|
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||||
|
|
||||||
|
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(normalsOffset, mesh.normals.constData(), mesh.normals.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(tangentsOffset, mesh.tangents.constData(),
|
||||||
|
mesh.tangents.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
||||||
|
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||||
|
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
|
||||||
|
// if there's no springiness, then the cluster indices/weights can be static
|
||||||
|
} else if (mesh.springiness == 0.0f) {
|
||||||
|
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
||||||
|
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
||||||
|
networkMesh.vertexBuffer.allocate(clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
networkMesh.vertexBuffer.write(clusterIndicesOffset, mesh.clusterIndices.constData(),
|
||||||
|
mesh.clusterIndices.size() * sizeof(glm::vec4));
|
||||||
|
networkMesh.vertexBuffer.write(clusterWeightsOffset, mesh.clusterWeights.constData(),
|
||||||
|
mesh.clusterWeights.size() * sizeof(glm::vec4));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
||||||
|
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
||||||
|
networkMesh.vertexBuffer.allocate(texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
networkMesh.vertexBuffer.write(0, mesh.tangents.constData(), mesh.tangents.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(colorsOffset, mesh.colors.constData(), mesh.colors.size() * sizeof(glm::vec3));
|
||||||
|
networkMesh.vertexBuffer.write(texCoordsOffset, mesh.texCoords.constData(),
|
||||||
|
mesh.texCoords.size() * sizeof(glm::vec2));
|
||||||
|
}
|
||||||
|
|
||||||
|
networkMesh.vertexBuffer.release();
|
||||||
|
|
||||||
|
_meshes.append(networkMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkGeometry::handleReplyError() {
|
||||||
|
QDebug debug = qDebug() << _reply->errorString();
|
||||||
|
|
||||||
|
QNetworkReply::NetworkError error = _reply->error();
|
||||||
|
_reply->disconnect(this);
|
||||||
|
_reply->deleteLater();
|
||||||
|
_reply = NULL;
|
||||||
|
|
||||||
// retry for certain types of failures
|
// retry for certain types of failures
|
||||||
switch (error) {
|
switch (error) {
|
||||||
|
@ -394,164 +577,19 @@ void NetworkGeometry::handleModelReplyError() {
|
||||||
const int MAX_ATTEMPTS = 8;
|
const int MAX_ATTEMPTS = 8;
|
||||||
const int BASE_DELAY_MS = 1000;
|
const int BASE_DELAY_MS = 1000;
|
||||||
if (++_attempts < MAX_ATTEMPTS) {
|
if (++_attempts < MAX_ATTEMPTS) {
|
||||||
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeModelRequest()));
|
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest()));
|
||||||
debug << " -- retrying...";
|
debug << " -- retrying...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// fall through to final failure
|
// fall through to final failure
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
maybeLoadFallback();
|
_failedToLoad = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkGeometry::handleMappingReplyError() {
|
|
||||||
_mappingReply->disconnect(this);
|
|
||||||
_mappingReply->deleteLater();
|
|
||||||
_mappingReply = NULL;
|
|
||||||
|
|
||||||
maybeReadModelWithMapping();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::maybeReadModelWithMapping() {
|
|
||||||
if (_modelReply == NULL || !_modelReply->isFinished() || (_mappingReply != NULL && !_mappingReply->isFinished())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl url = _modelReply->url();
|
|
||||||
QByteArray model = _modelReply->readAll();
|
|
||||||
_modelReply->disconnect(this);
|
|
||||||
_modelReply->deleteLater();
|
|
||||||
_modelReply = NULL;
|
|
||||||
|
|
||||||
QByteArray mapping;
|
|
||||||
if (_mappingReply != NULL) {
|
|
||||||
mapping = _mappingReply->readAll();
|
|
||||||
_mappingReply->disconnect(this);
|
|
||||||
_mappingReply->deleteLater();
|
|
||||||
_mappingReply = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
_geometry = url.path().toLower().endsWith(".svo") ? readSVO(model) : readFBX(model, mapping);
|
|
||||||
|
|
||||||
} catch (const QString& error) {
|
|
||||||
qDebug() << "Error reading " << url << ": " << error;
|
|
||||||
maybeLoadFallback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (const FBXMesh& mesh, _geometry.meshes) {
|
|
||||||
NetworkMesh networkMesh;
|
|
||||||
|
|
||||||
int totalIndices = 0;
|
|
||||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
|
||||||
NetworkMeshPart networkPart;
|
|
||||||
QString basePath = url.path();
|
|
||||||
basePath = basePath.left(basePath.lastIndexOf('/') + 1);
|
|
||||||
if (!part.diffuseFilename.isEmpty()) {
|
|
||||||
url.setPath(basePath + part.diffuseFilename);
|
|
||||||
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(url, false, mesh.isEye);
|
|
||||||
}
|
|
||||||
if (!part.normalFilename.isEmpty()) {
|
|
||||||
url.setPath(basePath + part.normalFilename);
|
|
||||||
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(url, true);
|
|
||||||
}
|
|
||||||
networkMesh.parts.append(networkPart);
|
|
||||||
|
|
||||||
totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
glGenBuffers(1, &networkMesh.indexBufferID);
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndices * sizeof(int), NULL, GL_STATIC_DRAW);
|
|
||||||
int offset = 0;
|
|
||||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
|
||||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.quadIndices.size() * sizeof(int),
|
|
||||||
part.quadIndices.constData());
|
|
||||||
offset += part.quadIndices.size() * sizeof(int);
|
|
||||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, part.triangleIndices.size() * sizeof(int),
|
|
||||||
part.triangleIndices.constData());
|
|
||||||
offset += part.triangleIndices.size() * sizeof(int);
|
|
||||||
}
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
||||||
|
|
||||||
glGenBuffers(1, &networkMesh.vertexBufferID);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
|
||||||
|
|
||||||
// if we don't need to do any blending or springing, then the positions/normals can be static
|
|
||||||
if (mesh.blendshapes.isEmpty() && mesh.springiness == 0.0f) {
|
|
||||||
int normalsOffset = mesh.vertices.size() * sizeof(glm::vec3);
|
|
||||||
int tangentsOffset = normalsOffset + mesh.normals.size() * sizeof(glm::vec3);
|
|
||||||
int colorsOffset = tangentsOffset + mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
|
||||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
NULL, GL_STATIC_DRAW);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.vertices.size() * sizeof(glm::vec3), mesh.vertices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, normalsOffset, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, tangentsOffset, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
|
|
||||||
mesh.texCoords.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterIndices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterWeights.constData());
|
|
||||||
|
|
||||||
// if there's no springiness, then the cluster indices/weights can be static
|
|
||||||
} else if (mesh.springiness == 0.0f) {
|
|
||||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
int clusterIndicesOffset = texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2);
|
|
||||||
int clusterWeightsOffset = clusterIndicesOffset + mesh.clusterIndices.size() * sizeof(glm::vec4);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, clusterWeightsOffset + mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
NULL, GL_STATIC_DRAW);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2), mesh.texCoords.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterIndicesOffset, mesh.clusterIndices.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterIndices.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, clusterWeightsOffset, mesh.clusterWeights.size() * sizeof(glm::vec4),
|
|
||||||
mesh.clusterWeights.constData());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3);
|
|
||||||
int texCoordsOffset = colorsOffset + mesh.colors.size() * sizeof(glm::vec3);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, texCoordsOffset + mesh.texCoords.size() * sizeof(glm::vec2), NULL, GL_STATIC_DRAW);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh.tangents.size() * sizeof(glm::vec3), mesh.tangents.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, colorsOffset, mesh.colors.size() * sizeof(glm::vec3), mesh.colors.constData());
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, texCoordsOffset, mesh.texCoords.size() * sizeof(glm::vec2),
|
|
||||||
mesh.texCoords.constData());
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
||||||
|
|
||||||
_meshes.append(networkMesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit loaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::loadFallback() {
|
|
||||||
_geometry = _fallback->_geometry;
|
|
||||||
_meshes = _fallback->_meshes;
|
|
||||||
emit loaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkGeometry::maybeLoadFallback() {
|
|
||||||
if (_fallback) {
|
|
||||||
if (_fallback->isLoaded()) {
|
|
||||||
loadFallback();
|
|
||||||
} else {
|
|
||||||
connect(_fallback.data(), SIGNAL(loaded()), SLOT(loadFallback()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkMeshPart::isTranslucent() const {
|
bool NetworkMeshPart::isTranslucent() const {
|
||||||
return diffuseTexture && diffuseTexture->isTranslucent();
|
return diffuseTexture && diffuseTexture->isTranslucent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,20 @@
|
||||||
#ifndef __interface__GeometryCache__
|
#ifndef __interface__GeometryCache__
|
||||||
#define __interface__GeometryCache__
|
#define __interface__GeometryCache__
|
||||||
|
|
||||||
|
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QMap>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QOpenGLBuffer>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QWeakPointer>
|
#include <QWeakPointer>
|
||||||
|
|
||||||
#include "FBXReader.h"
|
#include "FBXReader.h"
|
||||||
#include "InterfaceConfig.h"
|
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class QOpenGLBuffer;
|
|
||||||
|
|
||||||
class NetworkGeometry;
|
class NetworkGeometry;
|
||||||
class NetworkMesh;
|
class NetworkMesh;
|
||||||
|
@ -59,41 +62,52 @@ class NetworkGeometry : public QObject {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback);
|
/// A hysteresis value indicating that we have no state memory.
|
||||||
|
static const float NO_HYSTERESIS;
|
||||||
|
|
||||||
|
NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& fallback,
|
||||||
|
const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl());
|
||||||
~NetworkGeometry();
|
~NetworkGeometry();
|
||||||
|
|
||||||
|
/// Checks whether the geometry is fulled loaded.
|
||||||
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
|
bool isLoaded() const { return !_geometry.joints.isEmpty(); }
|
||||||
|
|
||||||
|
/// Returns a pointer to the geometry appropriate for the specified distance.
|
||||||
|
/// \param hysteresis a hysteresis parameter that prevents rapid model switching
|
||||||
|
QSharedPointer<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis) const;
|
||||||
|
|
||||||
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
||||||
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
||||||
|
|
||||||
/// Returns the average color of all meshes in the geometry.
|
/// Returns the average color of all meshes in the geometry.
|
||||||
glm::vec4 computeAverageColor() const;
|
glm::vec4 computeAverageColor() const;
|
||||||
|
|
||||||
signals:
|
|
||||||
|
|
||||||
void loaded();
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
void makeModelRequest();
|
void makeRequest();
|
||||||
void handleModelReplyError();
|
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void handleMappingReplyError();
|
void handleReplyError();
|
||||||
void maybeReadModelWithMapping();
|
|
||||||
void loadFallback();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void maybeLoadFallback();
|
friend class GeometryCache;
|
||||||
|
|
||||||
QNetworkRequest _modelRequest;
|
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
|
||||||
QNetworkReply* _modelReply;
|
|
||||||
QNetworkReply* _mappingReply;
|
QNetworkRequest _request;
|
||||||
|
QNetworkReply* _reply;
|
||||||
|
QVariantHash _mapping;
|
||||||
|
QUrl _textureBase;
|
||||||
QSharedPointer<NetworkGeometry> _fallback;
|
QSharedPointer<NetworkGeometry> _fallback;
|
||||||
|
bool _startedLoading;
|
||||||
|
bool _failedToLoad;
|
||||||
|
|
||||||
int _attempts;
|
int _attempts;
|
||||||
|
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
|
||||||
FBXGeometry _geometry;
|
FBXGeometry _geometry;
|
||||||
QVector<NetworkMesh> _meshes;
|
QVector<NetworkMesh> _meshes;
|
||||||
|
|
||||||
|
QWeakPointer<NetworkGeometry> _lodParent;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The state associated with a single mesh part.
|
/// The state associated with a single mesh part.
|
||||||
|
@ -110,8 +124,8 @@ public:
|
||||||
class NetworkMesh {
|
class NetworkMesh {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
GLuint indexBufferID;
|
QOpenGLBuffer indexBuffer;
|
||||||
GLuint vertexBufferID;
|
QOpenGLBuffer vertexBuffer;
|
||||||
|
|
||||||
QVector<NetworkMeshPart> parts;
|
QVector<NetworkMeshPart> parts;
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,16 @@ void Model::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::simulate(float deltaTime) {
|
void Model::simulate(float deltaTime) {
|
||||||
|
// update our LOD
|
||||||
|
if (_geometry) {
|
||||||
|
QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(glm::distance(_translation,
|
||||||
|
Application::getInstance()->getCamera()->getPosition()), _lodHysteresis);
|
||||||
|
if (_geometry != geometry) {
|
||||||
|
deleteGeometry();
|
||||||
|
_dilatedTextures.clear();
|
||||||
|
_geometry = geometry;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -409,8 +419,9 @@ void Model::setURL(const QUrl& url, const QUrl& fallback) {
|
||||||
// delete our local geometry and custom textures
|
// delete our local geometry and custom textures
|
||||||
deleteGeometry();
|
deleteGeometry();
|
||||||
_dilatedTextures.clear();
|
_dilatedTextures.clear();
|
||||||
|
_lodHysteresis = NetworkGeometry::NO_HYSTERESIS;
|
||||||
|
|
||||||
_geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
|
_baseGeometry = _geometry = Application::getInstance()->getGeometryCache()->getGeometry(url, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec4 Model::computeAverageColor() const {
|
glm::vec4 Model::computeAverageColor() const {
|
||||||
|
@ -446,9 +457,9 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
|
bool Model::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||||
ModelCollisionInfo& collisionInfo, float boneScale, int skipIndex) const {
|
CollisionList& collisions, float boneScale, int skipIndex) const {
|
||||||
int jointIndex = -1;
|
bool collided = false;
|
||||||
const glm::vec3 relativeCenter = penetratorCenter - _translation;
|
const glm::vec3 relativeCenter = penetratorCenter - _translation;
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
glm::vec3 totalPenetration;
|
glm::vec3 totalPenetration;
|
||||||
|
@ -477,22 +488,22 @@ bool Model::findSphereCollision(const glm::vec3& penetratorCenter, float penetra
|
||||||
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
|
if (findSphereCapsuleConePenetration(relativeCenter, penetratorRadius, start, end,
|
||||||
startRadius, endRadius, bonePenetration)) {
|
startRadius, endRadius, bonePenetration)) {
|
||||||
totalPenetration = addPenetrations(totalPenetration, bonePenetration);
|
totalPenetration = addPenetrations(totalPenetration, bonePenetration);
|
||||||
// BUG: we currently overwrite the jointIndex with the last one found
|
CollisionInfo* collision = collisions.getNewCollision();
|
||||||
// which can cause incorrect collisions when colliding against more than
|
if (collision) {
|
||||||
// one joint.
|
collision->_type = MODEL_COLLISION;
|
||||||
// TODO: fix this.
|
collision->_data = (void*)(this);
|
||||||
jointIndex = i;
|
collision->_flags = i;
|
||||||
|
collision->_contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
|
||||||
|
collision->_penetration = totalPenetration;
|
||||||
|
collided = true;
|
||||||
|
} else {
|
||||||
|
// collisions are full, so we might as well break
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
outerContinue: ;
|
outerContinue: ;
|
||||||
}
|
}
|
||||||
if (jointIndex != -1) {
|
return collided;
|
||||||
// don't store collisionInfo._model at this stage, let the outer context do that
|
|
||||||
collisionInfo._penetration = totalPenetration;
|
|
||||||
collisionInfo._jointIndex = jointIndex;
|
|
||||||
collisionInfo._contactPoint = penetratorCenter + penetratorRadius * glm::normalize(totalPenetration);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::updateJointState(int index) {
|
void Model::updateJointState(int index) {
|
||||||
|
@ -725,24 +736,30 @@ void Model::renderCollisionProxies(float alpha) {
|
||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::collisionHitsMoveableJoint(ModelCollisionInfo& collision) const {
|
bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
|
||||||
|
if (collision._type == MODEL_COLLISION) {
|
||||||
// the joint is pokable by a collision if it exists and is free to move
|
// the joint is pokable by a collision if it exists and is free to move
|
||||||
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._jointIndex];
|
const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._flags];
|
||||||
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
|
if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// an empty freeLineage means the joint can't move
|
// an empty freeLineage means the joint can't move
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
const QVector<int>& freeLineage = geometry.joints.at(collision._jointIndex).freeLineage;
|
int jointIndex = collision._flags;
|
||||||
|
const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
|
||||||
return !freeLineage.isEmpty();
|
return !freeLineage.isEmpty();
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::applyCollision(CollisionInfo& collision) {
|
||||||
|
if (collision._type != MODEL_COLLISION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void Model::applyCollision(ModelCollisionInfo& collision) {
|
|
||||||
// This needs work. At the moment it can wiggle joints that are free to move (such as arms)
|
|
||||||
// but unmovable joints (such as torso) cannot be influenced at all.
|
|
||||||
glm::vec3 jointPosition(0.f);
|
glm::vec3 jointPosition(0.f);
|
||||||
if (getJointPosition(collision._jointIndex, jointPosition)) {
|
int jointIndex = collision._flags;
|
||||||
int jointIndex = collision._jointIndex;
|
if (getJointPosition(jointIndex, jointPosition)) {
|
||||||
const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
|
const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
|
||||||
if (joint.parentIndex != -1) {
|
if (joint.parentIndex != -1) {
|
||||||
// compute the approximate distance (travel) that the joint needs to move
|
// compute the approximate distance (travel) that the joint needs to move
|
||||||
|
@ -791,12 +808,16 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
||||||
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
|
(networkMesh.getTranslucentPartCount() == networkMesh.parts.size())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, networkMesh.indexBufferID);
|
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
|
||||||
|
|
||||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
int vertexCount = mesh.vertices.size();
|
int vertexCount = mesh.vertices.size();
|
||||||
|
if (vertexCount == 0) {
|
||||||
|
// sanity check
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, networkMesh.vertexBufferID);
|
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
|
||||||
|
|
||||||
ProgramObject* program = &_program;
|
ProgramObject* program = &_program;
|
||||||
ProgramObject* skinProgram = &_skinProgram;
|
ProgramObject* skinProgram = &_skinProgram;
|
||||||
|
@ -904,11 +925,11 @@ void Model::renderMeshes(float alpha, bool translucent) {
|
||||||
qint64 offset = 0;
|
qint64 offset = 0;
|
||||||
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
for (int j = 0; j < networkMesh.parts.size(); j++) {
|
||||||
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
const NetworkMeshPart& networkPart = networkMesh.parts.at(j);
|
||||||
|
const FBXMeshPart& part = mesh.parts.at(j);
|
||||||
if (networkPart.isTranslucent() != translucent) {
|
if (networkPart.isTranslucent() != translucent) {
|
||||||
|
offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const FBXMeshPart& part = mesh.parts.at(j);
|
|
||||||
|
|
||||||
// apply material properties
|
// apply material properties
|
||||||
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
glm::vec4 diffuse = glm::vec4(part.diffuseColor, alpha);
|
||||||
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
glm::vec4 specular = glm::vec4(part.specularColor, alpha);
|
||||||
|
|
|
@ -17,16 +17,6 @@
|
||||||
#include "ProgramObject.h"
|
#include "ProgramObject.h"
|
||||||
#include "TextureCache.h"
|
#include "TextureCache.h"
|
||||||
|
|
||||||
class Model;
|
|
||||||
|
|
||||||
// TODO: Andrew to move this into its own file
|
|
||||||
class ModelCollisionInfo : public CollisionInfo {
|
|
||||||
public:
|
|
||||||
ModelCollisionInfo() : CollisionInfo(), _model(NULL), _jointIndex(-1) {}
|
|
||||||
Model* _model;
|
|
||||||
int _jointIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A generic 3D model displaying geometry loaded from a URL.
|
/// A generic 3D model displaying geometry loaded from a URL.
|
||||||
class Model : public QObject {
|
class Model : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -162,17 +152,18 @@ public:
|
||||||
|
|
||||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||||
|
|
||||||
bool findSphereCollision(const glm::vec3& penetratorCenter, float penetratorRadius,
|
bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
|
||||||
ModelCollisionInfo& collision, float boneScale = 1.0f, int skipIndex = -1) const;
|
CollisionList& collisions, float boneScale = 1.0f, int skipIndex = -1) const;
|
||||||
|
|
||||||
void renderCollisionProxies(float alpha);
|
void renderCollisionProxies(float alpha);
|
||||||
|
|
||||||
|
/// \param collision details about the collisions
|
||||||
/// \return true if the collision is against a moveable joint
|
/// \return true if the collision is against a moveable joint
|
||||||
bool collisionHitsMoveableJoint(ModelCollisionInfo& collision) const;
|
bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
|
||||||
|
|
||||||
/// \param collisionInfo info about the collision
|
/// \param collision details about the collision
|
||||||
/// Use the collisionInfo to affect the model
|
/// Use the collision to affect the model
|
||||||
void applyCollision(ModelCollisionInfo& collisionInfo);
|
void applyCollision(CollisionInfo& collision);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
@ -236,6 +227,9 @@ private:
|
||||||
void deleteGeometry();
|
void deleteGeometry();
|
||||||
void renderMeshes(float alpha, bool translucent);
|
void renderMeshes(float alpha, bool translucent);
|
||||||
|
|
||||||
|
QSharedPointer<NetworkGeometry> _baseGeometry;
|
||||||
|
float _lodHysteresis;
|
||||||
|
|
||||||
float _pupilDilation;
|
float _pupilDilation;
|
||||||
std::vector<float> _blendshapeCoefficients;
|
std::vector<float> _blendshapeCoefficients;
|
||||||
|
|
||||||
|
|
61
interface/src/ui/Base3DOverlay.cpp
Normal file
61
interface/src/ui/Base3DOverlay.cpp
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// Base3DOverlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Base3DOverlay.h"
|
||||||
|
#include "TextRenderer.h"
|
||||||
|
|
||||||
|
const glm::vec3 DEFAULT_POSITION = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
const float DEFAULT_LINE_WIDTH = 1.0f;
|
||||||
|
|
||||||
|
Base3DOverlay::Base3DOverlay() :
|
||||||
|
_position(DEFAULT_POSITION),
|
||||||
|
_lineWidth(DEFAULT_LINE_WIDTH)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Base3DOverlay::~Base3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Base3DOverlay::setProperties(const QScriptValue& properties) {
|
||||||
|
Overlay::setProperties(properties);
|
||||||
|
|
||||||
|
QScriptValue position = properties.property("position");
|
||||||
|
|
||||||
|
// if "position" property was not there, check to see if they included aliases: start, point, p1
|
||||||
|
if (!position.isValid()) {
|
||||||
|
position = properties.property("start");
|
||||||
|
if (!position.isValid()) {
|
||||||
|
position = properties.property("p1");
|
||||||
|
if (!position.isValid()) {
|
||||||
|
position = properties.property("point");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position.isValid()) {
|
||||||
|
QScriptValue x = position.property("x");
|
||||||
|
QScriptValue y = position.property("y");
|
||||||
|
QScriptValue z = position.property("z");
|
||||||
|
if (x.isValid() && y.isValid() && z.isValid()) {
|
||||||
|
glm::vec3 newPosition;
|
||||||
|
newPosition.x = x.toVariant().toFloat();
|
||||||
|
newPosition.y = y.toVariant().toFloat();
|
||||||
|
newPosition.z = z.toVariant().toFloat();
|
||||||
|
setPosition(newPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.property("lineWidth").isValid()) {
|
||||||
|
setLineWidth(properties.property("lineWidth").toVariant().toFloat());
|
||||||
|
}
|
||||||
|
}
|
36
interface/src/ui/Base3DOverlay.h
Normal file
36
interface/src/ui/Base3DOverlay.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// Base3DOverlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Base3DOverlay__
|
||||||
|
#define __interface__Base3DOverlay__
|
||||||
|
|
||||||
|
#include "Overlay.h"
|
||||||
|
|
||||||
|
class Base3DOverlay : public Overlay {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Base3DOverlay();
|
||||||
|
~Base3DOverlay();
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const glm::vec3& getPosition() const { return _position; }
|
||||||
|
float getLineWidth() const { return _lineWidth; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setPosition(const glm::vec3& position) { _position = position; }
|
||||||
|
void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
|
||||||
|
|
||||||
|
virtual void setProperties(const QScriptValue& properties);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
glm::vec3 _position;
|
||||||
|
float _lineWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Base3DOverlay__) */
|
44
interface/src/ui/Cube3DOverlay.cpp
Normal file
44
interface/src/ui/Cube3DOverlay.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// Cube3DOverlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Cube3DOverlay.h"
|
||||||
|
|
||||||
|
Cube3DOverlay::Cube3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Cube3DOverlay::~Cube3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cube3DOverlay::render() {
|
||||||
|
if (!_visible) {
|
||||||
|
return; // do nothing if we're not visible
|
||||||
|
}
|
||||||
|
|
||||||
|
const float MAX_COLOR = 255;
|
||||||
|
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
|
||||||
|
|
||||||
|
|
||||||
|
glDisable(GL_LIGHTING);
|
||||||
|
glPushMatrix();
|
||||||
|
glTranslatef(_position.x + _size * 0.5f,
|
||||||
|
_position.y + _size * 0.5f,
|
||||||
|
_position.z + _size * 0.5f);
|
||||||
|
glLineWidth(_lineWidth);
|
||||||
|
if (_isSolid) {
|
||||||
|
glutSolidCube(_size);
|
||||||
|
} else {
|
||||||
|
glutWireCube(_size);
|
||||||
|
}
|
||||||
|
glPopMatrix();
|
||||||
|
|
||||||
|
}
|
23
interface/src/ui/Cube3DOverlay.h
Normal file
23
interface/src/ui/Cube3DOverlay.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// Cube3DOverlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Cube3DOverlay__
|
||||||
|
#define __interface__Cube3DOverlay__
|
||||||
|
|
||||||
|
#include "Volume3DOverlay.h"
|
||||||
|
|
||||||
|
class Cube3DOverlay : public Volume3DOverlay {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Cube3DOverlay();
|
||||||
|
~Cube3DOverlay();
|
||||||
|
virtual void render();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Cube3DOverlay__) */
|
145
interface/src/ui/ImageOverlay.cpp
Normal file
145
interface/src/ui/ImageOverlay.cpp
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
//
|
||||||
|
// ImageOverlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QSvgRenderer>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "ImageOverlay.h"
|
||||||
|
|
||||||
|
ImageOverlay::ImageOverlay() :
|
||||||
|
_textureID(0),
|
||||||
|
_renderImage(false),
|
||||||
|
_textureBound(false),
|
||||||
|
_wantClipFromImage(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageOverlay::~ImageOverlay() {
|
||||||
|
if (_parent && _textureID) {
|
||||||
|
// do we need to call this?
|
||||||
|
//_parent->deleteTexture(_textureID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
|
||||||
|
void ImageOverlay::setImageURL(const QUrl& url) {
|
||||||
|
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
|
||||||
|
QNetworkAccessManager* manager = new QNetworkAccessManager(this);
|
||||||
|
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
|
||||||
|
manager->get(QNetworkRequest(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageOverlay::replyFinished(QNetworkReply* reply) {
|
||||||
|
|
||||||
|
// replace our byte array with the downloaded data
|
||||||
|
QByteArray rawData = reply->readAll();
|
||||||
|
_textureImage.loadFromData(rawData);
|
||||||
|
_renderImage = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageOverlay::render() {
|
||||||
|
if (!_visible) {
|
||||||
|
return; // do nothing if we're not visible
|
||||||
|
}
|
||||||
|
if (_renderImage && !_textureBound) {
|
||||||
|
_textureID = _parent->bindTexture(_textureImage);
|
||||||
|
_textureBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_renderImage) {
|
||||||
|
glEnable(GL_TEXTURE_2D);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, _textureID);
|
||||||
|
}
|
||||||
|
const float MAX_COLOR = 255;
|
||||||
|
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
|
||||||
|
|
||||||
|
float imageWidth = _textureImage.width();
|
||||||
|
float imageHeight = _textureImage.height();
|
||||||
|
|
||||||
|
QRect fromImage;
|
||||||
|
if (_wantClipFromImage) {
|
||||||
|
fromImage = _fromImage;
|
||||||
|
} else {
|
||||||
|
fromImage.setX(0);
|
||||||
|
fromImage.setY(0);
|
||||||
|
fromImage.setWidth(imageWidth);
|
||||||
|
fromImage.setHeight(imageHeight);
|
||||||
|
}
|
||||||
|
float x = fromImage.x() / imageWidth;
|
||||||
|
float y = fromImage.y() / imageHeight;
|
||||||
|
float w = fromImage.width() / imageWidth; // ?? is this what we want? not sure
|
||||||
|
float h = fromImage.height() / imageHeight;
|
||||||
|
|
||||||
|
glBegin(GL_QUADS);
|
||||||
|
if (_renderImage) {
|
||||||
|
glTexCoord2f(x, 1.0f - y);
|
||||||
|
}
|
||||||
|
glVertex2f(_bounds.left(), _bounds.top());
|
||||||
|
|
||||||
|
if (_renderImage) {
|
||||||
|
glTexCoord2f(x + w, 1.0f - y);
|
||||||
|
}
|
||||||
|
glVertex2f(_bounds.right(), _bounds.top());
|
||||||
|
|
||||||
|
if (_renderImage) {
|
||||||
|
glTexCoord2f(x + w, 1.0f - (y + h));
|
||||||
|
}
|
||||||
|
glVertex2f(_bounds.right(), _bounds.bottom());
|
||||||
|
|
||||||
|
if (_renderImage) {
|
||||||
|
glTexCoord2f(x, 1.0f - (y + h));
|
||||||
|
}
|
||||||
|
glVertex2f(_bounds.left(), _bounds.bottom());
|
||||||
|
glEnd();
|
||||||
|
if (_renderImage) {
|
||||||
|
glDisable(GL_TEXTURE_2D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageOverlay::setProperties(const QScriptValue& properties) {
|
||||||
|
Overlay2D::setProperties(properties);
|
||||||
|
|
||||||
|
QScriptValue subImageBounds = properties.property("subImage");
|
||||||
|
if (subImageBounds.isValid()) {
|
||||||
|
QRect oldSubImageRect = _fromImage;
|
||||||
|
QRect subImageRect = _fromImage;
|
||||||
|
if (subImageBounds.property("x").isValid()) {
|
||||||
|
subImageRect.setX(subImageBounds.property("x").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
subImageRect.setX(oldSubImageRect.x());
|
||||||
|
}
|
||||||
|
if (subImageBounds.property("y").isValid()) {
|
||||||
|
subImageRect.setY(subImageBounds.property("y").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
subImageRect.setY(oldSubImageRect.y());
|
||||||
|
}
|
||||||
|
if (subImageBounds.property("width").isValid()) {
|
||||||
|
subImageRect.setWidth(subImageBounds.property("width").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
subImageRect.setWidth(oldSubImageRect.width());
|
||||||
|
}
|
||||||
|
if (subImageBounds.property("height").isValid()) {
|
||||||
|
subImageRect.setHeight(subImageBounds.property("height").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
subImageRect.setHeight(oldSubImageRect.height());
|
||||||
|
}
|
||||||
|
setClipFromSource(subImageRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptValue imageURL = properties.property("imageURL");
|
||||||
|
if (imageURL.isValid()) {
|
||||||
|
setImageURL(imageURL.toVariant().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
60
interface/src/ui/ImageOverlay.h
Normal file
60
interface/src/ui/ImageOverlay.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// ImageOverlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__ImageOverlay__
|
||||||
|
#define __interface__ImageOverlay__
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QScriptValue>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Overlay.h"
|
||||||
|
#include "Overlay2D.h"
|
||||||
|
|
||||||
|
class ImageOverlay : public Overlay2D {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ImageOverlay();
|
||||||
|
~ImageOverlay();
|
||||||
|
virtual void render();
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const QRect& getClipFromSource() const { return _fromImage; }
|
||||||
|
const QUrl& getImageURL() const { return _imageURL; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setClipFromSource(const QRect& bounds) { _fromImage = bounds; _wantClipFromImage = true; }
|
||||||
|
void setImageURL(const QUrl& url);
|
||||||
|
virtual void setProperties(const QScriptValue& properties);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void replyFinished(QNetworkReply* reply); // we actually want to hide this...
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QUrl _imageURL;
|
||||||
|
QImage _textureImage;
|
||||||
|
GLuint _textureID;
|
||||||
|
QRect _fromImage; // where from in the image to sample
|
||||||
|
bool _renderImage; // is there an image associated with this overlay, or is it just a colored rectangle
|
||||||
|
bool _textureBound; // has the texture been bound
|
||||||
|
bool _wantClipFromImage;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__ImageOverlay__) */
|
60
interface/src/ui/Line3DOverlay.cpp
Normal file
60
interface/src/ui/Line3DOverlay.cpp
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// Line3DOverlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include "Line3DOverlay.h"
|
||||||
|
|
||||||
|
|
||||||
|
Line3DOverlay::Line3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Line3DOverlay::~Line3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Line3DOverlay::render() {
|
||||||
|
if (!_visible) {
|
||||||
|
return; // do nothing if we're not visible
|
||||||
|
}
|
||||||
|
|
||||||
|
const float MAX_COLOR = 255;
|
||||||
|
glDisable(GL_LIGHTING);
|
||||||
|
glLineWidth(_lineWidth);
|
||||||
|
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
|
||||||
|
|
||||||
|
glBegin(GL_LINES);
|
||||||
|
glVertex3f(_position.x, _position.y, _position.z);
|
||||||
|
glVertex3f(_end.x, _end.y, _end.z);
|
||||||
|
glEnd();
|
||||||
|
glEnable(GL_LIGHTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Line3DOverlay::setProperties(const QScriptValue& properties) {
|
||||||
|
Base3DOverlay::setProperties(properties);
|
||||||
|
|
||||||
|
QScriptValue end = properties.property("end");
|
||||||
|
// if "end" property was not there, check to see if they included aliases: endPoint, or p2
|
||||||
|
if (!end.isValid()) {
|
||||||
|
end = properties.property("endPoint");
|
||||||
|
if (!end.isValid()) {
|
||||||
|
end = properties.property("p2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (end.isValid()) {
|
||||||
|
QScriptValue x = end.property("x");
|
||||||
|
QScriptValue y = end.property("y");
|
||||||
|
QScriptValue z = end.property("z");
|
||||||
|
if (x.isValid() && y.isValid() && z.isValid()) {
|
||||||
|
glm::vec3 newEnd;
|
||||||
|
newEnd.x = x.toVariant().toFloat();
|
||||||
|
newEnd.y = y.toVariant().toFloat();
|
||||||
|
newEnd.z = z.toVariant().toFloat();
|
||||||
|
setEnd(newEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
interface/src/ui/Line3DOverlay.h
Normal file
34
interface/src/ui/Line3DOverlay.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// Line3DOverlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Line3DOverlay__
|
||||||
|
#define __interface__Line3DOverlay__
|
||||||
|
|
||||||
|
#include "Base3DOverlay.h"
|
||||||
|
|
||||||
|
class Line3DOverlay : public Base3DOverlay {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Line3DOverlay();
|
||||||
|
~Line3DOverlay();
|
||||||
|
virtual void render();
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const glm::vec3& getEnd() const { return _end; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setEnd(const glm::vec3& end) { _end = end; }
|
||||||
|
|
||||||
|
virtual void setProperties(const QScriptValue& properties);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
glm::vec3 _end;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Line3DOverlay__) */
|
|
@ -121,6 +121,12 @@ LodToolsDialog::~LodToolsDialog() {
|
||||||
delete _boundaryLevelAdjust;
|
delete _boundaryLevelAdjust;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LodToolsDialog::reloadSliders() {
|
||||||
|
_lodSize->setValue(Menu::getInstance()->getVoxelSizeScale() / TREE_SCALE);
|
||||||
|
_boundaryLevelAdjust->setValue(Menu::getInstance()->getBoundaryLevelAdjust());
|
||||||
|
_feedback->setText(getFeedbackText());
|
||||||
|
}
|
||||||
|
|
||||||
void LodToolsDialog::sizeScaleValueChanged(int value) {
|
void LodToolsDialog::sizeScaleValueChanged(int value) {
|
||||||
float realValue = value * TREE_SCALE;
|
float realValue = value * TREE_SCALE;
|
||||||
Menu::getInstance()->setVoxelSizeScale(realValue);
|
Menu::getInstance()->setVoxelSizeScale(realValue);
|
||||||
|
|
|
@ -28,6 +28,7 @@ public slots:
|
||||||
void sizeScaleValueChanged(int value);
|
void sizeScaleValueChanged(int value);
|
||||||
void boundaryLevelValueChanged(int value);
|
void boundaryLevelValueChanged(int value);
|
||||||
void resetClicked(bool checked);
|
void resetClicked(bool checked);
|
||||||
|
void reloadSliders();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
|
55
interface/src/ui/Overlay.cpp
Normal file
55
interface/src/ui/Overlay.cpp
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
//
|
||||||
|
// Overlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QSvgRenderer>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Overlay.h"
|
||||||
|
|
||||||
|
|
||||||
|
Overlay::Overlay() :
|
||||||
|
_parent(NULL),
|
||||||
|
_alpha(DEFAULT_ALPHA),
|
||||||
|
_color(DEFAULT_BACKGROUND_COLOR),
|
||||||
|
_visible(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlay::init(QGLWidget* parent) {
|
||||||
|
_parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Overlay::~Overlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlay::setProperties(const QScriptValue& properties) {
|
||||||
|
QScriptValue color = properties.property("color");
|
||||||
|
if (color.isValid()) {
|
||||||
|
QScriptValue red = color.property("red");
|
||||||
|
QScriptValue green = color.property("green");
|
||||||
|
QScriptValue blue = color.property("blue");
|
||||||
|
if (red.isValid() && green.isValid() && blue.isValid()) {
|
||||||
|
_color.red = red.toVariant().toInt();
|
||||||
|
_color.green = green.toVariant().toInt();
|
||||||
|
_color.blue = blue.toVariant().toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.property("alpha").isValid()) {
|
||||||
|
setAlpha(properties.property("alpha").toVariant().toFloat());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.property("visible").isValid()) {
|
||||||
|
setVisible(properties.property("visible").toVariant().toBool());
|
||||||
|
}
|
||||||
|
}
|
53
interface/src/ui/Overlay.h
Normal file
53
interface/src/ui/Overlay.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// Overlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Overlay__
|
||||||
|
#define __interface__Overlay__
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QScriptValue>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <SharedUtil.h> // for xColor
|
||||||
|
|
||||||
|
const xColor DEFAULT_BACKGROUND_COLOR = { 255, 255, 255 };
|
||||||
|
const float DEFAULT_ALPHA = 0.7f;
|
||||||
|
|
||||||
|
class Overlay : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Overlay();
|
||||||
|
~Overlay();
|
||||||
|
void init(QGLWidget* parent);
|
||||||
|
virtual void render() = 0;
|
||||||
|
|
||||||
|
// getters
|
||||||
|
bool getVisible() const { return _visible; }
|
||||||
|
const xColor& getColor() const { return _color; }
|
||||||
|
float getAlpha() const { return _alpha; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setVisible(bool visible) { _visible = visible; }
|
||||||
|
void setColor(const xColor& color) { _color = color; }
|
||||||
|
void setAlpha(float alpha) { _alpha = alpha; }
|
||||||
|
|
||||||
|
virtual void setProperties(const QScriptValue& properties);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QGLWidget* _parent;
|
||||||
|
float _alpha;
|
||||||
|
xColor _color;
|
||||||
|
bool _visible; // should the overlay be drawn at all
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Overlay__) */
|
63
interface/src/ui/Overlay2D.cpp
Normal file
63
interface/src/ui/Overlay2D.cpp
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// Overlay2D.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QSvgRenderer>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Overlay2D.h"
|
||||||
|
|
||||||
|
|
||||||
|
Overlay2D::Overlay2D() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Overlay2D::~Overlay2D() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlay2D::setProperties(const QScriptValue& properties) {
|
||||||
|
Overlay::setProperties(properties);
|
||||||
|
|
||||||
|
QScriptValue bounds = properties.property("bounds");
|
||||||
|
if (bounds.isValid()) {
|
||||||
|
QRect boundsRect;
|
||||||
|
boundsRect.setX(bounds.property("x").toVariant().toInt());
|
||||||
|
boundsRect.setY(bounds.property("y").toVariant().toInt());
|
||||||
|
boundsRect.setWidth(bounds.property("width").toVariant().toInt());
|
||||||
|
boundsRect.setHeight(bounds.property("height").toVariant().toInt());
|
||||||
|
setBounds(boundsRect);
|
||||||
|
} else {
|
||||||
|
QRect oldBounds = getBounds();
|
||||||
|
QRect newBounds = oldBounds;
|
||||||
|
|
||||||
|
if (properties.property("x").isValid()) {
|
||||||
|
newBounds.setX(properties.property("x").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
newBounds.setX(oldBounds.x());
|
||||||
|
}
|
||||||
|
if (properties.property("y").isValid()) {
|
||||||
|
newBounds.setY(properties.property("y").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
newBounds.setY(oldBounds.y());
|
||||||
|
}
|
||||||
|
if (properties.property("width").isValid()) {
|
||||||
|
newBounds.setWidth(properties.property("width").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
newBounds.setWidth(oldBounds.width());
|
||||||
|
}
|
||||||
|
if (properties.property("height").isValid()) {
|
||||||
|
newBounds.setHeight(properties.property("height").toVariant().toInt());
|
||||||
|
} else {
|
||||||
|
newBounds.setHeight(oldBounds.height());
|
||||||
|
}
|
||||||
|
setBounds(newBounds);
|
||||||
|
//qDebug() << "set bounds to " << getBounds();
|
||||||
|
}
|
||||||
|
}
|
51
interface/src/ui/Overlay2D.h
Normal file
51
interface/src/ui/Overlay2D.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// Overlay2D.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Overlay2D__
|
||||||
|
#define __interface__Overlay2D__
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QScriptValue>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <SharedUtil.h> // for xColor
|
||||||
|
|
||||||
|
#include "Overlay.h"
|
||||||
|
|
||||||
|
class Overlay2D : public Overlay {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Overlay2D();
|
||||||
|
~Overlay2D();
|
||||||
|
|
||||||
|
// getters
|
||||||
|
int getX() const { return _bounds.x(); }
|
||||||
|
int getY() const { return _bounds.y(); }
|
||||||
|
int getWidth() const { return _bounds.width(); }
|
||||||
|
int getHeight() const { return _bounds.height(); }
|
||||||
|
const QRect& getBounds() const { return _bounds; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setX(int x) { _bounds.setX(x); }
|
||||||
|
void setY(int y) { _bounds.setY(y); }
|
||||||
|
void setWidth(int width) { _bounds.setWidth(width); }
|
||||||
|
void setHeight(int height) { _bounds.setHeight(height); }
|
||||||
|
void setBounds(const QRect& bounds) { _bounds = bounds; }
|
||||||
|
|
||||||
|
virtual void setProperties(const QScriptValue& properties);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QRect _bounds; // where on the screen to draw
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Overlay2D__) */
|
129
interface/src/ui/Overlays.cpp
Normal file
129
interface/src/ui/Overlays.cpp
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
//
|
||||||
|
// Overlays.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include "Cube3DOverlay.h"
|
||||||
|
#include "ImageOverlay.h"
|
||||||
|
#include "Line3DOverlay.h"
|
||||||
|
#include "Overlays.h"
|
||||||
|
#include "Sphere3DOverlay.h"
|
||||||
|
#include "TextOverlay.h"
|
||||||
|
|
||||||
|
|
||||||
|
unsigned int Overlays::_nextOverlayID = 1;
|
||||||
|
|
||||||
|
Overlays::Overlays() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Overlays::~Overlays() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::init(QGLWidget* parent) {
|
||||||
|
_parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::render2D() {
|
||||||
|
foreach(Overlay* thisOverlay, _overlays2D) {
|
||||||
|
thisOverlay->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlays::render3D() {
|
||||||
|
foreach(Overlay* thisOverlay, _overlays3D) {
|
||||||
|
thisOverlay->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make multi-threaded safe
|
||||||
|
unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& properties) {
|
||||||
|
unsigned int thisID = 0;
|
||||||
|
bool created = false;
|
||||||
|
bool is3D = false;
|
||||||
|
Overlay* thisOverlay = NULL;
|
||||||
|
|
||||||
|
if (type == "image") {
|
||||||
|
thisOverlay = new ImageOverlay();
|
||||||
|
thisOverlay->init(_parent);
|
||||||
|
thisOverlay->setProperties(properties);
|
||||||
|
created = true;
|
||||||
|
} else if (type == "text") {
|
||||||
|
thisOverlay = new TextOverlay();
|
||||||
|
thisOverlay->init(_parent);
|
||||||
|
thisOverlay->setProperties(properties);
|
||||||
|
created = true;
|
||||||
|
} else if (type == "cube") {
|
||||||
|
thisOverlay = new Cube3DOverlay();
|
||||||
|
thisOverlay->init(_parent);
|
||||||
|
thisOverlay->setProperties(properties);
|
||||||
|
created = true;
|
||||||
|
is3D = true;
|
||||||
|
} else if (type == "sphere") {
|
||||||
|
thisOverlay = new Sphere3DOverlay();
|
||||||
|
thisOverlay->init(_parent);
|
||||||
|
thisOverlay->setProperties(properties);
|
||||||
|
created = true;
|
||||||
|
is3D = true;
|
||||||
|
} else if (type == "line3d") {
|
||||||
|
thisOverlay = new Line3DOverlay();
|
||||||
|
thisOverlay->init(_parent);
|
||||||
|
thisOverlay->setProperties(properties);
|
||||||
|
created = true;
|
||||||
|
is3D = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
thisID = _nextOverlayID;
|
||||||
|
_nextOverlayID++;
|
||||||
|
if (is3D) {
|
||||||
|
_overlays3D[thisID] = thisOverlay;
|
||||||
|
} else {
|
||||||
|
_overlays2D[thisID] = thisOverlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thisID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make multi-threaded safe
|
||||||
|
bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {
|
||||||
|
Overlay* thisOverlay = NULL;
|
||||||
|
if (_overlays2D.contains(id)) {
|
||||||
|
thisOverlay = _overlays2D[id];
|
||||||
|
} else if (_overlays3D.contains(id)) {
|
||||||
|
thisOverlay = _overlays3D[id];
|
||||||
|
}
|
||||||
|
if (thisOverlay) {
|
||||||
|
thisOverlay->setProperties(properties);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make multi-threaded safe
|
||||||
|
void Overlays::deleteOverlay(unsigned int id) {
|
||||||
|
if (_overlays2D.contains(id)) {
|
||||||
|
_overlays2D.erase(_overlays2D.find(id));
|
||||||
|
} else if (_overlays3D.contains(id)) {
|
||||||
|
_overlays3D.erase(_overlays3D.find(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
|
||||||
|
QMapIterator<unsigned int, Overlay*> i(_overlays2D);
|
||||||
|
i.toBack();
|
||||||
|
while (i.hasPrevious()) {
|
||||||
|
i.previous();
|
||||||
|
unsigned int thisID = i.key();
|
||||||
|
Overlay2D* thisOverlay = static_cast<Overlay2D*>(i.value());
|
||||||
|
if (thisOverlay->getVisible() && thisOverlay->getBounds().contains(point.x, point.y, false)) {
|
||||||
|
return thisID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
|
46
interface/src/ui/Overlays.h
Normal file
46
interface/src/ui/Overlays.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// Overlays.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Overlays__
|
||||||
|
#define __interface__Overlays__
|
||||||
|
|
||||||
|
#include <QScriptValue>
|
||||||
|
|
||||||
|
#include "Overlay.h"
|
||||||
|
|
||||||
|
class Overlays : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Overlays();
|
||||||
|
~Overlays();
|
||||||
|
void init(QGLWidget* parent);
|
||||||
|
void render3D();
|
||||||
|
void render2D();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/// adds an overlay with the specific properties
|
||||||
|
unsigned int addOverlay(const QString& type, const QScriptValue& properties);
|
||||||
|
|
||||||
|
/// edits an overlay updating only the included properties, will return the identified OverlayID in case of
|
||||||
|
/// successful edit, if the input id is for an unknown overlay this function will have no effect
|
||||||
|
bool editOverlay(unsigned int id, const QScriptValue& properties);
|
||||||
|
|
||||||
|
/// deletes a particle
|
||||||
|
void deleteOverlay(unsigned int id);
|
||||||
|
|
||||||
|
/// returns the top most overlay at the screen point, or 0 if not overlay at that point
|
||||||
|
unsigned int getOverlayAtPoint(const glm::vec2& point);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMap<unsigned int, Overlay*> _overlays2D;
|
||||||
|
QMap<unsigned int, Overlay*> _overlays3D;
|
||||||
|
static unsigned int _nextOverlayID;
|
||||||
|
QGLWidget* _parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Overlays__) */
|
45
interface/src/ui/Sphere3DOverlay.cpp
Normal file
45
interface/src/ui/Sphere3DOverlay.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// Sphere3DOverlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Sphere3DOverlay.h"
|
||||||
|
|
||||||
|
Sphere3DOverlay::Sphere3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Sphere3DOverlay::~Sphere3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sphere3DOverlay::render() {
|
||||||
|
if (!_visible) {
|
||||||
|
return; // do nothing if we're not visible
|
||||||
|
}
|
||||||
|
|
||||||
|
const float MAX_COLOR = 255;
|
||||||
|
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
|
||||||
|
|
||||||
|
|
||||||
|
glDisable(GL_LIGHTING);
|
||||||
|
glPushMatrix();
|
||||||
|
glTranslatef(_position.x + _size * 0.5f,
|
||||||
|
_position.y + _size * 0.5f,
|
||||||
|
_position.z + _size * 0.5f);
|
||||||
|
glLineWidth(_lineWidth);
|
||||||
|
const int slices = 15;
|
||||||
|
if (_isSolid) {
|
||||||
|
glutSolidSphere(_size, slices, slices);
|
||||||
|
} else {
|
||||||
|
glutWireSphere(_size, slices, slices);
|
||||||
|
}
|
||||||
|
glPopMatrix();
|
||||||
|
|
||||||
|
}
|
23
interface/src/ui/Sphere3DOverlay.h
Normal file
23
interface/src/ui/Sphere3DOverlay.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// Sphere3DOverlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Sphere3DOverlay__
|
||||||
|
#define __interface__Sphere3DOverlay__
|
||||||
|
|
||||||
|
#include "Volume3DOverlay.h"
|
||||||
|
|
||||||
|
class Sphere3DOverlay : public Volume3DOverlay {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Sphere3DOverlay();
|
||||||
|
~Sphere3DOverlay();
|
||||||
|
virtual void render();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Sphere3DOverlay__) */
|
82
interface/src/ui/TextOverlay.cpp
Normal file
82
interface/src/ui/TextOverlay.cpp
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
//
|
||||||
|
// TextOverlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "TextOverlay.h"
|
||||||
|
#include "TextRenderer.h"
|
||||||
|
|
||||||
|
TextOverlay::TextOverlay() :
|
||||||
|
_leftMargin(DEFAULT_MARGIN),
|
||||||
|
_topMargin(DEFAULT_MARGIN)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TextOverlay::~TextOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextOverlay::render() {
|
||||||
|
if (!_visible) {
|
||||||
|
return; // do nothing if we're not visible
|
||||||
|
}
|
||||||
|
|
||||||
|
const float MAX_COLOR = 255;
|
||||||
|
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
|
||||||
|
|
||||||
|
glBegin(GL_QUADS);
|
||||||
|
glVertex2f(_bounds.left(), _bounds.top());
|
||||||
|
glVertex2f(_bounds.right(), _bounds.top());
|
||||||
|
glVertex2f(_bounds.right(), _bounds.bottom());
|
||||||
|
glVertex2f(_bounds.left(), _bounds.bottom());
|
||||||
|
glEnd();
|
||||||
|
|
||||||
|
//TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
|
||||||
|
// EffectType effect = NO_EFFECT, int effectThickness = 1);
|
||||||
|
|
||||||
|
TextRenderer textRenderer(SANS_FONT_FAMILY, 11, 50);
|
||||||
|
const int leftAdjust = -1; // required to make text render relative to left edge of bounds
|
||||||
|
const int topAdjust = -2; // required to make text render relative to top edge of bounds
|
||||||
|
int x = _bounds.left() + _leftMargin + leftAdjust;
|
||||||
|
int y = _bounds.top() + _topMargin + topAdjust;
|
||||||
|
|
||||||
|
glColor3f(1.0f, 1.0f, 1.0f);
|
||||||
|
QStringList lines = _text.split("\n");
|
||||||
|
int lineOffset = 0;
|
||||||
|
foreach(QString thisLine, lines) {
|
||||||
|
if (lineOffset == 0) {
|
||||||
|
lineOffset = textRenderer.calculateHeight(qPrintable(thisLine));
|
||||||
|
}
|
||||||
|
lineOffset += textRenderer.draw(x, y + lineOffset, qPrintable(thisLine));
|
||||||
|
|
||||||
|
const int lineGap = 2;
|
||||||
|
lineOffset += lineGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextOverlay::setProperties(const QScriptValue& properties) {
|
||||||
|
Overlay2D::setProperties(properties);
|
||||||
|
|
||||||
|
QScriptValue text = properties.property("text");
|
||||||
|
if (text.isValid()) {
|
||||||
|
setText(text.toVariant().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.property("leftMargin").isValid()) {
|
||||||
|
setLeftMargin(properties.property("leftMargin").toVariant().toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.property("topMargin").isValid()) {
|
||||||
|
setTopMargin(properties.property("topMargin").toVariant().toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
59
interface/src/ui/TextOverlay.h
Normal file
59
interface/src/ui/TextOverlay.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
//
|
||||||
|
// TextOverlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__TextOverlay__
|
||||||
|
#define __interface__TextOverlay__
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QScriptValue>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Overlay.h"
|
||||||
|
#include "Overlay2D.h"
|
||||||
|
|
||||||
|
const int DEFAULT_MARGIN = 10;
|
||||||
|
|
||||||
|
class TextOverlay : public Overlay2D {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
TextOverlay();
|
||||||
|
~TextOverlay();
|
||||||
|
virtual void render();
|
||||||
|
|
||||||
|
// getters
|
||||||
|
const QString& getText() const { return _text; }
|
||||||
|
int getLeftMargin() const { return _leftMargin; }
|
||||||
|
int getTopMargin() const { return _topMargin; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setText(const QString& text) { _text = text; }
|
||||||
|
void setLeftMargin(int margin) { _leftMargin = margin; }
|
||||||
|
void setTopMargin(int margin) { _topMargin = margin; }
|
||||||
|
|
||||||
|
virtual void setProperties(const QScriptValue& properties);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QString _text;
|
||||||
|
int _leftMargin;
|
||||||
|
int _topMargin;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__TextOverlay__) */
|
|
@ -8,6 +8,8 @@
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QPaintEngine>
|
#include <QPaintEngine>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
#include "InterfaceConfig.h"
|
#include "InterfaceConfig.h"
|
||||||
#include "TextRenderer.h"
|
#include "TextRenderer.h"
|
||||||
|
@ -30,10 +32,25 @@ TextRenderer::~TextRenderer() {
|
||||||
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
|
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextRenderer::draw(int x, int y, const char* str) {
|
int TextRenderer::calculateHeight(const char* str) {
|
||||||
|
int maxHeight = 0;
|
||||||
|
for (const char* ch = str; *ch != 0; ch++) {
|
||||||
|
const Glyph& glyph = getGlyph(*ch);
|
||||||
|
if (glyph.textureID() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glyph.bounds().height() > maxHeight) {
|
||||||
|
maxHeight = glyph.bounds().height();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TextRenderer::draw(int x, int y, const char* str) {
|
||||||
glEnable(GL_TEXTURE_2D);
|
glEnable(GL_TEXTURE_2D);
|
||||||
|
|
||||||
|
int maxHeight = 0;
|
||||||
for (const char* ch = str; *ch != 0; ch++) {
|
for (const char* ch = str; *ch != 0; ch++) {
|
||||||
const Glyph& glyph = getGlyph(*ch);
|
const Glyph& glyph = getGlyph(*ch);
|
||||||
if (glyph.textureID() == 0) {
|
if (glyph.textureID() == 0) {
|
||||||
|
@ -41,6 +58,10 @@ void TextRenderer::draw(int x, int y, const char* str) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (glyph.bounds().height() > maxHeight) {
|
||||||
|
maxHeight = glyph.bounds().height();
|
||||||
|
}
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, glyph.textureID());
|
glBindTexture(GL_TEXTURE_2D, glyph.textureID());
|
||||||
|
|
||||||
int left = x + glyph.bounds().x();
|
int left = x + glyph.bounds().x();
|
||||||
|
@ -67,9 +88,10 @@ void TextRenderer::draw(int x, int y, const char* str) {
|
||||||
|
|
||||||
x += glyph.width();
|
x += glyph.width();
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
glDisable(GL_TEXTURE_2D);
|
glDisable(GL_TEXTURE_2D);
|
||||||
|
|
||||||
|
return maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextRenderer::computeWidth(char ch)
|
int TextRenderer::computeWidth(char ch)
|
||||||
|
|
|
@ -20,6 +20,16 @@
|
||||||
// a special "character" that renders as a solid block
|
// a special "character" that renders as a solid block
|
||||||
const char SOLID_BLOCK_CHAR = 127;
|
const char SOLID_BLOCK_CHAR = 127;
|
||||||
|
|
||||||
|
// the standard sans serif font family
|
||||||
|
#define SANS_FONT_FAMILY "Helvetica"
|
||||||
|
|
||||||
|
// the standard mono font family
|
||||||
|
#define MONO_FONT_FAMILY "Courier"
|
||||||
|
|
||||||
|
// the Inconsolata font family
|
||||||
|
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
|
||||||
|
|
||||||
|
|
||||||
class Glyph;
|
class Glyph;
|
||||||
|
|
||||||
class TextRenderer {
|
class TextRenderer {
|
||||||
|
@ -33,7 +43,11 @@ public:
|
||||||
|
|
||||||
const QFontMetrics& metrics() const { return _metrics; }
|
const QFontMetrics& metrics() const { return _metrics; }
|
||||||
|
|
||||||
void draw(int x, int y, const char* str);
|
// returns the height of the tallest character
|
||||||
|
int calculateHeight(const char* str);
|
||||||
|
|
||||||
|
// also returns the height of the tallest character
|
||||||
|
int draw(int x, int y, const char* str);
|
||||||
|
|
||||||
int computeWidth(char ch);
|
int computeWidth(char ch);
|
||||||
int computeWidth(const char* str);
|
int computeWidth(const char* str);
|
||||||
|
|
47
interface/src/ui/Volume3DOverlay.cpp
Normal file
47
interface/src/ui/Volume3DOverlay.cpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// Volume3DOverlay.cpp
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include "Volume3DOverlay.h"
|
||||||
|
|
||||||
|
const float DEFAULT_SIZE = 1.0f;
|
||||||
|
const bool DEFAULT_IS_SOLID = false;
|
||||||
|
|
||||||
|
Volume3DOverlay::Volume3DOverlay() :
|
||||||
|
_size(DEFAULT_SIZE),
|
||||||
|
_isSolid(DEFAULT_IS_SOLID)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Volume3DOverlay::~Volume3DOverlay() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Volume3DOverlay::setProperties(const QScriptValue& properties) {
|
||||||
|
Base3DOverlay::setProperties(properties);
|
||||||
|
|
||||||
|
if (properties.property("size").isValid()) {
|
||||||
|
setSize(properties.property("size").toVariant().toFloat());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.property("isSolid").isValid()) {
|
||||||
|
setIsSolid(properties.property("isSolid").toVariant().toBool());
|
||||||
|
}
|
||||||
|
if (properties.property("isWire").isValid()) {
|
||||||
|
setIsSolid(!properties.property("isWire").toVariant().toBool());
|
||||||
|
}
|
||||||
|
if (properties.property("solid").isValid()) {
|
||||||
|
setIsSolid(properties.property("solid").toVariant().toBool());
|
||||||
|
}
|
||||||
|
if (properties.property("wire").isValid()) {
|
||||||
|
setIsSolid(!properties.property("wire").toVariant().toBool());
|
||||||
|
}
|
||||||
|
}
|
42
interface/src/ui/Volume3DOverlay.h
Normal file
42
interface/src/ui/Volume3DOverlay.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// Volume3DOverlay.h
|
||||||
|
// interface
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef __interface__Volume3DOverlay__
|
||||||
|
#define __interface__Volume3DOverlay__
|
||||||
|
|
||||||
|
// include this before QGLWidget, which includes an earlier version of OpenGL
|
||||||
|
#include "InterfaceConfig.h"
|
||||||
|
|
||||||
|
#include <QGLWidget>
|
||||||
|
#include <QScriptValue>
|
||||||
|
|
||||||
|
#include "Base3DOverlay.h"
|
||||||
|
|
||||||
|
class Volume3DOverlay : public Base3DOverlay {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Volume3DOverlay();
|
||||||
|
~Volume3DOverlay();
|
||||||
|
|
||||||
|
// getters
|
||||||
|
float getSize() const { return _size; }
|
||||||
|
bool getIsSolid() const { return _isSolid; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setSize(float size) { _size = size; }
|
||||||
|
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
|
||||||
|
|
||||||
|
virtual void setProperties(const QScriptValue& properties);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float _size;
|
||||||
|
bool _isSolid;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* defined(__interface__Volume3DOverlay__) */
|
|
@ -52,8 +52,8 @@ static const float MIN_AVATAR_SCALE = .005f;
|
||||||
|
|
||||||
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
|
const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation
|
||||||
|
|
||||||
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fbx");
|
const QUrl DEFAULT_HEAD_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_head.fst");
|
||||||
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fbx");
|
const QUrl DEFAULT_BODY_MODEL_URL = QUrl("http://public.highfidelity.io/meshes/defaultAvatar_body.fst");
|
||||||
|
|
||||||
enum KeyState {
|
enum KeyState {
|
||||||
NO_KEY_DOWN = 0,
|
NO_KEY_DOWN = 0,
|
||||||
|
@ -135,11 +135,7 @@ public:
|
||||||
|
|
||||||
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
|
virtual const glm::vec3& getVelocity() const { return vec3Zero; }
|
||||||
|
|
||||||
virtual bool findSphereCollisionWithHands(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
|
virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool findSphereCollisionWithSkeleton(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +149,8 @@ public:
|
||||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||||
virtual void setDisplayName(const QString& displayName);
|
virtual void setDisplayName(const QString& displayName);
|
||||||
|
|
||||||
|
virtual float getBoundingRadius() const { return 1.f; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
glm::vec3 _position;
|
glm::vec3 _position;
|
||||||
glm::vec3 _handPosition;
|
glm::vec3 _handPosition;
|
||||||
|
|
|
@ -791,7 +791,6 @@ const char* OctreeSceneStats::getItemValue(Item item) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
sprintf(_itemValueBuffer, "");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return _itemValueBuffer;
|
return _itemValueBuffer;
|
||||||
|
|
|
@ -23,7 +23,6 @@ OctreeScriptingInterface::OctreeScriptingInterface(OctreeEditPacketSender* packe
|
||||||
}
|
}
|
||||||
|
|
||||||
OctreeScriptingInterface::~OctreeScriptingInterface() {
|
OctreeScriptingInterface::~OctreeScriptingInterface() {
|
||||||
qDebug() << "OctreeScriptingInterface::~OctreeScriptingInterface() this=" << this;
|
|
||||||
cleanupManagedObjects();
|
cleanupManagedObjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,11 @@
|
||||||
#include "ParticleEditPacketSender.h"
|
#include "ParticleEditPacketSender.h"
|
||||||
#include "ParticleTree.h"
|
#include "ParticleTree.h"
|
||||||
|
|
||||||
|
const int MAX_COLLISIONS_PER_PARTICLE = 16;
|
||||||
|
|
||||||
ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
|
ParticleCollisionSystem::ParticleCollisionSystem(ParticleEditPacketSender* packetSender,
|
||||||
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio,
|
ParticleTree* particles, VoxelTree* voxels, AbstractAudioInterface* audio,
|
||||||
AvatarHashMap* avatars) {
|
AvatarHashMap* avatars) : _collisions(MAX_COLLISIONS_PER_PARTICLE) {
|
||||||
init(packetSender, particles, voxels, audio, avatars);
|
init(packetSender, particles, voxels, audio, avatars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,43 +183,57 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
|
||||||
const float COLLISION_FREQUENCY = 0.5f;
|
const float COLLISION_FREQUENCY = 0.5f;
|
||||||
glm::vec3 penetration;
|
glm::vec3 penetration;
|
||||||
|
|
||||||
|
_collisions.clear();
|
||||||
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
|
foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
|
||||||
AvatarData* avatar = avatarPointer.data();
|
AvatarData* avatar = avatarPointer.data();
|
||||||
CollisionInfo collisionInfo;
|
|
||||||
collisionInfo._damping = DAMPING;
|
// use a very generous bounding radius since the arms can stretch
|
||||||
collisionInfo._elasticity = ELASTICITY;
|
float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
|
||||||
if (avatar->findSphereCollisionWithHands(center, radius, collisionInfo)) {
|
glm::vec3 relativePosition = center - avatar->getPosition();
|
||||||
// TODO: Andrew to resurrect particles-vs-avatar body collisions
|
if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
|
||||||
//avatar->findSphereCollisionWithSkeleton(center, radius, collisionInfo)) {
|
continue;
|
||||||
collisionInfo._addedVelocity /= (float)(TREE_SCALE);
|
}
|
||||||
glm::vec3 relativeVelocity = collisionInfo._addedVelocity - particle->getVelocity();
|
|
||||||
if (glm::dot(relativeVelocity, collisionInfo._penetration) < 0.f) {
|
if (avatar->findParticleCollisions(center, radius, _collisions)) {
|
||||||
|
int numCollisions = _collisions.size();
|
||||||
|
for (int i = 0; i < numCollisions; ++i) {
|
||||||
|
CollisionInfo* collision = _collisions.getCollision(i);
|
||||||
|
collision->_damping = DAMPING;
|
||||||
|
collision->_elasticity = ELASTICITY;
|
||||||
|
|
||||||
|
collision->_addedVelocity /= (float)(TREE_SCALE);
|
||||||
|
glm::vec3 relativeVelocity = collision->_addedVelocity - particle->getVelocity();
|
||||||
|
|
||||||
|
if (glm::dot(relativeVelocity, collision->_penetration) <= 0.f) {
|
||||||
// only collide when particle and collision point are moving toward each other
|
// only collide when particle and collision point are moving toward each other
|
||||||
// (doing this prevents some "collision snagging" when particle penetrates the object)
|
// (doing this prevents some "collision snagging" when particle penetrates the object)
|
||||||
|
|
||||||
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against the avatar.
|
// HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them.
|
||||||
|
if (collision->_type == PADDLE_HAND_COLLISION) {
|
||||||
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
|
// NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
|
||||||
// TODO: make this less hacky when we have more per-collision details
|
// TODO: make this less hacky when we have more per-collision details
|
||||||
float elasticity = ELASTICITY;
|
float elasticity = ELASTICITY;
|
||||||
float attenuationFactor = glm::length(collisionInfo._addedVelocity) / HALTING_SPEED;
|
float attenuationFactor = glm::length(collision->_addedVelocity) / HALTING_SPEED;
|
||||||
float damping = DAMPING;
|
float damping = DAMPING;
|
||||||
if (attenuationFactor < 1.f) {
|
if (attenuationFactor < 1.f) {
|
||||||
collisionInfo._addedVelocity *= attenuationFactor;
|
collision->_addedVelocity *= attenuationFactor;
|
||||||
elasticity *= attenuationFactor;
|
elasticity *= attenuationFactor;
|
||||||
// NOTE: the math below keeps the damping piecewise continuous,
|
// NOTE: the math below keeps the damping piecewise continuous,
|
||||||
// while ramping it up to 1.0 when attenuationFactor = 0
|
// while ramping it up to 1 when attenuationFactor = 0
|
||||||
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
|
damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// HACK END
|
// HACK END
|
||||||
|
|
||||||
updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY);
|
updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
|
||||||
collisionInfo._penetration /= (float)(TREE_SCALE);
|
collision->_penetration /= (float)(TREE_SCALE);
|
||||||
particle->applyHardCollision(collisionInfo);
|
particle->applyHardCollision(*collision);
|
||||||
queueParticlePropertiesUpdate(particle);
|
queueParticlePropertiesUpdate(particle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ParticleCollisionSystem::queueParticlePropertiesUpdate(Particle* particle) {
|
void ParticleCollisionSystem::queueParticlePropertiesUpdate(Particle* particle) {
|
||||||
// queue the result for sending to the particle server
|
// queue the result for sending to the particle server
|
||||||
|
|
|
@ -66,6 +66,7 @@ private:
|
||||||
VoxelTree* _voxels;
|
VoxelTree* _voxels;
|
||||||
AbstractAudioInterface* _audio;
|
AbstractAudioInterface* _audio;
|
||||||
AvatarHashMap* _avatars;
|
AvatarHashMap* _avatars;
|
||||||
|
CollisionList _collisions;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__ParticleCollisionSystem__) */
|
#endif /* defined(__hifi__ParticleCollisionSystem__) */
|
||||||
|
|
42
libraries/shared/src/CollisionInfo.cpp
Normal file
42
libraries/shared/src/CollisionInfo.cpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// CollisionInfo.cpp
|
||||||
|
// hifi
|
||||||
|
//
|
||||||
|
// Created by Andrew Meadows on 2014.02.14
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "CollisionInfo.h"
|
||||||
|
|
||||||
|
CollisionList::CollisionList(int maxSize) :
|
||||||
|
_maxSize(maxSize),
|
||||||
|
_size(0) {
|
||||||
|
_collisions.resize(_maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionInfo* CollisionList::getNewCollision() {
|
||||||
|
// return pointer to existing CollisionInfo, or NULL of list is full
|
||||||
|
return (_size < _maxSize) ? &(_collisions[++_size]) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionInfo* CollisionList::getCollision(int index) {
|
||||||
|
return (index > -1 && index < _size) ? &(_collisions[index]) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollisionList::clear() {
|
||||||
|
for (int i = 0; i < _size; ++i) {
|
||||||
|
// we only clear the important stuff
|
||||||
|
CollisionInfo& collision = _collisions[i];
|
||||||
|
collision._type = BASE_COLLISION;
|
||||||
|
collision._data = NULL; // CollisionInfo does not own whatever this points to.
|
||||||
|
collision._flags = 0;
|
||||||
|
// we rely on the consumer to properly overwrite these fields when the collision is "created"
|
||||||
|
//collision._damping;
|
||||||
|
//collision._elasticity;
|
||||||
|
//collision._contactPoint;
|
||||||
|
//collision._penetration;
|
||||||
|
//collision._addedVelocity;
|
||||||
|
}
|
||||||
|
_size = 0;
|
||||||
|
}
|
||||||
|
|
|
@ -11,15 +11,42 @@
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
const uint32_t COLLISION_GROUP_ENVIRONMENT = 1U << 0;
|
#include <QVector>
|
||||||
const uint32_t COLLISION_GROUP_AVATARS = 1U << 1;
|
|
||||||
const uint32_t COLLISION_GROUP_VOXELS = 1U << 2;
|
enum CollisionType {
|
||||||
const uint32_t COLLISION_GROUP_PARTICLES = 1U << 3;
|
BASE_COLLISION = 0,
|
||||||
|
PADDLE_HAND_COLLISION,
|
||||||
|
MODEL_COLLISION,
|
||||||
|
};
|
||||||
|
|
||||||
|
const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
|
||||||
|
const quint32 COLLISION_GROUP_AVATARS = 1U << 1;
|
||||||
|
const quint32 COLLISION_GROUP_VOXELS = 1U << 2;
|
||||||
|
const quint32 COLLISION_GROUP_PARTICLES = 1U << 3;
|
||||||
|
|
||||||
|
// CollisionInfo contains details about the collision between two things: BodyA and BodyB.
|
||||||
|
// The assumption is that the context that analyzes the collision knows about BodyA but
|
||||||
|
// does not necessarily know about BodyB. Hence the data storred in the CollisionInfo
|
||||||
|
// is expected to be relative to BodyA (for example the penetration points from A into B).
|
||||||
|
|
||||||
class CollisionInfo {
|
class CollisionInfo {
|
||||||
public:
|
public:
|
||||||
CollisionInfo()
|
CollisionInfo()
|
||||||
: _damping(0.f),
|
: _type(0),
|
||||||
|
_data(NULL),
|
||||||
|
_flags(0),
|
||||||
|
_damping(0.f),
|
||||||
|
_elasticity(1.f),
|
||||||
|
_contactPoint(0.f),
|
||||||
|
_penetration(0.f),
|
||||||
|
_addedVelocity(0.f) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionInfo(qint32 type)
|
||||||
|
: _type(type),
|
||||||
|
_data(NULL),
|
||||||
|
_flags(0),
|
||||||
|
_damping(0.f),
|
||||||
_elasticity(1.f),
|
_elasticity(1.f),
|
||||||
_contactPoint(0.f),
|
_contactPoint(0.f),
|
||||||
_penetration(0.f),
|
_penetration(0.f),
|
||||||
|
@ -28,13 +55,40 @@ public:
|
||||||
|
|
||||||
~CollisionInfo() {}
|
~CollisionInfo() {}
|
||||||
|
|
||||||
//glm::vec3 _normal;
|
qint32 _type; // type of Collision (will determine what is supposed to be in _data and _flags)
|
||||||
float _damping;
|
void* _data; // pointer to user supplied data
|
||||||
float _elasticity;
|
quint32 _flags; // 32 bits for whatever
|
||||||
glm::vec3 _contactPoint; // world-frame point on bodyA that is deepest into bodyB
|
|
||||||
glm::vec3 _penetration; // depth that bodyA penetrates into bodyB
|
float _damping; // range [0,1] of friction coeficient
|
||||||
glm::vec3 _addedVelocity;
|
float _elasticity; // range [0,1] of energy conservation
|
||||||
|
glm::vec3 _contactPoint; // world-frame point on BodyA that is deepest into BodyB
|
||||||
|
glm::vec3 _penetration; // depth that BodyA penetrates into BodyB
|
||||||
|
glm::vec3 _addedVelocity; // velocity of BodyB
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CollisionList is intended to be a recycled container. Fill the CollisionInfo's,
|
||||||
|
// use them, and then clear them for the next frame or context.
|
||||||
|
|
||||||
|
class CollisionList {
|
||||||
|
public:
|
||||||
|
CollisionList(int maxSize);
|
||||||
|
|
||||||
|
/// \return pointer to next collision. NULL if list is full.
|
||||||
|
CollisionInfo* getNewCollision();
|
||||||
|
|
||||||
|
/// \return pointer to collision by index. NULL if index out of bounds.
|
||||||
|
CollisionInfo* getCollision(int index);
|
||||||
|
|
||||||
|
/// \return number of valid collisions
|
||||||
|
int size() const { return _size; }
|
||||||
|
|
||||||
|
/// Clear valid collisions.
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _maxSize;
|
||||||
|
int _size;
|
||||||
|
QVector<CollisionInfo> _collisions;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__CollisionInfo__) */
|
#endif /* defined(__hifi__CollisionInfo__) */
|
||||||
|
|
|
@ -46,6 +46,8 @@ public:
|
||||||
_runningTotal(runningTotal),
|
_runningTotal(runningTotal),
|
||||||
_totalCalls(totalCalls) { }
|
_totalCalls(totalCalls) { }
|
||||||
|
|
||||||
|
quint64 elapsed() const { return (usecTimestampNow() - _start); };
|
||||||
|
|
||||||
~PerformanceWarning();
|
~PerformanceWarning();
|
||||||
|
|
||||||
static void setSuppressShortTimings(bool suppressShortTimings) { _suppressShortTimings = suppressShortTimings; }
|
static void setSuppressShortTimings(bool suppressShortTimings) { _suppressShortTimings = suppressShortTimings; }
|
||||||
|
|
Loading…
Reference in a new issue